[PATCH 3/3] i2c: atr: add passthrough flag

From: Cosmin Tanislav
Date: Mon Feb 03 2025 - 07:18:29 EST


Some I2C ATRs can have other I2C ATRs as children. The I2C messages of
the child ATRs need to be forwarded as-is since the parent I2C ATR can
only do address remapping for the direct children.

In the case of GMSL, the deserializer I2C ATR actually doesn't have I2C
address remapping hardware capabilities, but it is able to select which
GMSL link to talk to, allowing it to change the address of the
serializer.

The child ATRs need to have their alias pools defined in such a way to
prevent overlapping addresses between them, but there's no way around
this without orchestration between multiple ATR instances.

To allow for this use-case, add a flag that allows unmapped addresses
to be passed through, since they are already remapped by the child ATRs,
and disables dynamic remapping, since devices that need passthrough
messages to be forwarded as-is, can only handle remapping for their
direct children.

There's no case where a non-remapped address will hit the parent ATR.

Signed-off-by: Cosmin Tanislav <demonsingur@xxxxxxxxx>
---
drivers/i2c/i2c-atr.c | 26 ++++++++++++++++++--------
include/linux/i2c-atr.h | 20 +++++++++++++++++---
2 files changed, 35 insertions(+), 11 deletions(-)

