Some hardware designs have a GPIO used to control the reset of all the devices on and I2C bus. It's not possible for every child node to declare a reset-gpios property as only the first device probed would be able to successfully request it. Represent this kind of hardware design by associating the bus-reset-gpios with the parent I2C bus. The reset line will be released prior to the child I2C devices being probed. Signed-off-by: Chris Packham <chris.packham@xxxxxxxxxxxxxxxxxxx> --- Notes: Changes in v6: - Retarget changes at the i2c core instead of an individual driver Changes in v5: - Rename reset-gpios and reset-duration-us to bus-reset-gpios and bus-reset-duration-us as requested by Wolfram Changes in v4: - Add missing gpio/consumer.h - use fsleep() for enforcing reset-duration Changes in v3: - Rename reset-delay to reset-duration - Use reset-duration-us property to control the reset pulse rather than delaying after the reset Changes in v2: - Add a property to cover the length of delay after releasing the reset GPIO - Use dev_err_probe() when requesing the GPIO fails drivers/i2c/i2c-core-base.c | 39 +++++++++++++++++++++++++++++++++++++ include/linux/i2c.h | 3 +++ 2 files changed, 42 insertions(+) diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c index 60746652fd52..d7f53272487b 100644 --- a/drivers/i2c/i2c-core-base.c +++ b/drivers/i2c/i2c-core-base.c @@ -1468,6 +1468,39 @@ int i2c_handle_smbus_host_notify(struct i2c_adapter *adap, unsigned short addr) } EXPORT_SYMBOL_GPL(i2c_handle_smbus_host_notify); +static int i2c_setup_bus_reset_gpio(struct i2c_adapter *adap) +{ + int res; + + adap->reset_gpios = devm_gpiod_get_array_optional(&adap->dev, "bus-reset", GPIOD_OUT_HIGH); + if (IS_ERR(adap->reset_gpios)) + return dev_err_probe(&adap->dev, PTR_ERR(adap->reset_gpios), + "Cannot get reset gpio\n"); + res = device_property_read_u32(&adap->dev, "bus-reset-duration-us", &adap->reset_duration); + if (res) + adap->reset_duration = 1; + + return 0; +} + +static void i2c_deassert_bus_reset_gpio(struct i2c_adapter *adap) +{ + unsigned long *values; + + if (!adap->reset_gpios) + return; + + values = bitmap_zalloc(adap->reset_gpios->ndescs, GFP_KERNEL); + if (!values) + return; + + gpiod_set_array_value_cansleep(adap->reset_gpios->ndescs, + adap->reset_gpios->desc, adap->reset_gpios->info, + values); + + bitmap_free(values); +} + static int i2c_register_adapter(struct i2c_adapter *adap) { int res = -EINVAL; @@ -1521,6 +1554,10 @@ static int i2c_register_adapter(struct i2c_adapter *adap) if (res) goto out_reg; + res = i2c_setup_bus_reset_gpio(adap); + if (res) + goto out_reg; + device_enable_async_suspend(&adap->dev); pm_runtime_no_callbacks(&adap->dev); pm_suspend_ignore_children(&adap->dev, true); @@ -1539,6 +1576,8 @@ static int i2c_register_adapter(struct i2c_adapter *adap) dev_warn(&adap->dev, "Failed to create compatibility class link\n"); #endif + /* bring downstream devices out of reset */ + i2c_deassert_bus_reset_gpio(adap); /* create pre-declared device nodes */ of_i2c_register_devices(adap); diff --git a/include/linux/i2c.h b/include/linux/i2c.h index 0dae9db27538..1110a49dcdaf 100644 --- a/include/linux/i2c.h +++ b/include/linux/i2c.h @@ -746,6 +746,9 @@ struct i2c_adapter { struct irq_domain *host_notify_domain; struct regulator *bus_regulator; + + struct gpio_descs *reset_gpios; + u32 reset_duration; }; #define to_i2c_adapter(d) container_of(d, struct i2c_adapter, dev) -- 2.42.0