]> git.sur5r.net Git - u-boot/blobdiff - drivers/i2c/i2c-uclass.c
dm: i2c: implement gpio-based I2C deblock
[u-boot] / drivers / i2c / i2c-uclass.c
index 920811a075c68454908129497db1809d4bd9ec72..4ac6ef84f58007ca45987b4e3713dc8e501d9027 100644 (file)
 #include <malloc.h>
 #include <dm/device-internal.h>
 #include <dm/lists.h>
+#include <dm/pinctrl.h>
+#ifdef CONFIG_DM_GPIO
+#include <asm/gpio.h>
+#endif
 
 #define I2C_MAX_OFFSET_LEN     4
 
+enum {
+       PIN_SDA = 0,
+       PIN_SCL,
+       PIN_COUNT,
+};
+
 /* Useful debugging function */
 void i2c_dump_msgs(struct i2c_msg *msg, int nmsgs)
 {
@@ -445,20 +455,110 @@ int i2c_get_chip_offset_len(struct udevice *dev)
        return chip->offset_len;
 }
 
+#ifdef CONFIG_DM_GPIO
+static void i2c_gpio_set_pin(struct gpio_desc *pin, int bit)
+{
+       if (bit)
+               dm_gpio_set_dir_flags(pin, GPIOD_IS_IN);
+       else
+               dm_gpio_set_dir_flags(pin, GPIOD_IS_OUT |
+                                          GPIOD_ACTIVE_LOW |
+                                          GPIOD_IS_OUT_ACTIVE);
+}
+
+static int i2c_gpio_get_pin(struct gpio_desc *pin)
+{
+       return dm_gpio_get_value(pin);
+}
+
+static int i2c_deblock_gpio_loop(struct gpio_desc *sda_pin,
+                                struct gpio_desc *scl_pin)
+{
+       int counter = 9;
+       int ret = 0;
+
+       i2c_gpio_set_pin(sda_pin, 1);
+       i2c_gpio_set_pin(scl_pin, 1);
+       udelay(5);
+
+       /*  Toggle SCL until slave release SDA */
+       while (counter-- >= 0) {
+               i2c_gpio_set_pin(scl_pin, 1);
+               udelay(5);
+               i2c_gpio_set_pin(scl_pin, 0);
+               udelay(5);
+               if (i2c_gpio_get_pin(sda_pin))
+                       break;
+       }
+
+       /* Then, send I2C stop */
+       i2c_gpio_set_pin(sda_pin, 0);
+       udelay(5);
+
+       i2c_gpio_set_pin(scl_pin, 1);
+       udelay(5);
+
+       i2c_gpio_set_pin(sda_pin, 1);
+       udelay(5);
+
+       if (!i2c_gpio_get_pin(sda_pin) || !i2c_gpio_get_pin(scl_pin))
+               ret = -EREMOTEIO;
+
+       return ret;
+}
+
+static int i2c_deblock_gpio(struct udevice *bus)
+{
+       struct gpio_desc gpios[PIN_COUNT];
+       int ret, ret0;
+
+       ret = gpio_request_list_by_name(bus, "gpios", gpios,
+                                       ARRAY_SIZE(gpios), GPIOD_IS_IN);
+       if (ret != ARRAY_SIZE(gpios)) {
+               debug("%s: I2C Node '%s' has no 'gpios' property %s\n",
+                     __func__, dev_read_name(bus), bus->name);
+               if (ret >= 0) {
+                       gpio_free_list(bus, gpios, ret);
+                       ret = -ENOENT;
+               }
+               goto out;
+       }
+
+       ret = pinctrl_select_state(bus, "gpio");
+       if (ret) {
+               debug("%s: I2C Node '%s' has no 'gpio' pinctrl state. %s\n",
+                     __func__, dev_read_name(bus), bus->name);
+               goto out_no_pinctrl;
+       }
+
+       ret0 = i2c_deblock_gpio_loop(&gpios[PIN_SDA], &gpios[PIN_SCL]);
+
+       ret = pinctrl_select_state(bus, "default");
+       if (ret) {
+               debug("%s: I2C Node '%s' has no 'default' pinctrl state. %s\n",
+                     __func__, dev_read_name(bus), bus->name);
+       }
+
+       ret = !ret ? ret0 : ret;
+
+out_no_pinctrl:
+       gpio_free_list(bus, gpios, ARRAY_SIZE(gpios));
+out:
+       return ret;
+}
+#else
+static int i2c_deblock_gpio(struct udevice *bus)
+{
+       return -ENOSYS;
+}
+#endif // CONFIG_DM_GPIO
+
 int i2c_deblock(struct udevice *bus)
 {
        struct dm_i2c_ops *ops = i2c_get_ops(bus);
 
-       /*
-        * We could implement a software deblocking here if we could get
-        * access to the GPIOs used by I2C, and switch them to GPIO mode
-        * and then back to I2C. This is somewhat beyond our powers in
-        * driver model at present, so for now just fail.
-        *
-        * See https://patchwork.ozlabs.org/patch/399040/
-        */
        if (!ops->deblock)
-               return -ENOSYS;
+               return i2c_deblock_gpio(bus);
 
        return ops->deblock(bus);
 }