i2c: designware: add i2c gpio recovery option
authorTim Sander <tim@krieglstein.org>
Thu, 2 Nov 2017 02:40:27 +0000 (10:40 +0800)
committerWolfram Sang <wsa@the-dreams.de>
Mon, 27 Nov 2017 17:39:38 +0000 (18:39 +0100)
This patch contains much input from Phil Reid and has been tested
on Intel/Altera Cyclone V SOC Hardware with Altera GPIO's for the
SCL and SDA GPIO's.

Acked-by: Jarkko Nikula <jarkko.nikula@linux.intel.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Tim Sander <tim@krieglstein.org>
Signed-off-by: Phil Reid <preid@electromag.com.au>
Signed-off-by: Wolfram Sang <wsa@the-dreams.de>
drivers/i2c/busses/i2c-designware-common.c
drivers/i2c/busses/i2c-designware-core.h
drivers/i2c/busses/i2c-designware-master.c

index 3d684c6..6b82809 100644 (file)
@@ -230,7 +230,11 @@ int i2c_dw_wait_bus_not_busy(struct dw_i2c_dev *dev)
        while (dw_readl(dev, DW_IC_STATUS) & DW_IC_STATUS_ACTIVITY) {
                if (timeout <= 0) {
                        dev_warn(dev->dev, "timeout waiting for bus ready\n");
-                       return -ETIMEDOUT;
+                       i2c_recover_bus(&dev->adapter);
+
+                       if (dw_readl(dev, DW_IC_STATUS) & DW_IC_STATUS_ACTIVITY)
+                               return -ETIMEDOUT;
+                       return 0;
                }
                timeout--;
                usleep_range(1000, 1100);
index 33c6c8f..d58a336 100644 (file)
@@ -286,6 +286,7 @@ struct dw_i2c_dev {
        void                    (*disable_int)(struct dw_i2c_dev *dev);
        int                     (*init)(struct dw_i2c_dev *dev);
        int                     mode;
+       struct i2c_bus_recovery_info rinfo;
 };
 
 #define ACCESS_SWAP            0x00000001
index 418c233..ae69188 100644 (file)
 #include <linux/err.h>
 #include <linux/errno.h>
 #include <linux/export.h>
+#include <linux/gpio/consumer.h>
 #include <linux/i2c.h>
 #include <linux/interrupt.h>
 #include <linux/io.h>
 #include <linux/module.h>
 #include <linux/pm_runtime.h>
+#include <linux/reset.h>
 
 #include "i2c-designware-core.h"
 
@@ -443,6 +445,7 @@ i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
        if (!wait_for_completion_timeout(&dev->cmd_complete, adap->timeout)) {
                dev_err(dev->dev, "controller timed out\n");
                /* i2c_dw_init implicitly disables the adapter */
+               i2c_recover_bus(&dev->adapter);
                i2c_dw_init_master(dev);
                ret = -ETIMEDOUT;
                goto done;
@@ -613,6 +616,56 @@ static irqreturn_t i2c_dw_isr(int this_irq, void *dev_id)
        return IRQ_HANDLED;
 }
 
+static void i2c_dw_prepare_recovery(struct i2c_adapter *adap)
+{
+       struct dw_i2c_dev *dev = i2c_get_adapdata(adap);
+
+       i2c_dw_disable(dev);
+       reset_control_assert(dev->rst);
+       i2c_dw_prepare_clk(dev, false);
+}
+
+static void i2c_dw_unprepare_recovery(struct i2c_adapter *adap)
+{
+       struct dw_i2c_dev *dev = i2c_get_adapdata(adap);
+
+       i2c_dw_prepare_clk(dev, true);
+       reset_control_deassert(dev->rst);
+       i2c_dw_init_master(dev);
+}
+
+static int i2c_dw_init_recovery_info(struct dw_i2c_dev *dev)
+{
+       struct i2c_bus_recovery_info *rinfo = &dev->rinfo;
+       struct i2c_adapter *adap = &dev->adapter;
+       struct gpio_desc *gpio;
+       int r;
+
+       gpio = devm_gpiod_get(dev->dev, "scl", GPIOD_OUT_HIGH);
+       if (IS_ERR(gpio)) {
+               r = PTR_ERR(gpio);
+               if (r == -ENOENT)
+                       return 0;
+               return r;
+       }
+       rinfo->scl_gpiod = gpio;
+
+       gpio = devm_gpiod_get_optional(dev->dev, "sda", GPIOD_IN);
+       if (IS_ERR(gpio))
+               return PTR_ERR(gpio);
+       rinfo->sda_gpiod = gpio;
+
+       rinfo->recover_bus = i2c_generic_scl_recovery;
+       rinfo->prepare_recovery = i2c_dw_prepare_recovery;
+       rinfo->unprepare_recovery = i2c_dw_unprepare_recovery;
+       adap->bus_recovery_info = rinfo;
+
+       dev_info(dev->dev, "running with gpio recovery mode! scl%s",
+                rinfo->sda_gpiod ? ",sda" : "");
+
+       return 0;
+}
+
 int i2c_dw_probe(struct dw_i2c_dev *dev)
 {
        struct i2c_adapter *adap = &dev->adapter;
@@ -652,6 +705,10 @@ int i2c_dw_probe(struct dw_i2c_dev *dev)
                return ret;
        }
 
+       ret = i2c_dw_init_recovery_info(dev);
+       if (ret)
+               return ret;
+
        /*
         * Increment PM usage count during adapter registration in order to
         * avoid possible spurious runtime suspend when adapter device is