diff --git a/drivers/i2c/i2c-atr.c b/drivers/i2c/i2c-atr.c
index 13f7e07fd8e87..5f0e8f1cf69f7 100644
--- a/drivers/i2c/i2c-atr.c
+++ b/drivers/i2c/i2c-atr.c
@@ -106,6 +106,7 @@ struct i2c_atr_chan {
* @lock: Lock for the I2C bus segment (see &struct i2c_lock_operations)
* @lock_key: Lock key for @lock
* @max_adapters: Maximum number of adapters this I2C ATR can have
+ * @flags: Flags for ATR
* @alias_pool: Optional common pool of available client aliases
* @i2c_nb: Notifier for remote client add & del events
* @adapter: Array of adapters
@@ -122,6 +123,7 @@ struct i2c_atr {
struct mutex lock;
struct lock_class_key lock_key;
int max_adapters;
+ u32 flags;

struct i2c_atr_alias_pool *alias_pool;

@@ -241,7 +243,7 @@ static void i2c_atr_release_alias(struct i2c_atr_alias_pool *alias_pool, u16 ali

/* Must be called with alias_pairs_lock held */
static struct i2c_atr_alias_pair *
-i2c_atr_find_mapping_by_addr(struct i2c_atr_chan *chan, u16 addr)
+i2c_atr_find_mapping_by_addr(struct i2c_atr_chan *chan, u16 addr, bool new_addr)
{
struct i2c_atr *atr = chan->atr;
struct i2c_atr_alias_pair *c2a;
@@ -260,6 +262,9 @@ i2c_atr_find_mapping_by_addr(struct i2c_atr_chan *chan, u16 addr)

ret = i2c_atr_reserve_alias(chan->alias_pool);
if (ret < 0) {
+ if (!new_addr && (atr->flags & I2C_ATR_PASSTHROUGH))
+ return NULL;
+
// If no free aliases are left, replace an existing one
if (unlikely(list_empty(alias_pairs)))
return NULL;
@@ -335,9 +340,12 @@ static int i2c_atr_map_msgs(struct i2c_atr_chan *chan, struct i2c_msg *msgs,
for (i = 0; i < num; i++) {
chan->orig_addrs[i] = msgs[i].addr;

- c2a = i2c_atr_find_mapping_by_addr(chan, msgs[i].addr);
+ c2a = i2c_atr_find_mapping_by_addr(chan, msgs[i].addr, false);

if (!c2a) {
+ if (atr->flags & I2C_ATR_PASSTHROUGH)
+ continue;
+
dev_err(atr->dev, "client 0x%02x not mapped!\n",
msgs[i].addr);

@@ -428,7 +436,7 @@ static int i2c_atr_smbus_xfer(struct i2c_adapter *adap, u16 addr,

mutex_lock(&chan->alias_pairs_lock);

- c2a = i2c_atr_find_mapping_by_addr(chan, addr);
+ c2a = i2c_atr_find_mapping_by_addr(chan, addr, false);

if (!c2a) {
dev_err(atr->dev, "client 0x%02x not mapped!\n", addr);
@@ -491,7 +499,7 @@ static int i2c_atr_attach_addr(struct i2c_adapter *adapter,

mutex_lock(&chan->alias_pairs_lock);

- c2a = i2c_atr_find_mapping_by_addr(chan, addr);
+ c2a = i2c_atr_find_mapping_by_addr(chan, addr, true);
if (!c2a) {
dev_err(atr->dev, "failed to find a free alias\n");
mutex_unlock(&chan->alias_pairs_lock);
@@ -517,7 +525,7 @@ static void i2c_atr_detach_addr(struct i2c_adapter *adapter,

mutex_lock(&chan->alias_pairs_lock);

- c2a = i2c_atr_find_mapping_by_addr(chan, addr);
+ c2a = i2c_atr_find_mapping_by_addr(chan, addr, false);
if (!c2a) {
/* This should never happen */
dev_warn(atr->dev, "Unable to find address mapping\n");
@@ -650,8 +658,9 @@ static int i2c_atr_parse_alias_pool(struct i2c_atr *atr)
return ret;
}

-struct i2c_atr *i2c_atr_new(struct i2c_adapter *parent, struct device *dev,
- const struct i2c_atr_ops *ops, int max_adapters)
+struct i2c_atr *i2c_atr_new_flags(struct i2c_adapter *parent, struct device *dev,
+ const struct i2c_atr_ops *ops, int max_adapters,
+ u32 flags)
{
struct i2c_atr *atr;
int ret;
@@ -673,6 +682,7 @@ struct i2c_atr *i2c_atr_new(struct i2c_adapter *parent, struct device *dev,
atr->dev = dev;
atr->ops = ops;
atr->max_adapters = max_adapters;
+ atr->flags = flags;

if (parent->algo->master_xfer)
atr->algo.master_xfer = i2c_atr_master_xfer;
@@ -700,7 +710,7 @@ struct i2c_atr *i2c_atr_new(struct i2c_adapter *parent, struct device *dev,

return ERR_PTR(ret);
}
-EXPORT_SYMBOL_NS_GPL(i2c_atr_new, "I2C_ATR");
+EXPORT_SYMBOL_NS_GPL(i2c_atr_new_flags, "I2C_ATR");

void i2c_atr_delete(struct i2c_atr *atr)
{
diff --git a/include/linux/i2c-atr.h b/include/linux/i2c-atr.h
index 1c3a5bcd939fc..116067b5b9ba6 100644
--- a/include/linux/i2c-atr.h
+++ b/include/linux/i2c-atr.h
@@ -18,6 +18,15 @@ struct device;
struct fwnode_handle;
struct i2c_atr;

+/**
+ * enum i2c_atr_flags - Flags for an I2C ATR driver
+ *
+ * @I2C_ATR_PASSTHROUGH: Allow unmapped incoming addresses to pass through
+ */
+enum i2c_atr_flags {
+ I2C_ATR_PASSTHROUGH = BIT(0),
+};
+
/**
* struct i2c_atr_ops - Callbacks from ATR to the device driver.
* @attach_addr: Notify the driver of a new device connected on a child
@@ -60,11 +69,12 @@ struct i2c_atr_adap_desc {
};

/**
- * i2c_atr_new() - Allocate and initialize an I2C ATR helper.
+ * i2c_atr_new_flags() - Allocate and initialize an I2C ATR helper.
* @parent: The parent (upstream) adapter
* @dev: The device acting as an ATR
* @ops: Driver-specific callbacks
* @max_adapters: Maximum number of child adapters
+ * @flags: Flags for ATR
*
* The new ATR helper is connected to the parent adapter but has no child
* adapters. Call i2c_atr_add_adapter() to add some.
@@ -73,8 +83,12 @@ struct i2c_atr_adap_desc {
*
* Return: pointer to the new ATR helper object, or ERR_PTR
*/
-struct i2c_atr *i2c_atr_new(struct i2c_adapter *parent, struct device *dev,
- const struct i2c_atr_ops *ops, int max_adapters);
+struct i2c_atr *i2c_atr_new_flags(struct i2c_adapter *parent, struct device *dev,
+ const struct i2c_atr_ops *ops, int max_adapters,
+ u32 flags);
+
+#define i2c_atr_new(parent, dev, ops, max_adapters) \
+ i2c_atr_new_flags(parent, dev, ops, max_adapters, 0)

/**
* i2c_atr_delete - Delete an I2C ATR helper.
--
2.48.1