There may be several reasons why you may need to use a certain speed
on an I2C bus. E.g.
- When several devices are attached to the bus, the speed must be
selected according to the slowest device.
- Electrical conditions may limit the usuable speed on the bus for
different reasons.
With an I2C multiplexer, it is possible to group the attached devices
after their preferred speed by e.g. put all "slow" devices on a separate
channel on the multiplexer.
Consider the following topology:
.----------. 100kHz .--------.
.--------. 400kHz | |--------| dev D1 |
| root |--+-----| I2C MUX | '--------'
'--------' | | |--. 400kHz .--------.
| '----------' '-------| dev D2 |
| .--------. '--------'
'--| dev D3 |
'--------'
One requirement with this design is that a multiplexer may only use the
same or lower bus speed as its parent.
Otherwise, if the multiplexer would have to increase the bus frequency,
then all siblings (D3 in this case) would run into a clock speed it may
not support.
The bus frequency for each channel is set in the devicetree. As the
i2c-mux bindings import the i2c-controller schema, the clock-frequency
property is already allowed.
If no clock-frequency property is set, the channel inherit their parent
bus speed.
The following example uses dt bindings to illustrate the topology above:
i2c {
clock-frequency = <400000>;
i2c-mux {
i2c@0 {
clock-frequency = <100000>;
D1 {
...
};
};
i2c@1 {
D2 {
...
};
};
};
D3 {
...
}
};
Signed-off-by: Marcus Folkesson <marcus.folkesson@gmail.com>
---
drivers/i2c/i2c-mux.c | 145 +++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 133 insertions(+), 12 deletions(-)
diff --git a/drivers/i2c/i2c-mux.c b/drivers/i2c/i2c-mux.c
index d59644e50f14..9116ee3fd979 100644
--- a/drivers/i2c/i2c-mux.c
+++ b/drivers/i2c/i2c-mux.c
@@ -36,6 +36,100 @@ struct i2c_mux_priv {
u32 chan_id;
};
+static struct i2c_mux_core *i2c_mux_topmost_mux_locked(struct i2c_adapter *adap)
+{
+ struct i2c_adapter *curr = adap;
+ struct i2c_adapter *parent;
+ struct i2c_mux_core *result = NULL;
+
+ do {
+ parent = i2c_parent_is_i2c_adapter(curr);
+ if (parent) {
+ struct i2c_mux_priv *priv = curr->algo_data;
+
+ if (priv && priv->muxc && priv->muxc->mux_locked)
+ result = priv->muxc;
+ }
+ curr = parent;
+ } while (parent);
+
+ return result;
+}
+
+static int i2c_mux_select_chan(struct i2c_adapter *adap, u32 chan_id)
+{
+ struct i2c_mux_priv *priv = adap->algo_data;
+ struct i2c_mux_core *muxc = priv->muxc;
+ struct i2c_adapter *parent = muxc->parent;
+ struct i2c_mux_core *mux_locked_ancestor = NULL;
+ struct i2c_adapter *root;
+ int ret;
+
+ if (priv->adap.clock_hz && priv->adap.clock_hz != parent->clock_hz) {
+ mux_locked_ancestor = i2c_mux_topmost_mux_locked(adap);
+ root = i2c_root_adapter(&adap->dev);
+
+ /*
+ * If there's a mux-locked mux in our ancestry, lock the parent
+ * of the topmost one. Mux-locked muxes don't propagate locking
+ * to their parents, so we must explicitly acquire the lock above
+ * the highest mux-locked ancestor to reach the root adapter.
+ */
+ if (mux_locked_ancestor)
+ i2c_lock_bus(mux_locked_ancestor->parent, I2C_LOCK_ROOT_ADAPTER);
+
+ ret = i2c_adapter_set_clk_freq(root, priv->adap.clock_hz);
+
+ if (mux_locked_ancestor)
+ i2c_unlock_bus(mux_locked_ancestor->parent, I2C_LOCK_ROOT_ADAPTER);
+
+ if (ret < 0) {
+ dev_err(&adap->dev,
+ "Failed to set clock frequency %dHz on root adapter %s: %d\n",
+ priv->adap.clock_hz, root->name, ret);
+
+ return ret;
+ }
+ }
+
+ return muxc->select(muxc, priv->chan_id);
+}
+
+static void i2c_mux_deselect_chan(struct i2c_adapter *adap, u32 chan_id)
+{
+ struct i2c_mux_priv *priv = adap->algo_data;
+ struct i2c_mux_core *muxc = priv->muxc;
+ struct i2c_adapter *parent = muxc->parent;
+ struct i2c_mux_core *mux_locked_ancestor = NULL;
+ struct i2c_adapter *root;
+ int ret;
+
+ if (parent->clock_hz && parent->clock_hz != priv->adap.clock_hz) {
+ mux_locked_ancestor = i2c_mux_topmost_mux_locked(adap);
+ root = i2c_root_adapter(&parent->dev);
+
+ /*
+ * If there's a mux-locked mux in our ancestry, lock the parent
+ * of the topmost one. Mux-locked muxes don't propagate locking
+ * to their parents, so we must explicitly acquire the lock above
+ * the highest mux-locked ancestor to reach the root adapter.
+ */
+ if (mux_locked_ancestor)
+ i2c_lock_bus(mux_locked_ancestor->parent, I2C_LOCK_ROOT_ADAPTER);
+
+ ret = i2c_adapter_set_clk_freq(root, parent->clock_hz);
+
+ if (mux_locked_ancestor)
+ i2c_unlock_bus(mux_locked_ancestor->parent, I2C_LOCK_ROOT_ADAPTER);
+
+ if (ret < 0)
+ return;
+ }
+
+ if (muxc->deselect)
+ muxc->deselect(muxc, priv->chan_id);
+}
+
static int __i2c_mux_master_xfer(struct i2c_adapter *adap,
struct i2c_msg msgs[], int num)
{
@@ -46,11 +140,11 @@ static int __i2c_mux_master_xfer(struct i2c_adapter *adap,
/* Switch to the right mux port and perform the transfer. */
- ret = muxc->select(muxc, priv->chan_id);
+ ret = i2c_mux_select_chan(adap, priv->chan_id);
if (ret >= 0)
ret = __i2c_transfer(parent, msgs, num);
- if (muxc->deselect)
- muxc->deselect(muxc, priv->chan_id);
+
+ i2c_mux_deselect_chan(adap, priv->chan_id);
return ret;
}
@@ -65,11 +159,11 @@ static int i2c_mux_master_xfer(struct i2c_adapter *adap,
/* Switch to the right mux port and perform the transfer. */
- ret = muxc->select(muxc, priv->chan_id);
+ ret = i2c_mux_select_chan(adap, priv->chan_id);
if (ret >= 0)
ret = i2c_transfer(parent, msgs, num);
- if (muxc->deselect)
- muxc->deselect(muxc, priv->chan_id);
+
+ i2c_mux_deselect_chan(adap, priv->chan_id);
return ret;
}
@@ -86,12 +180,12 @@ static int __i2c_mux_smbus_xfer(struct i2c_adapter *adap,
/* Select the right mux port and perform the transfer. */
- ret = muxc->select(muxc, priv->chan_id);
+ ret = i2c_mux_select_chan(adap, priv->chan_id);
if (ret >= 0)
ret = __i2c_smbus_xfer(parent, addr, flags,
read_write, command, size, data);
- if (muxc->deselect)
- muxc->deselect(muxc, priv->chan_id);
+
+ i2c_mux_deselect_chan(adap, priv->chan_id);
return ret;
}
@@ -108,12 +202,12 @@ static int i2c_mux_smbus_xfer(struct i2c_adapter *adap,
/* Select the right mux port and perform the transfer. */
- ret = muxc->select(muxc, priv->chan_id);
+ ret = i2c_mux_select_chan(adap, priv->chan_id);
if (ret >= 0)
ret = i2c_smbus_xfer(parent, addr, flags,
read_write, command, size, data);
- if (muxc->deselect)
- muxc->deselect(muxc, priv->chan_id);
+
+ i2c_mux_deselect_chan(adap, priv->chan_id);
return ret;
}
@@ -223,6 +317,7 @@ struct i2c_adapter *i2c_root_adapter(struct device *dev)
}
EXPORT_SYMBOL_GPL(i2c_root_adapter);
+
struct i2c_mux_core *i2c_mux_alloc(struct i2c_adapter *parent,
struct device *dev, int max_adapters,
int sizeof_priv, u32 flags,
@@ -362,6 +457,32 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc,
}
}
+ of_property_read_u32(child, "clock-frequency", &priv->adap.clock_hz);
+
+ /* If the mux adapter has no clock-frequency property, inherit from parent */
+ if (!priv->adap.clock_hz)
+ priv->adap.clock_hz = parent->clock_hz;
+
+ /*
+ * Warn if the mux adapter is not parent-locked as
+ * this may cause issues for some hardware topologies.
+ */
+ if ((priv->adap.clock_hz < parent->clock_hz) && muxc->mux_locked)
+ dev_warn(muxc->dev,
+ "channel %u is slower than parent on a non parent-locked mux\n",
+ chan_id);
+
+ /* We don't support mux adapters faster than their parent */
+ if (priv->adap.clock_hz > parent->clock_hz) {
+ dev_err(muxc->dev,
+ "channel (%u) is faster (%u) than parent (%u)\n",
+ chan_id, priv->adap.clock_hz, parent->clock_hz);
+
+ of_node_put(mux_node);
+ ret = -EINVAL;
+ goto err_free_priv;
+ }
+
priv->adap.dev.of_node = child;
of_node_put(mux_node);
}
--
2.52.0
Hi!
2026-02-13 at 12:06, Marcus Folkesson wrote:
> There may be several reasons why you may need to use a certain speed
> on an I2C bus. E.g.
>
> - When several devices are attached to the bus, the speed must be
> selected according to the slowest device.
>
> - Electrical conditions may limit the usuable speed on the bus for
> different reasons.
>
> With an I2C multiplexer, it is possible to group the attached devices
> after their preferred speed by e.g. put all "slow" devices on a separate
> channel on the multiplexer.
>
> Consider the following topology:
>
> .----------. 100kHz .--------.
> .--------. 400kHz | |--------| dev D1 |
> | root |--+-----| I2C MUX | '--------'
> '--------' | | |--. 400kHz .--------.
> | '----------' '-------| dev D2 |
> | .--------. '--------'
> '--| dev D3 |
> '--------'
>
> One requirement with this design is that a multiplexer may only use the
> same or lower bus speed as its parent.
> Otherwise, if the multiplexer would have to increase the bus frequency,
> then all siblings (D3 in this case) would run into a clock speed it may
> not support.
>
> The bus frequency for each channel is set in the devicetree. As the
> i2c-mux bindings import the i2c-controller schema, the clock-frequency
> property is already allowed.
> If no clock-frequency property is set, the channel inherit their parent
> bus speed.
>
> The following example uses dt bindings to illustrate the topology above:
>
> i2c {
> clock-frequency = <400000>;
>
> i2c-mux {
> i2c@0 {
> clock-frequency = <100000>;
>
> D1 {
> ...
> };
> };
>
> i2c@1 {
> D2 {
> ...
> };
> };
> };
>
> D3 {
> ...
> }
> };
>
> Signed-off-by: Marcus Folkesson <marcus.folkesson@gmail.com>
> ---
> drivers/i2c/i2c-mux.c | 145 +++++++++++++++++++++++++++++++++++++++++++++-----
> 1 file changed, 133 insertions(+), 12 deletions(-)
>
> diff --git a/drivers/i2c/i2c-mux.c b/drivers/i2c/i2c-mux.c
> index d59644e50f14..9116ee3fd979 100644
> --- a/drivers/i2c/i2c-mux.c
> +++ b/drivers/i2c/i2c-mux.c
> @@ -36,6 +36,100 @@ struct i2c_mux_priv {
> u32 chan_id;
> };
>
> +static struct i2c_mux_core *i2c_mux_topmost_mux_locked(struct i2c_adapter *adap)
> +{
> + struct i2c_adapter *curr = adap;
> + struct i2c_adapter *parent;
> + struct i2c_mux_core *result = NULL;
> +
> + do {
> + parent = i2c_parent_is_i2c_adapter(curr);
> + if (parent) {
> + struct i2c_mux_priv *priv = curr->algo_data;
> +
> + if (priv && priv->muxc && priv->muxc->mux_locked)
> + result = priv->muxc;
> + }
> + curr = parent;
> + } while (parent);
> +
> + return result;
> +}
> +
> +static int i2c_mux_select_chan(struct i2c_adapter *adap, u32 chan_id)
> +{
> + struct i2c_mux_priv *priv = adap->algo_data;
> + struct i2c_mux_core *muxc = priv->muxc;
> + struct i2c_adapter *parent = muxc->parent;
> + struct i2c_mux_core *mux_locked_ancestor = NULL;
> + struct i2c_adapter *root;
> + int ret;
> +
> + if (priv->adap.clock_hz && priv->adap.clock_hz != parent->clock_hz) {
> + mux_locked_ancestor = i2c_mux_topmost_mux_locked(adap);
> + root = i2c_root_adapter(&adap->dev);
> +
> + /*
> + * If there's a mux-locked mux in our ancestry, lock the parent
> + * of the topmost one. Mux-locked muxes don't propagate locking
> + * to their parents, so we must explicitly acquire the lock above
> + * the highest mux-locked ancestor to reach the root adapter.
> + */
As may be more apparent from my recent v4 response(?), this is not
what you want. You want mux_locked_ancestor to be the nearest
mux-locked ancestor.
> + if (mux_locked_ancestor)
> + i2c_lock_bus(mux_locked_ancestor->parent, I2C_LOCK_ROOT_ADAPTER);
> +
> + ret = i2c_adapter_set_clk_freq(root, priv->adap.clock_hz);
> +
> + if (mux_locked_ancestor)
> + i2c_unlock_bus(mux_locked_ancestor->parent, I2C_LOCK_ROOT_ADAPTER);
> +
> + if (ret < 0) {
> + dev_err(&adap->dev,
> + "Failed to set clock frequency %dHz on root adapter %s: %d\n",
> + priv->adap.clock_hz, root->name, ret);
> +
> + return ret;
> + }
> + }
> +
Here, you set the frequency /before/ ->select.
> + return muxc->select(muxc, priv->chan_id);
> +}
> +
> +static void i2c_mux_deselect_chan(struct i2c_adapter *adap, u32 chan_id)
> +{
> + struct i2c_mux_priv *priv = adap->algo_data;
> + struct i2c_mux_core *muxc = priv->muxc;
> + struct i2c_adapter *parent = muxc->parent;
> + struct i2c_mux_core *mux_locked_ancestor = NULL;
> + struct i2c_adapter *root;
> + int ret;
And here, you restore the frequency, but /before/ ->deselect.
Thers's no symmetry in that...
Cheers,
Peter
> + if (parent->clock_hz && parent->clock_hz != priv->adap.clock_hz) {
> + mux_locked_ancestor = i2c_mux_topmost_mux_locked(adap);
> + root = i2c_root_adapter(&parent->dev);
> +
> + /*
> + * If there's a mux-locked mux in our ancestry, lock the parent
> + * of the topmost one. Mux-locked muxes don't propagate locking
> + * to their parents, so we must explicitly acquire the lock above
> + * the highest mux-locked ancestor to reach the root adapte> + */
> + if (mux_locked_ancestor)
> + i2c_lock_bus(mux_locked_ancestor->parent, I2C_LOCK_ROOT_ADAPTER);
> +
> + ret = i2c_adapter_set_clk_freq(root, parent->clock_hz);
> +
> + if (mux_locked_ancestor)
> + i2c_unlock_bus(mux_locked_ancestor->parent, I2C_LOCK_ROOT_ADAPTER);
> +
> + if (ret < 0)
> + return;
> + }
> +
> + if (muxc->deselect)
> + muxc->deselect(muxc, priv->chan_id);
> +}
Hi Peter!
On Fri, Feb 13, 2026 at 12:48:13PM +0100, Peter Rosin wrote:
> Hi!
>
[...]
> > + if (priv->adap.clock_hz && priv->adap.clock_hz != parent->clock_hz) {
> > + mux_locked_ancestor = i2c_mux_topmost_mux_locked(adap);
> > + root = i2c_root_adapter(&adap->dev);
> > +
> > + /*
> > + * If there's a mux-locked mux in our ancestry, lock the parent
> > + * of the topmost one. Mux-locked muxes don't propagate locking
> > + * to their parents, so we must explicitly acquire the lock above
> > + * the highest mux-locked ancestor to reach the root adapter.
> > + */
>
> As may be more apparent from my recent v4 response(?), this is not
> what you want. You want mux_locked_ancestor to be the nearest
> mux-locked ancestor.
Hrm, I think we are pointing at the same thing but naming it differently; M1 in this chain:
Root - P1 - M1 - M2 - P2 - D1
I'm thinking 'topmost' means the one closest to the root (ancestor most
far away), but I don't stick to that naming and I am happy to change it.
>
>
> > + if (mux_locked_ancestor)
> > + i2c_lock_bus(mux_locked_ancestor->parent, I2C_LOCK_ROOT_ADAPTER);
> > +
> > + ret = i2c_adapter_set_clk_freq(root, priv->adap.clock_hz);
> > +
> > + if (mux_locked_ancestor)
> > + i2c_unlock_bus(mux_locked_ancestor->parent, I2C_LOCK_ROOT_ADAPTER);
> > +
> > + if (ret < 0) {
> > + dev_err(&adap->dev,
> > + "Failed to set clock frequency %dHz on root adapter %s: %d\n",
> > + priv->adap.clock_hz, root->name, ret);
> > +
> > + return ret;
> > + }
> > + }
> > +
>
> Here, you set the frequency /before/ ->select.
>
> > + return muxc->select(muxc, priv->chan_id);
> > +}
> > +
> > +static void i2c_mux_deselect_chan(struct i2c_adapter *adap, u32 chan_id)
> > +{
> > + struct i2c_mux_priv *priv = adap->algo_data;
> > + struct i2c_mux_core *muxc = priv->muxc;
> > + struct i2c_adapter *parent = muxc->parent;
> > + struct i2c_mux_core *mux_locked_ancestor = NULL;
> > + struct i2c_adapter *root;
> > + int ret;
>
> And here, you restore the frequency, but /before/ ->deselect.
>
> Thers's no symmetry in that...
Good point, I will change that.
>
> Cheers,
> Peter
Best regards,
Marcus Folkesson
Hi!
2026-02-13 at 17:05, Marcus Folkesson wrote:
> Hi Peter!
>
> On Fri, Feb 13, 2026 at 12:48:13PM +0100, Peter Rosin wrote:
>> Hi!
>>
>
> [...]
>
>>> + if (priv->adap.clock_hz && priv->adap.clock_hz != parent->clock_hz) {
>>> + mux_locked_ancestor = i2c_mux_topmost_mux_locked(adap);
>>> + root = i2c_root_adapter(&adap->dev);
>>> +
>>> + /*
>>> + * If there's a mux-locked mux in our ancestry, lock the parent
>>> + * of the topmost one. Mux-locked muxes don't propagate locking
>>> + * to their parents, so we must explicitly acquire the lock above
>>> + * the highest mux-locked ancestor to reach the root adapter.
>>> + */
>>
>> As may be more apparent from my recent v4 response(?), this is not
>> what you want. You want mux_locked_ancestor to be the nearest
>> mux-locked ancestor.
>
> Hrm, I think we are pointing at the same thing but naming it differently; M1 in this chain:
> Root - P1 - M1 - M2 - P2 - D1
>
> I'm thinking 'topmost' means the one closest to the root (ancestor most
> far away), but I don't stick to that naming and I am happy to change it.
That fact that we both point to M1 is incidental. If you instead have
Root - P1 - M0 - P0 - M1 - M2 - P2 - D1
you still want to "restart" locking from M1, not M0. I think.
Cheers,
Peter
On Fri, Feb 13, 2026 at 12:06:51PM +0100, Marcus Folkesson wrote:
> There may be several reasons why you may need to use a certain speed
> on an I2C bus. E.g.
>
> - When several devices are attached to the bus, the speed must be
> selected according to the slowest device.
>
> - Electrical conditions may limit the usuable speed on the bus for
> different reasons.
>
> With an I2C multiplexer, it is possible to group the attached devices
> after their preferred speed by e.g. put all "slow" devices on a separate
> channel on the multiplexer.
>
> Consider the following topology:
>
> .----------. 100kHz .--------.
> .--------. 400kHz | |--------| dev D1 |
> | root |--+-----| I2C MUX | '--------'
> '--------' | | |--. 400kHz .--------.
> | '----------' '-------| dev D2 |
> | .--------. '--------'
> '--| dev D3 |
> '--------'
>
> One requirement with this design is that a multiplexer may only use the
> same or lower bus speed as its parent.
> Otherwise, if the multiplexer would have to increase the bus frequency,
> then all siblings (D3 in this case) would run into a clock speed it may
> not support.
>
> The bus frequency for each channel is set in the devicetree. As the
> i2c-mux bindings import the i2c-controller schema, the clock-frequency
> property is already allowed.
> If no clock-frequency property is set, the channel inherit their parent
> bus speed.
...
> +static int i2c_mux_select_chan(struct i2c_adapter *adap, u32 chan_id)
> +{
> + struct i2c_mux_priv *priv = adap->algo_data;
> + struct i2c_mux_core *muxc = priv->muxc;
> + struct i2c_adapter *parent = muxc->parent;
> + struct i2c_mux_core *mux_locked_ancestor = NULL;
> + struct i2c_adapter *root;
> + int ret;
> +
> + if (priv->adap.clock_hz && priv->adap.clock_hz != parent->clock_hz) {
> + mux_locked_ancestor = i2c_mux_topmost_mux_locked(adap);
> + root = i2c_root_adapter(&adap->dev);
> +
> + /*
> + * If there's a mux-locked mux in our ancestry, lock the parent
> + * of the topmost one. Mux-locked muxes don't propagate locking
> + * to their parents, so we must explicitly acquire the lock above
> + * the highest mux-locked ancestor to reach the root adapter.
> + */
> + if (mux_locked_ancestor)
> + i2c_lock_bus(mux_locked_ancestor->parent, I2C_LOCK_ROOT_ADAPTER);
> +
> + ret = i2c_adapter_set_clk_freq(root, priv->adap.clock_hz);
> +
> + if (mux_locked_ancestor)
> + i2c_unlock_bus(mux_locked_ancestor->parent, I2C_LOCK_ROOT_ADAPTER);
> + if (ret < 0) {
Would it (ever) have any positive returned values?
Ditto for other similar cases.
> + dev_err(&adap->dev,
> + "Failed to set clock frequency %dHz on root adapter %s: %d\n",
> + priv->adap.clock_hz, root->name, ret);
> +
> + return ret;
> + }
> + }
> +
> + return muxc->select(muxc, priv->chan_id);
> +}
...
> @@ -223,6 +317,7 @@ struct i2c_adapter *i2c_root_adapter(struct device *dev)
> }
> EXPORT_SYMBOL_GPL(i2c_root_adapter);
>
> +
> struct i2c_mux_core *i2c_mux_alloc(struct i2c_adapter *parent,
Stray and unneeded change.
> struct device *dev, int max_adapters,
> int sizeof_priv, u32 flags,
...
> + of_property_read_u32(child, "clock-frequency", &priv->adap.clock_hz);
Why OF-centric APIs? Muxes may and do appear on other systems as well.
Okay, this function seems fully OF-centric :-(
--
With Best Regards,
Andy Shevchenko
© 2016 - 2026 Red Hat, Inc.