[PATCH v6 2/2] i2c: add an optional bus-reset-gpios property

From: Chris Packham
Date: Tue Nov 14 2023 - 22:58:10 EST


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