i2c: designware: Implement support for SMBus block read and write
authorTin Huynh <tnhuynh@apm.com>
Thu, 10 Nov 2016 02:56:33 +0000 (09:56 +0700)
committerWolfram Sang <wsa@the-dreams.de>
Fri, 18 Nov 2016 01:06:41 +0000 (02:06 +0100)
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>
drivers/i2c/busses/i2c-designware-core.c
drivers/i2c/busses/i2c-designware-pcidrv.c
drivers/i2c/busses/i2c-designware-platdrv.c

index 11e866d053680e5af3ba5a06dbaf078700724aa5..4f56328b906fbef4f992c7e583739b630646f035 100644 (file)
@@ -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--;
                }
 
index 96f8230cd2d3352fa52678bc4be9f6e2f39d397f..8ffe2da6ca2f36aee8cb5d98313f0cf503fc05f1 100644 (file)
@@ -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 */
index 0b42a12171f3a146ddc61ac6e2ed6d9d9cacf12e..886fb622ca384292a0ed5e3b4a3102c9c1b92df4 100644 (file)
@@ -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 |