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. -----BEGIN PGP SIGNATURE----- iQI0BAABCAAeBQJX34wqFxxwLnphYmVsQHBlbmd1dHJvbml4LmRlAAoJEFDCiBxw nmDr7J0QAOhuvrQ03OhQ/WlnvdAfHG8+G7qFT38zzG9klWBhGYWgPNoZyTGCjmOB pljEo3lAPjc5YIZcPWpDFU4GIE875lE4vKlpnjIHyFJKaAcnZxy3fh7AEdEjlfhd PdHRpsLNsTxfZbvQnELwPl9qx1+OQjlSUCAQn8kFyyy5ejOWg3lXcZenPr/DWKxs KpQjV3k/HZ5EbRLxxhMYCm5xgb0bI3oVjWw48xnLfIOY3UFlzaJ1dTmPCaWmA4VZ 7js52FRoeqCcq8OYEcq8Ix/72eNtWW7STJOlMqrB0TbTGKUwK0FEPu7k0xcn2OwK 6quA8s/1WzTxqkBAkhN7UkUlZVonle+V7iAhe0zAE4zNIUttyUhIazPVGPqWRLf2 FQdZC2r0hyRL5SijS/QowbPL75zB8cBQ/l9Et67wH2eSCC/ZijlQoAKtvWE4a5Tt KuW8YPwGUJpVHiUpur+/tyWLtPgr40IwEfwx52F4HfT+4aY5rmKyHbi3y6nkK3vC aXl1dtomU8p2tYf1iuXymI5e9gaz5GdEiR30ynIBMBsfgawUJL7bTDG9NFuhaioU PoHia1zWBOXL8gYrvVT7tU4yeN9onx7LjPS8V8r0BszZ/6Y2hIfvXAX6zO9TLj4Y kmWBJdCp937y/+DJ25YxA2e67ManONx7XHv5T2aix3w64uKzCZIw =aAzo -----END PGP SIGNATURE----- Merge tag 'dw-hdmi-next-2016-09-19' of git://git.pengutronix.de/git/pza/linux into drm-next 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
This commit is contained in:
Коммит
90233ee5d1
|
@ -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:
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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__ */
|
||||
|
|
Загрузка…
Ссылка в новой задаче