]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
Merge tag 'dw-hdmi-next-2016-09-19' of git://git.pengutronix.de/git/pza/linux into...
authorDave Airlie <airlied@redhat.com>
Thu, 10 Nov 2016 23:57:54 +0000 (09:57 +1000)
committerDave Airlie <airlied@redhat.com>
Thu, 10 Nov 2016 23:57:54 +0000 (09:57 +1000)
dw-hdmi i2c master controller

- add support for the HDMI I2C master controller, for boards that
  can have their DDC pins connected only to the HDMI TX directly.

* tag 'dw-hdmi-next-2016-09-19' of git://git.pengutronix.de/git/pza/linux:
  drm: bridge/dw_hdmi: add dw hdmi i2c bus adapter support
  drm: dw_hdmi: use of_get_i2c_adapter_by_node interface

Documentation/devicetree/bindings/display/bridge/dw_hdmi.txt
drivers/gpu/drm/bridge/dw-hdmi.c
drivers/gpu/drm/bridge/dw-hdmi.h

index dc1452f0d5d81a7cc931cb52fbe89b6f5751e706..5e9a84d6e5f14c107b0b7d55269b71316da12aef 100644 (file)
@@ -19,7 +19,9 @@ Required properties:
 
 Optional properties
 - reg-io-width: the width of the reg:1,4, default set to 1 if not present
-- ddc-i2c-bus: phandle of an I2C controller used for DDC EDID probing
+- ddc-i2c-bus: phandle of an I2C controller used for DDC EDID probing,
+  if the property is omitted, a functionally reduced I2C bus
+  controller on DW HDMI is probed
 - clocks, clock-names: phandle to the HDMI CEC clock, name should be "cec"
 
 Example:
