summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorGao Pan <b54642@freescale.com>2015-10-23 20:28:54 +0800
committerWolfram Sang <wsa@the-dreams.de>2015-10-23 21:55:50 +0200
commit1c4b6c3bcf30d0804db0d0647d8ebeb862c6f7e5 (patch)
treea8773ecfa4e4f91be9bb41335d8326f78390fedb /drivers
parent3861841d2205714bfabaee4efcbb5e4202884852 (diff)
i2c: imx: implement bus recovery
Implement bus recovery methods for i2c-imx so we can recover from situations where SCL/SDA are stuck low. Once i2c bus SCL/SDA are stuck low during transfer, config the i2c pinctrl to gpio mode by calling pinctrl sleep set function, and then use GPIO to emulate the i2c protocol to send nine dummy clock to recover i2c device. After recovery, set i2c pinctrl to default group setting. Signed-off-by: Fugang Duan <B38611@freescale.com> Signed-off-by: Gao Pan <b54642@freescale.com> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de> Signed-off-by: Wolfram Sang <wsa@the-dreams.de>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/i2c/busses/i2c-imx.c71
1 files changed, 71 insertions, 0 deletions
diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c
index 785aa674a4da..8d46e74c720a 100644
--- a/drivers/i2c/busses/i2c-imx.c
+++ b/drivers/i2c/busses/i2c-imx.c
@@ -49,6 +49,7 @@
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_dma.h>
+#include <linux/of_gpio.h>
#include <linux/platform_data/i2c-imx.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
@@ -207,6 +208,11 @@ struct imx_i2c_struct {
unsigned int cur_clk;
unsigned int bitrate;
const struct imx_i2c_hwdata *hwdata;
+ struct i2c_bus_recovery_info rinfo;
+
+ struct pinctrl *pinctrl;
+ struct pinctrl_state *pinctrl_pins_default;
+ struct pinctrl_state *pinctrl_pins_gpio;
struct imx_i2c_dma *dma;
};
@@ -896,6 +902,13 @@ static int i2c_imx_xfer(struct i2c_adapter *adapter,
/* Start I2C transfer */
result = i2c_imx_start(i2c_imx);
+ if (result) {
+ if (i2c_imx->adapter.bus_recovery_info) {
+ i2c_recover_bus(&i2c_imx->adapter);
+ result = i2c_imx_start(i2c_imx);
+ }
+ }
+
if (result)
goto fail0;
@@ -956,6 +969,55 @@ fail0:
return (result < 0) ? result : num;
}
+static void i2c_imx_prepare_recovery(struct i2c_adapter *adap)
+{
+ struct imx_i2c_struct *i2c_imx;
+
+ i2c_imx = container_of(adap, struct imx_i2c_struct, adapter);
+
+ pinctrl_select_state(i2c_imx->pinctrl, i2c_imx->pinctrl_pins_gpio);
+}
+
+static void i2c_imx_unprepare_recovery(struct i2c_adapter *adap)
+{
+ struct imx_i2c_struct *i2c_imx;
+
+ i2c_imx = container_of(adap, struct imx_i2c_struct, adapter);
+
+ pinctrl_select_state(i2c_imx->pinctrl, i2c_imx->pinctrl_pins_default);
+}
+
+static void i2c_imx_init_recovery_info(struct imx_i2c_struct *i2c_imx,
+ struct platform_device *pdev)
+{
+ struct i2c_bus_recovery_info *rinfo = &i2c_imx->rinfo;
+
+ i2c_imx->pinctrl_pins_default = pinctrl_lookup_state(i2c_imx->pinctrl,
+ PINCTRL_STATE_DEFAULT);
+ i2c_imx->pinctrl_pins_gpio = pinctrl_lookup_state(i2c_imx->pinctrl,
+ "gpio");
+ rinfo->sda_gpio = of_get_named_gpio_flags(pdev->dev.of_node,
+ "sda-gpios", 0, NULL);
+ rinfo->scl_gpio = of_get_named_gpio_flags(pdev->dev.of_node,
+ "scl-gpios", 0, NULL);
+
+ if (!gpio_is_valid(rinfo->sda_gpio) ||
+ !gpio_is_valid(rinfo->scl_gpio) ||
+ IS_ERR(i2c_imx->pinctrl_pins_default) ||
+ IS_ERR(i2c_imx->pinctrl_pins_gpio)) {
+ dev_dbg(&pdev->dev, "recovery information incomplete\n");
+ return;
+ }
+
+ dev_dbg(&pdev->dev, "using scl-gpio %d and sda-gpio %d for recovery\n",
+ rinfo->sda_gpio, rinfo->scl_gpio);
+
+ rinfo->prepare_recovery = i2c_imx_prepare_recovery;
+ rinfo->unprepare_recovery = i2c_imx_unprepare_recovery;
+ rinfo->recover_bus = i2c_generic_gpio_recovery;
+ i2c_imx->adapter.bus_recovery_info = rinfo;
+}
+
static u32 i2c_imx_func(struct i2c_adapter *adapter)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL
@@ -1023,6 +1085,13 @@ static int i2c_imx_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "can't enable I2C clock\n");
return ret;
}
+
+ i2c_imx->pinctrl = devm_pinctrl_get(&pdev->dev);
+ if (IS_ERR(i2c_imx->pinctrl)) {
+ ret = PTR_ERR(i2c_imx->pinctrl);
+ goto clk_disable;
+ }
+
/* Request IRQ */
ret = devm_request_irq(&pdev->dev, irq, i2c_imx_isr, 0,
pdev->name, i2c_imx);
@@ -1056,6 +1125,8 @@ static int i2c_imx_probe(struct platform_device *pdev)
goto clk_disable;
}
+ i2c_imx_init_recovery_info(i2c_imx, pdev);
+
/* Set up platform driver data */
platform_set_drvdata(pdev, i2c_imx);
clk_disable_unprepare(i2c_imx->clk);