summaryrefslogtreecommitdiffstats
path: root/drivers/hid/i2c-hid/i2c-hid.c
diff options
context:
space:
mode:
authorMauro Carvalho Chehab <mchehab@s-opensource.com>2016-12-26 14:11:35 -0200
committerMauro Carvalho Chehab <mchehab@s-opensource.com>2016-12-26 14:11:35 -0200
commitbd361f5de2b338218c276d17a510701a16075deb (patch)
treee0d60ab13fd29d7a56131c9364c9b53baba4940a /drivers/hid/i2c-hid/i2c-hid.c
parentc739c0a7c3c2472d7562b8f802cdce44d2597c8b (diff)
parent7ce7d89f48834cefece7804d38fc5d85382edf77 (diff)
downloadlinux-stable-bd361f5de2b338218c276d17a510701a16075deb.tar.gz
linux-stable-bd361f5de2b338218c276d17a510701a16075deb.tar.bz2
linux-stable-bd361f5de2b338218c276d17a510701a16075deb.zip
Merge tag 'v4.10-rc1' into patchwork
Linux 4.10-rc1 * tag 'v4.10-rc1': (11427 commits) Linux 4.10-rc1 powerpc: Fix build warning on 32-bit PPC avoid spurious "may be used uninitialized" warning mm: add PageWaiters indicating tasks are waiting for a page bit mm: Use owner_priv bit for PageSwapCache, valid when PageSwapBacked ktime: Get rid of ktime_equal() ktime: Cleanup ktime_set() usage ktime: Get rid of the union clocksource: Use a plain u64 instead of cycle_t irqchip/armada-xp: Consolidate hotplug state space irqchip/gic: Consolidate hotplug state space coresight/etm3/4x: Consolidate hotplug state space cpu/hotplug: Cleanup state names cpu/hotplug: Remove obsolete cpu hotplug register/unregister functions staging/lustre/libcfs: Convert to hotplug state machine scsi/bnx2i: Convert to hotplug state machine scsi/bnx2fc: Convert to hotplug state machine cpu/hotplug: Prevent overwriting of callbacks x86/msr: Remove bogus cleanup from the error path bus: arm-ccn: Prevent hotplug callback leak ...
Diffstat (limited to 'drivers/hid/i2c-hid/i2c-hid.c')
-rw-r--r--drivers/hid/i2c-hid/i2c-hid.c146
1 files changed, 91 insertions, 55 deletions
diff --git a/drivers/hid/i2c-hid/i2c-hid.c b/drivers/hid/i2c-hid/i2c-hid.c
index b3ec4f2de875..78fb32a7b103 100644
--- a/drivers/hid/i2c-hid/i2c-hid.c
+++ b/drivers/hid/i2c-hid/i2c-hid.c
@@ -22,6 +22,7 @@
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/input.h>
+#include <linux/irq.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/pm.h>
@@ -37,10 +38,15 @@
#include <linux/mutex.h>
#include <linux/acpi.h>
#include <linux/of.h>
-#include <linux/gpio/consumer.h>
+#include <linux/regulator/consumer.h>
#include <linux/i2c/i2c-hid.h>
+#include "../hid-ids.h"
+
+/* quirks to control the device */
+#define I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV BIT(0)
+
/* flags */
#define I2C_HID_STARTED 0
#define I2C_HID_RESET_PENDING 1
@@ -143,10 +149,9 @@ struct i2c_hid {
char *argsbuf; /* Command arguments buffer */
unsigned long flags; /* device flags */
+ unsigned long quirks; /* Various quirks */
wait_queue_head_t wait; /* For waiting the interrupt */
- struct gpio_desc *desc;
- int irq;
struct i2c_hid_platform_data pdata;
@@ -154,6 +159,39 @@ struct i2c_hid {
struct mutex reset_lock;
};
+static const struct i2c_hid_quirks {
+ __u16 idVendor;
+ __u16 idProduct;
+ __u32 quirks;
+} i2c_hid_quirks[] = {
+ { USB_VENDOR_ID_WEIDA, USB_DEVICE_ID_WEIDA_8752,
+ I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV },
+ { USB_VENDOR_ID_WEIDA, USB_DEVICE_ID_WEIDA_8755,
+ I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV },
+ { 0, 0 }
+};
+
+/*
+ * i2c_hid_lookup_quirk: return any quirks associated with a I2C HID device
+ * @idVendor: the 16-bit vendor ID
+ * @idProduct: the 16-bit product ID
+ *
+ * Returns: a u32 quirks value.
+ */
+static u32 i2c_hid_lookup_quirk(const u16 idVendor, const u16 idProduct)
+{
+ u32 quirks = 0;
+ int n;
+
+ for (n = 0; i2c_hid_quirks[n].idVendor; n++)
+ if (i2c_hid_quirks[n].idVendor == idVendor &&
+ (i2c_hid_quirks[n].idProduct == (__u16)HID_ANY_ID ||
+ i2c_hid_quirks[n].idProduct == idProduct))
+ quirks = i2c_hid_quirks[n].quirks;
+
+ return quirks;
+}
+
static int __i2c_hid_command(struct i2c_client *client,
const struct i2c_hid_cmd *command, u8 reportID,
u8 reportType, u8 *args, int args_len,
@@ -346,11 +384,27 @@ static int i2c_hid_set_power(struct i2c_client *client, int power_state)
i2c_hid_dbg(ihid, "%s\n", __func__);
+ /*
+ * Some devices require to send a command to wakeup before power on.
+ * 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) {
+ ret = i2c_hid_command(client, &hid_set_power_cmd, NULL, 0);
+
+ /* Device was already activated */
+ if (!ret)
+ goto set_pwr_exit;
+ }
+
ret = __i2c_hid_command(client, &hid_set_power_cmd, power_state,
0, NULL, 0, NULL, 0);
+
if (ret)
dev_err(&client->dev, "failed to change power setting.\n");
+set_pwr_exit:
return ret;
}
@@ -716,9 +770,11 @@ static int i2c_hid_start(struct hid_device *hid)
i2c_hid_find_max_report(hid, HID_FEATURE_REPORT, &bufsize);
if (bufsize > ihid->bufsize) {
+ disable_irq(client->irq);
i2c_hid_free_buffers(ihid);
ret = i2c_hid_alloc_buffers(ihid, bufsize);
+ enable_irq(client->irq);
if (ret)
return ret;
@@ -806,18 +862,21 @@ static struct hid_ll_driver i2c_hid_ll_driver = {
static int i2c_hid_init_irq(struct i2c_client *client)
{
struct i2c_hid *ihid = i2c_get_clientdata(client);
+ unsigned long irqflags = 0;
int ret;
- dev_dbg(&client->dev, "Requesting IRQ: %d\n", ihid->irq);
+ dev_dbg(&client->dev, "Requesting IRQ: %d\n", client->irq);
- ret = request_threaded_irq(ihid->irq, NULL, i2c_hid_irq,
- IRQF_TRIGGER_LOW | IRQF_ONESHOT,
- client->name, ihid);
+ if (!irq_get_trigger_type(client->irq))
+ irqflags = IRQF_TRIGGER_LOW;
+
+ ret = request_threaded_irq(client->irq, NULL, i2c_hid_irq,
+ irqflags | IRQF_ONESHOT, client->name, ihid);
if (ret < 0) {
dev_warn(&client->dev,
"Could not register for %s interrupt, irq = %d,"
" ret = %d\n",
- client->name, ihid->irq, ret);
+ client->name, client->irq, ret);
return ret;
}
@@ -864,14 +923,6 @@ static int i2c_hid_fetch_hid_descriptor(struct i2c_hid *ihid)
}
#ifdef CONFIG_ACPI
-
-/* Default GPIO mapping */
-static const struct acpi_gpio_params i2c_hid_irq_gpio = { 0, 0, true };
-static const struct acpi_gpio_mapping i2c_hid_acpi_gpios[] = {
- { "gpios", &i2c_hid_irq_gpio, 1 },
- { },
-};
-
static int i2c_hid_acpi_pdata(struct i2c_client *client,
struct i2c_hid_platform_data *pdata)
{
@@ -882,7 +933,6 @@ static int i2c_hid_acpi_pdata(struct i2c_client *client,
union acpi_object *obj;
struct acpi_device *adev;
acpi_handle handle;
- int ret;
handle = ACPI_HANDLE(&client->dev);
if (!handle || acpi_bus_get_device(handle, &adev))
@@ -898,9 +948,7 @@ static int i2c_hid_acpi_pdata(struct i2c_client *client,
pdata->hid_descriptor_address = obj->integer.value;
ACPI_FREE(obj);
- /* GPIOs are optional */
- ret = acpi_dev_add_driver_gpios(adev, i2c_hid_acpi_gpios);
- return ret < 0 && ret != -ENXIO ? ret : 0;
+ return 0;
}
static const struct acpi_device_id i2c_hid_acpi_match[] = {
@@ -964,6 +1012,19 @@ static int i2c_hid_probe(struct i2c_client *client,
dbg_hid("HID probe called for i2c 0x%02x\n", client->addr);
+ if (!client->irq) {
+ dev_err(&client->dev,
+ "HID over i2c has not been provided an Int IRQ\n");
+ return -EINVAL;
+ }
+
+ if (client->irq < 0) {
+ if (client->irq != -EPROBE_DEFER)
+ dev_err(&client->dev,
+ "HID over i2c doesn't have a valid IRQ\n");
+ return client->irq;
+ }
+
ihid = kzalloc(sizeof(struct i2c_hid), GFP_KERNEL);
if (!ihid)
return -ENOMEM;
@@ -983,23 +1044,6 @@ static int i2c_hid_probe(struct i2c_client *client,
ihid->pdata = *platform_data;
}
- if (client->irq > 0) {
- ihid->irq = client->irq;
- } else if (ACPI_COMPANION(&client->dev)) {
- ihid->desc = gpiod_get(&client->dev, NULL, GPIOD_IN);
- if (IS_ERR(ihid->desc)) {
- dev_err(&client->dev, "Failed to get GPIO interrupt\n");
- return PTR_ERR(ihid->desc);
- }
-
- ihid->irq = gpiod_to_irq(ihid->desc);
- if (ihid->irq < 0) {
- gpiod_put(ihid->desc);
- dev_err(&client->dev, "Failed to convert GPIO to IRQ\n");
- return ihid->irq;
- }
- }
-
i2c_set_clientdata(client, ihid);
ihid->client = client;
@@ -1050,6 +1094,8 @@ static int i2c_hid_probe(struct i2c_client *client,
client->name, hid->vendor, hid->product);
strlcpy(hid->phys, dev_name(&client->dev), sizeof(hid->phys));
+ ihid->quirks = i2c_hid_lookup_quirk(hid->vendor, hid->product);
+
ret = hid_add_device(hid);
if (ret) {
if (ret != -ENODEV)
@@ -1064,16 +1110,13 @@ err_mem_free:
hid_destroy_device(hid);
err_irq:
- free_irq(ihid->irq, ihid);
+ free_irq(client->irq, ihid);
err_pm:
pm_runtime_put_noidle(&client->dev);
pm_runtime_disable(&client->dev);
err:
- if (ihid->desc)
- gpiod_put(ihid->desc);
-
i2c_hid_free_buffers(ihid);
kfree(ihid);
return ret;
@@ -1092,18 +1135,13 @@ static int i2c_hid_remove(struct i2c_client *client)
hid = ihid->hid;
hid_destroy_device(hid);
- free_irq(ihid->irq, ihid);
+ free_irq(client->irq, ihid);
if (ihid->bufsize)
i2c_hid_free_buffers(ihid);
- if (ihid->desc)
- gpiod_put(ihid->desc);
-
kfree(ihid);
- acpi_dev_remove_driver_gpios(ACPI_COMPANION(&client->dev));
-
return 0;
}
@@ -1142,11 +1180,11 @@ static int i2c_hid_suspend(struct device *dev)
/* Save some power */
i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
- disable_irq(ihid->irq);
+ disable_irq(client->irq);
}
if (device_may_wakeup(&client->dev)) {
- wake_status = enable_irq_wake(ihid->irq);
+ wake_status = enable_irq_wake(client->irq);
if (!wake_status)
ihid->irq_wake_enabled = true;
else
@@ -1166,7 +1204,7 @@ static int i2c_hid_resume(struct device *dev)
int wake_status;
if (device_may_wakeup(&client->dev) && ihid->irq_wake_enabled) {
- wake_status = disable_irq_wake(ihid->irq);
+ wake_status = disable_irq_wake(client->irq);
if (!wake_status)
ihid->irq_wake_enabled = false;
else
@@ -1179,7 +1217,7 @@ static int i2c_hid_resume(struct device *dev)
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
- enable_irq(ihid->irq);
+ enable_irq(client->irq);
ret = i2c_hid_hwreset(client);
if (ret)
return ret;
@@ -1197,19 +1235,17 @@ static int i2c_hid_resume(struct device *dev)
static int i2c_hid_runtime_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
- struct i2c_hid *ihid = i2c_get_clientdata(client);
i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
- disable_irq(ihid->irq);
+ disable_irq(client->irq);
return 0;
}
static int i2c_hid_runtime_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
- struct i2c_hid *ihid = i2c_get_clientdata(client);
- enable_irq(ihid->irq);
+ enable_irq(client->irq);
i2c_hid_set_power(client, I2C_HID_PWR_ON);
return 0;
}