Skip to content

Commit

Permalink
HID: mcp2221: Handle reads greater than 60 bytes
Browse files Browse the repository at this point in the history
When a user requests more than 60 bytes of data the MCP2221 must chunk
the data in chunks up to 60 bytes long (see command/response code 0x40
in the datasheet).
In order to signal that the device has more data the (undocumented) byte
at byte index 2 of the Get I2C Data response uses the value 0x54. This
contrasts with the case for the final data chunk where the value
returned is 0x55 (MCP2221_I2C_READ_COMPL). The fact that 0x55 was not
returned in the response was interpreted by the driver as a failure
meaning that all reads of more than 60 bytes would fail.

Add support for reads that are split over multiple chunks by looking for
the response code indicating that more data is expected and continuing
the read as the code intended. Some timing delays are required to ensure
the chip has time to refill its FIFO as data is read in from the I2C
bus. This timing has been tested in my system when configured for bus
speeds of 50KHz, 100KHz, and 400KHz and operates well.

Signed-off-by: Hamish Martin <hamish.martin@alliedtelesis.co.nz>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
  • Loading branch information
Hamish Martin authored and Jiri Kosina committed Nov 21, 2023
1 parent 02a4675 commit 2682468
Showing 1 changed file with 23 additions and 9 deletions.
32 changes: 23 additions & 9 deletions drivers/hid/hid-mcp2221.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ enum {
MCP2221_I2C_MASK_ADDR_NACK = 0x40,
MCP2221_I2C_WRADDRL_SEND = 0x21,
MCP2221_I2C_ADDR_NACK = 0x25,
MCP2221_I2C_READ_PARTIAL = 0x54,
MCP2221_I2C_READ_COMPL = 0x55,
MCP2221_ALT_F_NOT_GPIOV = 0xEE,
MCP2221_ALT_F_NOT_GPIOD = 0xEF,
Expand Down Expand Up @@ -297,6 +298,7 @@ static int mcp_i2c_smbus_read(struct mcp2221 *mcp,
{
int ret;
u16 total_len;
int retries = 0;

mcp->txbuf[0] = type;
if (msg) {
Expand All @@ -320,20 +322,31 @@ static int mcp_i2c_smbus_read(struct mcp2221 *mcp,
mcp->rxbuf_idx = 0;

do {
/* Wait for the data to be read by the device */
usleep_range(980, 1000);

memset(mcp->txbuf, 0, 4);
mcp->txbuf[0] = MCP2221_I2C_GET_DATA;

ret = mcp_send_data_req_status(mcp, mcp->txbuf, 1);
if (ret)
return ret;

ret = mcp_chk_last_cmd_status_free_bus(mcp);
if (ret)
return ret;

usleep_range(980, 1000);
if (ret) {
if (retries < 5) {
/* The data wasn't ready to read.
* Wait a bit longer and try again.
*/
usleep_range(90, 100);
retries++;
} else {
return ret;
}
} else {
retries = 0;
}
} while (mcp->rxbuf_idx < total_len);

usleep_range(980, 1000);
ret = mcp_chk_last_cmd_status_free_bus(mcp);

return ret;
}

Expand Down Expand Up @@ -799,7 +812,8 @@ static int mcp2221_raw_event(struct hid_device *hdev,
mcp->status = -EIO;
break;
}
if (data[2] == MCP2221_I2C_READ_COMPL) {
if (data[2] == MCP2221_I2C_READ_COMPL ||
data[2] == MCP2221_I2C_READ_PARTIAL) {
buf = mcp->rxbuf;
memcpy(&buf[mcp->rxbuf_idx], &data[4], data[3]);
mcp->rxbuf_idx = mcp->rxbuf_idx + data[3];
Expand Down

0 comments on commit 2682468

Please sign in to comment.