From 934d1431929bb5ecde68fb38fe4b2a6f4ab0fba6 Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Mon, 3 Jul 2017 21:35:20 +0530 Subject: drm/sun4i: constify drm_plane_helper_funcs drm_plane_helper_funcs are not supposed to change at runtime. All functions working with drm_plane_helper_funcs provided by work with const drm_plane_helper_funcs. So mark the non-const structs as const. File size before: text data bss dec hex filename 981 40 0 1021 3fd drivers/gpu/drm/sun4i/sun4i_layer.o File size After adding 'const': text data bss dec hex filename 1021 0 0 1021 3fd drivers/gpu/drm/sun4i/sun4i_layer.o Signed-off-by: Arvind Yadav Signed-off-by: Maxime Ripard --- drivers/gpu/drm/sun4i/sun4i_layer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/sun4i/sun4i_layer.c b/drivers/gpu/drm/sun4i/sun4i_layer.c index ead4f9d4c1ee..6a887ac28aae 100644 --- a/drivers/gpu/drm/sun4i/sun4i_layer.c +++ b/drivers/gpu/drm/sun4i/sun4i_layer.c @@ -52,7 +52,7 @@ static void sun4i_backend_layer_atomic_update(struct drm_plane *plane, sun4i_backend_layer_enable(backend, layer->id, true); } -static struct drm_plane_helper_funcs sun4i_backend_layer_helper_funcs = { +static const struct drm_plane_helper_funcs sun4i_backend_layer_helper_funcs = { .atomic_check = sun4i_backend_layer_atomic_check, .atomic_disable = sun4i_backend_layer_atomic_disable, .atomic_update = sun4i_backend_layer_atomic_update, -- cgit v1.2.3 From f0a3dd33ba685bc50f78455aec832ebcc129a687 Mon Sep 17 00:00:00 2001 From: Jonathan Liu Date: Sun, 2 Jul 2017 17:27:10 +1000 Subject: drm/sun4i: hdmi: Implement I2C adapter for A10s DDC bus The documentation for drm_do_get_edid in drivers/gpu/drm/drm_edid.c states: "As in the general case the DDC bus is accessible by the kernel at the I2C level, drivers must make all reasonable efforts to expose it as an I2C adapter and use drm_get_edid() instead of abusing this function." Exposing the DDC bus as an I2C adapter is more beneficial as it can be used for purposes other than reading the EDID such as modifying the EDID or using the HDMI DDC pins as an I2C bus through the I2C dev interface from userspace (e.g. i2c-tools). Implement this for A10s. Signed-off-by: Jonathan Liu Signed-off-by: Maxime Ripard --- drivers/gpu/drm/sun4i/Makefile | 1 + drivers/gpu/drm/sun4i/sun4i_hdmi.h | 24 ++++ drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 101 ++------------- drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c | 220 +++++++++++++++++++++++++++++++++ 4 files changed, 256 insertions(+), 90 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..0957ff2076ac 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi.h +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h @@ -96,6 +96,7 @@ #define SUN4I_HDMI_DDC_CTRL_ENABLE BIT(31) #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_WRITE (1 << 8) #define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ (0 << 8) #define SUN4I_HDMI_DDC_CTRL_RESET BIT(0) @@ -105,14 +106,34 @@ #define SUN4I_HDMI_DDC_ADDR_OFFSET(off) (((off) & 0xff) << 8) #define SUN4I_HDMI_DDC_ADDR_SLAVE(addr) ((addr) & 0xff) +#define SUN4I_HDMI_DDC_INT_STATUS_REG 0x50c +#define SUN4I_HDMI_DDC_INT_STATUS_ILLEGAL_FIFO_OPERATION BIT(7) +#define SUN4I_HDMI_DDC_INT_STATUS_DDC_RX_FIFO_UNDERFLOW BIT(6) +#define SUN4I_HDMI_DDC_INT_STATUS_DDC_TX_FIFO_OVERFLOW BIT(5) +#define SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST BIT(4) +#define SUN4I_HDMI_DDC_INT_STATUS_ARBITRATION_ERROR BIT(3) +#define SUN4I_HDMI_DDC_INT_STATUS_ACK_ERROR BIT(2) +#define SUN4I_HDMI_DDC_INT_STATUS_BUS_ERROR BIT(1) +#define SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE BIT(0) + #define SUN4I_HDMI_DDC_FIFO_CTRL_REG 0x510 #define SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR BIT(31) +#define SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES(n) (((n) & 0xf) << 4) +#define SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MASK GENMASK(7, 4) +#define SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MAX (BIT(4) - 1) +#define SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES(n) ((n) & 0xf) +#define SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MASK GENMASK(3, 0) +#define SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MAX (BIT(4) - 1) #define SUN4I_HDMI_DDC_FIFO_DATA_REG 0x518 + #define SUN4I_HDMI_DDC_BYTE_COUNT_REG 0x51c +#define SUN4I_HDMI_DDC_BYTE_COUNT_MAX (BIT(10) - 1) #define SUN4I_HDMI_DDC_CMD_REG 0x520 #define SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ 6 +#define SUN4I_HDMI_DDC_CMD_IMPLICIT_READ 5 +#define SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE 3 #define SUN4I_HDMI_DDC_CLK_REG 0x528 #define SUN4I_HDMI_DDC_CLK_M(m) (((m) & 0x7) << 3) @@ -146,6 +167,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 +176,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 device *dev, 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..b74607feb35c 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; } @@ -407,9 +323,9 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master, SUN4I_HDMI_PLL_CTRL_PLL_EN; writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); - ret = sun4i_ddc_create(hdmi, hdmi->tmds_clk); + ret = sun4i_hdmi_i2c_create(dev, hdmi); if (ret) { - dev_err(dev, "Couldn't create the DDC clock\n"); + dev_err(dev, "Couldn't create the HDMI I2C adapter\n"); return ret; } @@ -422,13 +338,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 +369,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 +381,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..2e42d09ab42e --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2016 Maxime Ripard + * Copyright (C) 2017 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" + +#define SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK ( \ + SUN4I_HDMI_DDC_INT_STATUS_ILLEGAL_FIFO_OPERATION | \ + SUN4I_HDMI_DDC_INT_STATUS_DDC_RX_FIFO_UNDERFLOW | \ + SUN4I_HDMI_DDC_INT_STATUS_DDC_TX_FIFO_OVERFLOW | \ + SUN4I_HDMI_DDC_INT_STATUS_ARBITRATION_ERROR | \ + SUN4I_HDMI_DDC_INT_STATUS_ACK_ERROR | \ + SUN4I_HDMI_DDC_INT_STATUS_BUS_ERROR \ +) + +/* FIFO request bit is set when FIFO level is above RX_THRESHOLD during read */ +#define RX_THRESHOLD SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MAX +/* FIFO request bit is set when FIFO level is below TX_THRESHOLD during write */ +#define TX_THRESHOLD 1 + +static int fifo_transfer(struct sun4i_hdmi *hdmi, u8 *buf, int len, bool read) +{ + /* + * 1 byte takes 9 clock cycles (8 bits + 1 ACK) = 90 us for 100 kHz + * clock. As clock rate is fixed, just round it up to 100 us. + */ + const unsigned long byte_time_ns = 100; + const u32 mask = SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK | + SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST | + SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE; + u32 reg; + + /* Limit transfer length by FIFO threshold */ + len = min_t(int, len, read ? (RX_THRESHOLD + 1) : + (SUN4I_HDMI_DDC_FIFO_SIZE - TX_THRESHOLD + 1)); + + /* Wait until error, FIFO request bit set or transfer complete */ + if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG, reg, + reg & mask, len * byte_time_ns, 100000)) + return -ETIMEDOUT; + + if (reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK) + return -EIO; + + if (read) + readsb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG, buf, len); + else + writesb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG, buf, len); + + /* Clear FIFO request bit */ + writel(SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST, + hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG); + + return len; +} + +static int xfer_msg(struct sun4i_hdmi *hdmi, struct i2c_msg *msg) +{ + int i, len; + u32 reg; + + /* Set FIFO direction */ + reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); + reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK; + reg |= (msg->flags & I2C_M_RD) ? + SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ : + SUN4I_HDMI_DDC_CTRL_FIFO_DIR_WRITE; + writel(reg, hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); + + /* Set I2C address */ + writel(SUN4I_HDMI_DDC_ADDR_SLAVE(msg->addr), + hdmi->base + SUN4I_HDMI_DDC_ADDR_REG); + + /* Set FIFO RX/TX thresholds and clear FIFO */ + reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG); + reg |= SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR; + reg &= ~SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MASK; + reg |= SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES(RX_THRESHOLD); + reg &= ~SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MASK; + reg |= SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES(TX_THRESHOLD); + writel(reg, hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG); + if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG, + reg, + !(reg & SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR), + 100, 2000)) + return -EIO; + + /* Set transfer length */ + writel(msg->len, hdmi->base + SUN4I_HDMI_DDC_BYTE_COUNT_REG); + + /* Set command */ + 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); + + /* Clear interrupt status bits */ + writel(SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK | + SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST | + SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE, + hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG); + + /* Start command */ + reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); + writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD, + hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); + + /* Transfer bytes */ + for (i = 0; i < msg->len; i += len) { + len = fifo_transfer(hdmi, msg->buf + i, msg->len - i, + msg->flags & I2C_M_RD); + if (len <= 0) + return len; + } + + /* Wait for command to finish */ + if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, + reg, + !(reg & SUN4I_HDMI_DDC_CTRL_START_CMD), + 100, 100000)) + return -EIO; + + /* Check for errors */ + reg = readl(hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG); + if ((reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK) || + !(reg & SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE)) { + return -EIO; + } + + 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, i, ret = num; + + for (i = 0; i < num; i++) { + if (!msgs[i].len) + return -EINVAL; + if (msgs[i].len > SUN4I_HDMI_DDC_BYTE_COUNT_MAX) + return -EINVAL; + } + + /* 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, +}; + +int sun4i_hdmi_i2c_create(struct device *dev, struct sun4i_hdmi *hdmi) +{ + struct i2c_adapter *adap; + int ret = 0; + + ret = sun4i_ddc_create(hdmi, hdmi->tmds_clk); + if (ret) + return ret; + + adap = devm_kzalloc(dev, sizeof(*adap), GFP_KERNEL); + if (!adap) + return -ENOMEM; + + adap->owner = THIS_MODULE; + adap->class = I2C_CLASS_DDC; + adap->algo = &sun4i_hdmi_i2c_algorithm; + strlcpy(adap->name, "sun4i_hdmi_i2c adapter", sizeof(adap->name)); + i2c_set_adapdata(adap, hdmi); + + ret = i2c_add_adapter(adap); + if (ret) + return ret; + + hdmi->i2c = adap; + + return ret; +} -- cgit v1.2.3 From f4c08112ad35f6a1eb7ba498a47cc58c08a175a8 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Thu, 13 Jul 2017 11:54:00 +0200 Subject: drm/sun4i: Add if statement instead of depends on The depends on relationship is obvious, and using an if statement will propagate it to every option without the need for each and every one of them to define it. Reviewed-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard --- drivers/gpu/drm/sun4i/Kconfig | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig index 5bcad8f5fb4f..5cc116692913 100644 --- a/drivers/gpu/drm/sun4i/Kconfig +++ b/drivers/gpu/drm/sun4i/Kconfig @@ -13,9 +13,10 @@ config DRM_SUN4I Display Engine. If M is selected the module will be called sun4i-drm. +if DRM_SUN4I + config DRM_SUN4I_HDMI tristate "Allwinner A10 HDMI Controller Support" - depends on DRM_SUN4I default DRM_SUN4I help Choose this option if you have an Allwinner SoC with an HDMI @@ -23,7 +24,6 @@ config DRM_SUN4I_HDMI config DRM_SUN4I_BACKEND tristate "Support for Allwinner A10 Display Engine Backend" - depends on DRM_SUN4I default DRM_SUN4I help Choose this option if you have an Allwinner SoC with the @@ -33,10 +33,11 @@ config DRM_SUN4I_BACKEND config DRM_SUN8I_MIXER tristate "Support for Allwinner Display Engine 2.0 Mixer" - depends on DRM_SUN4I default MACH_SUN8I help Choose this option if you have an Allwinner SoC with the Allwinner Display Engine 2.0, which has a mixer to do some graphics mixture and feed graphics to TCON, If M is selected the module will be called sun8i-mixer. + +endif -- cgit v1.2.3 From 7d1a2bf41f06b360724f9b2156cc8a2da816b15c Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Wed, 21 Jun 2017 15:54:53 +0200 Subject: drm/sun4i: Remove useless atomic_check The atomic_check callback is optional, and we don't implement anything in some parts of our drivers. Let's remove it. Reviewed-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard --- drivers/gpu/drm/sun4i/sun4i_layer.c | 7 ------- drivers/gpu/drm/sun4i/sun4i_rgb.c | 8 -------- drivers/gpu/drm/sun4i/sun4i_tv.c | 8 -------- 3 files changed, 23 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun4i_layer.c b/drivers/gpu/drm/sun4i/sun4i_layer.c index 6a887ac28aae..b0c1155891b4 100644 --- a/drivers/gpu/drm/sun4i/sun4i_layer.c +++ b/drivers/gpu/drm/sun4i/sun4i_layer.c @@ -25,12 +25,6 @@ struct sun4i_plane_desc { uint32_t nformats; }; -static int sun4i_backend_layer_atomic_check(struct drm_plane *plane, - struct drm_plane_state *state) -{ - return 0; -} - static void sun4i_backend_layer_atomic_disable(struct drm_plane *plane, struct drm_plane_state *old_state) { @@ -53,7 +47,6 @@ static void sun4i_backend_layer_atomic_update(struct drm_plane *plane, } static const struct drm_plane_helper_funcs sun4i_backend_layer_helper_funcs = { - .atomic_check = sun4i_backend_layer_atomic_check, .atomic_disable = sun4i_backend_layer_atomic_disable, .atomic_update = sun4i_backend_layer_atomic_update, }; diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c index 422b191faa77..76362c09c608 100644 --- a/drivers/gpu/drm/sun4i/sun4i_rgb.c +++ b/drivers/gpu/drm/sun4i/sun4i_rgb.c @@ -128,13 +128,6 @@ static struct drm_connector_funcs sun4i_rgb_con_funcs = { .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, }; -static int sun4i_rgb_atomic_check(struct drm_encoder *encoder, - struct drm_crtc_state *crtc_state, - struct drm_connector_state *conn_state) -{ - return 0; -} - static void sun4i_rgb_encoder_enable(struct drm_encoder *encoder) { struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder); @@ -182,7 +175,6 @@ static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder, } static struct drm_encoder_helper_funcs sun4i_rgb_enc_helper_funcs = { - .atomic_check = sun4i_rgb_atomic_check, .mode_set = sun4i_rgb_encoder_mode_set, .disable = sun4i_rgb_encoder_disable, .enable = sun4i_rgb_encoder_enable, diff --git a/drivers/gpu/drm/sun4i/sun4i_tv.c b/drivers/gpu/drm/sun4i/sun4i_tv.c index 338b9e5bb2a3..73bfe7b1cd78 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tv.c +++ b/drivers/gpu/drm/sun4i/sun4i_tv.c @@ -341,13 +341,6 @@ static void sun4i_tv_mode_to_drm_mode(const struct tv_mode *tv_mode, mode->vtotal = mode->vsync_end + tv_mode->vback_porch; } -static int sun4i_tv_atomic_check(struct drm_encoder *encoder, - struct drm_crtc_state *crtc_state, - struct drm_connector_state *conn_state) -{ - return 0; -} - static void sun4i_tv_disable(struct drm_encoder *encoder) { struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder); @@ -489,7 +482,6 @@ static void sun4i_tv_mode_set(struct drm_encoder *encoder, } static struct drm_encoder_helper_funcs sun4i_tv_helper_funcs = { - .atomic_check = sun4i_tv_atomic_check, .disable = sun4i_tv_disable, .enable = sun4i_tv_enable, .mode_set = sun4i_tv_mode_set, -- cgit v1.2.3 From 9cbcbdbaff9e5dbf1b8f4eb28b4f0e18e0f36bbc Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Wed, 21 Jun 2017 15:31:28 +0200 Subject: drm/sun4i: tcon: remove unused function Even though that function is defined in the TCON header, it's not defined nor used anywhere. Remove the prototype. Reviewed-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard --- drivers/gpu/drm/sun4i/sun4i_tcon.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h index e3c50ecdcd04..552c88ec16be 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.h +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h @@ -194,8 +194,6 @@ void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel); void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable); /* Mode Related Controls */ -void sun4i_tcon_switch_interlace(struct sun4i_tcon *tcon, - bool enable); void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel, struct drm_encoder *encoder); void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, -- cgit v1.2.3 From 9181b5bbdff2739efa196c204cb307cc3cab210a Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Tue, 18 Jul 2017 12:02:01 +0800 Subject: dt-bindings: display: sunxi: Improve endpoint ID scheme readability The explanation for the endpoint ID numbering scheme is convoluted and hard to understand. This patch aims to improve the readability of it by combining the existing two paragraphs, while also providing a diagram example, and how endpoints should be numbered based on that example. Signed-off-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard --- .../bindings/display/sunxi/sun4i-drm.txt | 36 ++++++++++++++++------ 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt index b83e6018041d..2ee6ff0ef98e 100644 --- a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt +++ b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt @@ -4,15 +4,33 @@ Allwinner A10 Display Pipeline The Allwinner A10 Display pipeline is composed of several components that are going to be documented below: -For the input port of all components up to the TCON in the display -pipeline, if there are multiple components, the local endpoint IDs -must correspond to the index of the upstream block. For example, if -the remote endpoint is Frontend 1, then the local endpoint ID must -be 1. - -Conversely, for the output ports of the same group, the remote endpoint -ID must be the index of the local hardware block. If the local backend -is backend 1, then the remote endpoint ID must be 1. +For all connections between components up to the TCONs in the display +pipeline, when there are multiple components of the same type at the +same depth, the local endpoint ID must be the same as the remote +component's index. For example, if the remote endpoint is Frontend 1, +then the local endpoint ID must be 1. + + Frontend 0 [0] ------- [0] Backend 0 [0] ------- [0] TCON 0 + [1] -- -- [1] [1] -- -- [1] + \ / \ / + X X + / \ / \ + [0] -- -- [0] [0] -- -- [0] + Frontend 1 [1] ------- [1] Backend 1 [1] ------- [1] TCON 1 + +For a two pipeline system such as the one depicted above, the lines +represent the connections between the components, while the numbers +within the square brackets corresponds to the ID of the local endpoint. + +The same rule also applies to DE 2.0 mixer-TCON connections: + + Mixer 0 [0] ----------- [0] TCON 0 + [1] ---- ---- [1] + \ / + X + / \ + [0] ---- ---- [0] + Mixer 1 [1] ----------- [1] TCON 1 HDMI Encoder ------------ -- cgit v1.2.3 From 998140d26723bcddef5857e39077898b0d1bdb8f Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Tue, 11 Jul 2017 08:30:44 +0200 Subject: sun4i_hdmi: add CEC support Add HDMI CEC support to the Allwinner A10 SoC. This SoC uses a poor-man's CEC implementation by polling the CEC pin. It is using the CEC_PIN core implementation for such devices to do the heavy lifting. It just provides the callbacks to read/drive the CEC pin. Signed-off-by: Hans Verkuil Tested-by: Maxime Ripard Signed-off-by: Maxime Ripard --- drivers/gpu/drm/sun4i/Kconfig | 9 ++++++ drivers/gpu/drm/sun4i/sun4i_hdmi.h | 8 +++++ drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 55 +++++++++++++++++++++++++++++++++- 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig index 5cc116692913..06f05302ee75 100644 --- a/drivers/gpu/drm/sun4i/Kconfig +++ b/drivers/gpu/drm/sun4i/Kconfig @@ -22,6 +22,15 @@ config DRM_SUN4I_HDMI Choose this option if you have an Allwinner SoC with an HDMI controller. +config DRM_SUN4I_HDMI_CEC + bool "Allwinner A10 HDMI CEC Support" + depends on DRM_SUN4I_HDMI + select CEC_CORE + depends on CEC_PIN + help + Choose this option if you have an Allwinner SoC with an HDMI + controller and want to use CEC. + config DRM_SUN4I_BACKEND tristate "Support for Allwinner A10 Display Engine Backend" default DRM_SUN4I diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h index 0957ff2076ac..1457750988da 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi.h +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h @@ -15,6 +15,8 @@ #include #include +#include + #define SUN4I_HDMI_CTRL_REG 0x004 #define SUN4I_HDMI_CTRL_ENABLE BIT(31) @@ -86,6 +88,11 @@ #define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK BIT(21) #define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT 21 +#define SUN4I_HDMI_CEC 0x214 +#define SUN4I_HDMI_CEC_ENABLE BIT(11) +#define SUN4I_HDMI_CEC_TX BIT(9) +#define SUN4I_HDMI_CEC_RX BIT(8) + #define SUN4I_HDMI_PKT_CTRL_REG(n) (0x2f0 + (4 * (n))) #define SUN4I_HDMI_PKT_CTRL_TYPE(n, t) ((t) << (((n) % 4) * 4)) @@ -172,6 +179,7 @@ struct sun4i_hdmi { struct sun4i_drv *drv; bool hdmi_monitor; + struct cec_adapter *cec_adap; }; int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk); diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c index b74607feb35c..863a51618819 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c @@ -197,6 +197,7 @@ static int sun4i_hdmi_get_modes(struct drm_connector *connector) hdmi->hdmi_monitor ? "an HDMI" : "a DVI"); drm_mode_connector_update_edid_property(connector, edid); + cec_s_phys_addr_from_edid(hdmi->cec_adap, edid); ret = drm_add_edid_modes(connector, edid); kfree(edid); @@ -215,8 +216,10 @@ sun4i_hdmi_connector_detect(struct drm_connector *connector, bool force) if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_HPD_REG, reg, reg & SUN4I_HDMI_HPD_HIGH, - 0, 500000)) + 0, 500000)) { + cec_phys_addr_invalidate(hdmi->cec_adap); return connector_status_disconnected; + } return connector_status_connected; } @@ -231,6 +234,40 @@ static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = { .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, }; +#ifdef CONFIG_DRM_SUN4I_HDMI_CEC +static bool sun4i_hdmi_cec_pin_read(struct cec_adapter *adap) +{ + struct sun4i_hdmi *hdmi = cec_get_drvdata(adap); + + return readl(hdmi->base + SUN4I_HDMI_CEC) & SUN4I_HDMI_CEC_RX; +} + +static void sun4i_hdmi_cec_pin_low(struct cec_adapter *adap) +{ + struct sun4i_hdmi *hdmi = cec_get_drvdata(adap); + + /* Start driving the CEC pin low */ + writel(SUN4I_HDMI_CEC_ENABLE, hdmi->base + SUN4I_HDMI_CEC); +} + +static void sun4i_hdmi_cec_pin_high(struct cec_adapter *adap) +{ + struct sun4i_hdmi *hdmi = cec_get_drvdata(adap); + + /* + * Stop driving the CEC pin, the pull up will take over + * unless another CEC device is driving the pin low. + */ + writel(0, hdmi->base + SUN4I_HDMI_CEC); +} + +static const struct cec_pin_ops sun4i_hdmi_cec_pin_ops = { + .read = sun4i_hdmi_cec_pin_read, + .low = sun4i_hdmi_cec_pin_low, + .high = sun4i_hdmi_cec_pin_high, +}; +#endif + static int sun4i_hdmi_bind(struct device *dev, struct device *master, void *data) { @@ -348,6 +385,17 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master, goto err_del_i2c_adapter; } +#ifdef CONFIG_DRM_SUN4I_HDMI_CEC + hdmi->cec_adap = cec_pin_allocate_adapter(&sun4i_hdmi_cec_pin_ops, + hdmi, "sun4i", CEC_CAP_TRANSMIT | CEC_CAP_LOG_ADDRS | + CEC_CAP_PASSTHROUGH | CEC_CAP_RC); + ret = PTR_ERR_OR_ZERO(hdmi->cec_adap); + if (ret < 0) + goto err_cleanup_connector; + writel(readl(hdmi->base + SUN4I_HDMI_CEC) & ~SUN4I_HDMI_CEC_TX, + hdmi->base + SUN4I_HDMI_CEC); +#endif + drm_connector_helper_add(&hdmi->connector, &sun4i_hdmi_connector_helper_funcs); ret = drm_connector_init(drm, &hdmi->connector, @@ -363,11 +411,15 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master, hdmi->connector.polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; + ret = cec_register_adapter(hdmi->cec_adap, dev); + if (ret < 0) + goto err_cleanup_connector; drm_mode_connector_attach_encoder(&hdmi->connector, &hdmi->encoder); return 0; err_cleanup_connector: + cec_delete_adapter(hdmi->cec_adap); drm_encoder_cleanup(&hdmi->encoder); err_del_i2c_adapter: i2c_del_adapter(hdmi->i2c); @@ -379,6 +431,7 @@ static void sun4i_hdmi_unbind(struct device *dev, struct device *master, { struct sun4i_hdmi *hdmi = dev_get_drvdata(dev); + cec_unregister_adapter(hdmi->cec_adap); drm_connector_cleanup(&hdmi->connector); drm_encoder_cleanup(&hdmi->encoder); i2c_del_adapter(hdmi->i2c); -- cgit v1.2.3