diff options
-rw-r--r-- | arch/arm/include/asm/imx-common/mxc_i2c.h | 10 | ||||
-rw-r--r-- | drivers/i2c/mxc_i2c.c | 101 |
2 files changed, 102 insertions, 9 deletions
diff --git a/arch/arm/include/asm/imx-common/mxc_i2c.h b/arch/arm/include/asm/imx-common/mxc_i2c.h index 355b25e..b0b6d61 100644 --- a/arch/arm/include/asm/imx-common/mxc_i2c.h +++ b/arch/arm/include/asm/imx-common/mxc_i2c.h @@ -5,6 +5,7 @@ */ #ifndef __ASM_ARCH_MXC_MXC_I2C_H__ #define __ASM_ARCH_MXC_MXC_I2C_H__ +#include <asm-generic/gpio.h> #include <asm/imx-common/iomux-v3.h> struct i2c_pin_ctrl { @@ -30,6 +31,10 @@ struct i2c_pads_info { * The following two is only to be compatible with non-DM part. * @idle_bus_fn: function to force bus idle * @idle_bus_data: parameter for idle_bus_fun + * For DM: + * bus: The device structure for i2c bus controller + * scl-gpio: specify the gpio related to SCL pin + * sda-gpio: specify the gpio related to SDA pin */ struct mxc_i2c_bus { /* @@ -46,6 +51,11 @@ struct mxc_i2c_bus { #ifndef CONFIG_DM_I2C int (*idle_bus_fn)(void *p); void *idle_bus_data; +#else + struct udevice *bus; + /* Use gpio to force bus idle when bus state is abnormal */ + struct gpio_desc scl_gpio; + struct gpio_desc sda_gpio; #endif }; diff --git a/drivers/i2c/mxc_i2c.c b/drivers/i2c/mxc_i2c.c index b2d15c9..445fa21 100644 --- a/drivers/i2c/mxc_i2c.c +++ b/drivers/i2c/mxc_i2c.c @@ -23,6 +23,7 @@ #include <i2c.h> #include <watchdog.h> #include <dm.h> +#include <dm/pinctrl.h> #include <fdtdec.h> DECLARE_GLOBAL_DATA_PTR; @@ -334,17 +335,74 @@ int i2c_idle_bus(struct mxc_i2c_bus *i2c_bus) } #else /* - * Since pinmux is not supported, implement a weak function here. - * You can implement your i2c_bus_idle in board file. When pinctrl - * is supported, this can be removed. + * See Linux Documentation/devicetree/bindings/i2c/i2c-imx.txt + * " + * scl-gpios: specify the gpio related to SCL pin + * sda-gpios: specify the gpio related to SDA pin + * add pinctrl to configure i2c pins to gpio function for i2c + * bus recovery, call it "gpio" state + * " + * + * The i2c_idle_bus is an implementation following Linux Kernel. */ -int __i2c_idle_bus(struct mxc_i2c_bus *i2c_bus) +int i2c_idle_bus(struct mxc_i2c_bus *i2c_bus) { - return 0; -} + struct udevice *bus = i2c_bus->bus; + struct gpio_desc *scl_gpio = &i2c_bus->scl_gpio; + struct gpio_desc *sda_gpio = &i2c_bus->sda_gpio; + int sda, scl; + int i, ret = 0; + ulong elapsed, start_time; -int i2c_idle_bus(struct mxc_i2c_bus *i2c_bus) - __attribute__((weak, alias("__i2c_idle_bus"))); + if (pinctrl_select_state(bus, "gpio")) { + dev_dbg(bus, "Can not to switch to use gpio pinmux\n"); + /* + * GPIO pinctrl for i2c force idle is not a must, + * but it is strongly recommended to be used. + * Because it can help you to recover from bad + * i2c bus state. Do not return failure, because + * it is not a must. + */ + return 0; + } + + dm_gpio_set_dir_flags(scl_gpio, GPIOD_IS_IN); + dm_gpio_set_dir_flags(sda_gpio, GPIOD_IS_IN); + scl = dm_gpio_get_value(scl_gpio); + sda = dm_gpio_get_value(sda_gpio); + + if ((sda & scl) == 1) + goto exit; /* Bus is idle already */ + + /* Send high and low on the SCL line */ + for (i = 0; i < 9; i++) { + dm_gpio_set_dir_flags(scl_gpio, GPIOD_IS_OUT); + dm_gpio_set_value(scl_gpio, 0); + udelay(50); + dm_gpio_set_dir_flags(scl_gpio, GPIOD_IS_IN); + udelay(50); + } + start_time = get_timer(0); + for (;;) { + dm_gpio_set_dir_flags(scl_gpio, GPIOD_IS_IN); + dm_gpio_set_dir_flags(sda_gpio, GPIOD_IS_IN); + scl = dm_gpio_get_value(scl_gpio); + sda = dm_gpio_get_value(sda_gpio); + if ((sda & scl) == 1) + break; + WATCHDOG_RESET(); + elapsed = get_timer(start_time); + if (elapsed > (CONFIG_SYS_HZ / 5)) { /* .2 seconds */ + ret = -EBUSY; + printf("%s: failed to clear bus, sda=%d scl=%d\n", __func__, sda, scl); + break; + } + } + +exit: + pinctrl_select_state(bus, "default"); + return ret; +} #endif static int i2c_init_transfer(struct mxc_i2c_bus *i2c_bus, u8 chip, @@ -664,8 +722,10 @@ static int mxc_i2c_set_bus_speed(struct udevice *bus, unsigned int speed) static int mxc_i2c_probe(struct udevice *bus) { struct mxc_i2c_bus *i2c_bus = dev_get_priv(bus); + const void *fdt = gd->fdt_blob; + int node = bus->of_offset; fdt_addr_t addr; - int ret; + int ret, ret2; i2c_bus->driver_data = dev_get_driver_data(bus); @@ -675,12 +735,35 @@ static int mxc_i2c_probe(struct udevice *bus) i2c_bus->base = addr; i2c_bus->index = bus->seq; + i2c_bus->bus = bus; /* Enable clk */ ret = enable_i2c_clk(1, bus->seq); if (ret < 0) return ret; + /* + * See Documentation/devicetree/bindings/i2c/i2c-imx.txt + * Use gpio to force bus idle when necessary. + */ + ret = fdt_find_string(fdt, node, "pinctrl-names", "gpio"); + if (ret < 0) { + dev_info(dev, "i2c bus %d at %lu, no gpio pinctrl state.\n", bus->seq, i2c_bus->base); + } else { + ret = gpio_request_by_name_nodev(fdt, node, "scl-gpios", + 0, &i2c_bus->scl_gpio, + GPIOD_IS_OUT); + ret2 = gpio_request_by_name_nodev(fdt, node, "sda-gpios", + 0, &i2c_bus->sda_gpio, + GPIOD_IS_OUT); + if (!dm_gpio_is_valid(&i2c_bus->sda_gpio) | + !dm_gpio_is_valid(&i2c_bus->scl_gpio) | + ret | ret2) { + dev_err(dev, "i2c bus %d at %lu, fail to request scl/sda gpio\n", bus->seq, i2c_bus->base); + return -ENODEV; + } + } + ret = i2c_idle_bus(i2c_bus); if (ret < 0) { /* Disable clk */ |