summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Krummenacher <max.krummenacher@toradex.com>2015-04-02 12:42:51 +0200
committerMax Krummenacher <max.krummenacher@toradex.com>2015-04-02 16:23:30 +0200
commit88731dc0e8b6ba244e097b412b302acf3b9cc889 (patch)
tree8c2d0b647a336c68274c41f89ee6d33372ab185d
parent512dc8ed77ec5ad4cf0be3c70732bf583410c7c2 (diff)
i2c-imx: add gpio recovery functionality
If the I2C is disturbed by other signals sometimes the i2c-imx bus gets stuck with SDA being low. This adds a recovery function to the bus driver. When e.g. a device driver detects a stuck bus int i2c_recover_bus(struct i2c_adapter *adap) can be called to try to recover the bus.
-rw-r--r--Documentation/devicetree/bindings/i2c/i2c-imx.txt23
-rw-r--r--arch/arm/boot/dts/imx6qdl-apalis.dtsi12
-rw-r--r--drivers/i2c/busses/i2c-imx.c73
-rw-r--r--drivers/i2c/i2c-core.c1
4 files changed, 108 insertions, 1 deletions
diff --git a/Documentation/devicetree/bindings/i2c/i2c-imx.txt b/Documentation/devicetree/bindings/i2c/i2c-imx.txt
index 3614242e7732..9f3924fb503d 100644
--- a/Documentation/devicetree/bindings/i2c/i2c-imx.txt
+++ b/Documentation/devicetree/bindings/i2c/i2c-imx.txt
@@ -23,3 +23,26 @@ i2c@70038000 { /* HS-I2C on i.MX51 */
interrupts = <64>;
clock-frequency = <400000>;
};
+
+The driver can provide recovery functionality for cases were the bus gets
+stack with SDA pulled low.
+In order to enable this one has to specify an additional pinctrl property with
+the name 'recovery' to mux GPIO functionality on SDA/SCA and a gpios
+property with the GPIOs for SDA/SCL.
+The lack of the gpios property disables the functionality.
+
+Examples:
+&i2c3 {
+ clock-frequency = <100000>;
+ pinctrl-names = "default", "recovery";
+ pinctrl-0 = <&pinctrl_i2c3_1>;
+ pinctrl-1 = <&pinctrl_i2c3_recovery_1>;
+ gpios = <&gpio3 18 0 /* sda */
+ &gpio3 17 0 /* scl */
+ >;
+ status = "disabled";
+};
+
+When e.g. a device driver detects a stuck bus
+int i2c_recover_bus(struct i2c_adapter *adap)
+can be called to try to recover the bus.
diff --git a/arch/arm/boot/dts/imx6qdl-apalis.dtsi b/arch/arm/boot/dts/imx6qdl-apalis.dtsi
index f57b0750a5f1..545e23a048db 100644
--- a/arch/arm/boot/dts/imx6qdl-apalis.dtsi
+++ b/arch/arm/boot/dts/imx6qdl-apalis.dtsi
@@ -409,8 +409,12 @@
*/
&i2c3 {
clock-frequency = <100000>;
- pinctrl-names = "default";
+ pinctrl-names = "default", "recovery";
pinctrl-0 = <&pinctrl_i2c3_1>;
+ pinctrl-1 = <&pinctrl_i2c3_recovery_1>;
+ gpios = <&gpio3 18 0 /* sda */
+ &gpio3 17 0 /* scl */
+ >;
status = "disabled";
};
@@ -540,6 +544,12 @@
MX6QDL_PAD_EIM_D16__GPIO3_IO16 PAD_CTRL_HYS_PU /* DDC bitbang */
>;
};
+ pinctrl_i2c3_recovery_1: i2c2_recovery {
+ fsl,pins = <
+ MX6QDL_PAD_EIM_D17__GPIO3_IO17 PAD_CTRL_HYS_PU /* DDC bitbang */
+ MX6QDL_PAD_EIM_D18__GPIO3_IO18 PAD_CTRL_HYS_PU /* DDC bitbang */
+ >;
+ };
pinctrl_mmc_cd: gpio_mmc_cd {
fsl,pins = <
MX6QDL_PAD_DI0_PIN4__GPIO4_IO20 PAD_CTRL_NO /* MMC1 CD */
diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c
index 9782527a8668..54a4b349f76e 100644
--- a/drivers/i2c/busses/i2c-imx.c
+++ b/drivers/i2c/busses/i2c-imx.c
@@ -50,6 +50,7 @@
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_device.h>
+#include <linux/of_gpio.h>
#include <linux/of_i2c.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_data/i2c-imx.h>
@@ -495,16 +496,71 @@ static struct i2c_algorithm i2c_imx_algo = {
.functionality = i2c_imx_func,
};
+static int of_i2c_imx_get_pins(struct device_node *np,
+ unsigned int *sda_pin, unsigned int *scl_pin)
+{
+ if (of_gpio_count(np) < 2)
+ return -ENODEV;
+
+ *sda_pin = of_get_gpio(np, 0);
+ *scl_pin = of_get_gpio(np, 1);
+ if (!gpio_is_valid(*sda_pin) || !gpio_is_valid(*scl_pin)) {
+ pr_err("%s: invalid GPIO pins, sda=%d/scl=%d\n",
+ np->full_name, *sda_pin, *scl_pin);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+/* i2c bus recovery routines */
+static int get_scl_gpio_value(struct i2c_adapter *adap)
+{
+ return gpio_get_value(adap->bus_recovery_info->scl_gpio);
+}
+
+static void set_scl_gpio_value(struct i2c_adapter *adap, int val)
+{
+ gpio_set_value(adap->bus_recovery_info->scl_gpio, val);
+}
+
+static int get_sda_gpio_value(struct i2c_adapter *adap)
+{
+ return gpio_get_value(adap->bus_recovery_info->sda_gpio);
+}
+
+int i2c_imx_recover_bus(struct i2c_adapter *adap)
+{
+ /* i2c_imx_(un)prepare_recovery can not be used for pinmuxing, as we
+ * need the data from struct i2c_adapter */
+ int ret;
+
+ if(!devm_pinctrl_get_select(adap->dev.parent, "recovery")) {
+ dev_err(adap->dev.parent, "%s> switching to 'recover' pinmux failed\n", __func__);
+ }
+
+ ret = i2c_generic_gpio_recovery(adap);
+
+ if(!devm_pinctrl_get_select_default(adap->dev.parent)) {
+ dev_err(adap->dev.parent, "<%s> switching from 'recover' to 'default' pinmux failed\n", __func__);
+ }
+
+ return ret;
+}
+
static int __init i2c_imx_probe(struct platform_device *pdev)
{
const struct of_device_id *of_id = of_match_device(i2c_imx_dt_ids,
&pdev->dev);
struct imx_i2c_struct *i2c_imx;
+ struct i2c_bus_recovery_info *bri;
struct resource *res;
struct imxi2c_platform_data *pdata = pdev->dev.platform_data;
struct pinctrl *pinctrl;
void __iomem *base;
int irq, ret;
+ int sda_gpio = -1;
+ int scl_gpio = -1;
dev_dbg(&pdev->dev, "<%s>\n", __func__);
@@ -570,6 +626,23 @@ static int __init i2c_imx_probe(struct platform_device *pdev)
/* Set up adapter data */
i2c_set_adapdata(&i2c_imx->adapter, i2c_imx);
+ /* Set up adapter recovery info */
+ if (of_i2c_imx_get_pins(pdev->dev.of_node, &sda_gpio, &scl_gpio) >= 0) {
+ bri = devm_kzalloc(&pdev->dev,
+ sizeof(struct i2c_bus_recovery_info), GFP_KERNEL);
+ if (!bri)
+ dev_info(&pdev->dev, "can't allocate i2c recovery struct, recovery will not work\n");
+ else {
+ bri->recover_bus = i2c_imx_recover_bus;
+ bri->get_sda = get_sda_gpio_value;
+ bri->get_scl = get_scl_gpio_value;
+ bri->set_scl = set_scl_gpio_value;
+ bri->sda_gpio = sda_gpio;
+ bri->scl_gpio = scl_gpio;
+ i2c_imx->adapter.bus_recovery_info = bri;
+ }
+ }
+
/* Set up clock divider */
i2c_imx->bitrate = IMX_I2C_BIT_RATE;
ret = of_property_read_u32(pdev->dev.of_node,
diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c
index 48e31ed69dbf..49e278ef7587 100644
--- a/drivers/i2c/i2c-core.c
+++ b/drivers/i2c/i2c-core.c
@@ -229,6 +229,7 @@ int i2c_recover_bus(struct i2c_adapter *adap)
dev_dbg(&adap->dev, "Trying i2c bus recovery\n");
return adap->bus_recovery_info->recover_bus(adap);
}
+EXPORT_SYMBOL_GPL(i2c_recover_bus);
static int i2c_device_probe(struct device *dev)
{