diff options
-rw-r--r-- | Documentation/devicetree/bindings/i2c/i2c-imx.txt | 23 | ||||
-rw-r--r-- | drivers/i2c/busses/i2c-imx.c | 73 |
2 files changed, 96 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/i2c/i2c-imx.txt b/Documentation/devicetree/bindings/i2c/i2c-imx.txt index ce4311d726ae..50f1ad81331f 100644 --- a/Documentation/devicetree/bindings/i2c/i2c-imx.txt +++ b/Documentation/devicetree/bindings/i2c/i2c-imx.txt @@ -38,3 +38,26 @@ i2c0: i2c@40066000 { /* i2c0 on vf610 */ <&edma0 0 51>; dma-names = "rx","tx"; }; + +The driver can provide recovery functionality for cases were the bus gets +stuck 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/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c index f1341a2371de..4a1048169174 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> @@ -968,15 +969,70 @@ 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 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 = dev_get_platdata(&pdev->dev); void __iomem *base; int irq, ret; + int sda_gpio = -1; + int scl_gpio = -1; dma_addr_t phy_addr; dev_dbg(&pdev->dev, "<%s>\n", __func__); @@ -1038,6 +1094,23 @@ static int 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, |