[PATCH] i2c: iproc: recover the bus on transfer timeouts

Jonas Gorski <[email protected]>
Newsgroups gmane.linux.drivers.i2c,gmane.linux.ports.arm.kernel,gmane.linux.kernel
Message-ID <[email protected]>
Just resetting the controller is not always enough to recover from a
timed out transfer, the bus may need to be cleared as well.

So add support for generic SCL recovery by switching to bit-banged mode
for manual control of SCL and SDA signals, and using it when SDA is
still low after a timeout.

Clearing SDA does not reset START_BUSY, so regardless of clearing SDA,
the controller might still need to be reset as well.

This is loosely inspired by iproc_smb_sda_low_recovery() from the
non-upstream iproc smbus driver implementation [1].

[1] https://github.com/opencomputeproject/onie/blob/master/patches/kernel/3.2.69/driver-iproc-smbus.patch

Signed-off-by: Jonas Gorski <[email protected]>
---
 drivers/i2c/busses/i2c-bcm-iproc.c | 92 ++++++++++++++++++++++++++++++
 1 file changed, 92 insertions(+)

diff --git a/drivers/i2c/busses/i2c-bcm-iproc.c b/drivers/i2c/busses/i2c-bcm-iproc.c
index 66bb3bfdaa4c..1d3732c76c09 100644
--- a/drivers/i2c/busses/i2c-bcm-iproc.c
+++ b/drivers/i2c/busses/i2c-bcm-iproc.c
@@ -15,6 +15,7 @@
 #define CFG_OFFSET                   0x00
 #define CFG_RESET_SHIFT              31
 #define CFG_EN_SHIFT                 30
+#define CFG_BITBANG_EN_SHIFT         29
 #define CFG_SLAVE_ADDR_0_SHIFT       28
 #define CFG_M_RETRY_CNT_SHIFT        16
 #define CFG_M_RETRY_CNT_MASK         0x0f
@@ -56,6 +57,12 @@
 #define S_FIFO_RX_THLD_SHIFT         8
 #define S_FIFO_RX_THLD_MASK          0x3f
 
+#define BITBANG_CTRL_OFFSET          0x14
+#define BITBANG_CLK_IN_SHIFT         31
+#define BITBANG_CLK_OUT_EN_SHIFT     30
+#define BITBANG_DATA_IN_SHIFT        29
+#define BITBANG_DATA_OUT_EN_SHIFT    28
+
 #define M_CMD_OFFSET                 0x30
 #define M_CMD_START_BUSY_SHIFT       31
 #define M_CMD_STATUS_SHIFT           25
@@ -715,6 +722,76 @@ static void bcm_iproc_i2c_init(struct bcm_iproc_i2c_dev *iproc_i2c)
 	iproc_i2c_wr_reg(iproc_i2c, IS_OFFSET, 0xffffffff);
 }
 
