diff options
Diffstat (limited to 'drivers/hid/i2c-hid/i2c-hid-core.c')
-rw-r--r-- | drivers/hid/i2c-hid/i2c-hid-core.c | 143 |
1 files changed, 64 insertions, 79 deletions
diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 2735cd585a..d965382196 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -44,12 +44,12 @@ #include "i2c-hid.h" /* quirks to control the device */ -#define I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV BIT(0) -#define I2C_HID_QUIRK_NO_IRQ_AFTER_RESET BIT(1) -#define I2C_HID_QUIRK_BOGUS_IRQ BIT(4) -#define I2C_HID_QUIRK_RESET_ON_RESUME BIT(5) -#define I2C_HID_QUIRK_BAD_INPUT_SIZE BIT(6) -#define I2C_HID_QUIRK_NO_WAKEUP_AFTER_RESET BIT(7) +#define I2C_HID_QUIRK_NO_IRQ_AFTER_RESET BIT(0) +#define I2C_HID_QUIRK_BOGUS_IRQ BIT(1) +#define I2C_HID_QUIRK_RESET_ON_RESUME BIT(2) +#define I2C_HID_QUIRK_BAD_INPUT_SIZE BIT(3) +#define I2C_HID_QUIRK_NO_WAKEUP_AFTER_RESET BIT(4) +#define I2C_HID_QUIRK_NO_SLEEP_ON_SUSPEND BIT(5) /* Command opcodes */ #define I2C_HID_OPCODE_RESET 0x01 @@ -64,7 +64,6 @@ /* flags */ #define I2C_HID_STARTED 0 #define I2C_HID_RESET_PENDING 1 -#define I2C_HID_READ_PENDING 2 #define I2C_HID_PWR_ON 0x00 #define I2C_HID_PWR_SLEEP 0x01 @@ -120,8 +119,6 @@ static const struct i2c_hid_quirks { __u16 idProduct; __u32 quirks; } i2c_hid_quirks[] = { - { USB_VENDOR_ID_WEIDA, HID_ANY_ID, - I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV }, { I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288, I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, { I2C_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_VOYO_WINPAD_A15, @@ -134,6 +131,8 @@ static const struct i2c_hid_quirks { I2C_HID_QUIRK_RESET_ON_RESUME }, { USB_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_LENOVO_LEGION_Y720, I2C_HID_QUIRK_BAD_INPUT_SIZE }, + { I2C_VENDOR_ID_CIRQUE, I2C_PRODUCT_ID_CIRQUE_1063, + I2C_HID_QUIRK_NO_SLEEP_ON_SUSPEND }, /* * Sending the wakeup after reset actually break ELAN touchscreen controller */ @@ -190,15 +189,10 @@ static int i2c_hid_xfer(struct i2c_hid *ihid, msgs[n].len = recv_len; msgs[n].buf = recv_buf; n++; - - set_bit(I2C_HID_READ_PENDING, &ihid->flags); } ret = i2c_transfer(client->adapter, msgs, n); - if (recv_len) - clear_bit(I2C_HID_READ_PENDING, &ihid->flags); - if (ret != n) return ret < 0 ? ret : -EIO; @@ -395,8 +389,7 @@ static int i2c_hid_set_power(struct i2c_hid *ihid, int power_state) * The call will get a return value (EREMOTEIO) but device will be * triggered and activated. After that, it goes like a normal device. */ - if (power_state == I2C_HID_PWR_ON && - ihid->quirks & I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV) { + if (power_state == I2C_HID_PWR_ON) { ret = i2c_hid_set_power_command(ihid, I2C_HID_PWR_ON); /* Device was already activated */ @@ -426,12 +419,23 @@ set_pwr_exit: return ret; } -static int i2c_hid_execute_reset(struct i2c_hid *ihid) +static int i2c_hid_start_hwreset(struct i2c_hid *ihid) { size_t length = 0; int ret; - i2c_hid_dbg(ihid, "resetting...\n"); + i2c_hid_dbg(ihid, "%s\n", __func__); + + /* + * This prevents sending feature reports while the device is + * being reset. Otherwise we may lose the reset complete + * interrupt. + */ + lockdep_assert_held(&ihid->reset_lock); + + ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON); + if (ret) + return ret; /* Prepare reset command. Command register goes first. */ *(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister; @@ -444,60 +448,40 @@ static int i2c_hid_execute_reset(struct i2c_hid *ihid) ret = i2c_hid_xfer(ihid, ihid->cmdbuf, length, NULL, 0); if (ret) { - dev_err(&ihid->client->dev, "failed to reset device.\n"); - goto out; - } - - if (ihid->quirks & I2C_HID_QUIRK_NO_IRQ_AFTER_RESET) { - msleep(100); - goto out; + dev_err(&ihid->client->dev, + "failed to reset device: %d\n", ret); + goto err_clear_reset; } - i2c_hid_dbg(ihid, "%s: waiting...\n", __func__); - if (!wait_event_timeout(ihid->wait, - !test_bit(I2C_HID_RESET_PENDING, &ihid->flags), - msecs_to_jiffies(5000))) { - ret = -ENODATA; - goto out; - } - i2c_hid_dbg(ihid, "%s: finished.\n", __func__); + return 0; -out: +err_clear_reset: clear_bit(I2C_HID_RESET_PENDING, &ihid->flags); + i2c_hid_set_power(ihid, I2C_HID_PWR_SLEEP); return ret; } -static int i2c_hid_hwreset(struct i2c_hid *ihid) +static int i2c_hid_finish_hwreset(struct i2c_hid *ihid) { - int ret; - - i2c_hid_dbg(ihid, "%s\n", __func__); - - /* - * This prevents sending feature reports while the device is - * being reset. Otherwise we may lose the reset complete - * interrupt. - */ - mutex_lock(&ihid->reset_lock); + int ret = 0; - ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON); - if (ret) - goto out_unlock; + i2c_hid_dbg(ihid, "%s: waiting...\n", __func__); - ret = i2c_hid_execute_reset(ihid); - if (ret) { - dev_err(&ihid->client->dev, - "failed to reset device: %d\n", ret); - i2c_hid_set_power(ihid, I2C_HID_PWR_SLEEP); - goto out_unlock; + if (ihid->quirks & I2C_HID_QUIRK_NO_IRQ_AFTER_RESET) { + msleep(100); + clear_bit(I2C_HID_RESET_PENDING, &ihid->flags); + } else if (!wait_event_timeout(ihid->wait, + !test_bit(I2C_HID_RESET_PENDING, &ihid->flags), + msecs_to_jiffies(1000))) { + dev_warn(&ihid->client->dev, "device did not ack reset within 1000 ms\n"); + clear_bit(I2C_HID_RESET_PENDING, &ihid->flags); } + i2c_hid_dbg(ihid, "%s: finished.\n", __func__); /* At least some SIS devices need this after reset */ if (!(ihid->quirks & I2C_HID_QUIRK_NO_WAKEUP_AFTER_RESET)) ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON); -out_unlock: - mutex_unlock(&ihid->reset_lock); return ret; } @@ -566,9 +550,6 @@ static irqreturn_t i2c_hid_irq(int irq, void *dev_id) { struct i2c_hid *ihid = dev_id; - if (test_bit(I2C_HID_READ_PENDING, &ihid->flags)) - return IRQ_HANDLED; - i2c_hid_get_input(ihid); return IRQ_HANDLED; @@ -729,11 +710,10 @@ static int i2c_hid_parse(struct hid_device *hid) struct i2c_client *client = hid->driver_data; struct i2c_hid *ihid = i2c_get_clientdata(client); struct i2c_hid_desc *hdesc = &ihid->hdesc; + char *rdesc = NULL, *use_override = NULL; unsigned int rsize; - char *rdesc; int ret; int tries = 3; - char *use_override; i2c_hid_dbg(ihid, "entering %s\n", __func__); @@ -743,11 +723,15 @@ static int i2c_hid_parse(struct hid_device *hid) return -EINVAL; } + mutex_lock(&ihid->reset_lock); do { - ret = i2c_hid_hwreset(ihid); - if (ret) + ret = i2c_hid_start_hwreset(ihid); + if (ret == 0) + ret = i2c_hid_finish_hwreset(ihid); + else msleep(1000); } while (tries-- > 0 && ret); + mutex_unlock(&ihid->reset_lock); if (ret) return ret; @@ -760,11 +744,8 @@ static int i2c_hid_parse(struct hid_device *hid) i2c_hid_dbg(ihid, "Using a HID report descriptor override\n"); } else { rdesc = kzalloc(rsize, GFP_KERNEL); - - if (!rdesc) { - dbg_hid("couldn't allocate rdesc memory\n"); + if (!rdesc) return -ENOMEM; - } i2c_hid_dbg(ihid, "asking HID report descriptor\n"); @@ -773,23 +754,21 @@ static int i2c_hid_parse(struct hid_device *hid) rdesc, rsize); if (ret) { hid_err(hid, "reading report descriptor failed\n"); - kfree(rdesc); - return -EIO; + goto out; } } i2c_hid_dbg(ihid, "Report Descriptor: %*ph\n", rsize, rdesc); ret = hid_parse_report(hid, rdesc, rsize); + if (ret) + dbg_hid("parsing report descriptor failed\n"); + +out: if (!use_override) kfree(rdesc); - if (ret) { - dbg_hid("parsing report descriptor failed\n"); - return ret; - } - - return 0; + return ret; } static int i2c_hid_start(struct hid_device *hid) @@ -958,7 +937,8 @@ static int i2c_hid_core_suspend(struct i2c_hid *ihid, bool force_poweroff) return ret; /* Save some power */ - i2c_hid_set_power(ihid, I2C_HID_PWR_SLEEP); + if (!(ihid->quirks & I2C_HID_QUIRK_NO_SLEEP_ON_SUSPEND)) + i2c_hid_set_power(ihid, I2C_HID_PWR_SLEEP); disable_irq(client->irq); @@ -987,10 +967,15 @@ static int i2c_hid_core_resume(struct i2c_hid *ihid) * However some ALPS touchpads generate IRQ storm without reset, so * let's still reset them here. */ - if (ihid->quirks & I2C_HID_QUIRK_RESET_ON_RESUME) - ret = i2c_hid_hwreset(ihid); - else + if (ihid->quirks & I2C_HID_QUIRK_RESET_ON_RESUME) { + mutex_lock(&ihid->reset_lock); + ret = i2c_hid_start_hwreset(ihid); + if (ret == 0) + ret = i2c_hid_finish_hwreset(ihid); + mutex_unlock(&ihid->reset_lock); + } else { ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON); + } if (ret) return ret; |