summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/i2c/i2c-imx.txt23
-rw-r--r--drivers/i2c/busses/i2c-imx.c73
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,