index ab7023e5dfdec6d5981f48a46f35aca67df8ac7b..b71088dab2685b56e30179f0c13410f22138ea1f 100644 (file)
@@ -1,14 +1,15 @@
 /*
+ * 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>
@@ -101,6 +102,17 @@ struct hdmi_data_info {
        struct hdmi_vmode video_mode;
 };
 
+struct dw_hdmi_i2c {
+       struct i2c_adapter      adap;
+
+       struct mutex            lock;   /* used to serialize data transfers */
+       struct completion       cmp;
+       u8                      stat;
+
+       u8                      slave_reg;
+       bool                    is_regaddr;
+};
+
 struct dw_hdmi {
        struct drm_connector connector;
        struct drm_encoder *encoder;
@@ -111,6 +123,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;
@@ -198,6 +211,201 @@ 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)
+{
+       /* Software reset */
+       hdmi_writeb(hdmi, 0x00, HDMI_I2CM_SOFTRSTZ);
+
+       /* Set Standard Mode speed (determined to be 100KHz on iMX6) */
+       hdmi_writeb(hdmi, 0x00, HDMI_I2CM_DIV);
+
+       /* 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);
+}
+
+static int dw_hdmi_i2c_read(struct dw_hdmi *hdmi,
+                           unsigned char *buf, unsigned int length)
+{
+       struct dw_hdmi_i2c *i2c = hdmi->i2c;
+       int stat;
+
+       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--) {
+               reinit_completion(&i2c->cmp);
+
+               hdmi_writeb(hdmi, i2c->slave_reg++, HDMI_I2CM_ADDRESS);
+               hdmi_writeb(hdmi, HDMI_I2CM_OPERATION_READ,
+                           HDMI_I2CM_OPERATION);
+
+               stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10);
+               if (!stat)
+                       return -EAGAIN;
+
+               /* Check for error condition on the bus */
+               if (i2c->stat & HDMI_IH_I2CM_STAT0_ERROR)
+                       return -EIO;
+
+               *buf++ = hdmi_readb(hdmi, HDMI_I2CM_DATAI);
+       }
+
+       return 0;
+}
+
+static int dw_hdmi_i2c_write(struct dw_hdmi *hdmi,
+                            unsigned char *buf, unsigned int length)
+{
+       struct dw_hdmi_i2c *i2c = hdmi->i2c;
+       int stat;
+
+       if (!i2c->is_regaddr) {
+               /* Use the first write byte as register address */
+               i2c->slave_reg = buf[0];
+               length--;
+               buf++;
+               i2c->is_regaddr = true;
+       }
+
+       while (length--) {
+               reinit_completion(&i2c->cmp);
+
+               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);
+
+               stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10);
+               if (!stat)
+                       return -EAGAIN;
+
+               /* Check for error condition on the bus */
+               if (i2c->stat & HDMI_IH_I2CM_STAT0_ERROR)
+                       return -EIO;
+       }
+
+       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;
+       u8 addr = msgs[0].addr;
+       int i, ret = 0;
+
+       dev_dbg(hdmi->dev, "xfer: num: %d, addr: %#x\n", num, addr);
+
+       for (i = 0; i < num; i++) {
+               if (msgs[i].addr != addr) {
+                       dev_warn(hdmi->dev,
+                                "unsupported transfer, changed slave address\n");
+                       return -EOPNOTSUPP;
+               }
+
+               if (msgs[i].len == 0) {
+                       dev_dbg(hdmi->dev,
+                               "unsupported transfer %d/%d, no data\n",
+                               i + 1, num);
+                       return -EOPNOTSUPP;
+               }
+       }
+
+       mutex_lock(&i2c->lock);
+
+       /* Unmute DONE and ERROR interrupts */
+       hdmi_writeb(hdmi, 0x00, HDMI_IH_MUTE_I2CM_STAT0);
+
+       /* Set slave device address taken from the first I2C message */
+       hdmi_writeb(hdmi, addr, HDMI_I2CM_SLAVE);
+
+       /* Set slave device register address on transfer */
+       i2c->is_regaddr = false;
+
+       for (i = 0; i < num; i++) {
+               dev_dbg(hdmi->dev, "xfer: num: %d/%d, len: %d, flags: %#x\n",
+                       i + 1, num, msgs[i].len, msgs[i].flags);
+
+               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;
+
+       /* Mute DONE and ERROR interrupts */
+       hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE,
+                   HDMI_IH_MUTE_I2CM_STAT0);
+
+       mutex_unlock(&i2c->lock);
+
+       return ret;
+}
+
+static u32 dw_hdmi_i2c_func(struct i2c_adapter *adapter)
+{
+       return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const 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;
+       struct dw_hdmi_i2c *i2c;
+       int ret;
+
+       i2c = devm_kzalloc(hdmi->dev, sizeof(*i2c), GFP_KERNEL);
+       if (!i2c)
+               return ERR_PTR(-ENOMEM);
+
+       mutex_init(&i2c->lock);
+       init_completion(&i2c->cmp);
+
+       adap = &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->dev, i2c);
+               return ERR_PTR(ret);
+       }
+
+       hdmi->i2c = i2c;
+
+       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)
 {
@@ -1512,16 +1720,40 @@ static const struct drm_bridge_funcs dw_hdmi_bridge_funcs = {
        .mode_set = dw_hdmi_bridge_mode_set,
 };
 
+static irqreturn_t dw_hdmi_i2c_irq(struct dw_hdmi *hdmi)
+{
+       struct dw_hdmi_i2c *i2c = hdmi->i2c;
+       unsigned int stat;
+
+       stat = hdmi_readb(hdmi, HDMI_IH_I2CM_STAT0);
+       if (!stat)
+               return IRQ_NONE;
+
+       hdmi_writeb(hdmi, stat, HDMI_IH_I2CM_STAT0);
+
+       i2c->stat = stat;
+
+       complete(&i2c->cmp);
+
+       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)
@@ -1681,7 +1913,7 @@ int dw_hdmi_bind(struct device *dev, struct device *master,
 
        ddc_node = of_parse_phandle(np, "ddc-i2c-bus", 0);
        if (ddc_node) {
-               hdmi->ddc = of_find_i2c_adapter_by_node(ddc_node);
+               hdmi->ddc = of_get_i2c_adapter_by_node(ddc_node);
                of_node_put(ddc_node);
                if (!hdmi->ddc) {
                        dev_dbg(hdmi->dev, "failed to read ddc node\n");
@@ -1693,20 +1925,22 @@ int dw_hdmi_bind(struct device *dev, struct device *master,
        }
 
        hdmi->regs = devm_ioremap_resource(dev, iores);
-       if (IS_ERR(hdmi->regs))
-               return PTR_ERR(hdmi->regs);
+       if (IS_ERR(hdmi->regs)) {
+               ret = PTR_ERR(hdmi->regs);
+               goto err_res;
+       }
 
        hdmi->isfr_clk = devm_clk_get(hdmi->dev, "isfr");
        if (IS_ERR(hdmi->isfr_clk)) {
                ret = PTR_ERR(hdmi->isfr_clk);
                dev_err(hdmi->dev, "Unable to get HDMI isfr clk: %d\n", ret);
-               return ret;
+               goto err_res;
        }
 
        ret = clk_prepare_enable(hdmi->isfr_clk);
        if (ret) {
                dev_err(hdmi->dev, "Cannot enable HDMI isfr clock: %d\n", ret);
-               return ret;
+               goto err_res;
        }
 
        hdmi->iahb_clk = devm_clk_get(hdmi->dev, "iahb");
@@ -1744,6 +1978,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.
@@ -1784,14 +2025,25 @@ int dw_hdmi_bind(struct device *dev, struct device *master,
                hdmi->audio = platform_device_register_full(&pdevinfo);
        }
 
+       /* Reset HDMI DDC I2C master controller and mute I2CM interrupts */
+       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);
+               hdmi->ddc = NULL;
+       }
+
        clk_disable_unprepare(hdmi->iahb_clk);
 err_isfr:
        clk_disable_unprepare(hdmi->isfr_clk);
+err_res:
+       i2c_put_adapter(hdmi->ddc);
 
        return ret;
 }
@@ -1809,13 +2061,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");
index fc9a560429d6efe94a83c93162c68373e8d6b90a..6aadc840e888ead57a9f5168a52a02732489707e 100644 (file)
@@ -566,6 +566,10 @@ enum {
        HDMI_IH_PHY_STAT0_TX_PHY_LOCK = 0x2,
        HDMI_IH_PHY_STAT0_HPD = 0x1,
 
+/* IH_I2CM_STAT0 and IH_MUTE_I2CM_STAT0 field values */
+       HDMI_IH_I2CM_STAT0_DONE = 0x2,
+       HDMI_IH_I2CM_STAT0_ERROR = 0x1,
+
 /* IH_MUTE_I2CMPHY_STAT0 field values */
        HDMI_IH_MUTE_I2CMPHY_STAT0_I2CMPHYDONE = 0x2,
        HDMI_IH_MUTE_I2CMPHY_STAT0_I2CMPHYERROR = 0x1,
@@ -1032,6 +1036,21 @@ enum {
        HDMI_A_VIDPOLCFG_HSYNCPOL_MASK = 0x2,
        HDMI_A_VIDPOLCFG_HSYNCPOL_ACTIVE_HIGH = 0x2,
        HDMI_A_VIDPOLCFG_HSYNCPOL_ACTIVE_LOW = 0x0,
+
+/* I2CM_OPERATION field values */
+       HDMI_I2CM_OPERATION_WRITE = 0x10,
+       HDMI_I2CM_OPERATION_READ_EXT = 0x2,
+       HDMI_I2CM_OPERATION_READ = 0x1,
+
+/* I2CM_INT field values */
+       HDMI_I2CM_INT_DONE_POL = 0x8,
+       HDMI_I2CM_INT_DONE_MASK = 0x4,
+
+/* I2CM_CTLINT field values */
+       HDMI_I2CM_CTLINT_NAC_POL = 0x80,
+       HDMI_I2CM_CTLINT_NAC_MASK = 0x40,
+       HDMI_I2CM_CTLINT_ARB_POL = 0x8,
+       HDMI_I2CM_CTLINT_ARB_MASK = 0x4,
 };
 
 #endif /* __DW_HDMI_H__ */