From 3045696d0ce663d67c95dcb8206d3de57f6841ec Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 31 Jan 2020 13:46:25 +0100 Subject: [PATCH 01/11] HID: quirks: Remove ITE 8595 entry from hid_have_special_driver The ITE 8595 chip used in various 2-in-1 keyboard docks works fine with the hid-generic driver (minus the RF_KILL key) and also keeps working fine when swapping drivers, so there is no need to have it in the hid_have_special_driver list. Note the other 2 USB ids in hid-ite.c were never added to hid_have_special_driver. Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-quirks.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 0e7b2d998395..503cfbe207ab 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -397,9 +397,6 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A081) }, { HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A0C2) }, #endif -#if IS_ENABLED(CONFIG_HID_ITE) - { HID_USB_DEVICE(USB_VENDOR_ID_ITE, USB_DEVICE_ID_ITE8595) }, -#endif #if IS_ENABLED(CONFIG_HID_ICADE) { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ION, USB_DEVICE_ID_ICADE) }, #endif From 630dd6eaffc8adb11885b51d232c987415657230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Mon, 13 Jan 2020 19:23:00 +0000 Subject: [PATCH 02/11] HID: logitech-dj: add debug msg when exporting a HID++ report descriptors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When exporting all other types of report descriptors we print a debug message. Not doing so for HID++ descriptors makes unaware users think that no HID++ descriptor was exported. Signed-off-by: Filipe Laíns Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-dj.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index bb50d6e7745b..37e54a401452 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -1368,6 +1368,8 @@ static int logi_dj_ll_parse(struct hid_device *hid) } if (djdev->reports_supported & HIDPP) { + dbg_hid("%s: sending a HID++ descriptor, reports_supported: %llx\n", + __func__, djdev->reports_supported); rdcat(rdesc, &rsize, hidpp_descriptor, sizeof(hidpp_descriptor)); } From 67a95c21463d066060b0f66d65a75d45bb386ffb Mon Sep 17 00:00:00 2001 From: Rishi Gupta Date: Tue, 28 Jan 2020 09:48:57 +0530 Subject: [PATCH 03/11] HID: mcp2221: add usb to i2c-smbus host bridge MCP2221 is a USB HID to I2C/SMbus host bridge device. This commit implements i2c and smbus host adapter support. 7-bit address and i2c multi-message transaction is also supported. Signed-off-by: Rishi Gupta Signed-off-by: Jiri Kosina --- MAINTAINERS | 7 + drivers/hid/Kconfig | 10 + drivers/hid/Makefile | 1 + drivers/hid/hid-ids.h | 1 + drivers/hid/hid-mcp2221.c | 742 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 761 insertions(+) create mode 100644 drivers/hid/hid-mcp2221.c diff --git a/MAINTAINERS b/MAINTAINERS index 8f27f40d22bb..352a67fe7598 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10236,6 +10236,13 @@ F: drivers/net/can/m_can/m_can.c F: drivers/net/can/m_can/m_can.h F: drivers/net/can/m_can/m_can_platform.c +MCP2221A MICROCHIP USB-HID TO I2C BRIDGE DRIVER +M: Rishi Gupta +L: linux-i2c@vger.kernel.org +L: linux-input@vger.kernel.org +S: Maintained +F: drivers/hid/hid-mcp2221.c + MCP4018 AND MCP4531 MICROCHIP DIGITAL POTENTIOMETER DRIVERS M: Peter Rosin L: linux-iio@vger.kernel.org diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 494a39e74939..5db6e6a709e0 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1145,6 +1145,16 @@ config HID_ALPS Say Y here if you have a Alps touchpads over i2c-hid or usbhid and want support for its special functionalities. +config HID_MCP2221 + tristate "Microchip MCP2221 HID USB-to-I2C/SMbus host support" + depends on USB_HID && I2C + ---help--- + Provides I2C and SMBUS host adapter functionality over USB-HID + through MCP2221 device. + + To compile this driver as a module, choose M here: the module + will be called hid-mcp2221.ko. + endmenu endif # HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index bfefa365b1ce..21052a78a344 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -70,6 +70,7 @@ obj-$(CONFIG_HID_LOGITECH_HIDPP) += hid-logitech-hidpp.o obj-$(CONFIG_HID_MACALLY) += hid-macally.o obj-$(CONFIG_HID_MAGICMOUSE) += hid-magicmouse.o obj-$(CONFIG_HID_MALTRON) += hid-maltron.o +obj-$(CONFIG_HID_MCP2221) += hid-mcp2221.o obj-$(CONFIG_HID_MAYFLASH) += hid-mf.o obj-$(CONFIG_HID_MICROSOFT) += hid-microsoft.o obj-$(CONFIG_HID_MONTEREY) += hid-monterey.o diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 3a400ce603c4..53236ac34fcb 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -819,6 +819,7 @@ #define USB_DEVICE_ID_PICK16F1454 0x0042 #define USB_DEVICE_ID_PICK16F1454_V2 0xf2f7 #define USB_DEVICE_ID_LUXAFOR 0xf372 +#define USB_DEVICE_ID_MCP2221 0x00dd #define USB_VENDOR_ID_MICROSOFT 0x045e #define USB_DEVICE_ID_SIDEWINDER_GV 0x003b diff --git a/drivers/hid/hid-mcp2221.c b/drivers/hid/hid-mcp2221.c new file mode 100644 index 000000000000..d958475f8c81 --- /dev/null +++ b/drivers/hid/hid-mcp2221.c @@ -0,0 +1,742 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MCP2221A - Microchip USB to I2C Host Protocol Bridge + * + * Copyright (c) 2020, Rishi Gupta + * + * Datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/20005565B.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "hid-ids.h" + +/* Commands codes in a raw output report */ +enum { + MCP2221_I2C_WR_DATA = 0x90, + MCP2221_I2C_WR_NO_STOP = 0x94, + MCP2221_I2C_RD_DATA = 0x91, + MCP2221_I2C_RD_RPT_START = 0x93, + MCP2221_I2C_GET_DATA = 0x40, + MCP2221_I2C_PARAM_OR_STATUS = 0x10, + MCP2221_I2C_SET_SPEED = 0x20, + MCP2221_I2C_CANCEL = 0x10, +}; + +/* Response codes in a raw input report */ +enum { + MCP2221_SUCCESS = 0x00, + MCP2221_I2C_ENG_BUSY = 0x01, + MCP2221_I2C_START_TOUT = 0x12, + MCP2221_I2C_STOP_TOUT = 0x62, + MCP2221_I2C_WRADDRL_TOUT = 0x23, + MCP2221_I2C_WRDATA_TOUT = 0x44, + MCP2221_I2C_WRADDRL_NACK = 0x25, + MCP2221_I2C_MASK_ADDR_NACK = 0x40, + MCP2221_I2C_WRADDRL_SEND = 0x21, + MCP2221_I2C_ADDR_NACK = 0x25, + MCP2221_I2C_READ_COMPL = 0x55, +}; + +/* + * There is no way to distinguish responses. Therefore next command + * is sent only after response to previous has been received. Mutex + * lock is used for this purpose mainly. + */ +struct mcp2221 { + struct hid_device *hdev; + struct i2c_adapter adapter; + struct mutex lock; + struct completion wait_in_report; + u8 *rxbuf; + u8 txbuf[64]; + int rxbuf_idx; + int status; + u8 cur_i2c_clk_div; +}; + +/* + * Default i2c bus clock frequency 400 kHz. Modify this if you + * want to set some other frequency (min 50 kHz - max 400 kHz). + */ +static uint i2c_clk_freq = 400; + +/* Synchronously send output report to the device */ +static int mcp_send_report(struct mcp2221 *mcp, + u8 *out_report, size_t len) +{ + u8 *buf; + int ret; + + buf = kmemdup(out_report, len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* mcp2221 uses interrupt endpoint for out reports */ + ret = hid_hw_output_report(mcp->hdev, buf, len); + kfree(buf); + + if (ret < 0) + return ret; + return 0; +} + +/* + * Send o/p report to the device and wait for i/p report to be + * received from the device. If the device does not respond, + * we timeout. + */ +static int mcp_send_data_req_status(struct mcp2221 *mcp, + u8 *out_report, int len) +{ + int ret; + unsigned long t; + + reinit_completion(&mcp->wait_in_report); + + ret = mcp_send_report(mcp, out_report, len); + if (ret) + return ret; + + t = wait_for_completion_timeout(&mcp->wait_in_report, + msecs_to_jiffies(4000)); + if (!t) + return -ETIMEDOUT; + + return mcp->status; +} + +/* Check pass/fail for actual communication with i2c slave */ +static int mcp_chk_last_cmd_status(struct mcp2221 *mcp) +{ + memset(mcp->txbuf, 0, 8); + mcp->txbuf[0] = MCP2221_I2C_PARAM_OR_STATUS; + + return mcp_send_data_req_status(mcp, mcp->txbuf, 8); +} + +/* Cancels last command releasing i2c bus just in case occupied */ +static int mcp_cancel_last_cmd(struct mcp2221 *mcp) +{ + memset(mcp->txbuf, 0, 8); + mcp->txbuf[0] = MCP2221_I2C_PARAM_OR_STATUS; + mcp->txbuf[2] = MCP2221_I2C_CANCEL; + + return mcp_send_data_req_status(mcp, mcp->txbuf, 8); +} + +static int mcp_set_i2c_speed(struct mcp2221 *mcp) +{ + int ret; + + memset(mcp->txbuf, 0, 8); + mcp->txbuf[0] = MCP2221_I2C_PARAM_OR_STATUS; + mcp->txbuf[3] = MCP2221_I2C_SET_SPEED; + mcp->txbuf[4] = mcp->cur_i2c_clk_div; + + ret = mcp_send_data_req_status(mcp, mcp->txbuf, 8); + if (ret) { + /* Small delay is needed here */ + usleep_range(980, 1000); + mcp_cancel_last_cmd(mcp); + } + + return 0; +} + +/* + * An output report can contain minimum 1 and maximum 60 user data + * bytes. If the number of data bytes is more then 60, we send it + * in chunks of 60 bytes. Last chunk may contain exactly 60 or less + * bytes. Total number of bytes is informed in very first report to + * mcp2221, from that point onwards it first collect all the data + * from host and then send to i2c slave device. + */ +static int mcp_i2c_write(struct mcp2221 *mcp, + struct i2c_msg *msg, int type, u8 last_status) +{ + int ret, len, idx, sent; + + idx = 0; + sent = 0; + if (msg->len < 60) + len = msg->len; + else + len = 60; + + do { + mcp->txbuf[0] = type; + mcp->txbuf[1] = msg->len & 0xff; + mcp->txbuf[2] = msg->len >> 8; + mcp->txbuf[3] = (u8)(msg->addr << 1); + + memcpy(&mcp->txbuf[4], &msg->buf[idx], len); + + ret = mcp_send_data_req_status(mcp, mcp->txbuf, len + 4); + if (ret) + return ret; + + usleep_range(980, 1000); + + if (last_status) { + ret = mcp_chk_last_cmd_status(mcp); + if (ret) + return ret; + } + + sent = sent + len; + if (sent >= msg->len) + break; + + idx = idx + len; + if ((msg->len - sent) < 60) + len = msg->len - sent; + else + len = 60; + + /* + * Testing shows delay is needed between successive writes + * otherwise next write fails on first-try from i2c core. + * This value is obtained through automated stress testing. + */ + usleep_range(980, 1000); + } while (len > 0); + + return ret; +} + +/* + * Device reads all data (0 - 65535 bytes) from i2c slave device and + * stores it in device itself. This data is read back from device to + * host in multiples of 60 bytes using input reports. + */ +static int mcp_i2c_smbus_read(struct mcp2221 *mcp, + struct i2c_msg *msg, int type, u16 smbus_addr, + u8 smbus_len, u8 *smbus_buf) +{ + int ret; + u16 total_len; + + mcp->txbuf[0] = type; + if (msg) { + mcp->txbuf[1] = msg->len & 0xff; + mcp->txbuf[2] = msg->len >> 8; + mcp->txbuf[3] = (u8)(msg->addr << 1); + total_len = msg->len; + mcp->rxbuf = msg->buf; + } else { + mcp->txbuf[1] = smbus_len; + mcp->txbuf[2] = 0; + mcp->txbuf[3] = (u8)(smbus_addr << 1); + total_len = smbus_len; + mcp->rxbuf = smbus_buf; + } + + ret = mcp_send_data_req_status(mcp, mcp->txbuf, 4); + if (ret) + return ret; + + mcp->rxbuf_idx = 0; + + do { + 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(mcp); + if (ret) + return ret; + + usleep_range(980, 1000); + } while (mcp->rxbuf_idx < total_len); + + return ret; +} + +static int mcp_i2c_xfer(struct i2c_adapter *adapter, + struct i2c_msg msgs[], int num) +{ + int ret; + struct mcp2221 *mcp = i2c_get_adapdata(adapter); + + hid_hw_power(mcp->hdev, PM_HINT_FULLON); + + mutex_lock(&mcp->lock); + + /* Setting speed before every transaction is required for mcp2221 */ + ret = mcp_set_i2c_speed(mcp); + if (ret) + goto exit; + + if (num == 1) { + if (msgs->flags & I2C_M_RD) { + ret = mcp_i2c_smbus_read(mcp, msgs, MCP2221_I2C_RD_DATA, + 0, 0, NULL); + } else { + ret = mcp_i2c_write(mcp, msgs, MCP2221_I2C_WR_DATA, 1); + } + if (ret) + goto exit; + ret = num; + } else if (num == 2) { + /* Ex transaction; send reg address and read its contents */ + if (msgs[0].addr == msgs[1].addr && + !(msgs[0].flags & I2C_M_RD) && + (msgs[1].flags & I2C_M_RD)) { + + ret = mcp_i2c_write(mcp, &msgs[0], + MCP2221_I2C_WR_NO_STOP, 0); + if (ret) + goto exit; + + ret = mcp_i2c_smbus_read(mcp, &msgs[1], + MCP2221_I2C_RD_RPT_START, + 0, 0, NULL); + if (ret) + goto exit; + ret = num; + } else { + dev_err(&adapter->dev, + "unsupported multi-msg i2c transaction\n"); + ret = -EOPNOTSUPP; + } + } else { + dev_err(&adapter->dev, + "unsupported multi-msg i2c transaction\n"); + ret = -EOPNOTSUPP; + } + +exit: + hid_hw_power(mcp->hdev, PM_HINT_NORMAL); + mutex_unlock(&mcp->lock); + return ret; +} + +static int mcp_smbus_write(struct mcp2221 *mcp, u16 addr, + u8 command, u8 *buf, u8 len, int type, + u8 last_status) +{ + int data_len, ret; + + mcp->txbuf[0] = type; + mcp->txbuf[1] = len + 1; /* 1 is due to command byte itself */ + mcp->txbuf[2] = 0; + mcp->txbuf[3] = (u8)(addr << 1); + mcp->txbuf[4] = command; + + switch (len) { + case 0: + data_len = 5; + break; + case 1: + mcp->txbuf[5] = buf[0]; + data_len = 6; + break; + case 2: + mcp->txbuf[5] = buf[0]; + mcp->txbuf[6] = buf[1]; + data_len = 7; + break; + default: + memcpy(&mcp->txbuf[5], buf, len); + data_len = len + 5; + } + + ret = mcp_send_data_req_status(mcp, mcp->txbuf, data_len); + if (ret) + return ret; + + if (last_status) { + usleep_range(980, 1000); + + ret = mcp_chk_last_cmd_status(mcp); + if (ret) + return ret; + } + + return ret; +} + +static int mcp_smbus_xfer(struct i2c_adapter *adapter, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, + union i2c_smbus_data *data) +{ + int ret; + struct mcp2221 *mcp = i2c_get_adapdata(adapter); + + hid_hw_power(mcp->hdev, PM_HINT_FULLON); + + mutex_lock(&mcp->lock); + + ret = mcp_set_i2c_speed(mcp); + if (ret) + goto exit; + + switch (size) { + + case I2C_SMBUS_QUICK: + if (read_write == I2C_SMBUS_READ) + ret = mcp_i2c_smbus_read(mcp, NULL, MCP2221_I2C_RD_DATA, + addr, 0, &data->byte); + else + ret = mcp_smbus_write(mcp, addr, command, NULL, + 0, MCP2221_I2C_WR_DATA, 1); + break; + case I2C_SMBUS_BYTE: + if (read_write == I2C_SMBUS_READ) + ret = mcp_i2c_smbus_read(mcp, NULL, MCP2221_I2C_RD_DATA, + addr, 1, &data->byte); + else + ret = mcp_smbus_write(mcp, addr, command, NULL, + 0, MCP2221_I2C_WR_DATA, 1); + break; + case I2C_SMBUS_BYTE_DATA: + if (read_write == I2C_SMBUS_READ) { + ret = mcp_smbus_write(mcp, addr, command, NULL, + 0, MCP2221_I2C_WR_NO_STOP, 0); + if (ret) + goto exit; + + ret = mcp_i2c_smbus_read(mcp, NULL, + MCP2221_I2C_RD_RPT_START, + addr, 1, &data->byte); + } else { + ret = mcp_smbus_write(mcp, addr, command, &data->byte, + 1, MCP2221_I2C_WR_DATA, 1); + } + break; + case I2C_SMBUS_WORD_DATA: + if (read_write == I2C_SMBUS_READ) { + ret = mcp_smbus_write(mcp, addr, command, NULL, + 0, MCP2221_I2C_WR_NO_STOP, 0); + if (ret) + goto exit; + + ret = mcp_i2c_smbus_read(mcp, NULL, + MCP2221_I2C_RD_RPT_START, + addr, 2, (u8 *)&data->word); + } else { + ret = mcp_smbus_write(mcp, addr, command, + (u8 *)&data->word, 2, + MCP2221_I2C_WR_DATA, 1); + } + break; + case I2C_SMBUS_BLOCK_DATA: + if (read_write == I2C_SMBUS_READ) { + ret = mcp_smbus_write(mcp, addr, command, NULL, + 0, MCP2221_I2C_WR_NO_STOP, 1); + if (ret) + goto exit; + + mcp->rxbuf_idx = 0; + mcp->rxbuf = data->block; + mcp->txbuf[0] = MCP2221_I2C_GET_DATA; + ret = mcp_send_data_req_status(mcp, mcp->txbuf, 1); + if (ret) + goto exit; + } else { + if (!data->block[0]) { + ret = -EINVAL; + goto exit; + } + ret = mcp_smbus_write(mcp, addr, command, data->block, + data->block[0] + 1, + MCP2221_I2C_WR_DATA, 1); + } + break; + case I2C_SMBUS_I2C_BLOCK_DATA: + if (read_write == I2C_SMBUS_READ) { + ret = mcp_smbus_write(mcp, addr, command, NULL, + 0, MCP2221_I2C_WR_NO_STOP, 1); + if (ret) + goto exit; + + mcp->rxbuf_idx = 0; + mcp->rxbuf = data->block; + mcp->txbuf[0] = MCP2221_I2C_GET_DATA; + ret = mcp_send_data_req_status(mcp, mcp->txbuf, 1); + if (ret) + goto exit; + } else { + if (!data->block[0]) { + ret = -EINVAL; + goto exit; + } + ret = mcp_smbus_write(mcp, addr, command, + &data->block[1], data->block[0], + MCP2221_I2C_WR_DATA, 1); + } + break; + case I2C_SMBUS_PROC_CALL: + ret = mcp_smbus_write(mcp, addr, command, + (u8 *)&data->word, + 2, MCP2221_I2C_WR_NO_STOP, 0); + if (ret) + goto exit; + + ret = mcp_i2c_smbus_read(mcp, NULL, + MCP2221_I2C_RD_RPT_START, + addr, 2, (u8 *)&data->word); + break; + case I2C_SMBUS_BLOCK_PROC_CALL: + ret = mcp_smbus_write(mcp, addr, command, data->block, + data->block[0] + 1, + MCP2221_I2C_WR_NO_STOP, 0); + if (ret) + goto exit; + + ret = mcp_i2c_smbus_read(mcp, NULL, + MCP2221_I2C_RD_RPT_START, + addr, I2C_SMBUS_BLOCK_MAX, + data->block); + break; + default: + dev_err(&mcp->adapter.dev, + "unsupported smbus transaction size:%d\n", size); + ret = -EOPNOTSUPP; + } + +exit: + hid_hw_power(mcp->hdev, PM_HINT_NORMAL); + mutex_unlock(&mcp->lock); + return ret; +} + +static u32 mcp_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | + I2C_FUNC_SMBUS_READ_BLOCK_DATA | + I2C_FUNC_SMBUS_BLOCK_PROC_CALL | + (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_PEC); +} + +static const struct i2c_algorithm mcp_i2c_algo = { + .master_xfer = mcp_i2c_xfer, + .smbus_xfer = mcp_smbus_xfer, + .functionality = mcp_i2c_func, +}; + +/* Gives current state of i2c engine inside mcp2221 */ +static int mcp_get_i2c_eng_state(struct mcp2221 *mcp, + u8 *data, u8 idx) +{ + int ret; + + switch (data[idx]) { + case MCP2221_I2C_WRADDRL_NACK: + case MCP2221_I2C_WRADDRL_SEND: + ret = -ENXIO; + break; + case MCP2221_I2C_START_TOUT: + case MCP2221_I2C_STOP_TOUT: + case MCP2221_I2C_WRADDRL_TOUT: + case MCP2221_I2C_WRDATA_TOUT: + ret = -ETIMEDOUT; + break; + case MCP2221_I2C_ENG_BUSY: + ret = -EAGAIN; + break; + case MCP2221_SUCCESS: + ret = 0x00; + break; + default: + ret = -EIO; + } + + return ret; +} + +/* + * MCP2221 uses interrupt endpoint for input reports. This function + * is called by HID layer when it receives i/p report from mcp2221, + * which is actually a response to the previously sent command. + * + * MCP2221A firmware specific return codes are parsed and 0 or + * appropriate negative error code is returned. Delayed response + * results in timeout error and stray reponses results in -EIO. + */ +static int mcp2221_raw_event(struct hid_device *hdev, + struct hid_report *report, u8 *data, int size) +{ + u8 *buf; + struct mcp2221 *mcp = hid_get_drvdata(hdev); + + switch (data[0]) { + + case MCP2221_I2C_WR_DATA: + case MCP2221_I2C_WR_NO_STOP: + case MCP2221_I2C_RD_DATA: + case MCP2221_I2C_RD_RPT_START: + switch (data[1]) { + case MCP2221_SUCCESS: + mcp->status = 0; + break; + default: + mcp->status = mcp_get_i2c_eng_state(mcp, data, 2); + } + complete(&mcp->wait_in_report); + break; + + case MCP2221_I2C_PARAM_OR_STATUS: + switch (data[1]) { + case MCP2221_SUCCESS: + if ((mcp->txbuf[3] == MCP2221_I2C_SET_SPEED) && + (data[3] != MCP2221_I2C_SET_SPEED)) { + mcp->status = -EAGAIN; + break; + } + if (data[20] & MCP2221_I2C_MASK_ADDR_NACK) { + mcp->status = -ENXIO; + break; + } + mcp->status = mcp_get_i2c_eng_state(mcp, data, 8); + break; + default: + mcp->status = -EIO; + } + complete(&mcp->wait_in_report); + break; + + case MCP2221_I2C_GET_DATA: + switch (data[1]) { + case MCP2221_SUCCESS: + if (data[2] == MCP2221_I2C_ADDR_NACK) { + mcp->status = -ENXIO; + break; + } + if (!mcp_get_i2c_eng_state(mcp, data, 2) + && (data[3] == 0)) { + mcp->status = 0; + break; + } + if (data[3] == 127) { + mcp->status = -EIO; + break; + } + if (data[2] == MCP2221_I2C_READ_COMPL) { + buf = mcp->rxbuf; + memcpy(&buf[mcp->rxbuf_idx], &data[4], data[3]); + mcp->rxbuf_idx = mcp->rxbuf_idx + data[3]; + mcp->status = 0; + break; + } + mcp->status = -EIO; + break; + default: + mcp->status = -EIO; + } + complete(&mcp->wait_in_report); + break; + + default: + mcp->status = -EIO; + complete(&mcp->wait_in_report); + } + + return 1; +} + +static int mcp2221_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int ret; + struct mcp2221 *mcp; + + mcp = devm_kzalloc(&hdev->dev, sizeof(*mcp), GFP_KERNEL); + if (!mcp) + return -ENOMEM; + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "can't parse reports\n"); + return ret; + } + + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); + if (ret) { + hid_err(hdev, "can't start hardware\n"); + return ret; + } + + ret = hid_hw_open(hdev); + if (ret) { + hid_err(hdev, "can't open device\n"); + goto err_hstop; + } + + mutex_init(&mcp->lock); + init_completion(&mcp->wait_in_report); + hid_set_drvdata(hdev, mcp); + mcp->hdev = hdev; + + /* Set I2C bus clock diviser */ + if (i2c_clk_freq > 400) + i2c_clk_freq = 400; + if (i2c_clk_freq < 50) + i2c_clk_freq = 50; + mcp->cur_i2c_clk_div = (12000000 / (i2c_clk_freq * 1000)) - 3; + + mcp->adapter.owner = THIS_MODULE; + mcp->adapter.class = I2C_CLASS_HWMON; + mcp->adapter.algo = &mcp_i2c_algo; + mcp->adapter.retries = 1; + mcp->adapter.dev.parent = &hdev->dev; + snprintf(mcp->adapter.name, sizeof(mcp->adapter.name), + "MCP2221 usb-i2c bridge on hidraw%d", + ((struct hidraw *)hdev->hidraw)->minor); + + ret = i2c_add_adapter(&mcp->adapter); + if (ret) { + hid_err(hdev, "can't add usb-i2c adapter: %d\n", ret); + goto err_i2c; + } + i2c_set_adapdata(&mcp->adapter, mcp); + + return 0; + +err_i2c: + hid_hw_close(mcp->hdev); +err_hstop: + hid_hw_stop(mcp->hdev); + return ret; +} + +static void mcp2221_remove(struct hid_device *hdev) +{ + struct mcp2221 *mcp = hid_get_drvdata(hdev); + + i2c_del_adapter(&mcp->adapter); + hid_hw_close(mcp->hdev); + hid_hw_stop(mcp->hdev); +} + +static const struct hid_device_id mcp2221_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_MCP2221) }, + { } +}; +MODULE_DEVICE_TABLE(hid, mcp2221_devices); + +static struct hid_driver mcp2221_driver = { + .name = "mcp2221", + .id_table = mcp2221_devices, + .probe = mcp2221_probe, + .remove = mcp2221_remove, + .raw_event = mcp2221_raw_event, +}; + +/* Register with HID core */ +module_hid_driver(mcp2221_driver); + +MODULE_AUTHOR("Rishi Gupta "); +MODULE_DESCRIPTION("MCP2221 Microchip HID USB to I2C master bridge"); +MODULE_LICENSE("GPL v2"); From 8c9d734cdffc6e41344653ae9884feece33b4f92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Wed, 15 Jan 2020 20:18:11 +0000 Subject: [PATCH 04/11] HID: logitech-dj: add support for the static device in the Powerplay mat/receiver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Logitech G Powerplay has a lightspeed receiver with a static HID++ device with ID 7 attached to it to. It is used to configure the led on the mat. For this reason I increased the max number of devices. Signed-off-by: Filipe Laíns Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-dj.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index 37e54a401452..ed9b1c1f460d 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -16,11 +16,11 @@ #include #include "hid-ids.h" -#define DJ_MAX_PAIRED_DEVICES 6 +#define DJ_MAX_PAIRED_DEVICES 7 #define DJ_MAX_NUMBER_NOTIFS 8 #define DJ_RECEIVER_INDEX 0 #define DJ_DEVICE_INDEX_MIN 1 -#define DJ_DEVICE_INDEX_MAX 6 +#define DJ_DEVICE_INDEX_MAX 7 #define DJREPORT_SHORT_LENGTH 15 #define DJREPORT_LONG_LENGTH 32 @@ -980,6 +980,11 @@ static void logi_hidpp_recv_queue_notif(struct hid_device *hdev, break; } + /* custom receiver device (eg. powerplay) */ + if (hidpp_report->device_index == 7) { + workitem.reports_supported |= HIDPP; + } + if (workitem.type == WORKITEM_TYPE_EMPTY) { hid_warn(hdev, "unusable device of type %s (0x%02x) connected on slot %d", From b08e8d8a508a61d626e6fdd75869776dbe1b2e14 Mon Sep 17 00:00:00 2001 From: Lucas Tanure Date: Sat, 29 Feb 2020 17:43:33 +0000 Subject: [PATCH 05/11] HID: appleir: Remove unnecessary goto label Signed-off-by: Lucas Tanure Signed-off-by: Jiri Kosina --- drivers/hid/hid-appleir.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/hid/hid-appleir.c b/drivers/hid/hid-appleir.c index bf8d4afe0d6a..aafc285b538f 100644 --- a/drivers/hid/hid-appleir.c +++ b/drivers/hid/hid-appleir.c @@ -284,10 +284,8 @@ static int appleir_probe(struct hid_device *hid, const struct hid_device_id *id) struct appleir *appleir; appleir = kzalloc(sizeof(struct appleir), GFP_KERNEL); - if (!appleir) { - ret = -ENOMEM; - goto allocfail; - } + if (!appleir) + return -ENOMEM; appleir->hid = hid; @@ -314,7 +312,6 @@ static int appleir_probe(struct hid_device *hid, const struct hid_device_id *id) return 0; fail: kfree(appleir); -allocfail: return ret; } From 910a7e89cec65efad254c947ce2bf8bf5b370962 Mon Sep 17 00:00:00 2001 From: Lucas Tanure Date: Sat, 29 Feb 2020 17:43:34 +0000 Subject: [PATCH 06/11] HID: appleir: Use devm_kzalloc() instead of kzalloc() Signed-off-by: Lucas Tanure Signed-off-by: Jiri Kosina --- drivers/hid/hid-appleir.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-appleir.c b/drivers/hid/hid-appleir.c index aafc285b538f..8deded185725 100644 --- a/drivers/hid/hid-appleir.c +++ b/drivers/hid/hid-appleir.c @@ -283,7 +283,7 @@ static int appleir_probe(struct hid_device *hid, const struct hid_device_id *id) int ret; struct appleir *appleir; - appleir = kzalloc(sizeof(struct appleir), GFP_KERNEL); + appleir = devm_kzalloc(&hid->dev, sizeof(struct appleir), GFP_KERNEL); if (!appleir) return -ENOMEM; @@ -311,7 +311,7 @@ static int appleir_probe(struct hid_device *hid, const struct hid_device_id *id) return 0; fail: - kfree(appleir); + devm_kfree(&hid->dev, appleir); return ret; } @@ -320,7 +320,6 @@ static void appleir_remove(struct hid_device *hid) struct appleir *appleir = hid_get_drvdata(hid); hid_hw_stop(hid); del_timer_sync(&appleir->key_up_timer); - kfree(appleir); } static const struct hid_device_id appleir_devices[] = { From b8a75eaddae9410767c7d95a1c5f3a547aae7b81 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 15 Mar 2020 18:34:49 +0100 Subject: [PATCH 07/11] HID: lg-g15: Do not fail the probe when we fail to disable F# emulation By default the G1-G12 keys on the Logitech gaming keyboards send F1 - F12 when in "generic HID" mode. The first thing the hid-lg-g15 driver does is disable this behavior. We have received a bugreport that this does not work when the keyboard is connected through an Aten KVM switch. Using a gaming keyboard with a KVM is a bit weird setup, but still we can try to fail a bit more gracefully here. On the G510 keyboards the same USB-interface which is used for the gaming keys is also used for the media-keys. Before this commit we would call hid_hw_stop() on failure to disable the F# emulation and then exit the probe method with an error code. This not only causes us to not handle the gaming-keys, but this also breaks the media keys which is a regression compared to the situation when these keyboards where handled by the generic hidinput driver. This commit changes the error handling to clear the hiddev drvdata (to disable our .raw_event handler) and then returning from the probe method with success. The net result of this is that, when connected through a KVM, things work as well as they did before the hid-lg-g15 driver was introduced. Fixes: ad4203f5a243 ("HID: lg-g15: Add support for the G510 keyboards' gaming keys") BugLink: https://bugzilla.redhat.com/show_bug.cgi?id=1806321 Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-lg-g15.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-lg-g15.c b/drivers/hid/hid-lg-g15.c index 8a9268a5c66a..ad4b5412a9f4 100644 --- a/drivers/hid/hid-lg-g15.c +++ b/drivers/hid/hid-lg-g15.c @@ -803,8 +803,10 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id) } if (ret < 0) { - hid_err(hdev, "Error disabling keyboard emulation for the G-keys\n"); - goto error_hw_stop; + hid_err(hdev, "Error %d disabling keyboard emulation for the G-keys, falling back to generic hid-input driver\n", + ret); + hid_set_drvdata(hdev, NULL); + return 0; } /* Get initial brightness levels */ From 77a36a3ab4ff17fad23831192e3694a3c5a1750d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C4=8Cavoj?= Date: Fri, 13 Mar 2020 03:12:38 +0100 Subject: [PATCH 08/11] HID: Add driver fixing Glorious PC Gaming Race mouse report descriptor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Glorious Model O mice (and also at least the Model O-, which is driver-wise the same mouse) have a bug in the descriptor of HID Report with ID 2. This report is used for Consumer Control buttons, which can be mapped using the provided Windows only software. Here is an excerpt from the original descriptor: INPUT(2)[INPUT] Field(0) Flags( Constant Variable Absolute ) Field(1) Flags( Constant Variable Absolute ) Field(2) Flags( Constant Variable Absolute ) The issue is the Constant flag specified on all 3 fields, which causes the hid driver to ignore changes in these fields and essentialy causes the buttons to not work at all. The submitted driver patches the descriptor to end up with the following: INPUT(2)[INPUT] Field(0) Flags( Variable Relative ) Field(1) Flags( Variable Relative ) Field(2) Flags( Variable Relative ) The Constant bit is reset and the Relative bit has been set in order to prevent repeat events when holding down the button. Additionally, the device name is changed from the hardware-reported "SINOWEALTH Wired Gaming Mouse" to "Glorious Model O" or "Glorious Model D". Signed-off-by: Samuel Čavoj Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 7 ++++ drivers/hid/Makefile | 1 + drivers/hid/hid-glorious.c | 86 ++++++++++++++++++++++++++++++++++++++ drivers/hid/hid-ids.h | 4 ++ 4 files changed, 98 insertions(+) create mode 100644 drivers/hid/hid-glorious.c diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 494a39e74939..945533b36010 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -362,6 +362,13 @@ config HID_GFRM ---help--- Support for Google Fiber TV Box remote controls +config HID_GLORIOUS + tristate "Glorious PC Gaming Race mice" + depends on HID + help + Support for Glorious PC Gaming Race mice such as + the Glorious Model O, O- and D. + config HID_HOLTEK tristate "Holtek HID devices" depends on USB_HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index bfefa365b1ce..be0f38dcf942 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -48,6 +48,7 @@ obj-$(CONFIG_HID_ELO) += hid-elo.o obj-$(CONFIG_HID_EZKEY) += hid-ezkey.o obj-$(CONFIG_HID_GEMBIRD) += hid-gembird.o obj-$(CONFIG_HID_GFRM) += hid-gfrm.o +obj-$(CONFIG_HID_GLORIOUS) += hid-glorious.o obj-$(CONFIG_HID_GOOGLE_HAMMER) += hid-google-hammer.o obj-$(CONFIG_HID_GT683R) += hid-gt683r.o obj-$(CONFIG_HID_GYRATION) += hid-gyration.o diff --git a/drivers/hid/hid-glorious.c b/drivers/hid/hid-glorious.c new file mode 100644 index 000000000000..558eb08c19ef --- /dev/null +++ b/drivers/hid/hid-glorious.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * USB HID driver for Glorious PC Gaming Race + * Glorious Model O, O- and D mice. + * + * Copyright (c) 2020 Samuel Čavoj + */ + +/* + */ + +#include +#include + +#include "hid-ids.h" + +MODULE_AUTHOR("Samuel Čavoj "); +MODULE_DESCRIPTION("HID driver for Glorious PC Gaming Race mice"); + +/* + * Glorious Model O and O- specify the const flag in the consumer input + * report descriptor, which leads to inputs being ignored. Fix this + * by patching the descriptor. + */ +static __u8 *glorious_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + if (*rsize == 213 && + rdesc[84] == 129 && rdesc[112] == 129 && rdesc[140] == 129 && + rdesc[85] == 3 && rdesc[113] == 3 && rdesc[141] == 3) { + hid_info(hdev, "patching Glorious Model O consumer control report descriptor\n"); + rdesc[85] = rdesc[113] = rdesc[141] = \ + HID_MAIN_ITEM_VARIABLE | HID_MAIN_ITEM_RELATIVE; + } + return rdesc; +} + +static void glorious_update_name(struct hid_device *hdev) +{ + const char *model = "Device"; + + switch (hdev->product) { + case USB_DEVICE_ID_GLORIOUS_MODEL_O: + model = "Model O"; break; + case USB_DEVICE_ID_GLORIOUS_MODEL_D: + model = "Model D"; break; + } + + snprintf(hdev->name, sizeof(hdev->name), "%s %s", "Glorious", model); +} + +static int glorious_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int ret; + + hdev->quirks |= HID_QUIRK_INPUT_PER_APP; + + ret = hid_parse(hdev); + if (ret) + return ret; + + glorious_update_name(hdev); + + return hid_hw_start(hdev, HID_CONNECT_DEFAULT); +} + +static const struct hid_device_id glorious_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_GLORIOUS, + USB_DEVICE_ID_GLORIOUS_MODEL_O) }, + { HID_USB_DEVICE(USB_VENDOR_ID_GLORIOUS, + USB_DEVICE_ID_GLORIOUS_MODEL_D) }, + { } +}; +MODULE_DEVICE_TABLE(hid, glorious_devices); + +static struct hid_driver glorious_driver = { + .name = "glorious", + .id_table = glorious_devices, + .probe = glorious_probe, + .report_fixup = glorious_report_fixup +}; + +module_hid_driver(glorious_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 9f2213426556..54474205b12c 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -464,6 +464,10 @@ #define USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_010A 0x010a #define USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_E100 0xe100 +#define USB_VENDOR_ID_GLORIOUS 0x258a +#define USB_DEVICE_ID_GLORIOUS_MODEL_D 0x0033 +#define USB_DEVICE_ID_GLORIOUS_MODEL_O 0x0036 + #define I2C_VENDOR_ID_GOODIX 0x27c6 #define I2C_DEVICE_ID_GOODIX_01F0 0x01f0 From 71559219ce36d5861ecc1d31697687c08819b6e5 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Thu, 19 Mar 2020 16:31:08 -0500 Subject: [PATCH 09/11] HID: intel-ish-hid: ishtp-dev.h: Replace zero-length array with flexible-array member The current codebase makes use of the zero-length array language extension to the C90 standard, but the preferred mechanism to declare variable-length types such as these ones is a flexible array member[1][2], introduced in C99: struct foo { int stuff; struct boo array[]; }; By making use of the mechanism above, we will get a compiler warning in case the flexible array does not occur last in the structure, which will help us prevent some kind of undefined behavior bugs from being inadvertently introduced[3] to the codebase from now on. Also, notice that, dynamic memory allocations won't be affected by this change: "Flexible array members have incomplete type, and so the sizeof operator may not be applied. As a quirk of the original implementation of zero-length arrays, sizeof evaluates to zero."[1] This issue was found with the help of Coccinelle. [1] https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html [2] https://github.com/KSPP/linux/issues/21 [3] commit 76497732932f ("cxgb3/l2t: Fix undefined behaviour") Signed-off-by: Gustavo A. R. Silva Signed-off-by: Jiri Kosina --- drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h b/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h index 39e0e6c73adf..1cc6364aa957 100644 --- a/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h +++ b/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h @@ -214,7 +214,7 @@ struct ishtp_device { const struct ishtp_hw_ops *ops; size_t mtu; uint32_t ishtp_msg_hdr; - char hw[0] __aligned(sizeof(void *)); + char hw[] __aligned(sizeof(void *)); }; static inline unsigned long ishtp_secs_to_jiffies(unsigned long sec) From 56d8623cedf929bfb1385868e423b978d0e76eea Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Thu, 19 Mar 2020 16:29:50 -0500 Subject: [PATCH 10/11] HID: intel-ish-hid: hbm.h: Replace zero-length array with flexible-array member The current codebase makes use of the zero-length array language extension to the C90 standard, but the preferred mechanism to declare variable-length types such as these ones is a flexible array member[1][2], introduced in C99: struct foo { int stuff; struct boo array[]; }; By making use of the mechanism above, we will get a compiler warning in case the flexible array does not occur last in the structure, which will help us prevent some kind of undefined behavior bugs from being inadvertently introduced[3] to the codebase from now on. Also, notice that, dynamic memory allocations won't be affected by this change: "Flexible array members have incomplete type, and so the sizeof operator may not be applied. As a quirk of the original implementation of zero-length arrays, sizeof evaluates to zero."[1] This issue was found with the help of Coccinelle. [1] https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html [2] https://github.com/KSPP/linux/issues/21 [3] commit 76497732932f ("cxgb3/l2t: Fix undefined behaviour") Signed-off-by: Gustavo A. R. Silva Signed-off-by: Jiri Kosina --- drivers/hid/intel-ish-hid/ishtp/hbm.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/intel-ish-hid/ishtp/hbm.h b/drivers/hid/intel-ish-hid/ishtp/hbm.h index bb85985b1620..7c445b203f2a 100644 --- a/drivers/hid/intel-ish-hid/ishtp/hbm.h +++ b/drivers/hid/intel-ish-hid/ishtp/hbm.h @@ -82,7 +82,7 @@ struct ishtp_msg_hdr { struct ishtp_bus_message { uint8_t hbm_cmd; - uint8_t data[0]; + uint8_t data[]; } __packed; /** From 2e1b9e1edff7fe19d37f0a5993ac03c5389af809 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sat, 21 Mar 2020 07:40:48 +0100 Subject: [PATCH 11/11] HID: rmi: Simplify an error handling path in 'rmi_hid_read_block()' The 'RMI_READ_REQUEST_PENDING' bit is already cleared in the error handling path. There is no need to reset it twice. Signed-off-by: Christophe JAILLET Signed-off-by: Jiri Kosina --- drivers/hid/hid-rmi.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/hid/hid-rmi.c b/drivers/hid/hid-rmi.c index 9ce22acdfaca..8cffa84c9650 100644 --- a/drivers/hid/hid-rmi.c +++ b/drivers/hid/hid-rmi.c @@ -217,7 +217,6 @@ static int rmi_hid_read_block(struct rmi_transport_dev *xport, u16 addr, ret = rmi_write_report(hdev, data->writeReport, data->output_report_size); if (ret != data->output_report_size) { - clear_bit(RMI_READ_REQUEST_PENDING, &data->flags); dev_err(&hdev->dev, "failed to write request output report (%d)\n", ret);