Message ID | 20200818234215.2255611-1-furquan@google.com (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | drivers: input: Use single i2c_transfer transaction when using RM_CMD_BANK_SWITCH | expand |
Hi Furquan, On Tue, Aug 18, 2020 at 04:42:15PM -0700, Furquan Shaikh wrote: > On an AMD chromebook, where the same I2C bus is shared by both Raydium > touchscreen and a trackpad device, it is observed that interleaving of > I2C messages when raydium_i2c_read_message() is called leads to the > Raydium touch IC reporting incorrect information. This is the sequence > that was observed to result in the above issue: > > * I2C write to Raydium device for RM_CMD_BANK_SWITCH > * I2C write to trackpad device > * I2C read from trackpad device > * I2C write to Raydium device for setting address > * I2C read from Raydium device >>>> This provides incorrect > information > > This change updates raydium_i2c_read_message and > raydium_i2c_send_message to perform all the I2C transfers in the > function as part of a single i2c_transfer transaction. This ensures > that no transactions are initiated to any other device on the same bus > in between and hence the information obtained from the touchscreen > device is correct. Verified with the patch across multiple > reboots (>100) that the information reported by the Raydium > touchscreen device during probe is correct. The devices (touchpad and touchscreen) have to have different addresses and therefore should be able to operate independently of each other. Are you sure that the problem is not in i2c controller implementation that mixes up data streams from 2 separate devices? Thanks.
Hello Dmitry, On Tue, Aug 18, 2020 at 5:08 PM Dmitry Torokhov <dmitry.torokhov@gmail.com> wrote: > > Hi Furquan, > > On Tue, Aug 18, 2020 at 04:42:15PM -0700, Furquan Shaikh wrote: > > On an AMD chromebook, where the same I2C bus is shared by both Raydium > > touchscreen and a trackpad device, it is observed that interleaving of > > I2C messages when raydium_i2c_read_message() is called leads to the > > Raydium touch IC reporting incorrect information. This is the sequence > > that was observed to result in the above issue: > > > > * I2C write to Raydium device for RM_CMD_BANK_SWITCH > > * I2C write to trackpad device > > * I2C read from trackpad device > > * I2C write to Raydium device for setting address > > * I2C read from Raydium device >>>> This provides incorrect > > information > > > > This change updates raydium_i2c_read_message and > > raydium_i2c_send_message to perform all the I2C transfers in the > > function as part of a single i2c_transfer transaction. This ensures > > that no transactions are initiated to any other device on the same bus > > in between and hence the information obtained from the touchscreen > > device is correct. Verified with the patch across multiple > > reboots (>100) that the information reported by the Raydium > > touchscreen device during probe is correct. > > The devices (touchpad and touchscreen) have to have different addresses > and therefore should be able to operate independently of each other. Are > you sure that the problem is not in i2c controller implementation that > mixes up data streams from 2 separate devices? Yes, the I2C addresses for the touchpad and touchscreen devices are different on this platform. Based on i2c tracing and the operations performed by the driver, the transactions are being sent out to the correct address. The issue occurs only when a transaction for the touchpad device is sent after the `RM_CMD_BANK_SWITCH` command is sent to the touchscreen device and before the driver reads back the required data. Snippet of tracing in good and bad case (with comments): Bad case: ``` i2c_write: i2c-0 #0 a=039 f=0000 l=5 [aa-20-00-1b-94] <<<<<< RM_CMD_BANK_SWITCH command to touchscreen device i2c_result: i2c-0 n=1 ret=1 i2c_write: i2c-0 #0 a=015 f=0000 l=2 [03-01] <<<<<< Transaction to touchpad device i2c_read: i2c-0 #1 a=015 f=0001 l=2 i2c_reply: i2c-0 #1 a=015 f=0001 l=2 [00-10] i2c_result: i2c-0 n=2 ret=2 i2c_write: i2c-0 #0 a=039 f=0000 l=1 [94] <<<<<< Write/read after RM_CMD_BANK_SWITCH to touchscreen device i2c_read: i2c-0 #1 a=039 f=0001 l=16 i2c_reply: i2c-0 #1 a=039 f=0001 l=16 [15-00-20-74-07-00-d8-02-00-00-00-ef-ff-ff-ff-00] <<<<<< Incorrect data i2c_result: i2c-0 n=2 ret=2 ``` Good case: ``` i2c_write: i2c-0 #0 a=015 f=0000 l=2 [03-01] <<<<<< Same transaction as above to touchpad device with same response from touchpad device i2c_read: i2c-0 #1 a=015 f=0001 l=2 i2c_reply: i2c-0 #1 a=015 f=0001 l=2 [00-10] i2c_result: i2c-0 n=2 ret=2 ... i2c_write: i2c-0 #0 a=039 f=0000 l=5 [aa-20-00-1b-94] <<<<<< RM_CMD_BANK_SWITCH command to touchscreen device i2c_result: i2c-0 n=1 ret=1 i2c_write: i2c-0 #0 a=039 f=0000 l=1 [94] <<<<<< Write/read after RM_CMD_BANK_SWITCH to touchscreen device i2c_read: i2c-0 #1 a=039 f=0001 l=16 i2c_reply: i2c-0 #1 a=039 f=0001 l=16 [42-18-22-a2-01-01-01-00-4a-2a-80-07-38-04-18-18] <<<<<< Correct data ``` It can be seen that the transaction which got interleaved in the bad case is also sent out in the good case with the same response from the touchpad device. Based on this evidence, the issue points towards a problem with touchscreen firmware expectations. Also, this same designware I2C driver is used on many other platforms as well. What is unique about this platform is that the touchpad and touchscreen devices share the same bus. Thanks, Furquan > > Thanks. > > -- > Dmitry
Hi Furquan, On Tue, Aug 18, 2020 at 04:42:15PM -0700, Furquan Shaikh wrote: > On an AMD chromebook, where the same I2C bus is shared by both Raydium > touchscreen and a trackpad device, it is observed that interleaving of > I2C messages when raydium_i2c_read_message() is called leads to the > Raydium touch IC reporting incorrect information. This is the sequence > that was observed to result in the above issue: > > * I2C write to Raydium device for RM_CMD_BANK_SWITCH > * I2C write to trackpad device > * I2C read from trackpad device > * I2C write to Raydium device for setting address > * I2C read from Raydium device >>>> This provides incorrect > information > > This change updates raydium_i2c_read_message and > raydium_i2c_send_message to perform all the I2C transfers in the > function as part of a single i2c_transfer transaction. This ensures > that no transactions are initiated to any other device on the same bus > in between and hence the information obtained from the touchscreen > device is correct. Verified with the patch across multiple > reboots (>100) that the information reported by the Raydium > touchscreen device during probe is correct. > > Signed-off-by: Furquan Shaikh <furquan@google.com> > --- > drivers/input/touchscreen/raydium_i2c_ts.c | 102 ++++++++++++++++----- > 1 file changed, 80 insertions(+), 22 deletions(-) > > diff --git a/drivers/input/touchscreen/raydium_i2c_ts.c b/drivers/input/touchscreen/raydium_i2c_ts.c > index fe245439adee..11c00d341eb1 100644 > --- a/drivers/input/touchscreen/raydium_i2c_ts.c > +++ b/drivers/input/touchscreen/raydium_i2c_ts.c > @@ -111,6 +111,15 @@ struct raydium_info { > u8 y_res; /* units/mm */ > }; > > +/* > + * Header to be sent for RM_CMD_BANK_SWITCH command. This is used by > + * raydium_i2c_{read|send}_message below. > + */ > +struct __packed raydium_bank_switch_header { > + u8 cmd; > + __be32 be_addr; > +}; I believe the preferred placement of __packed attribute is after the definition: dtor@dtor-ws:~/kernel/work $ git grep "struct __packed" | wc -l 210 dtor@dtor-ws:~/kernel/work $ git grep "} __packed" | wc -l 8718 > + > /* struct raydium_data - represents state of Raydium touchscreen device */ > struct raydium_data { > struct i2c_client *client; > @@ -198,22 +207,44 @@ static int raydium_i2c_read(struct i2c_client *client, > static int raydium_i2c_read_message(struct i2c_client *client, > u32 addr, void *data, size_t len) > { > - __be32 be_addr; > - size_t xfer_len; > - int error; > + int ret; > > while (len) { > - xfer_len = min_t(size_t, len, RM_MAX_READ_SIZE); > - > - be_addr = cpu_to_be32(addr); > + u8 read_addr = addr & 0xff; > + struct raydium_bank_switch_header header = { > + .cmd = RM_CMD_BANK_SWITCH, > + .be_addr = cpu_to_be32(addr), > + }; > + size_t xfer_len = min_t(size_t, len, RM_MAX_READ_SIZE); > + /* > + * Perform as a single i2c_transfer transaction to ensure that > + * no other I2C transactions are initiated on the bus to any > + * other device in between. Initiating transacations to other > + * devices after RM_CMD_BANK_SWITCH is sent is known to cause > + * read issues. > + */ > + struct i2c_msg xfer[] = { > + { > + .addr = client->addr, > + .len = sizeof(header), > + .buf = (u8 *)&header, > + }, > + { > + .addr = client->addr, > + .len = 1, > + .buf = &read_addr, > + }, > + { > + .addr = client->addr, > + .flags = I2C_M_RD, > + .len = xfer_len, > + .buf = data, > + } > + }; I think this can be moved out of while loop. I also wonder if this can be actually combined with raydium_i2c_read(). As far as I understand read/writes to register above 255 require page select write, so we can always prepare the header and then submit either 3 or 2 messages in the transfer depending on the register we are dealing with. Or maybe convert to regmap? Thanks.
diff --git a/drivers/input/touchscreen/raydium_i2c_ts.c b/drivers/input/touchscreen/raydium_i2c_ts.c index fe245439adee..11c00d341eb1 100644 --- a/drivers/input/touchscreen/raydium_i2c_ts.c +++ b/drivers/input/touchscreen/raydium_i2c_ts.c @@ -111,6 +111,15 @@ struct raydium_info { u8 y_res; /* units/mm */ }; +/* + * Header to be sent for RM_CMD_BANK_SWITCH command. This is used by + * raydium_i2c_{read|send}_message below. + */ +struct __packed raydium_bank_switch_header { + u8 cmd; + __be32 be_addr; +}; + /* struct raydium_data - represents state of Raydium touchscreen device */ struct raydium_data { struct i2c_client *client; @@ -198,22 +207,44 @@ static int raydium_i2c_read(struct i2c_client *client, static int raydium_i2c_read_message(struct i2c_client *client, u32 addr, void *data, size_t len) { - __be32 be_addr; - size_t xfer_len; - int error; + int ret; while (len) { - xfer_len = min_t(size_t, len, RM_MAX_READ_SIZE); - - be_addr = cpu_to_be32(addr); + u8 read_addr = addr & 0xff; + struct raydium_bank_switch_header header = { + .cmd = RM_CMD_BANK_SWITCH, + .be_addr = cpu_to_be32(addr), + }; + size_t xfer_len = min_t(size_t, len, RM_MAX_READ_SIZE); + /* + * Perform as a single i2c_transfer transaction to ensure that + * no other I2C transactions are initiated on the bus to any + * other device in between. Initiating transacations to other + * devices after RM_CMD_BANK_SWITCH is sent is known to cause + * read issues. + */ + struct i2c_msg xfer[] = { + { + .addr = client->addr, + .len = sizeof(header), + .buf = (u8 *)&header, + }, + { + .addr = client->addr, + .len = 1, + .buf = &read_addr, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = xfer_len, + .buf = data, + } + }; - error = raydium_i2c_send(client, RM_CMD_BANK_SWITCH, - &be_addr, sizeof(be_addr)); - if (!error) - error = raydium_i2c_read(client, addr & 0xff, - data, xfer_len); - if (error) - return error; + ret = i2c_transfer(client->adapter, xfer, ARRAY_SIZE(xfer)); + if (ret != ARRAY_SIZE(xfer)) + return ret < 0 ? ret : -EIO; len -= xfer_len; data += xfer_len; @@ -224,22 +255,49 @@ static int raydium_i2c_read_message(struct i2c_client *client, } static int raydium_i2c_send_message(struct i2c_client *client, - u32 addr, const void *data, size_t len) + u32 addr, void *data, size_t len) { - __be32 be_addr = cpu_to_be32(addr); - int error; + int tries = 0; - error = raydium_i2c_send(client, RM_CMD_BANK_SWITCH, - &be_addr, sizeof(be_addr)); - if (!error) - error = raydium_i2c_send(client, addr & 0xff, data, len); + do { + int ret; + u8 write_addr = addr & 0xff; + struct raydium_bank_switch_header header = { + .cmd = RM_CMD_BANK_SWITCH, + .be_addr = cpu_to_be32(addr), + }; + + struct i2c_msg xfer[] = { + { + .addr = client->addr, + .len = sizeof(header), + .buf = (u8 *)&header, + }, + { + .addr = client->addr, + .len = 1, + .buf = &write_addr, + }, + { + .addr = client->addr, + .len = len, + .buf = data, + } + }; - return error; + ret = i2c_transfer(client->adapter, xfer, ARRAY_SIZE(xfer)); + if (likely(ret == ARRAY_SIZE(xfer))) + return 0; + + msleep(20); + } while (++tries < RM_MAX_RETRIES); + + return -EIO; } static int raydium_i2c_sw_reset(struct i2c_client *client) { - const u8 soft_rst_cmd = 0x01; + u8 soft_rst_cmd = 0x01; int error; error = raydium_i2c_send_message(client, RM_RESET_MSG_ADDR,
On an AMD chromebook, where the same I2C bus is shared by both Raydium touchscreen and a trackpad device, it is observed that interleaving of I2C messages when raydium_i2c_read_message() is called leads to the Raydium touch IC reporting incorrect information. This is the sequence that was observed to result in the above issue: * I2C write to Raydium device for RM_CMD_BANK_SWITCH * I2C write to trackpad device * I2C read from trackpad device * I2C write to Raydium device for setting address * I2C read from Raydium device >>>> This provides incorrect information This change updates raydium_i2c_read_message and raydium_i2c_send_message to perform all the I2C transfers in the function as part of a single i2c_transfer transaction. This ensures that no transactions are initiated to any other device on the same bus in between and hence the information obtained from the touchscreen device is correct. Verified with the patch across multiple reboots (>100) that the information reported by the Raydium touchscreen device during probe is correct. Signed-off-by: Furquan Shaikh <furquan@google.com> --- drivers/input/touchscreen/raydium_i2c_ts.c | 102 ++++++++++++++++----- 1 file changed, 80 insertions(+), 22 deletions(-)