diff mbox series

[U-Boot,v2,2/2] dm: i2c: implement gpio-based I2C deblock

Message ID 1522162347-30184-3-git-send-email-al.kochet@gmail.com
State Accepted
Commit aa54192d4a87460e105264a8156f1d7b1d212b0b
Delegated to: Heiko Schocher
Headers show
Series dm: i2c: implement gpio-based I2C deblock | expand

Commit Message

Alexander Kochetkov March 27, 2018, 2:52 p.m. UTC
The commit implement a gpio-based software deblocking. The code
extract I2C pins description from device tree, switch pins to GPIO
mode, toggle SCL until slave release SDA, send I2C stop and switch
I2C pins back to I2C mode.

Signed-off-by: Alexander Kochetkov <al.kochet@gmail.com>
---
 drivers/i2c/i2c-uclass.c |  118 ++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 109 insertions(+), 9 deletions(-)

Comments

Heiko Schocher April 11, 2018, 4:33 p.m. UTC | #1
Hello Alexander,

Am 27.03.2018 um 16:52 schrieb Alexander Kochetkov:
> The commit implement a gpio-based software deblocking. The code
> extract I2C pins description from device tree, switch pins to GPIO
> mode, toggle SCL until slave release SDA, send I2C stop and switch
> I2C pins back to I2C mode.
> 
> Signed-off-by: Alexander Kochetkov <al.kochet@gmail.com>
> ---
>   drivers/i2c/i2c-uclass.c |  118 ++++++++++++++++++++++++++++++++++++++++++----
>   1 file changed, 109 insertions(+), 9 deletions(-)

Applied to u-boot-i2c master

Thanks!

bye,
Heiko
diff mbox series

Patch

diff --git a/drivers/i2c/i2c-uclass.c b/drivers/i2c/i2c-uclass.c
index 920811a..4ac6ef8 100644
--- a/drivers/i2c/i2c-uclass.c
+++ b/drivers/i2c/i2c-uclass.c
@@ -11,9 +11,19 @@ 
 #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);
 }