aboutsummaryrefslogtreecommitdiff
path: root/drivers/i2c/i2c-uclass.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/i2c/i2c-uclass.c')
-rw-r--r--drivers/i2c/i2c-uclass.c118
1 files changed, 109 insertions, 9 deletions
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);
}