From patchwork Mon Jun 12 05:52:35 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jonathan Liu X-Patchwork-Id: 9780513 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id CBF4360212 for ; Mon, 12 Jun 2017 06:59:32 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id BC4D528415 for ; Mon, 12 Jun 2017 06:59:32 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id B0A6E284C6; Mon, 12 Jun 2017 06:59:32 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-4.1 required=2.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, RCVD_IN_DNSWL_MED, T_DKIM_INVALID autolearn=ham version=3.3.1 Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 0777F28415 for ; Mon, 12 Jun 2017 06:59:32 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 243BA89F85; Mon, 12 Jun 2017 06:59:26 +0000 (UTC) X-Original-To: dri-devel@lists.freedesktop.org Delivered-To: dri-devel@lists.freedesktop.org Received: from mail-pf0-x243.google.com (mail-pf0-x243.google.com [IPv6:2607:f8b0:400e:c00::243]) by gabe.freedesktop.org (Postfix) with ESMTPS id 03D7389A5D for ; Mon, 12 Jun 2017 05:51:30 +0000 (UTC) Received: by mail-pf0-x243.google.com with SMTP id y7so15377551pfd.3 for ; Sun, 11 Jun 2017 22:51:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id; bh=bFv7r8ZcBWLWGfNFCq+hmAGLogvOvcGAHexJRX+NSCw=; b=Gzk4m4YsHhfwGKcQBMzBJL1RNGz5lzi0geheK8JGvESZRiPMG+OmCU18Pmu9m/7H43 5oKc1gWK0WWzOa7gAxnA2GMInRDFqKMwuSkYYb1hgI7V1g9jQerz5SST0MvxWFkYnhzf BcAFJarV/PPrHRmX8WI827cdNzwFNbiJ7bZ+TZK3N53K8sNIbhGAOBYI9UThPzC6HCDH pytsEqi1sCvRkM17a51dTVYnRdIJf2hHJjX09OmibgdAz22Go7pGiDvHqCC0F7SGo54l ztdXhzh5waTekiGjLnEIKLAi48UBsTzQVeM+iakD2e7RGeIC9QRovAzuPoAtMuEHM4Qa fnGQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=bFv7r8ZcBWLWGfNFCq+hmAGLogvOvcGAHexJRX+NSCw=; b=dVtQmW2q7qkapZ5yS4HKwNn9P74RmfDai8E2gWGutItFk3OwQb4wPy1IdjwyaKvJuy rMmvsw8EXR1651v/Ui8/n2x6zl4w2AlAU1N0EqlNwQNXTmICYCG/QA8BqRszQfyhWVKa Bifh6cLC6r4qLrI5+bCCj+jOr3dDCjbH7/8PbAJb5py+fxE4TCPE2XcOKCHxMtgyRGa9 cg+HzfIxYyJpUprnwyp6AxOyfhNxI2OIA41jT9+u2keGdOx1Z10+cQNxBEAdo/v9cfnS kkmiTvMredePAwTPJgsKhtbAFzP+QQ16LWp2S+VNtTs/Df+iW3l1bwqanD+pUsE6YSLg b2mQ== X-Gm-Message-State: AODbwcDlKuxOJpnje83GmwbQ33WXPC+7qjxmpwc80XtiQunPaPv7inJN GJgQ0xeIjz6qaw== X-Received: by 10.98.160.220 with SMTP id p89mr39463970pfl.184.1497246689507; Sun, 11 Jun 2017 22:51:29 -0700 (PDT) Received: from 60-242-179-244.static.tpgi.com.au (60-242-179-244.static.tpgi.com.au. [60.242.179.244]) by smtp.gmail.com with ESMTPSA id g78sm15477672pfb.122.2017.06.11.22.51.27 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Sun, 11 Jun 2017 22:51:28 -0700 (PDT) From: Jonathan Liu To: Maxime Ripard , David Airlie , Chen-Yu Tsai Subject: [PATCH v2] drm/sun4i: hdmi: Implement I2C adapter for A10s DDC bus Date: Mon, 12 Jun 2017 15:52:35 +1000 Message-Id: <20170612055235.31019-1-net147@gmail.com> X-Mailer: git-send-email 2.12.2 X-Mailman-Approved-At: Mon, 12 Jun 2017 06:59:24 +0000 Cc: linux-arm-kernel@lists.infradead.org, linux-sunxi@googlegroups.com, Jonathan Liu , linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" X-Virus-Scanned: ClamAV using ClamSMTP The drm_get_edid function should be used instead of drm_do_get_edid by exposing the DDC bus as an I2C adapter. Implement this for A10s. Signed-off-by: Jonathan Liu --- Changes for v2: - Rebased against Maxime's sunxi-drm/for-next branch - Fix up error paths in sun4i_hdmi_bind so that the I2C adapter is deleted if any of the calls after the I2C adapter is created fails - Remove unnecessary includes in sun4i_hdmi_i2c.c drivers/gpu/drm/sun4i/Makefile | 1 + drivers/gpu/drm/sun4i/sun4i_hdmi.h | 11 ++- drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 103 +++------------------ drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c | 163 +++++++++++++++++++++++++++++++++ 4 files changed, 189 insertions(+), 89 deletions(-) create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile index e29fd3a2ba9c..43c753cafc88 100644 --- a/drivers/gpu/drm/sun4i/Makefile +++ b/drivers/gpu/drm/sun4i/Makefile @@ -2,6 +2,7 @@ sun4i-drm-y += sun4i_drv.o sun4i-drm-y += sun4i_framebuffer.o sun4i-drm-hdmi-y += sun4i_hdmi_enc.o +sun4i-drm-hdmi-y += sun4i_hdmi_i2c.o sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h index 2f2f2ff1ea63..4c01dbe89cd9 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi.h +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h @@ -97,6 +97,7 @@ #define SUN4I_HDMI_DDC_CTRL_START_CMD BIT(30) #define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK BIT(8) #define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ (0 << 8) +#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_WRITE (1 << 8) #define SUN4I_HDMI_DDC_CTRL_RESET BIT(0) #define SUN4I_HDMI_DDC_ADDR_REG 0x504 @@ -105,6 +106,10 @@ #define SUN4I_HDMI_DDC_ADDR_OFFSET(off) (((off) & 0xff) << 8) #define SUN4I_HDMI_DDC_ADDR_SLAVE(addr) ((addr) & 0xff) +#define SUN4I_HDMI_DDC_INTERRUPT_STATUS_REG 0x50c +#define SUN4I_HDMI_DDC_INTERRUPT_STATUS_FIFO_REQUEST BIT(4) +#define SUN4I_HDMI_DDC_INTERRUPT_STATUS_TRANSFER_COMPLETE BIT(0) + #define SUN4I_HDMI_DDC_FIFO_CTRL_REG 0x510 #define SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR BIT(31) @@ -112,7 +117,8 @@ #define SUN4I_HDMI_DDC_BYTE_COUNT_REG 0x51c #define SUN4I_HDMI_DDC_CMD_REG 0x520 -#define SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ 6 +#define SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE 3 +#define SUN4I_HDMI_DDC_CMD_IMPLICIT_READ 5 #define SUN4I_HDMI_DDC_CLK_REG 0x528 #define SUN4I_HDMI_DDC_CLK_M(m) (((m) & 0x7) << 3) @@ -146,6 +152,8 @@ struct sun4i_hdmi { struct clk *ddc_clk; struct clk *tmds_clk; + struct i2c_adapter *i2c; + struct sun4i_drv *drv; bool hdmi_monitor; @@ -153,5 +161,6 @@ struct sun4i_hdmi { int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk); int sun4i_tmds_create(struct sun4i_hdmi *hdmi); +int sun4i_hdmi_i2c_create(struct sun4i_hdmi *hdmi); #endif /* _SUN4I_HDMI_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c index d3398f6250ef..2a8c0b14eabc 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c @@ -29,8 +29,6 @@ #include "sun4i_hdmi.h" #include "sun4i_tcon.h" -#define DDC_SEGMENT_ADDR 0x30 - static inline struct sun4i_hdmi * drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder) { @@ -184,93 +182,13 @@ static const struct drm_encoder_funcs sun4i_hdmi_funcs = { .destroy = drm_encoder_cleanup, }; -static int sun4i_hdmi_read_sub_block(struct sun4i_hdmi *hdmi, - unsigned int blk, unsigned int offset, - u8 *buf, unsigned int count) -{ - unsigned long reg; - int i; - - reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); - reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK; - writel(reg | SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ, - hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); - - writel(SUN4I_HDMI_DDC_ADDR_SEGMENT(offset >> 8) | - SUN4I_HDMI_DDC_ADDR_EDDC(DDC_SEGMENT_ADDR << 1) | - SUN4I_HDMI_DDC_ADDR_OFFSET(offset) | - SUN4I_HDMI_DDC_ADDR_SLAVE(DDC_ADDR), - hdmi->base + SUN4I_HDMI_DDC_ADDR_REG); - - reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG); - writel(reg | SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR, - hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG); - - writel(count, hdmi->base + SUN4I_HDMI_DDC_BYTE_COUNT_REG); - writel(SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ, - hdmi->base + SUN4I_HDMI_DDC_CMD_REG); - - reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); - writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD, - hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); - - if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg, - !(reg & SUN4I_HDMI_DDC_CTRL_START_CMD), - 100, 100000)) - return -EIO; - - for (i = 0; i < count; i++) - buf[i] = readb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG); - - return 0; -} - -static int sun4i_hdmi_read_edid_block(void *data, u8 *buf, unsigned int blk, - size_t length) -{ - struct sun4i_hdmi *hdmi = data; - int retry = 2, i; - - do { - for (i = 0; i < length; i += SUN4I_HDMI_DDC_FIFO_SIZE) { - unsigned char offset = blk * EDID_LENGTH + i; - unsigned int count = min((unsigned int)SUN4I_HDMI_DDC_FIFO_SIZE, - length - i); - int ret; - - ret = sun4i_hdmi_read_sub_block(hdmi, blk, offset, - buf + i, count); - if (ret) - return ret; - } - } while (!drm_edid_block_valid(buf, blk, true, NULL) && (retry--)); - - return 0; -} - static int sun4i_hdmi_get_modes(struct drm_connector *connector) { struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector); - unsigned long reg; struct edid *edid; int ret; - /* Reset i2c controller */ - writel(SUN4I_HDMI_DDC_CTRL_ENABLE | SUN4I_HDMI_DDC_CTRL_RESET, - hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); - if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg, - !(reg & SUN4I_HDMI_DDC_CTRL_RESET), - 100, 2000)) - return -EIO; - - writel(SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE | - SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE, - hdmi->base + SUN4I_HDMI_DDC_LINE_CTRL_REG); - - clk_prepare_enable(hdmi->ddc_clk); - clk_set_rate(hdmi->ddc_clk, 100000); - - edid = drm_do_get_edid(connector, sun4i_hdmi_read_edid_block, hdmi); + edid = drm_get_edid(connector, hdmi->i2c); if (!edid) return 0; @@ -282,8 +200,6 @@ static int sun4i_hdmi_get_modes(struct drm_connector *connector) ret = drm_add_edid_modes(connector, edid); kfree(edid); - clk_disable_unprepare(hdmi->ddc_clk); - return ret; } @@ -413,6 +329,12 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master, return ret; } + ret = sun4i_hdmi_i2c_create(hdmi); + if (ret) { + dev_err(dev, "Couldn't create the HDMI I2C adapter\n"); + return ret; + } + drm_encoder_helper_add(&hdmi->encoder, &sun4i_hdmi_helper_funcs); ret = drm_encoder_init(drm, @@ -422,13 +344,15 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master, NULL); if (ret) { dev_err(dev, "Couldn't initialise the HDMI encoder\n"); - return ret; + goto err_del_i2c_adapter; } hdmi->encoder.possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); - if (!hdmi->encoder.possible_crtcs) - return -EPROBE_DEFER; + if (!hdmi->encoder.possible_crtcs) { + ret = -EPROBE_DEFER; + goto err_del_i2c_adapter; + } drm_connector_helper_add(&hdmi->connector, &sun4i_hdmi_connector_helper_funcs); @@ -451,6 +375,8 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master, err_cleanup_connector: drm_encoder_cleanup(&hdmi->encoder); +err_del_i2c_adapter: + i2c_del_adapter(hdmi->i2c); return ret; } @@ -461,6 +387,7 @@ static void sun4i_hdmi_unbind(struct device *dev, struct device *master, drm_connector_cleanup(&hdmi->connector); drm_encoder_cleanup(&hdmi->encoder); + i2c_del_adapter(hdmi->i2c); } static const struct component_ops sun4i_hdmi_ops = { diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c new file mode 100644 index 000000000000..389b770be09b --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2017 Jonathan Liu + * + * Jonathan Liu + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include +#include +#include + +#include "sun4i_hdmi.h" + +static int xfer_msg_chunk(struct sun4i_hdmi *hdmi, struct i2c_msg *msg) +{ + u32 count = min_t(u32, msg->len, SUN4I_HDMI_DDC_FIFO_SIZE); + u32 reg; + int i; + + reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG); + writel(reg | SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR, + hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG); + + writel(count, hdmi->base + + SUN4I_HDMI_DDC_BYTE_COUNT_REG); + writel(msg->flags & I2C_M_RD + ? SUN4I_HDMI_DDC_CMD_IMPLICIT_READ + : SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE, + hdmi->base + SUN4I_HDMI_DDC_CMD_REG); + + reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); + writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD, + hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); + + if (!(msg->flags & I2C_M_RD)) { + for (i = 0; i < count; i++) { + writeb(*msg->buf++, hdmi->base + + SUN4I_HDMI_DDC_FIFO_DATA_REG); + --msg->len; + } + } + + if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, + reg, + !(reg & SUN4I_HDMI_DDC_CTRL_START_CMD), + 100, 100000)) + return -EIO; + + reg = readl(hdmi->base + SUN4I_HDMI_DDC_INTERRUPT_STATUS_REG); + reg &= ~SUN4I_HDMI_DDC_INTERRUPT_STATUS_FIFO_REQUEST; + + if (reg != SUN4I_HDMI_DDC_INTERRUPT_STATUS_TRANSFER_COMPLETE) + return -EIO; + + if (msg->flags & I2C_M_RD) { + for (i = 0; i < count; i++) { + *msg->buf++ = readb(hdmi->base + + SUN4I_HDMI_DDC_FIFO_DATA_REG); + --msg->len; + } + } + + return 0; +} + +static int xfer_msg(struct sun4i_hdmi *hdmi, struct i2c_msg *msg) +{ + u32 reg; + int ret; + + if (!msg->len) + return -EIO; + + while (msg->len) { + reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); + reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK; + + writel(reg | (msg->flags & I2C_M_RD + ? SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ + : SUN4I_HDMI_DDC_CTRL_FIFO_DIR_WRITE), + hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); + + writel(SUN4I_HDMI_DDC_ADDR_SLAVE(msg->addr), + hdmi->base + SUN4I_HDMI_DDC_ADDR_REG); + + ret = xfer_msg_chunk(hdmi, msg); + if (ret) + return ret; + } + + return 0; +} + +static int sun4i_hdmi_i2c_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + struct sun4i_hdmi *hdmi = i2c_get_adapdata(adap); + u32 reg; + int err, ret = num; + int i; + + /* Reset i2c controller */ + writel(SUN4I_HDMI_DDC_CTRL_ENABLE | SUN4I_HDMI_DDC_CTRL_RESET, + hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); + if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg, + !(reg & SUN4I_HDMI_DDC_CTRL_RESET), + 100, 2000)) + return -EIO; + + writel(SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE | + SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE, + hdmi->base + SUN4I_HDMI_DDC_LINE_CTRL_REG); + + clk_prepare_enable(hdmi->ddc_clk); + clk_set_rate(hdmi->ddc_clk, 100000); + + for (i = 0; i < num; i++) { + err = xfer_msg(hdmi, &msgs[i]); + if (err) { + ret = err; + break; + } + } + + clk_disable_unprepare(hdmi->ddc_clk); + return ret; +} + +static u32 sun4i_hdmi_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm sun4i_hdmi_i2c_algorithm = { + .master_xfer = sun4i_hdmi_i2c_xfer, + .functionality = sun4i_hdmi_i2c_func, +}; + +static struct i2c_adapter sun4i_hdmi_i2c_adapter = { + .owner = THIS_MODULE, + .class = I2C_CLASS_DDC, + .algo = &sun4i_hdmi_i2c_algorithm, + .name = "sun4i_hdmi_i2c adapter", +}; + +int sun4i_hdmi_i2c_create(struct sun4i_hdmi *hdmi) +{ + int ret = 0; + + i2c_set_adapdata(&sun4i_hdmi_i2c_adapter, hdmi); + + ret = i2c_add_adapter(&sun4i_hdmi_i2c_adapter); + if (ret) + return ret; + + hdmi->i2c = &sun4i_hdmi_i2c_adapter; + + return ret; +}