This change adds support of internal HDMI I2C master controller, originally the controller has its own separate driver written from scratch http://patchwork.ozlabs.org/patch/405100 but due to shared register space and interrupt with HDMI driver, it makes sense to merge the code of both drivers.
The main purpose of this functionality is to support reading EDID from an HDMI monitor on boards, which don't have an I2C bus connected to DDC pins.
To use/test the change "ddc-i2c-bus" DT property must be omitted and pin settings must be updated accordingly, here is an example for iMX6 SabreLite:
-----------8<----------- diff --git a/arch/arm/boot/dts/imx6qdl-sabrelite.dtsi b/arch/arm/boot/dts/imx6qdl-sabrelite.dtsi index 0b28a9d..22d4431 100644 --- a/arch/arm/boot/dts/imx6qdl-sabrelite.dtsi +++ b/arch/arm/boot/dts/imx6qdl-sabrelite.dtsi @@ -174,7 +174,6 @@ };
&hdmi { - ddc-i2c-bus = <&i2c2>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_hdmi>; status = "okay"; };
@@ -193,13 +192,6 @@ }; };
-&i2c2 { - clock-frequency = <100000>; - pinctrl-names = "default"; - pinctrl-0 = <&pinctrl_i2c2>; - status = "okay"; -}; - &i2c3 { clock-frequency = <100000>; pinctrl-names = "default"; @@ -284,10 +276,10 @@ >; };
- pinctrl_i2c2: i2c2grp { + pinctrl_hdmi: hdmigrp { fsl,pins = < - MX6QDL_PAD_KEY_COL3__I2C2_SCL 0x4001b8b1 - MX6QDL_PAD_KEY_ROW3__I2C2_SDA 0x4001b8b1 + MX6QDL_PAD_KEY_COL3__HDMI_TX_DDC_SCL 0x4001b8b1 + MX6QDL_PAD_KEY_ROW3__HDMI_TX_DDC_SDA 0x4001b8b1 >; };
-----------8<-----------
Vladimir Zapolskiy (2): drm: bridge: fix register I2CM_ADDRESS register name drm: bridge: add dw hdmi i2c bus adapter support
drivers/gpu/drm/bridge/dw_hdmi.c | 332 ++++++++++++++++++++++++++++++++++++++- drivers/gpu/drm/bridge/dw_hdmi.h | 8 +- 2 files changed, 330 insertions(+), 10 deletions(-)
I2CM_ADDRESS became a MESS, fix it, also change guarding define to __DW_HDMI_H__ , since the driver is not IMX specific.
Signed-off-by: Vladimir Zapolskiy vladimir_zapolskiy@mentor.com --- drivers/gpu/drm/bridge/dw_hdmi.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/bridge/dw_hdmi.h b/drivers/gpu/drm/bridge/dw_hdmi.h index 175dbc8..ee7f7ed 100644 --- a/drivers/gpu/drm/bridge/dw_hdmi.h +++ b/drivers/gpu/drm/bridge/dw_hdmi.h @@ -7,8 +7,8 @@ * (at your option) any later version. */
-#ifndef __IMX_HDMI_H__ -#define __IMX_HDMI_H__ +#ifndef __DW_HDMI_H__ +#define __DW_HDMI_H__
/* Identification Registers */ #define HDMI_DESIGN_ID 0x0000 @@ -525,7 +525,7 @@
/* I2C Master Registers (E-DDC) */ #define HDMI_I2CM_SLAVE 0x7E00 -#define HDMI_I2CMESS 0x7E01 +#define HDMI_I2CM_ADDRESS 0x7E01 #define HDMI_I2CM_DATAO 0x7E02 #define HDMI_I2CM_DATAI 0x7E03 #define HDMI_I2CM_OPERATION 0x7E04 @@ -1031,4 +1031,4 @@ enum { HDMI_A_VIDPOLCFG_HSYNCPOL_ACTIVE_LOW = 0x0, };
-#endif /* __IMX_HDMI_H__ */ +#endif /* __DW_HDMI_H__ */
The change adds support of internal HDMI I2C master controller, this subdevice is used by default, if "ddc-i2c-bus" DT property is omitted.
The main purpose of this functionality is to support reading EDID from an HDMI monitor on boards, which don't have an I2C bus connected to DDC pins.
Signed-off-by: Vladimir Zapolskiy vladimir_zapolskiy@mentor.com --- drivers/gpu/drm/bridge/dw_hdmi.c | 332 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 326 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/bridge/dw_hdmi.c b/drivers/gpu/drm/bridge/dw_hdmi.c index 49cafb6..2a52449 100644 --- a/drivers/gpu/drm/bridge/dw_hdmi.c +++ b/drivers/gpu/drm/bridge/dw_hdmi.c @@ -1,15 +1,17 @@ /* + * DesignWare High-Definition Multimedia Interface (HDMI) driver + * + * Copyright (C) 2013-2015 Mentor Graphics Inc. * Copyright (C) 2011-2013 Freescale Semiconductor, Inc. + * Copyright (C) 2010, Guennadi Liakhovetski g.liakhovetski@gmx.de * * 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. * - * Designware High-Definition Multimedia Interface (HDMI) driver - * - * Copyright (C) 2010, Guennadi Liakhovetski g.liakhovetski@gmx.de */ + #include <linux/module.h> #include <linux/irq.h> #include <linux/delay.h> @@ -28,6 +30,26 @@
#include "dw_hdmi.h"
+/* HDMI_IH_I2CM_STAT0 and HDMI_IH_MUTE_I2CM_STAT0 register bits */ +#define HDMI_IH_I2CM_STAT0_ERROR BIT(0) +#define HDMI_IH_I2CM_STAT0_DONE BIT(1) + +/* HDMI_I2CM_OPERATION register bits */ +#define HDMI_I2CM_OPERATION_READ BIT(0) +#define HDMI_I2CM_OPERATION_READ_EXT BIT(1) +#define HDMI_I2CM_OPERATION_WRITE BIT(4) + +/* HDMI_I2CM_INT register bits */ +#define HDMI_I2CM_INT_DONE_MASK BIT(2) +#define HDMI_I2CM_INT_DONE_POL BIT(3) + +/* HDMI_I2CM_CTLINT register bits */ +#define HDMI_I2CM_CTLINT_ARB_MASK BIT(2) +#define HDMI_I2CM_CTLINT_ARB_POL BIT(3) +#define HDMI_I2CM_CTLINT_NAC_MASK BIT(6) +#define HDMI_I2CM_CTLINT_NAC_POL BIT(7) + + #define HDMI_EDID_LEN 512
#define RGB 0 @@ -102,6 +124,17 @@ struct hdmi_data_info { struct hdmi_vmode video_mode; };
+struct dw_hdmi_i2c { + struct i2c_adapter adap; + + spinlock_t lock; + struct completion cmp; + u8 stat; + + u8 slave_reg; + bool is_regaddr; +}; + struct dw_hdmi { struct drm_connector connector; struct drm_encoder *encoder; @@ -111,6 +144,7 @@ struct dw_hdmi { struct device *dev; struct clk *isfr_clk; struct clk *iahb_clk; + struct dw_hdmi_i2c *i2c;
struct hdmi_data_info hdmi_data; const struct dw_hdmi_plat_data *plat_data; @@ -179,6 +213,242 @@ static void hdmi_mask_writeb(struct dw_hdmi *hdmi, u8 data, unsigned int reg, hdmi_modb(hdmi, data << shift, mask, reg); }
+static void dw_hdmi_i2c_init(struct dw_hdmi *hdmi) +{ + unsigned long flags; + + spin_lock_irqsave(&hdmi->i2c->lock, flags); + + /* Set Fast Mode speed */ + hdmi_writeb(hdmi, 0x0b, HDMI_I2CM_DIV); + + /* Software reset */ + hdmi_writeb(hdmi, 0x00, HDMI_I2CM_SOFTRSTZ); + + /* Set done, not acknowledged and arbitration interrupt polarities */ + hdmi_writeb(hdmi, HDMI_I2CM_INT_DONE_POL, HDMI_I2CM_INT); + hdmi_writeb(hdmi, HDMI_I2CM_CTLINT_NAC_POL | HDMI_I2CM_CTLINT_ARB_POL, + HDMI_I2CM_CTLINT); + + /* Clear DONE and ERROR interrupts */ + hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE, + HDMI_IH_I2CM_STAT0); + + /* Mute DONE and ERROR interrupts */ + hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE, + HDMI_IH_MUTE_I2CM_STAT0); + + spin_unlock_irqrestore(&hdmi->i2c->lock, flags); +} + +static int dw_hdmi_i2c_read(struct dw_hdmi *hdmi, + unsigned char *buf, int length) +{ + int stat; + unsigned long flags; + struct dw_hdmi_i2c *i2c = hdmi->i2c; + + spin_lock_irqsave(&i2c->lock, flags); + + if (!i2c->is_regaddr) { + dev_dbg(hdmi->dev, "set read register address to 0\n"); + i2c->slave_reg = 0x00; + i2c->is_regaddr = true; + } + + while (length--) { + hdmi_writeb(hdmi, i2c->slave_reg++, HDMI_I2CM_ADDRESS); + hdmi_writeb(hdmi, + HDMI_I2CM_OPERATION_READ, HDMI_I2CM_OPERATION); + i2c->stat = 0; + + spin_unlock_irqrestore(&i2c->lock, flags); + + stat = wait_for_completion_interruptible_timeout(&i2c->cmp, + HZ / 10); + if (!stat) + return -ETIMEDOUT; + if (stat < 0) + return stat; + + spin_lock_irqsave(&i2c->lock, flags); + + /* Check for error condition on the bus */ + if (i2c->stat & HDMI_IH_I2CM_STAT0_ERROR) { + spin_unlock_irqrestore(&i2c->lock, flags); + return -EIO; + } + + *buf++ = hdmi_readb(hdmi, HDMI_I2CM_DATAI); + } + + spin_unlock_irqrestore(&i2c->lock, flags); + + return 0; +} + +static int dw_hdmi_i2c_write(struct dw_hdmi *hdmi, + unsigned char *buf, int length) +{ + int stat; + unsigned long flags; + struct dw_hdmi_i2c *i2c = hdmi->i2c; + + spin_lock_irqsave(&i2c->lock, flags); + + if (!i2c->is_regaddr) { + if (length) { + /* Use the first write byte as register address */ + i2c->slave_reg = buf[0]; + length--; + buf++; + } else { + dev_dbg(hdmi->dev, "set write register address to 0\n"); + i2c->slave_reg = 0x00; + } + i2c->is_regaddr = true; + } + + while (length--) { + hdmi_writeb(hdmi, *buf++, HDMI_I2CM_DATAO); + hdmi_writeb(hdmi, i2c->slave_reg++, HDMI_I2CM_ADDRESS); + hdmi_writeb(hdmi, + HDMI_I2CM_OPERATION_WRITE, HDMI_I2CM_OPERATION); + i2c->stat = 0; + + spin_unlock_irqrestore(&i2c->lock, flags); + + stat = wait_for_completion_interruptible_timeout(&i2c->cmp, + HZ / 10); + if (!stat) + return -ETIMEDOUT; + if (stat < 0) + return stat; + + spin_lock_irqsave(&i2c->lock, flags); + + /* Check for error condition on the bus */ + if (i2c->stat & HDMI_IH_I2CM_STAT0_ERROR) { + spin_unlock_irqrestore(&i2c->lock, flags); + return -EIO; + } + } + + spin_unlock_irqrestore(&i2c->lock, flags); + + return 0; +} + +static int dw_hdmi_i2c_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + struct dw_hdmi *hdmi = i2c_get_adapdata(adap); + struct dw_hdmi_i2c *i2c = hdmi->i2c; + + int i, ret; + u8 addr; + unsigned long flags; + + dev_dbg(hdmi->dev, "xfer: num: %d, addr: 0x%x\n", num, msgs[0].addr); + + spin_lock_irqsave(&i2c->lock, flags); + + hdmi_writeb(hdmi, 0x00, HDMI_IH_MUTE_I2CM_STAT0); + + /* Set slave device address from the first transaction */ + addr = msgs[0].addr; + hdmi_writeb(hdmi, addr, HDMI_I2CM_SLAVE); + + /* Set slave device register address on transfer */ + i2c->is_regaddr = false; + + spin_unlock_irqrestore(&i2c->lock, flags); + + for (i = 0; i < num; i++) { + dev_dbg(hdmi->dev, "xfer: num: %d/%d, len: %d, flags: 0x%x\n", + i + 1, num, msgs[i].len, msgs[i].flags); + + if (msgs[i].addr != addr) { + dev_warn(hdmi->dev, + "unsupported transaction, changed slave address\n"); + ret = -EOPNOTSUPP; + break; + } + + if (msgs[i].len == 0) { + dev_dbg(hdmi->dev, + "unsupported transaction %d/%d, no data\n", + i + 1, num); + ret = -EOPNOTSUPP; + break; + } + + if (msgs[i].flags & I2C_M_RD) + ret = dw_hdmi_i2c_read(hdmi, msgs[i].buf, msgs[i].len); + else + ret = dw_hdmi_i2c_write(hdmi, msgs[i].buf, msgs[i].len); + + if (ret < 0) + break; + } + + if (!ret) + ret = num; + + spin_lock_irqsave(&i2c->lock, flags); + + /* Mute DONE and ERROR interrupts */ + hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE, + HDMI_IH_MUTE_I2CM_STAT0); + + spin_unlock_irqrestore(&i2c->lock, flags); + + return ret; +} + +static u32 dw_hdmi_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static struct i2c_algorithm dw_hdmi_algorithm = { + .master_xfer = dw_hdmi_i2c_xfer, + .functionality = dw_hdmi_i2c_func, +}; + +static struct i2c_adapter *dw_hdmi_i2c_adapter(struct dw_hdmi *hdmi) +{ + struct i2c_adapter *adap; + int ret; + + hdmi->i2c = devm_kzalloc(hdmi->dev, sizeof(*hdmi->i2c), GFP_KERNEL); + if (!hdmi->i2c) + return ERR_PTR(-ENOMEM); + + spin_lock_init(&hdmi->i2c->lock); + init_completion(&hdmi->i2c->cmp); + + adap = &hdmi->i2c->adap; + adap->class = I2C_CLASS_DDC; + adap->owner = THIS_MODULE; + adap->dev.parent = hdmi->dev; + adap->algo = &dw_hdmi_algorithm; + strlcpy(adap->name, "DesignWare HDMI", sizeof(adap->name)); + i2c_set_adapdata(adap, hdmi); + + ret = i2c_add_adapter(adap); + if (ret) { + dev_warn(hdmi->dev, "cannot add %s I2C adapter\n", adap->name); + devm_kfree(hdmi->i2c); + hdmi->i2c = NULL; + return ERR_PTR(ret); + } + + dev_info(hdmi->dev, "registered %s I2C bus driver\n", adap->name); + + return adap; +} + static void hdmi_set_cts_n(struct dw_hdmi *hdmi, unsigned int cts, unsigned int n) { @@ -1466,16 +1736,47 @@ struct drm_bridge_funcs dw_hdmi_bridge_funcs = { .mode_fixup = dw_hdmi_bridge_mode_fixup, };
+static irqreturn_t dw_hdmi_i2c_irq(struct dw_hdmi *hdmi) +{ + struct dw_hdmi_i2c *i2c = hdmi->i2c; + unsigned long flags; + + spin_lock_irqsave(&i2c->lock, flags); + + i2c->stat = hdmi_readb(hdmi, HDMI_IH_I2CM_STAT0); + if (!i2c->stat) { + spin_unlock_irqrestore(&i2c->lock, flags); + return IRQ_NONE; + } + + hdmi_writeb(hdmi, i2c->stat, HDMI_IH_I2CM_STAT0); + complete(&i2c->cmp); + + dev_dbg(hdmi->dev, "i2cm_stat: 0x%02x, addr: 0x%02x, reg: 0x%02x\n", + i2c->stat, hdmi_readb(hdmi, HDMI_I2CM_SLAVE), + hdmi_readb(hdmi, HDMI_I2CM_ADDRESS)); + + spin_unlock_irqrestore(&i2c->lock, flags); + + return IRQ_HANDLED; +} + static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id) { struct dw_hdmi *hdmi = dev_id; u8 intr_stat; + irqreturn_t ret = IRQ_NONE; + + if (hdmi->i2c) + ret = dw_hdmi_i2c_irq(hdmi);
intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0); - if (intr_stat) + if (intr_stat) { hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0); + return IRQ_WAKE_THREAD; + }
- return intr_stat ? IRQ_WAKE_THREAD : IRQ_NONE; + return ret; }
static irqreturn_t dw_hdmi_irq(int irq, void *dev_id) @@ -1654,6 +1955,13 @@ int dw_hdmi_bind(struct device *dev, struct device *master, */ hdmi_init_clk_regenerator(hdmi);
+ /* If DDC bus is not specified, try to register HDMI I2C bus */ + if (!hdmi->ddc) { + hdmi->ddc = dw_hdmi_i2c_adapter(hdmi); + if (IS_ERR(hdmi->ddc)) + hdmi->ddc = NULL; + } + /* * Configure registers related to HDMI interrupt * generation before registering IRQ. @@ -1674,11 +1982,18 @@ int dw_hdmi_bind(struct device *dev, struct device *master, /* Unmute interrupts */ hdmi_writeb(hdmi, ~HDMI_IH_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0);
+ /* Unmute I2CM interrupts and reset HDMI DDC I2C master controller */ + if (hdmi->i2c) + dw_hdmi_i2c_init(hdmi); + dev_set_drvdata(dev, hdmi);
return 0;
err_iahb: + if (hdmi->i2c) + i2c_del_adapter(&hdmi->i2c->adap); + clk_disable_unprepare(hdmi->iahb_clk); err_isfr: clk_disable_unprepare(hdmi->isfr_clk); @@ -1699,13 +2014,18 @@ void dw_hdmi_unbind(struct device *dev, struct device *master, void *data)
clk_disable_unprepare(hdmi->iahb_clk); clk_disable_unprepare(hdmi->isfr_clk); - i2c_put_adapter(hdmi->ddc); + + if (hdmi->i2c) + i2c_del_adapter(&hdmi->i2c->adap); + else + i2c_put_adapter(hdmi->ddc); } EXPORT_SYMBOL_GPL(dw_hdmi_unbind);
MODULE_AUTHOR("Sascha Hauer s.hauer@pengutronix.de"); MODULE_AUTHOR("Andy Yan andy.yan@rock-chips.com"); MODULE_AUTHOR("Yakir Yang ykk@rock-chips.com"); +MODULE_AUTHOR("Vladimir Zapolskiy vladimir_zapolskiy@mentor.com"); MODULE_DESCRIPTION("DW HDMI transmitter driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:dw-hdmi");
On 17.05.2015 21:03, Vladimir Zapolskiy wrote:
The change adds support of internal HDMI I2C master controller, this subdevice is used by default, if "ddc-i2c-bus" DT property is omitted.
The main purpose of this functionality is to support reading EDID from an HDMI monitor on boards, which don't have an I2C bus connected to DDC pins.
Signed-off-by: Vladimir Zapolskiy vladimir_zapolskiy@mentor.com
drivers/gpu/drm/bridge/dw_hdmi.c | 332 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 326 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/bridge/dw_hdmi.c b/drivers/gpu/drm/bridge/dw_hdmi.c index 49cafb6..2a52449 100644 --- a/drivers/gpu/drm/bridge/dw_hdmi.c +++ b/drivers/gpu/drm/bridge/dw_hdmi.c
[snip]
- ret = i2c_add_adapter(adap);
- if (ret) {
dev_warn(hdmi->dev, "cannot add %s I2C adapter\n", adap->name);
devm_kfree(hdmi->i2c);
Immediately found a compilation problem in last minute update, I'll resend the change, sorry.
hdmi->i2c = NULL;
return ERR_PTR(ret);
- }
-- With best wishes, Vladimir
dri-devel@lists.freedesktop.org