+static void bcm_iproc_i2c_prepare_recovery(struct i2c_adapter *adapter)
+{
+	struct bcm_iproc_i2c_dev *iproc_i2c = i2c_get_adapdata(adapter);
+	u32 val;
+
+	/* enable bit-bang mode to allow manually driving SDA/SCL */
+	val = iproc_i2c_rd_reg(iproc_i2c, CFG_OFFSET);
+	val |= BIT(CFG_BITBANG_EN_SHIFT);
+	iproc_i2c_wr_reg(iproc_i2c, CFG_OFFSET, val);
+	usleep_range(50, 75);
+}
+
+static void bcm_iproc_i2c_unprepare_recovery(struct i2c_adapter *adapter)
+{
+	struct bcm_iproc_i2c_dev *iproc_i2c = i2c_get_adapdata(adapter);
+	u32 val;
+
+	/* disable bit-bang mode again */
+	val = iproc_i2c_rd_reg(iproc_i2c, CFG_OFFSET);
+	val &= ~BIT(CFG_BITBANG_EN_SHIFT);
+	iproc_i2c_wr_reg(iproc_i2c, CFG_OFFSET, val);
+	usleep_range(10, 15);
+}
+
+static int bcm_iproc_i2c_get_scl(struct i2c_adapter *adapter)
+{
+	struct bcm_iproc_i2c_dev *iproc_i2c = i2c_get_adapdata(adapter);
+	u32 val;
+
+	val = iproc_i2c_rd_reg(iproc_i2c, BITBANG_CTRL_OFFSET);
+
+	return !!(val & BIT(BITBANG_CLK_IN_SHIFT));
+}
+
+static void bcm_iproc_i2c_set_scl(struct i2c_adapter *adapter, int scl)
+{
+	struct bcm_iproc_i2c_dev *iproc_i2c = i2c_get_adapdata(adapter);
+	u32 val;
+
+	val = iproc_i2c_rd_reg(iproc_i2c, BITBANG_CTRL_OFFSET);
+	if (scl)
+		val |= BIT(BITBANG_CLK_OUT_EN_SHIFT);
+	else
+		val &= ~BIT(BITBANG_CLK_OUT_EN_SHIFT);
+	iproc_i2c_wr_reg(iproc_i2c, BITBANG_CTRL_OFFSET, val);
+}
+
+static int bcm_iproc_i2c_get_sda(struct i2c_adapter *adapter)
+{
+	struct bcm_iproc_i2c_dev *iproc_i2c = i2c_get_adapdata(adapter);
+	u32 val;
+
+	val = iproc_i2c_rd_reg(iproc_i2c, BITBANG_CTRL_OFFSET);
+
+	return !!(val & BIT(BITBANG_DATA_IN_SHIFT));
+}
+
+static void bcm_iproc_i2c_set_sda(struct i2c_adapter *adapter, int sda)
+{
+	struct bcm_iproc_i2c_dev *iproc_i2c = i2c_get_adapdata(adapter);
+	u32 val;
+
+	val = iproc_i2c_rd_reg(iproc_i2c, BITBANG_CTRL_OFFSET);
+	if (sda)
+		val |= BIT(BITBANG_DATA_OUT_EN_SHIFT);
+	else
+		val &= ~BIT(BITBANG_DATA_OUT_EN_SHIFT);
+	iproc_i2c_wr_reg(iproc_i2c, BITBANG_CTRL_OFFSET, val);
+}
+
 static int bcm_iproc_i2c_check_status(struct bcm_iproc_i2c_dev *iproc_i2c,
 				      struct i2c_msg *msg)
 {
@@ -803,6 +880,10 @@ static int bcm_iproc_i2c_xfer_wait(struct bcm_iproc_i2c_dev *iproc_i2c,
 	}
 
 	if (!time_left && !iproc_i2c->xfer_is_done) {
+		/* Recover bus if SDA is held low */
+		if (bcm_iproc_i2c_get_sda(&iproc_i2c->adapter) == 0)
+			i2c_recover_bus(&iproc_i2c->adapter);
+
 		/*
 		 * The controller may fail to clear START_BUSY after a timeout,
 		 * reset the controller to recover in that case.
@@ -1064,6 +1145,16 @@ static const struct i2c_adapter_quirks bcm_iproc_i2c_quirks = {
 	.max_read_len = M_RX_MAX_READ_LEN,
 };
 
+static struct i2c_bus_recovery_info bcm_iproc_i2c_recovery_info = {
+	.recover_bus = i2c_generic_scl_recovery,
+	.get_scl = bcm_iproc_i2c_get_scl,
+	.set_scl = bcm_iproc_i2c_set_scl,
+	.get_sda = bcm_iproc_i2c_get_sda,
+	.set_sda = bcm_iproc_i2c_set_sda,
+	.prepare_recovery = bcm_iproc_i2c_prepare_recovery,
+	.unprepare_recovery = bcm_iproc_i2c_unprepare_recovery,
+};
+
 static int bcm_iproc_i2c_cfg_speed(struct bcm_iproc_i2c_dev *iproc_i2c)
 {
 	unsigned int bus_speed;
@@ -1163,6 +1254,7 @@ static int bcm_iproc_i2c_probe(struct platform_device *pdev)
 		 of_node_full_name(iproc_i2c->device->of_node));
 	adap->algo = &bcm_iproc_algo;
 	adap->quirks = &bcm_iproc_i2c_quirks;
+	adap->bus_recovery_info = &bcm_iproc_i2c_recovery_info;
 	adap->dev.parent = &pdev->dev;
 	adap->dev.of_node = pdev->dev.of_node;
 
-- 
2.55.0


-- 
BISDN GmbH
Körnerstraße 7-10
10785 Berlin
Germany
Phone: +49 30 610 816 100
Managing Directors: Dr.-Ing. Hagen Woesner, Andreas Köpsel


Commercial 
register: 
Amtsgericht Berlin-Charlottenburg HRB 141569 B
VAT ID No: 
DE283257294 <http://goog_1008408251>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.