diff mbox series

[v2,1/7] i2c: mux: add the ability to share mux core address with child nodes

Message ID 20240506-dev-mule-i2c-mux-v2-1-a91c954f65d7@cherry.de (mailing list archive)
State New
Headers show
Series Add Mule I2C multiplexer support | expand

Commit Message

Farouk Bouabid May 6, 2024, 11:37 a.m. UTC
Allow the mux core (if it's an i2c device) to have the same address as
a child device. This is useful when the mux core can only use an i2c
address that is used by a child device because no other addresses are
free to use. eg. the mux core can only use address 0x18 which is used by
amc6821 connected to the mux.

Signed-off-by: Farouk Bouabid <farouk.bouabid@cherry.de>
---
 drivers/i2c/i2c-core-base.c |  6 +++++-
 drivers/i2c/i2c-mux.c       | 25 ++++++++++++++++++++++++-
 include/linux/i2c-mux.h     |  1 +
 include/linux/i2c.h         |  7 +++++++
 4 files changed, 37 insertions(+), 2 deletions(-)

Comments

Peter Rosin May 6, 2024, 9:26 p.m. UTC | #1
Hi!

Regarding the subject (and elsewhere) I think of "mux core" as roughly
the code in the i2c-mux.c file. So, for me, the "mux core" does not have
an address, it is a mux "driver instance" or "device" that sits on the
I2C address that you need to share.

2024-05-06 at 13:37, Farouk Bouabid wrote:
> Allow the mux core (if it's an i2c device) to have the same address as
> a child device. This is useful when the mux core can only use an i2c
> address that is used by a child device because no other addresses are
> free to use. eg. the mux core can only use address 0x18 which is used by
> amc6821 connected to the mux.

Use I2C in text, not i2c (applies to other patches as well).

> 
> Signed-off-by: Farouk Bouabid <farouk.bouabid@cherry.de>
> ---
>  drivers/i2c/i2c-core-base.c |  6 +++++-
>  drivers/i2c/i2c-mux.c       | 25 ++++++++++++++++++++++++-
>  include/linux/i2c-mux.h     |  1 +
>  include/linux/i2c.h         |  7 +++++++
>  4 files changed, 37 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c
> index ff5c486a1dbb..ce2425b0486d 100644
> --- a/drivers/i2c/i2c-core-base.c
> +++ b/drivers/i2c/i2c-core-base.c
> @@ -821,9 +821,13 @@ static int i2c_check_mux_children(struct device *dev, void *addrp)
>  static int i2c_check_addr_busy(struct i2c_adapter *adapter, int addr)
>  {
>  	struct i2c_adapter *parent = i2c_parent_is_i2c_adapter(adapter);
> +	bool skip_check = false;
>  	int result = 0;
>  
> -	if (parent)
> +	if (adapter->quirks && adapter->quirks->flags & I2C_AQ_SKIP_ADDR_CHECK)
> +		skip_check = adapter->quirks->skip_addr_in_parent == addr;
> +
> +	if (parent && !skip_check)
>  		result = i2c_check_mux_parents(parent, addr);
>  
>  	if (!result)
> diff --git a/drivers/i2c/i2c-mux.c b/drivers/i2c/i2c-mux.c
> index 57ff09f18c37..bdb75a130cab 100644
> --- a/drivers/i2c/i2c-mux.c
> +++ b/drivers/i2c/i2c-mux.c
> @@ -334,7 +334,30 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc,
>  	priv->adap.dev.parent = &parent->dev;
>  	priv->adap.retries = parent->retries;
>  	priv->adap.timeout = parent->timeout;
> -	priv->adap.quirks = parent->quirks;
> +	/*
> +	 * When creating the adapter, the node devices are checked for i2c address
> +	 * match with other devices on the parent adapter, among which is the mux core itself.
> +	 * If a match is found the node device is not probed successfully.
> +	 * Allow the mux to have the same address as a child device by skipping this check.
> +	 */
> +	if (muxc->share_addr_with_children && muxc->dev->type == &i2c_client_type) {
> +		struct i2c_adapter_quirks *quirks = devm_kzalloc(muxc->dev,
> +								 sizeof(*quirks), GFP_KERNEL);
> +		struct i2c_client *client = to_i2c_client(muxc->dev);

The above section has overly long lines without good reason. Please
rewrite as something like this:

	/*
	 * When creating the adapter, the node devices are checked for I2C
	 * address match with other devices on the parent adapter, among
	 * which is the mux device itself. If a match is found the node
	 * device is not probed successfully. Allow the mux to have the
	 * same address as a child device by skipping this check.
	 */
	if (muxc->share_addr_with_children &&
	    muxc->dev->type == &i2c_client_type)
	{
		struct i2c_client *client = to_i2c_client(muxc->dev);
		struct i2c_adapter_quirks *quirks;

		quirks = devm_kzalloc(muxc->dev, sizeof(*quirks), GFP_KERNEL);

I also wonder if that second condition (...->type == &i2c_client_type) should
be a WARN_ON_ONCE? I don't see how the flag can be set sanely on an adapter
that is not itself an I2C client. Can it?

> +
> +		if (!quirks)
> +			return -ENOMEM;
> +
> +		if (parent->quirks)
> +			memcpy(quirks, parent->quirks, sizeof(*quirks));
> +
> +		quirks->flags |= I2C_AQ_SKIP_ADDR_CHECK;
> +		quirks->skip_addr_in_parent = client->addr;
> +		priv->adap.quirks = quirks;

The I2C_AQ_SKIP_ADDR_CHECK flag should probably not be propagated?

	} else if (priv->adap.quirks &&
		   priv->adap.quirks->flags & I2C_AQ_SKIP_ADDR_CHECK)
	{
		struct i2c_adapter_quirks *quirks;

		quirks = devm_kzalloc(muxc->dev, sizeof(*quirks), GFP_KERNEL);
		if (!quirks)
			return -ENOMEM;

		memcpy(quirks, parent->quirks, sizeof(*quirks));
		quirks->flags &= ~I2C_AQ_SKIP_ADDR_CHECK;
		priv->adap.quirks = quirks;

(My code written directly in the mua, there's probably something silly in
there...)

Cheers,
Peter

> +	} else {
> +		priv->adap.quirks = parent->quirks;
> +	}
> +
>  	if (muxc->mux_locked)
>  		priv->adap.lock_ops = &i2c_mux_lock_ops;
>  	else
> diff --git a/include/linux/i2c-mux.h b/include/linux/i2c-mux.h
> index 98ef73b7c8fd..17ac68bf1703 100644
> --- a/include/linux/i2c-mux.h
> +++ b/include/linux/i2c-mux.h
> @@ -21,6 +21,7 @@ struct i2c_mux_core {
>  	unsigned int mux_locked:1;
>  	unsigned int arbitrator:1;
>  	unsigned int gate:1;
> +	unsigned int share_addr_with_children:1;
>  
>  	void *priv;
>  
> diff --git a/include/linux/i2c.h b/include/linux/i2c.h
> index 5e6cd43a6dbd..c3acbaaadae9 100644
> --- a/include/linux/i2c.h
> +++ b/include/linux/i2c.h
> @@ -670,6 +670,7 @@ int i2c_generic_scl_recovery(struct i2c_adapter *adap);
>   * @max_read_len: maximum length of a read message
>   * @max_comb_1st_msg_len: maximum length of the first msg in a combined message
>   * @max_comb_2nd_msg_len: maximum length of the second msg in a combined message
> + * @skip_addr_in_parent: No conflict check on parent adapter for a given address
>   *
>   * Note about combined messages: Some I2C controllers can only send one message
>   * per transfer, plus something called combined message or write-then-read.
> @@ -690,6 +691,7 @@ struct i2c_adapter_quirks {
>  	u16 max_read_len;
>  	u16 max_comb_1st_msg_len;
>  	u16 max_comb_2nd_msg_len;
> +	unsigned short skip_addr_in_parent;
>  };
>  
>  /* enforce max_num_msgs = 2 and use max_comb_*_len for length checks */
> @@ -711,6 +713,11 @@ struct i2c_adapter_quirks {
>  #define I2C_AQ_NO_ZERO_LEN		(I2C_AQ_NO_ZERO_LEN_READ | I2C_AQ_NO_ZERO_LEN_WRITE)
>  /* adapter cannot do repeated START */
>  #define I2C_AQ_NO_REP_START		BIT(7)
> +/**
> + * do not check for conflict on a given address
> + * used accordingly with "struct i2c_adapter_quirks.skip_addr_in_parent"
> + */
> +#define I2C_AQ_SKIP_ADDR_CHECK	BIT(8)
>  
>  /*
>   * i2c_adapter is the structure used to identify a physical i2c bus along
>
Quentin Schulz May 7, 2024, 9:48 a.m. UTC | #2
Hi Peter,

On 5/6/24 11:26 PM, Peter Rosin wrote:
> Hi!
> 
> Regarding the subject (and elsewhere) I think of "mux core" as roughly
> the code in the i2c-mux.c file. So, for me, the "mux core" does not have
> an address, it is a mux "driver instance" or "device" that sits on the
> I2C address that you need to share.
> 

I'm the one who suggested mux core here (privately) :)

The issue is that in my head a mux device is the i2c adapter/bus (from 
i2c-mux.yaml dt binding example):

"""
     i2c {
         #address-cells = <1>;
         #size-cells = <0>;

         i2c-mux@70 {
             compatible = "nxp,pca9548";
             reg = <0x70>;
             #address-cells = <1>;
             #size-cells = <0>;

             i2c@3 {
                 #address-cells = <1>;
                 #size-cells = <0>;
                 reg = <3>;

                 gpio@20 {
                     compatible = "nxp,pca9555";
                     gpio-controller;
                     #gpio-cells = <2>;
                     reg = <0x20>;
                 };
             };
             i2c@4 {
                 #address-cells = <1>;
                 #size-cells = <0>;
                 reg = <4>;

                 gpio@20 {
                     compatible = "nxp,pca9555";
                     gpio-controller;
                     #gpio-cells = <2>;
                     reg = <0x20>;
                 };
             };
         };
     };
"""

"mux core" here would refer to i2c-mux@70, "mux device"/"mux" i2c@3 or 
i2c@4. E.g. when I'm saying "in mux 3", I'm talking about i2c@3 here.

For me a driver instance is a device, so "mux driver instance" would be 
a "mux device". Ah... naming is hard. Anyway, up to you, I just wanted 
to make sure we're talking about the same thing and there's no confusion 
here.

[...]
> I also wonder if that second condition (...->type == &i2c_client_type) should
> be a WARN_ON_ONCE? I don't see how the flag can be set sanely on an adapter
> that is not itself an I2C client. Can it?
> 

Agreed, good suggestion here... Though... 
https://lwn.net/Articles/969923/ it seems new additions of WARN_ON are 
now discouraged? Not looking to start a discussion here about whether 
WARN_ON is good or bad, merely pointing at this if it was missed somehow.

>> +
>> +		if (!quirks)
>> +			return -ENOMEM;
>> +
>> +		if (parent->quirks)
>> +			memcpy(quirks, parent->quirks, sizeof(*quirks));
>> +
>> +		quirks->flags |= I2C_AQ_SKIP_ADDR_CHECK;
>> +		quirks->skip_addr_in_parent = client->addr;
>> +		priv->adap.quirks = quirks;
> 
> The I2C_AQ_SKIP_ADDR_CHECK flag should probably not be propagated?
> 

Oh... you mean if we have a mux on an i2c adapter of a mux and the 
adapters handled by the parent mux have SKIP_ADDR set and we don't want 
the adapters handled by the leaf mux to have this flag as well? Is that 
what you meant?

Cheers,
Quentin
diff mbox series

Patch

diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c
index ff5c486a1dbb..ce2425b0486d 100644
--- a/drivers/i2c/i2c-core-base.c
+++ b/drivers/i2c/i2c-core-base.c
@@ -821,9 +821,13 @@  static int i2c_check_mux_children(struct device *dev, void *addrp)
 static int i2c_check_addr_busy(struct i2c_adapter *adapter, int addr)
 {
 	struct i2c_adapter *parent = i2c_parent_is_i2c_adapter(adapter);
+	bool skip_check = false;
 	int result = 0;
 
-	if (parent)
+	if (adapter->quirks && adapter->quirks->flags & I2C_AQ_SKIP_ADDR_CHECK)
+		skip_check = adapter->quirks->skip_addr_in_parent == addr;
+
+	if (parent && !skip_check)
 		result = i2c_check_mux_parents(parent, addr);
 
 	if (!result)
diff --git a/drivers/i2c/i2c-mux.c b/drivers/i2c/i2c-mux.c
index 57ff09f18c37..bdb75a130cab 100644
--- a/drivers/i2c/i2c-mux.c
+++ b/drivers/i2c/i2c-mux.c
@@ -334,7 +334,30 @@  int i2c_mux_add_adapter(struct i2c_mux_core *muxc,
 	priv->adap.dev.parent = &parent->dev;
 	priv->adap.retries = parent->retries;
 	priv->adap.timeout = parent->timeout;
-	priv->adap.quirks = parent->quirks;
+	/*
+	 * When creating the adapter, the node devices are checked for i2c address
+	 * match with other devices on the parent adapter, among which is the mux core itself.
+	 * If a match is found the node device is not probed successfully.
+	 * Allow the mux to have the same address as a child device by skipping this check.
+	 */
+	if (muxc->share_addr_with_children && muxc->dev->type == &i2c_client_type) {
+		struct i2c_adapter_quirks *quirks = devm_kzalloc(muxc->dev,
+								 sizeof(*quirks), GFP_KERNEL);
+		struct i2c_client *client = to_i2c_client(muxc->dev);
+
+		if (!quirks)
+			return -ENOMEM;
+
+		if (parent->quirks)
+			memcpy(quirks, parent->quirks, sizeof(*quirks));
+
+		quirks->flags |= I2C_AQ_SKIP_ADDR_CHECK;
+		quirks->skip_addr_in_parent = client->addr;
+		priv->adap.quirks = quirks;
+	} else {
+		priv->adap.quirks = parent->quirks;
+	}
+
 	if (muxc->mux_locked)
 		priv->adap.lock_ops = &i2c_mux_lock_ops;
 	else
diff --git a/include/linux/i2c-mux.h b/include/linux/i2c-mux.h
index 98ef73b7c8fd..17ac68bf1703 100644
--- a/include/linux/i2c-mux.h
+++ b/include/linux/i2c-mux.h
@@ -21,6 +21,7 @@  struct i2c_mux_core {
 	unsigned int mux_locked:1;
 	unsigned int arbitrator:1;
 	unsigned int gate:1;
+	unsigned int share_addr_with_children:1;
 
 	void *priv;
 
diff --git a/include/linux/i2c.h b/include/linux/i2c.h
index 5e6cd43a6dbd..c3acbaaadae9 100644
--- a/include/linux/i2c.h
+++ b/include/linux/i2c.h
@@ -670,6 +670,7 @@  int i2c_generic_scl_recovery(struct i2c_adapter *adap);
  * @max_read_len: maximum length of a read message
  * @max_comb_1st_msg_len: maximum length of the first msg in a combined message
  * @max_comb_2nd_msg_len: maximum length of the second msg in a combined message
+ * @skip_addr_in_parent: No conflict check on parent adapter for a given address
  *
  * Note about combined messages: Some I2C controllers can only send one message
  * per transfer, plus something called combined message or write-then-read.
@@ -690,6 +691,7 @@  struct i2c_adapter_quirks {
 	u16 max_read_len;
 	u16 max_comb_1st_msg_len;
 	u16 max_comb_2nd_msg_len;
+	unsigned short skip_addr_in_parent;
 };
 
 /* enforce max_num_msgs = 2 and use max_comb_*_len for length checks */
@@ -711,6 +713,11 @@  struct i2c_adapter_quirks {
 #define I2C_AQ_NO_ZERO_LEN		(I2C_AQ_NO_ZERO_LEN_READ | I2C_AQ_NO_ZERO_LEN_WRITE)
 /* adapter cannot do repeated START */
 #define I2C_AQ_NO_REP_START		BIT(7)
+/**
+ * do not check for conflict on a given address
+ * used accordingly with "struct i2c_adapter_quirks.skip_addr_in_parent"
+ */
+#define I2C_AQ_SKIP_ADDR_CHECK	BIT(8)
 
 /*
  * i2c_adapter is the structure used to identify a physical i2c bus along