From 177cd4fcb004e62b1ad5f5dfdd65e0d0b5187fea Mon Sep 17 00:00:00 2001 From: Algea Cao Date: Tue, 16 Apr 2024 11:45:30 +0800 Subject: [PATCH] drm: bridge: dw-hdmi-qp: Optimized hdmi ddc transfer process 1.Reading edid consecutively 16 bytes at a time to reduce the time consumed. 2.Increase the number of retries on failure to ensure successful transfer. Change-Id: I0ea92a4a80634771d1ecc7abe573986649a908e4 Signed-off-by: Algea Cao --- drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 150 ++++++++++++++----- drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h | 3 + 2 files changed, 114 insertions(+), 39 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c index b39c37fa59ed..b0a1fe0b0a11 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c @@ -50,6 +50,7 @@ #define HDMI_EDID_LEN 512 #define VENDOR_INFO_LEN 10 +#define HDMI_EDID_BLOCK_LEN 128 /* DW-HDMI Controller >= 0x200a are at least compliant with SCDC version 1 */ #define SCDC_MIN_SOURCE_VERSION 0x1 @@ -1013,7 +1014,8 @@ static int dw_hdmi_i2c_read(struct dw_hdmi_qp *hdmi, unsigned char *buf, unsigned int length) { struct dw_hdmi_qp_i2c *i2c = hdmi->i2c; - int stat; + int stat, retry; + bool read_edid = false; if (!i2c->is_regaddr) { dev_dbg(hdmi->dev, "set read register address to 0\n"); @@ -1021,36 +1023,86 @@ static int dw_hdmi_i2c_read(struct dw_hdmi_qp *hdmi, i2c->is_regaddr = true; } - while (length--) { - reinit_completion(&i2c->cmp); + /* edid reads are in 128 bytes. scdc reads are in 1 byte */ + if (length == HDMI_EDID_BLOCK_LEN) + read_edid = true; - hdmi_modb(hdmi, i2c->slave_reg++ << 12, I2CM_ADDR, + while (length > 0) { + retry = 100; + hdmi_modb(hdmi, i2c->slave_reg << 12, I2CM_ADDR, I2CM_INTERFACE_CONTROL0); - if (i2c->is_segment) - hdmi_modb(hdmi, I2CM_EXT_READ, I2CM_WR_MASK, + if (read_edid) { + hdmi_modb(hdmi, I2CM_16BYTES, I2CM_NBYTES_MASK, I2CM_INTERFACE_CONTROL0); - else - hdmi_modb(hdmi, I2CM_FM_READ, I2CM_WR_MASK, + i2c->slave_reg += 16; + length -= 16; + } else { + hdmi_modb(hdmi, I2CM_1BYTES, I2CM_NBYTES_MASK, I2CM_INTERFACE_CONTROL0); - - stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10); - if (!stat) { - dev_err(hdmi->dev, "i2c read time out!\n"); - hdmi_writel(hdmi, 0x01, I2CM_CONTROL0); - return -EAGAIN; + i2c->slave_reg++; + length--; } - /* Check for error condition on the bus */ - if (i2c->stat & I2CM_NACK_RCVD_IRQ) { - dev_err(hdmi->dev, "i2c read err!\n"); - hdmi_writel(hdmi, 0x01, I2CM_CONTROL0); + while (retry > 0) { + if (hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data) != + connector_status_connected) { + dev_dbg(hdmi->dev, "hdmi disconnect, stop ddc read\n"); + return -EPERM; + } + + reinit_completion(&i2c->cmp); + + if (i2c->is_segment) + hdmi_modb(hdmi, I2CM_EXT_READ, I2CM_WR_MASK, + I2CM_INTERFACE_CONTROL0); + else + hdmi_modb(hdmi, I2CM_FM_READ, I2CM_WR_MASK, + I2CM_INTERFACE_CONTROL0); + + stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10); + if (!stat) { + dev_err(hdmi->dev, "i2c read time out!\n"); + hdmi_writel(hdmi, 0x01, I2CM_CONTROL0); + retry -= 10; + continue; + } + + /* Check for error condition on the bus */ + if (i2c->stat & I2CM_NACK_RCVD_IRQ) { + dev_err(hdmi->dev, "i2c read err!\n"); + hdmi_writel(hdmi, 0x01, I2CM_CONTROL0); + retry--; + usleep_range(10000, 11000); + continue; + } + /* read success */ + break; + } + + if (retry <= 0) { + dev_err(hdmi->dev, "ddc read failed offset:0x%x\n", i2c->slave_reg); return -EIO; } - *buf++ = hdmi_readl(hdmi, I2CM_INTERFACE_RDDATA_0_3) & 0xff; - dev_dbg(hdmi->dev, "i2c read done! i2c->stat:%02x 0x%02x\n", - i2c->stat, hdmi_readl(hdmi, I2CM_INTERFACE_RDDATA_0_3)); + if (read_edid) { + u8 reg_offset, val_offset, i; + u32 val; + + for (i = 0; i < 16; i++) { + reg_offset = i / 4; + val_offset = (i % 4) * 8; + val = hdmi_readl(hdmi, I2CM_INTERFACE_RDDATA_0_3 + 4 * reg_offset); + *buf++ = (val & (0xff << val_offset)) >> val_offset; + dev_dbg(hdmi->dev, "i2c read done! i2c->stat:%02x 0x%02x\n", + i2c->stat, (val & (0xff << val_offset)) >> val_offset); + } + } else { + *buf++ = hdmi_readl(hdmi, I2CM_INTERFACE_RDDATA_0_3) & 0xff; + dev_dbg(hdmi->dev, "i2c read done! i2c->stat:%02x 0x%02x\n", + i2c->stat, hdmi_readl(hdmi, I2CM_INTERFACE_RDDATA_0_3)); + } + hdmi_modb(hdmi, 0, I2CM_WR_MASK, I2CM_INTERFACE_CONTROL0); } i2c->is_segment = false; @@ -1062,7 +1114,7 @@ static int dw_hdmi_i2c_write(struct dw_hdmi_qp *hdmi, unsigned char *buf, unsigned int length) { struct dw_hdmi_qp_i2c *i2c = hdmi->i2c; - int stat; + int stat, retry; if (!i2c->is_regaddr) { /* Use the first write byte as register address */ @@ -1073,28 +1125,48 @@ static int dw_hdmi_i2c_write(struct dw_hdmi_qp *hdmi, } while (length--) { - reinit_completion(&i2c->cmp); + retry = 100; - hdmi_writel(hdmi, *buf++, I2CM_INTERFACE_WRDATA_0_3); - hdmi_modb(hdmi, i2c->slave_reg++ << 12, I2CM_ADDR, - I2CM_INTERFACE_CONTROL0); - hdmi_modb(hdmi, I2CM_FM_WRITE, I2CM_WR_MASK, - I2CM_INTERFACE_CONTROL0); + while (retry > 0) { + if (hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data) != + connector_status_connected) { + dev_dbg(hdmi->dev, "hdmi disconnect, stop ddc write\n"); + return -EPERM; + } - stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10); - if (!stat) { - dev_err(hdmi->dev, "i2c write time out!\n"); - hdmi_writel(hdmi, 0x01, I2CM_CONTROL0); - return -EAGAIN; - } + reinit_completion(&i2c->cmp); - /* Check for error condition on the bus */ - if (i2c->stat & I2CM_NACK_RCVD_IRQ) { - dev_err(hdmi->dev, "i2c write nack!\n"); - hdmi_writel(hdmi, 0x01, I2CM_CONTROL0); - return -EIO; + hdmi_writel(hdmi, *buf++, I2CM_INTERFACE_WRDATA_0_3); + hdmi_modb(hdmi, i2c->slave_reg++ << 12, I2CM_ADDR, + I2CM_INTERFACE_CONTROL0); + hdmi_modb(hdmi, I2CM_FM_WRITE, I2CM_WR_MASK, + I2CM_INTERFACE_CONTROL0); + + stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10); + if (!stat) { + dev_err(hdmi->dev, "i2c write time out!\n"); + hdmi_writel(hdmi, 0x01, I2CM_CONTROL0); + retry -= 10; + continue; + } + + /* Check for error condition on the bus */ + if (i2c->stat & I2CM_NACK_RCVD_IRQ) { + dev_err(hdmi->dev, "i2c write nack!\n"); + hdmi_writel(hdmi, 0x01, I2CM_CONTROL0); + retry--; + usleep_range(10000, 11000); + continue; + } + /* write success */ + break; } hdmi_modb(hdmi, 0, I2CM_WR_MASK, I2CM_INTERFACE_CONTROL0); + + if (retry <= 0) { + dev_err(hdmi->dev, "ddc write failed\n"); + return -EIO; + } } dev_dbg(hdmi->dev, "i2c write done!\n"); return 0; diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h index e9b5e19a3be5..d5f02068dbfe 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h @@ -60,6 +60,9 @@ #define I2CM_ADDR 0xff000 #define I2CM_SLVADDR 0xfe0 #define I2CM_WR_MASK 0x1e +#define I2CM_NBYTES_MASK (0xf << 20) +#define I2CM_16BYTES (0xf << 20) +#define I2CM_1BYTES (0 << 20) #define I2CM_EXT_READ BIT(4) #define I2CM_SHORT_READ BIT(3) #define I2CM_FM_READ BIT(2)