RE: [PATCH] i2c: imx: add I2C_M_REV_DIR_ADDR and I2C_M_NOSTART

"Carlos Song (OSS)" <[email protected]>
Newsgroups dev.linux.lists.imx,org.infradead.lists.linux-arm-kernel,org.kernel.vger.linux-i2c,org.kernel.vger.linux-kernel
Message-ID <AM0PR04MB680259939B7ED9AF5BA5957BE8DC2@AM0PR04MB6802.eurprd04.prod.outlook.com>

> -----Original Message-----
> From: Vincent Jardin <[email protected]>
> Sent: Wednesday, August 12, 2026 1:47 PM
> To: Carlos Song (OSS) <[email protected]>
> Cc: Oleksij Rempel <[email protected]>; Pengutronix Kernel Team
> <[email protected]>; Andi Shyti <[email protected]>; Frank Li
> <[email protected]>; Sascha Hauer <[email protected]>; Fabio
> Estevam <[email protected]>; [email protected];
> [email protected]; [email protected];
> [email protected]
> Subject: Re: [PATCH] i2c: imx: add I2C_M_REV_DIR_ADDR and
> I2C_M_NOSTART
> 
> Hi Carlos,
> 
> Thanks a lot for the review, and no problem for the delay.
> 
> > This is a such rare i2c frame design in the Realtek RTL8366SE SMI read
> > frame, right?
> 
> It is rare, but I have been inspired for I2C_M_REV_DIR_ADDR by the source of
> two client drivers that use it.
> 
> Note that I2C_M_NOSTART is not rare at all. It is what regmap uses for a real
> gather write. regmap_i2c_gather_write() opens with
> 
>         if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_NOSTART))
>                 return -ENOTSUPP;
> 
> FYI, some clients that need REV_DIR_ADDR and NOSTART,
> 
>   drivers/input/joystick/as5011.c
>   drivers/video/fbdev/matrox/matroxfb_maven.c
> 
> both build the identical 2-message pattern,
> 
>   i2c_check_functionality(adapter,
>                                 I2C_FUNC_NOSTART |
>                                 I2C_FUNC_PROTOCOL_MANGLING)
> 
> I do not have those devices, so I did not check it beside code readings.
> 
> Some clients that need NOSTART alone,
> 
>   drivers/base/regmap/regmap-i2c.c
>   drivers/infiniband/hw/hfi1/qsfp.c
>   drivers/gpu/drm/i915/display/dvo_ivch.c
> 
> About the i2c masters, that use I2C_M_REV_DIR_ADDR in code,
> 
>   drivers/i2c/algos/i2c-algo-bit.c
>   drivers/i2c/algos/i2c-algo-pcf.c
>   drivers/i2c/busses/i2c-s3c2410.c <- the model I did investigate
>   drivers/i2c/busses/i2c-tegra-bpmp.c
>   drivers/media/pci/cobalt/cobalt-i2c.c
> 
> So a taxnonomy can be,
> 
>   adapter            REV_DIR impl   MANGLING adv   NOSTART adv
> usable
>   i2c-algo-bit           yes            yes            yes
> yes
>   i2c-algo-pcf           yes            yes            no
> no
>   i2c-s3c2410            yes            yes            yes
> yes
>   i2c-tegra-bpmp         yes            yes            yes
> yes
>   cobalt-i2c             yes         (private adapter, not exposed)
>   i2c-brcmstb            no             yes            yes
> no
>   i2c-pxa                no             yes            yes
> no
>   i2c-tegra              no             yes         yes (cond)
> no
>   i2c-imx (this patch)   yes            yes            yes
> yes
> 
> > Is Realtek RTL8366SE SMI driver upstream? Can I found the driver?
> 
> Not yet, and I cannot point you at a tree today. It is Realtek's "Unmanaged
> Switch" DSA driver, which is not public yet.
> 
> What I can share is the message construction, which is the part you asked
> about and is not Realtek-specific.
> 
>         static int as5011_i2c_read(struct i2c_client *client,
>                                    uint8_t aregaddr, signed char
> *value)
>         {
>                 uint8_t data[2] = { aregaddr };
>                 struct i2c_msg msg_set[2] = {
>                         {
>                                 .addr = client->addr,
>                                 .flags = I2C_M_REV_DIR_ADDR,
>                                 .len = 1,
>                                 .buf = (uint8_t *)data
>                         },
>                         {
>                                 .addr = client->addr,
>                                 .flags = I2C_M_RD |
> I2C_M_NOSTART,
>                                 .len = 1,
>                                 .buf = (uint8_t *)data
>                         }
>                 };
>                 int error;
> 
>                 error = i2c_transfer(client->adapter, msg_set, 2);
>                 if (error < 0)
>                         return error;
> 
>                 *value = data[0] & 0x80 ? -1 * (1 + ~data[0]) : data[0];
>                 return 0;
>         }
> 
> And the Realtek accessor, which is the same two messages with a 2-byte
> register and 2-byte data instead of 1 and 1:
> 
>         u8 ra[2]   = { reg & 0xff, (reg >> 8) & 0xff };
>         u8 data[2] = { 0xff, 0xff };
>         struct i2c_msg msgs[2] = {
>                 {
>                         .addr  = client->addr,
>                         .flags = I2C_M_REV_DIR_ADDR,
>                         .len   = sizeof(ra),
>                         .buf   = ra,
>                 }, {
>                         .addr  = client->addr,
>                         .flags = I2C_M_RD | I2C_M_NOSTART,
>                         .len   = sizeof(data),
>                         .buf   = data,
>                 },
>         };
> 
>         ret = i2c_transfer(client->adapter, msgs, 2);
>         if (ret != 2)
>                 return ret < 0 ? ret : -EIO;
> 
>         *val = data[0] | (data[1] << 8);
> 
> msgs[0] is a write message carrying I2C_M_REV_DIR_ADDR, so the address
> byte goes out with the read bit set while the master keeps transmitting the
> two register-address bytes. msgs[1] is the read half with I2C_M_NOSTART, so
> no repeated start is emitted and the controller simply turns the bus around.
> Writes are an ordinary unflagged 4-byte write and need nothing from this
> patch.
> 
> > ... Have you test this i2c-imx feature in your LS board with RTL8366SE?
> 
> Yes, on an LX2160A board carrying four RTL8366SE-CG. Two are strapped to
> the chip's 2-wire "EEPROM SMI" mode and hang off hardware i2c-imx
> controllers; the other two are on MDIO, for both the same switch registers are
> reachable both ways and should answer the same values.
> 
> First, the failure on an unpatched kernel, which is reproducible with nothing
> but i2ctools and is I think the clearest way to see the problem.
> These two commands are byte-identical:
> 
>         # i2ctransfer -y -f -a 1 w2@0x7c 0x00 0x13 r4@0x7c
>         0xff 0xff 0x00 0x00
>         # i2ctransfer -y -f -a 1 r6@0x7c        # no preceding write at all
>         0xff 0xff 0x00 0x00 0x00 0x00
> 
> Then, with this patch both chips answer correctly. For testing this patch, I did
> use the following that I have just pushed to help, for the record:
> 
>         https://github.com/vjardin/smi-probe
> 
>         # smi-probe -t i2c -b /dev/i2c-1 -a 0x7c id
>         chip_num    0x6980  CHIP_RTL8367E (inside RTL8366SE-CG)
>         chip_ver    0x0030
>         svlan_tpid  0x88a8  reset default, as expected
> 
>         # smi-probe -t i2c -b /dev/i2c-1 -a 0x7c rd 0x1300
>         reg 0x1300 = 0x6980
>         # smi-probe -t i2c -b /dev/i2c-1 -a 0x7c rd 0x1202
>         reg 0x1202 = 0x88a8
> 
>         # smi-probe -t i2c -b /dev/i2c-2 -a 0x5c rd 0x1202 # U19, IIC3
>         reg 0x1202 = 0x88a8
>         # smi-probe -t mdio -b 0x8b97000 -a 0x1a rd 0x1202 # MDIO
> control
>         reg 0x1202 = 0x88a8
> 
> Then it binds as a DSA switch and enumerates its four user ports:
> 
>         realtek-US-switch-dsa-i2c 2-005c: RTK DSA unit 0 (EEPROM SMI,
> addr 0x5c)
> 
> I have many other i2c devices on this board, no regression.
> 
> Thanks again for your review,
>   Vincent


