|
@@ -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)
|
|
|
{
|
|
@@ -1517,16 +1725,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)
|
|
@@ -1751,6 +1983,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.
|
|
@@ -1791,11 +2030,20 @@ 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);
|
|
@@ -1821,13 +2069,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");
|