diff options
author | Tin Huynh <tnhuynh@apm.com> | 2016-11-10 05:56:33 +0300 |
---|---|---|
committer | Wolfram Sang <wsa@the-dreams.de> | 2016-11-18 04:06:41 +0300 |
commit | c3ae106050b949d6c776c5434046c888a5a6298a (patch) | |
tree | 840a28fc29aa05b1711e11c79acee3ad8be366cc /drivers/i2c | |
parent | 379883cc67d890e0412d6e6c952d7eb0614b915a (diff) | |
download | linux-c3ae106050b949d6c776c5434046c888a5a6298a.tar.xz |
i2c: designware: Implement support for SMBus block read and write
Free and Open IPMI use SMBUS BLOCK Read/Write to support SSIF protocol.
However, I2C Designware Core Driver doesn't handle the case at the moment.
The below patch supports this feature.
Signed-off-by: Tin Huynh <tnhuynh@apm.com>
Acked-by: Jarkko Nikula <jarkko.nikula@linux.intel.com>
Reviewed-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Wolfram Sang <wsa@the-dreams.de>
Diffstat (limited to 'drivers/i2c')
-rw-r--r-- | drivers/i2c/busses/i2c-designware-core.c | 46 | ||||
-rw-r--r-- | drivers/i2c/busses/i2c-designware-pcidrv.c | 1 | ||||
-rw-r--r-- | drivers/i2c/busses/i2c-designware-platdrv.c | 1 |
3 files changed, 45 insertions, 3 deletions
diff --git a/drivers/i2c/busses/i2c-designware-core.c b/drivers/i2c/busses/i2c-designware-core.c index 11e866d05368..4f56328b906f 100644 --- a/drivers/i2c/busses/i2c-designware-core.c +++ b/drivers/i2c/busses/i2c-designware-core.c @@ -554,6 +554,8 @@ i2c_dw_xfer_msg(struct dw_i2c_dev *dev) intr_mask = DW_IC_INTR_DEFAULT_MASK; for (; dev->msg_write_idx < dev->msgs_num; dev->msg_write_idx++) { + u32 flags = msgs[dev->msg_write_idx].flags; + /* * if target address has changed, we need to * reprogram the target address in the i2c @@ -599,8 +601,15 @@ i2c_dw_xfer_msg(struct dw_i2c_dev *dev) * detected from the registers so we set it always * when writing/reading the last byte. */ + + /* + * i2c-core.c always sets the buffer length of + * I2C_FUNC_SMBUS_BLOCK_DATA to 1. The length will + * be adjusted when receiving the first byte. + * Thus we can't stop the transaction here. + */ if (dev->msg_write_idx == dev->msgs_num - 1 && - buf_len == 1) + buf_len == 1 && !(flags & I2C_M_RECV_LEN)) cmd |= BIT(9); if (need_restart) { @@ -625,7 +634,12 @@ i2c_dw_xfer_msg(struct dw_i2c_dev *dev) dev->tx_buf = buf; dev->tx_buf_len = buf_len; - if (buf_len > 0) { + /* + * Because we don't know the buffer length in the + * I2C_FUNC_SMBUS_BLOCK_DATA case, we can't stop + * the transaction here. + */ + if (buf_len > 0 || flags & I2C_M_RECV_LEN) { /* more bytes to be written */ dev->status |= STATUS_WRITE_IN_PROGRESS; break; @@ -646,6 +660,24 @@ i2c_dw_xfer_msg(struct dw_i2c_dev *dev) dw_writel(dev, intr_mask, DW_IC_INTR_MASK); } +static u8 +i2c_dw_recv_len(struct dw_i2c_dev *dev, u8 len) +{ + struct i2c_msg *msgs = dev->msgs; + u32 flags = msgs[dev->msg_read_idx].flags; + + /* + * Adjust the buffer length and mask the flag + * after receiving the first byte. + */ + len += (flags & I2C_CLIENT_PEC) ? 2 : 1; + dev->tx_buf_len = len - min_t(u8, len, dev->rx_outstanding); + msgs[dev->msg_read_idx].len = len; + msgs[dev->msg_read_idx].flags &= ~I2C_M_RECV_LEN; + + return len; +} + static void i2c_dw_read(struct dw_i2c_dev *dev) { @@ -670,7 +702,15 @@ i2c_dw_read(struct dw_i2c_dev *dev) rx_valid = dw_readl(dev, DW_IC_RXFLR); for (; len > 0 && rx_valid > 0; len--, rx_valid--) { - *buf++ = dw_readl(dev, DW_IC_DATA_CMD); + u32 flags = msgs[dev->msg_read_idx].flags; + + *buf = dw_readl(dev, DW_IC_DATA_CMD); + /* Ensure length byte is a valid value */ + if (flags & I2C_M_RECV_LEN && + *buf <= I2C_SMBUS_BLOCK_MAX && *buf > 0) { + len = i2c_dw_recv_len(dev, *buf); + } + buf++; dev->rx_outstanding--; } diff --git a/drivers/i2c/busses/i2c-designware-pcidrv.c b/drivers/i2c/busses/i2c-designware-pcidrv.c index 96f8230cd2d3..8ffe2da6ca2f 100644 --- a/drivers/i2c/busses/i2c-designware-pcidrv.c +++ b/drivers/i2c/busses/i2c-designware-pcidrv.c @@ -75,6 +75,7 @@ struct dw_pci_controller { I2C_FUNC_SMBUS_BYTE | \ I2C_FUNC_SMBUS_BYTE_DATA | \ I2C_FUNC_SMBUS_WORD_DATA | \ + I2C_FUNC_SMBUS_BLOCK_DATA | \ I2C_FUNC_SMBUS_I2C_BLOCK) /* Merrifield HCNT/LCNT/SDA hold time */ diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c index 0b42a12171f3..886fb622ca38 100644 --- a/drivers/i2c/busses/i2c-designware-platdrv.c +++ b/drivers/i2c/busses/i2c-designware-platdrv.c @@ -220,6 +220,7 @@ static int dw_i2c_plat_probe(struct platform_device *pdev) I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_I2C_BLOCK; dev->master_cfg = DW_IC_CON_MASTER | DW_IC_CON_SLAVE_DISABLE | |