Hi, Vincent

Thank you very much for such clear instructions. That helps lot to understand what happened.
Follow this, I also spend some time to learn and understand this case.

1. Why need I2C_M_REV_DIR_ADDR but not directly read?
The msg with I2C_M_REV_DIR_ADDR is a write msg in fact, it is a write message, master write data and target ack,
only w bit is toggled to r bit.
Because the device don't follow the standard I2C spec, it need this special frame to write:
[1] S + addr + r + [ACK] + data +[ACK] + data...
Yes, not wrong, a read bit on bus but it is a write msg for this device.

But the standard spec write msg is
[2] S + addr + w +[ACK] +data + [ACK] +data
So add this I2C_M_REV_DIR_ADDR flag to make this write msg w bit is changed to r bit, msg [2] become msg [1] so the device worked.
Only change the r/w bit, other data flow keep write logic.

2. What I2C_M_NOSTART is doing?
It help skip the msg repeat start header, next msg start from data. If the first msg setting this flag, it should be unsupported(so you add a loop check to make sure the first msg without I2C_M_NOSTART flag).

3. The msg with I2C_M_NOSTART should keep the same direction with pre msg?
No, not need. For example, RTL8366SE frame:

(1){S Addr Rd [A] reg[7:0] [A] reg[15:8] [A]} (2){ [data[7:0]] A [data[15:8]] NA} P

