drivers: i2c: tca954x: add support for idle disconnect

Add support for an optional "idle disconnect" feature in the TCA954x
I2C multiplexer. When enabled via the `i2c-mux-idle-disconnect` device
tree property, the driver will disconnect all channels after each
transfer. This helps avoid address conflicts when multiple multiplexers
are present on the same I2C bus.

Even if the I2C transfer fails, the driver will still attempt to
disconnect the channels to ensure the bus is left in a consistent state.
If the disconnect operation itself fails, its error code will be returned
unless the transfer already failed with a different error.

This implementation is inspired by the Linux kernel driver for PCA954x
I2C multiplexers. Special thanks to Ofir Shemesh for valuable suggestion.

Signed-off-by: Hank Wang <wanghanchi2000@gmail.com>
This commit is contained in:
Hank Wang 2025-03-09 00:13:10 +08:00 committed by Benjamin Cabé
commit e11634e733
2 changed files with 18 additions and 1 deletions

View file

@ -18,6 +18,7 @@ struct tca954x_root_config {
struct i2c_dt_spec i2c;
uint8_t nchans;
const struct gpio_dt_spec reset_gpios;
bool idle_disconnect;
};
struct tca954x_root_data {
@ -82,7 +83,7 @@ static int tca954x_transfer(const struct device *dev,
const struct tca954x_root_config *config =
get_root_config_from_channel(dev);
const struct tca954x_channel_config *down_cfg = dev->config;
int res;
int res, disconnect_res;
res = k_mutex_lock(&data->lock, K_MSEC(5000));
if (res != 0) {
@ -96,6 +97,14 @@ static int tca954x_transfer(const struct device *dev,
res = i2c_transfer(config->i2c.bus, msgs, num_msgs, addr);
if (config->idle_disconnect) {
/* Always attempt to disconnect, even if i2c_transfer fails */
disconnect_res = tca954x_set_channel(down_cfg->root, 0);
if (disconnect_res != 0 && res == 0) {
res = disconnect_res;
}
}
end_trans:
k_mutex_unlock(&data->lock);
return res;
@ -186,6 +195,7 @@ BUILD_ASSERT(CONFIG_I2C_TCA954X_CHANNEL_INIT_PRIO > CONFIG_I2C_TCA954X_ROOT_INIT
.nchans = ch, \
.reset_gpios = GPIO_DT_SPEC_GET_OR( \
DT_INST(inst, ti_tca##n##a), reset_gpios, {0}), \
.idle_disconnect = DT_INST_PROP(inst, i2c_mux_idle_disconnect), \
}; \
static struct tca954x_root_data tca##n##a_data_##inst = { \
.lock = Z_MUTEX_INITIALIZER(tca##n##a_data_##inst.lock), \

View file

@ -52,6 +52,13 @@ properties:
description: |
GPIO connected to the controller RESET pin. This pin is active-low.
i2c-mux-idle-disconnect:
type: boolean
description: |
Forces mux to disconnect all children in idle state. This is
necessary for example, if there are several multiplexers on the bus and
the devices behind them use same I2C addresses.
child-binding:
description: TCA954x I2C switch channel node
include: [i2c-controller.yaml]