For (1):
{S Addr Wr [A] reg[7:0] [A] reg[15:8] [A] is a write msg and "change the Wr bit to Rd bit" by I2C_M_REV_DIR_ADDR.
So i2c bus signal is
{S Addr Rd [A] reg[7:0] [A] reg[15:8] [A]}

For (2):
{Rs Addr Rd [A] [data[7:0]] A [data[15:8]] NA} is a read msg and "skip the {Rs Addr Rd [A]}" by I2C_M_NOSTART.
So i2c bus signal is [data[7:0]] A [data[15:8]] NA.

This is a write msg then a read msg. So you add the I2C_M_NOSTART flag handle in these 4 path: atomic read/irq read/atomic write/irq write.
Do I understand right?


About code, Could this part of the code be improved? Of course, that's a matter of personal preference.

@@ -1583,6 +1622,27 @@ static int i2c_imx_xfer_common(struct i2c_adapter *adapter,
        struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter);
        int use_dma = 0;
 
+       for (i = 0; i < num; i++) {
+               /*
+                * I2C_FUNC_PROTOCOL_MANGLING is advertised for
+                * I2C_M_REV_DIR_ADDR only; reject the flags it also covers but
+                * that this driver does not implement, rather than silently
+                * transferring something the caller did not ask for.
+                */
+               if (msgs[i].flags & (I2C_M_IGNORE_NAK | I2C_M_NO_RD_ACK |
+                                    I2C_M_STOP))
+                       return -EOPNOTSUPP;
+
+               if (!(msgs[i].flags & I2C_M_NOSTART))
+                       continue;
+
+               /* Nothing to continue from, and a zero-length continuation
+                * would leave the controller with no byte to clock.
+                */
+               if (i == 0 || msgs[i].len == 0)
+                       return -EOPNOTSUPP;
+       }
+
        /* Start I2C transfer */
        result = i2c_imx_start(i2c_imx, atomic);
        if (result) {


I prefer this:
1. Do a single function for this check.
static int i2c_imx_check_msgs()
2. Add macro definition.
#define I2C_IMX_UNSUPPORTED_PROTOCOL_MANGLING_FLAGS \
	(I2C_M_IGNORE_NAK | I2C_M_NO_RD_ACK | I2C_M_STOP)
3. drop continue and use:
		/* NOSTART-specific sanity checks. */
		if (msgs[i].flags & I2C_M_NOSTART) {
			/*
			 * NOSTART continues an already open frame. The first
			 * message has nothing to continue from, and a
			 * zero-length continuation leaves the controller with
			 * no byte to clock.
			 */
			if (i == 0 || msgs[i].len == 0)
				return -EOPNOTSUPP;
		}

It is up to you. All in all, I think this patch is nice for me and thank you again for the improvements.

Carlos Song
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.