diff options
Diffstat (limited to 'drivers/hid/i2c-hid')
-rw-r--r-- | drivers/hid/i2c-hid/Kconfig | 71 | ||||
-rw-r--r-- | drivers/hid/i2c-hid/Makefile | 14 | ||||
-rw-r--r-- | drivers/hid/i2c-hid/i2c-hid-acpi.c | 146 | ||||
-rw-r--r-- | drivers/hid/i2c-hid/i2c-hid-core.c | 1178 | ||||
-rw-r--r-- | drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c | 494 | ||||
-rw-r--r-- | drivers/hid/i2c-hid/i2c-hid-of-elan.c | 130 | ||||
-rw-r--r-- | drivers/hid/i2c-hid/i2c-hid-of-goodix.c | 178 | ||||
-rw-r--r-- | drivers/hid/i2c-hid/i2c-hid-of.c | 151 | ||||
-rw-r--r-- | drivers/hid/i2c-hid/i2c-hid.h | 45 |
9 files changed, 2407 insertions, 0 deletions
diff --git a/drivers/hid/i2c-hid/Kconfig b/drivers/hid/i2c-hid/Kconfig new file mode 100644 index 000000000..d65abe65c --- /dev/null +++ b/drivers/hid/i2c-hid/Kconfig @@ -0,0 +1,71 @@ +# SPDX-License-Identifier: GPL-2.0-only +menu "I2C HID support" + depends on I2C + +config I2C_HID_ACPI + tristate "HID over I2C transport layer ACPI driver" + default n + depends on I2C && INPUT && ACPI + help + Say Y here if you use a keyboard, a touchpad, a touchscreen, or any + other HID based devices which is connected to your computer via I2C. + This driver supports ACPI-based systems. + + If unsure, say N. + + This support is also available as a module. If so, the module + will be called i2c-hid-acpi. It will also build/depend on the + module i2c-hid. + +config I2C_HID_OF + tristate "HID over I2C transport layer Open Firmware driver" + default n + depends on I2C && INPUT && OF + help + Say Y here if you use a keyboard, a touchpad, a touchscreen, or any + other HID based devices which is connected to your computer via I2C. + This driver supports Open Firmware (Device Tree)-based systems. + + If unsure, say N. + + This support is also available as a module. If so, the module + will be called i2c-hid-of. It will also build/depend on the + module i2c-hid. + +config I2C_HID_OF_ELAN + tristate "Driver for Elan hid-i2c based devices on OF systems" + default n + depends on I2C && INPUT && OF + help + Say Y here if you want support for Elan i2c devices that use + the i2c-hid protocol on Open Firmware (Device Tree)-based + systems. + + If unsure, say N. + + This support is also available as a module. If so, the module + will be called i2c-hid-of-elan. It will also build/depend on + the module i2c-hid. + +config I2C_HID_OF_GOODIX + tristate "Driver for Goodix hid-i2c based devices on OF systems" + default n + depends on I2C && INPUT && OF + help + Say Y here if you want support for Goodix i2c devices that use + the i2c-hid protocol on Open Firmware (Device Tree)-based + systems. + + If unsure, say N. + + This support is also available as a module. If so, the module + will be called i2c-hid-of-goodix. It will also build/depend on + the module i2c-hid. + +endmenu + +config I2C_HID_CORE + tristate + default y if I2C_HID_ACPI=y || I2C_HID_OF=y || I2C_HID_OF_ELAN=y || I2C_HID_OF_GOODIX=y + default m if I2C_HID_ACPI=m || I2C_HID_OF=m || I2C_HID_OF_ELAN=m || I2C_HID_OF_GOODIX=m + select HID diff --git a/drivers/hid/i2c-hid/Makefile b/drivers/hid/i2c-hid/Makefile new file mode 100644 index 000000000..55bd5e0f3 --- /dev/null +++ b/drivers/hid/i2c-hid/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the I2C input drivers +# + +obj-$(CONFIG_I2C_HID_CORE) += i2c-hid.o + +i2c-hid-objs = i2c-hid-core.o +i2c-hid-$(CONFIG_DMI) += i2c-hid-dmi-quirks.o + +obj-$(CONFIG_I2C_HID_ACPI) += i2c-hid-acpi.o +obj-$(CONFIG_I2C_HID_OF) += i2c-hid-of.o +obj-$(CONFIG_I2C_HID_OF_ELAN) += i2c-hid-of-elan.o +obj-$(CONFIG_I2C_HID_OF_GOODIX) += i2c-hid-of-goodix.o diff --git a/drivers/hid/i2c-hid/i2c-hid-acpi.c b/drivers/hid/i2c-hid/i2c-hid-acpi.c new file mode 100644 index 000000000..6d35bb397 --- /dev/null +++ b/drivers/hid/i2c-hid/i2c-hid-acpi.c @@ -0,0 +1,146 @@ +/* + * HID over I2C ACPI Subclass + * + * Copyright (c) 2012 Benjamin Tissoires <benjamin.tissoires@gmail.com> + * Copyright (c) 2012 Ecole Nationale de l'Aviation Civile, France + * Copyright (c) 2012 Red Hat, Inc + * + * This code was forked out of the core code, which was partly based on + * "USB HID support for Linux": + * + * Copyright (c) 1999 Andreas Gal + * Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz> + * Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc + * Copyright (c) 2007-2008 Oliver Neukum + * Copyright (c) 2006-2010 Jiri Kosina + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include <linux/acpi.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pm.h> +#include <linux/uuid.h> + +#include "i2c-hid.h" + +struct i2c_hid_acpi { + struct i2chid_ops ops; + struct acpi_device *adev; +}; + +static const struct acpi_device_id i2c_hid_acpi_blacklist[] = { + /* + * The CHPN0001 ACPI device, which is used to describe the Chipone + * ICN8505 controller, has a _CID of PNP0C50 but is not HID compatible. + */ + { "CHPN0001" }, + /* + * The IDEA5002 ACPI device causes high interrupt usage and spurious + * wakeups from suspend. + */ + { "IDEA5002" }, + { } +}; + +/* HID I²C Device: 3cdff6f7-4267-4555-ad05-b30a3d8938de */ +static guid_t i2c_hid_guid = + GUID_INIT(0x3CDFF6F7, 0x4267, 0x4555, + 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE); + +static int i2c_hid_acpi_get_descriptor(struct acpi_device *adev) +{ + acpi_handle handle = acpi_device_handle(adev); + union acpi_object *obj; + u16 hid_descriptor_address; + + if (acpi_match_device_ids(adev, i2c_hid_acpi_blacklist) == 0) + return -ENODEV; + + obj = acpi_evaluate_dsm_typed(handle, &i2c_hid_guid, 1, 1, NULL, + ACPI_TYPE_INTEGER); + if (!obj) { + acpi_handle_err(handle, "Error _DSM call to get HID descriptor address failed\n"); + return -ENODEV; + } + + hid_descriptor_address = obj->integer.value; + ACPI_FREE(obj); + + return hid_descriptor_address; +} + +static void i2c_hid_acpi_shutdown_tail(struct i2chid_ops *ops) +{ + struct i2c_hid_acpi *ihid_acpi = container_of(ops, struct i2c_hid_acpi, ops); + + acpi_device_set_power(ihid_acpi->adev, ACPI_STATE_D3_COLD); +} + +static int i2c_hid_acpi_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct i2c_hid_acpi *ihid_acpi; + struct acpi_device *adev; + u16 hid_descriptor_address; + int ret; + + adev = ACPI_COMPANION(dev); + if (!adev) { + dev_err(&client->dev, "Error could not get ACPI device\n"); + return -ENODEV; + } + + ihid_acpi = devm_kzalloc(&client->dev, sizeof(*ihid_acpi), GFP_KERNEL); + if (!ihid_acpi) + return -ENOMEM; + + ihid_acpi->adev = adev; + ihid_acpi->ops.shutdown_tail = i2c_hid_acpi_shutdown_tail; + + ret = i2c_hid_acpi_get_descriptor(adev); + if (ret < 0) + return ret; + hid_descriptor_address = ret; + + acpi_device_fix_up_power(adev); + + if (acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0) { + device_set_wakeup_capable(dev, true); + device_set_wakeup_enable(dev, false); + } + + return i2c_hid_core_probe(client, &ihid_acpi->ops, + hid_descriptor_address, 0); +} + +static const struct acpi_device_id i2c_hid_acpi_match[] = { + { "ACPI0C50" }, + { "PNP0C50" }, + { } +}; +MODULE_DEVICE_TABLE(acpi, i2c_hid_acpi_match); + +static struct i2c_driver i2c_hid_acpi_driver = { + .driver = { + .name = "i2c_hid_acpi", + .pm = &i2c_hid_core_pm, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .acpi_match_table = i2c_hid_acpi_match, + }, + + .probe_new = i2c_hid_acpi_probe, + .remove = i2c_hid_core_remove, + .shutdown = i2c_hid_core_shutdown, +}; + +module_i2c_driver(i2c_hid_acpi_driver); + +MODULE_DESCRIPTION("HID over I2C ACPI driver"); +MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c new file mode 100644 index 000000000..969f8eb08 --- /dev/null +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -0,0 +1,1178 @@ +/* + * HID over I2C protocol implementation + * + * Copyright (c) 2012 Benjamin Tissoires <benjamin.tissoires@gmail.com> + * Copyright (c) 2012 Ecole Nationale de l'Aviation Civile, France + * Copyright (c) 2012 Red Hat, Inc + * + * This code is partly based on "USB HID support for Linux": + * + * Copyright (c) 1999 Andreas Gal + * Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz> + * Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc + * Copyright (c) 2007-2008 Oliver Neukum + * Copyright (c) 2006-2010 Jiri Kosina + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include <linux/module.h> +#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> +#include <linux/device.h> +#include <linux/wait.h> +#include <linux/err.h> +#include <linux/string.h> +#include <linux/list.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/hid.h> +#include <linux/mutex.h> +#include <asm/unaligned.h> + +#include "../hid-ids.h" +#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) + +/* Command opcodes */ +#define I2C_HID_OPCODE_RESET 0x01 +#define I2C_HID_OPCODE_GET_REPORT 0x02 +#define I2C_HID_OPCODE_SET_REPORT 0x03 +#define I2C_HID_OPCODE_GET_IDLE 0x04 +#define I2C_HID_OPCODE_SET_IDLE 0x05 +#define I2C_HID_OPCODE_GET_PROTOCOL 0x06 +#define I2C_HID_OPCODE_SET_PROTOCOL 0x07 +#define I2C_HID_OPCODE_SET_POWER 0x08 + +/* 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 + +/* debug option */ +static bool debug; +module_param(debug, bool, 0444); +MODULE_PARM_DESC(debug, "print a lot of debug information"); + +#define i2c_hid_dbg(ihid, fmt, arg...) \ +do { \ + if (debug) \ + dev_printk(KERN_DEBUG, &(ihid)->client->dev, fmt, ##arg); \ +} while (0) + +struct i2c_hid_desc { + __le16 wHIDDescLength; + __le16 bcdVersion; + __le16 wReportDescLength; + __le16 wReportDescRegister; + __le16 wInputRegister; + __le16 wMaxInputLength; + __le16 wOutputRegister; + __le16 wMaxOutputLength; + __le16 wCommandRegister; + __le16 wDataRegister; + __le16 wVendorID; + __le16 wProductID; + __le16 wVersionID; + __le32 reserved; +} __packed; + +/* The main device structure */ +struct i2c_hid { + struct i2c_client *client; /* i2c client */ + struct hid_device *hid; /* pointer to corresponding HID dev */ + struct i2c_hid_desc hdesc; /* the HID Descriptor */ + __le16 wHIDDescRegister; /* location of the i2c + * register of the HID + * descriptor. */ + unsigned int bufsize; /* i2c buffer size */ + u8 *inbuf; /* Input buffer */ + u8 *rawbuf; /* Raw Input buffer */ + u8 *cmdbuf; /* Command buffer */ + + unsigned long flags; /* device flags */ + unsigned long quirks; /* Various quirks */ + + wait_queue_head_t wait; /* For waiting the interrupt */ + + bool irq_wake_enabled; + struct mutex reset_lock; + + struct i2chid_ops *ops; +}; + +static const struct i2c_hid_quirks { + __u16 idVendor; + __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, + I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, + { I2C_VENDOR_ID_RAYDIUM, I2C_PRODUCT_ID_RAYDIUM_3118, + I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, + { USB_VENDOR_ID_ALPS_JP, HID_ANY_ID, + I2C_HID_QUIRK_RESET_ON_RESUME }, + { I2C_VENDOR_ID_SYNAPTICS, I2C_PRODUCT_ID_SYNAPTICS_SYNA2393, + I2C_HID_QUIRK_RESET_ON_RESUME }, + { USB_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_LENOVO_LEGION_Y720, + I2C_HID_QUIRK_BAD_INPUT_SIZE }, + /* + * Sending the wakeup after reset actually break ELAN touchscreen controller + */ + { USB_VENDOR_ID_ELAN, HID_ANY_ID, + I2C_HID_QUIRK_NO_WAKEUP_AFTER_RESET | + I2C_HID_QUIRK_BOGUS_IRQ }, + { 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_xfer(struct i2c_hid *ihid, + u8 *send_buf, int send_len, u8 *recv_buf, int recv_len) +{ + struct i2c_client *client = ihid->client; + struct i2c_msg msgs[2] = { 0 }; + int n = 0; + int ret; + + if (send_len) { + i2c_hid_dbg(ihid, "%s: cmd=%*ph\n", + __func__, send_len, send_buf); + + msgs[n].addr = client->addr; + msgs[n].flags = (client->flags & I2C_M_TEN) | I2C_M_DMA_SAFE; + msgs[n].len = send_len; + msgs[n].buf = send_buf; + n++; + } + + if (recv_len) { + msgs[n].addr = client->addr; + msgs[n].flags = (client->flags & I2C_M_TEN) | + I2C_M_RD | I2C_M_DMA_SAFE; + 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; + + return 0; +} + +static int i2c_hid_read_register(struct i2c_hid *ihid, __le16 reg, + void *buf, size_t len) +{ + *(__le16 *)ihid->cmdbuf = reg; + + return i2c_hid_xfer(ihid, ihid->cmdbuf, sizeof(__le16), buf, len); +} + +static size_t i2c_hid_encode_command(u8 *buf, u8 opcode, + int report_type, int report_id) +{ + size_t length = 0; + + if (report_id < 0x0F) { + buf[length++] = report_type << 4 | report_id; + buf[length++] = opcode; + } else { + buf[length++] = report_type << 4 | 0x0F; + buf[length++] = opcode; + buf[length++] = report_id; + } + + return length; +} + +static int i2c_hid_get_report(struct i2c_hid *ihid, + u8 report_type, u8 report_id, + u8 *recv_buf, size_t recv_len) +{ + size_t length = 0; + size_t ret_count; + int error; + + i2c_hid_dbg(ihid, "%s\n", __func__); + + /* Command register goes first */ + *(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister; + length += sizeof(__le16); + /* Next is GET_REPORT command */ + length += i2c_hid_encode_command(ihid->cmdbuf + length, + I2C_HID_OPCODE_GET_REPORT, + report_type, report_id); + /* + * Device will send report data through data register. Because + * command can be either 2 or 3 bytes destination for the data + * register may be not aligned. + */ + put_unaligned_le16(le16_to_cpu(ihid->hdesc.wDataRegister), + ihid->cmdbuf + length); + length += sizeof(__le16); + + /* + * In addition to report data device will supply data length + * in the first 2 bytes of the response, so adjust . + */ + error = i2c_hid_xfer(ihid, ihid->cmdbuf, length, + ihid->rawbuf, recv_len + sizeof(__le16)); + if (error) { + dev_err(&ihid->client->dev, + "failed to set a report to device: %d\n", error); + return error; + } + + /* The buffer is sufficiently aligned */ + ret_count = le16_to_cpup((__le16 *)ihid->rawbuf); + + /* Check for empty report response */ + if (ret_count <= sizeof(__le16)) + return 0; + + recv_len = min(recv_len, ret_count - sizeof(__le16)); + memcpy(recv_buf, ihid->rawbuf + sizeof(__le16), recv_len); + + if (report_id && recv_len != 0 && recv_buf[0] != report_id) { + dev_err(&ihid->client->dev, + "device returned incorrect report (%d vs %d expected)\n", + recv_buf[0], report_id); + return -EINVAL; + } + + return recv_len; +} + +static size_t i2c_hid_format_report(u8 *buf, int report_id, + const u8 *data, size_t size) +{ + size_t length = sizeof(__le16); /* reserve space to store size */ + + if (report_id) + buf[length++] = report_id; + + memcpy(buf + length, data, size); + length += size; + + /* Store overall size in the beginning of the buffer */ + put_unaligned_le16(length, buf); + + return length; +} + +/** + * i2c_hid_set_or_send_report: forward an incoming report to the device + * @ihid: the i2c hid device + * @report_type: 0x03 for HID_FEATURE_REPORT ; 0x02 for HID_OUTPUT_REPORT + * @report_id: the report ID + * @buf: the actual data to transfer, without the report ID + * @data_len: size of buf + * @do_set: true: use SET_REPORT HID command, false: send plain OUTPUT report + */ +static int i2c_hid_set_or_send_report(struct i2c_hid *ihid, + u8 report_type, u8 report_id, + const u8 *buf, size_t data_len, + bool do_set) +{ + size_t length = 0; + int error; + + i2c_hid_dbg(ihid, "%s\n", __func__); + + if (data_len > ihid->bufsize) + return -EINVAL; + + if (!do_set && le16_to_cpu(ihid->hdesc.wMaxOutputLength) == 0) + return -ENOSYS; + + if (do_set) { + /* Command register goes first */ + *(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister; + length += sizeof(__le16); + /* Next is SET_REPORT command */ + length += i2c_hid_encode_command(ihid->cmdbuf + length, + I2C_HID_OPCODE_SET_REPORT, + report_type, report_id); + /* + * Report data will go into the data register. Because + * command can be either 2 or 3 bytes destination for + * the data register may be not aligned. + */ + put_unaligned_le16(le16_to_cpu(ihid->hdesc.wDataRegister), + ihid->cmdbuf + length); + length += sizeof(__le16); + } else { + /* + * With simple "send report" all data goes into the output + * register. + */ + *(__le16 *)ihid->cmdbuf = ihid->hdesc.wOutputRegister; + length += sizeof(__le16); + } + + length += i2c_hid_format_report(ihid->cmdbuf + length, + report_id, buf, data_len); + + error = i2c_hid_xfer(ihid, ihid->cmdbuf, length, NULL, 0); + if (error) { + dev_err(&ihid->client->dev, + "failed to set a report to device: %d\n", error); + return error; + } + + return data_len; +} + +static int i2c_hid_set_power_command(struct i2c_hid *ihid, int power_state) +{ + size_t length; + + /* SET_POWER uses command register */ + *(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister; + length = sizeof(__le16); + + /* Now the command itself */ + length += i2c_hid_encode_command(ihid->cmdbuf + length, + I2C_HID_OPCODE_SET_POWER, + 0, power_state); + + return i2c_hid_xfer(ihid, ihid->cmdbuf, length, NULL, 0); +} + +static int i2c_hid_set_power(struct i2c_hid *ihid, int power_state) +{ + int ret; + + 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_set_power_command(ihid, I2C_HID_PWR_ON); + + /* Device was already activated */ + if (!ret) + goto set_pwr_exit; + } + + ret = i2c_hid_set_power_command(ihid, power_state); + if (ret) + dev_err(&ihid->client->dev, + "failed to change power setting.\n"); + +set_pwr_exit: + + /* + * The HID over I2C specification states that if a DEVICE needs time + * after the PWR_ON request, it should utilise CLOCK stretching. + * However, it has been observered that the Windows driver provides a + * 1ms sleep between the PWR_ON and RESET requests. + * According to Goodix Windows even waits 60 ms after (other?) + * PWR_ON requests. Testing has confirmed that several devices + * will not work properly without a delay after a PWR_ON request. + */ + if (!ret && power_state == I2C_HID_PWR_ON) + msleep(60); + + return ret; +} + +static int i2c_hid_execute_reset(struct i2c_hid *ihid) +{ + size_t length = 0; + int ret; + + i2c_hid_dbg(ihid, "resetting...\n"); + + /* Prepare reset command. Command register goes first. */ + *(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister; + length += sizeof(__le16); + /* Next is RESET command itself */ + length += i2c_hid_encode_command(ihid->cmdbuf + length, + I2C_HID_OPCODE_RESET, 0, 0); + + set_bit(I2C_HID_RESET_PENDING, &ihid->flags); + + 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; + } + + 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__); + +out: + clear_bit(I2C_HID_RESET_PENDING, &ihid->flags); + return ret; +} + +static int i2c_hid_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); + + ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON); + if (ret) + goto out_unlock; + + 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; + } + + /* 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; +} + +static void i2c_hid_get_input(struct i2c_hid *ihid) +{ + u16 size = le16_to_cpu(ihid->hdesc.wMaxInputLength); + u16 ret_size; + int ret; + + if (size > ihid->bufsize) + size = ihid->bufsize; + + ret = i2c_master_recv(ihid->client, ihid->inbuf, size); + if (ret != size) { + if (ret < 0) + return; + + dev_err(&ihid->client->dev, "%s: got %d data instead of %d\n", + __func__, ret, size); + return; + } + + /* Receiving buffer is properly aligned */ + ret_size = le16_to_cpup((__le16 *)ihid->inbuf); + if (!ret_size) { + /* host or device initiated RESET completed */ + if (test_and_clear_bit(I2C_HID_RESET_PENDING, &ihid->flags)) + wake_up(&ihid->wait); + return; + } + + if ((ihid->quirks & I2C_HID_QUIRK_BOGUS_IRQ) && ret_size == 0xffff) { + dev_warn_once(&ihid->client->dev, + "%s: IRQ triggered but there's no data\n", + __func__); + return; + } + + if (ret_size > size || ret_size < sizeof(__le16)) { + if (ihid->quirks & I2C_HID_QUIRK_BAD_INPUT_SIZE) { + *(__le16 *)ihid->inbuf = cpu_to_le16(size); + ret_size = size; + } else { + dev_err(&ihid->client->dev, + "%s: incomplete report (%d/%d)\n", + __func__, size, ret_size); + return; + } + } + + i2c_hid_dbg(ihid, "input: %*ph\n", ret_size, ihid->inbuf); + + if (test_bit(I2C_HID_STARTED, &ihid->flags)) { + if (ihid->hid->group != HID_GROUP_RMI) + pm_wakeup_event(&ihid->client->dev, 0); + + hid_input_report(ihid->hid, HID_INPUT_REPORT, + ihid->inbuf + sizeof(__le16), + ret_size - sizeof(__le16), 1); + } + + return; +} + +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; +} + +static int i2c_hid_get_report_length(struct hid_report *report) +{ + return ((report->size - 1) >> 3) + 1 + + report->device->report_enum[report->type].numbered + 2; +} + +/* + * Traverse the supplied list of reports and find the longest + */ +static void i2c_hid_find_max_report(struct hid_device *hid, unsigned int type, + unsigned int *max) +{ + struct hid_report *report; + unsigned int size; + + /* We should not rely on wMaxInputLength, as some devices may set it to + * a wrong length. */ + list_for_each_entry(report, &hid->report_enum[type].report_list, list) { + size = i2c_hid_get_report_length(report); + if (*max < size) + *max = size; + } +} + +static void i2c_hid_free_buffers(struct i2c_hid *ihid) +{ + kfree(ihid->inbuf); + kfree(ihid->rawbuf); + kfree(ihid->cmdbuf); + ihid->inbuf = NULL; + ihid->rawbuf = NULL; + ihid->cmdbuf = NULL; + ihid->bufsize = 0; +} + +static int i2c_hid_alloc_buffers(struct i2c_hid *ihid, size_t report_size) +{ + /* + * The worst case is computed from the set_report command with a + * reportID > 15 and the maximum report length. + */ + int cmd_len = sizeof(__le16) + /* command register */ + sizeof(u8) + /* encoded report type/ID */ + sizeof(u8) + /* opcode */ + sizeof(u8) + /* optional 3rd byte report ID */ + sizeof(__le16) + /* data register */ + sizeof(__le16) + /* report data size */ + sizeof(u8) + /* report ID if numbered report */ + report_size; + + ihid->inbuf = kzalloc(report_size, GFP_KERNEL); + ihid->rawbuf = kzalloc(report_size, GFP_KERNEL); + ihid->cmdbuf = kzalloc(cmd_len, GFP_KERNEL); + + if (!ihid->inbuf || !ihid->rawbuf || !ihid->cmdbuf) { + i2c_hid_free_buffers(ihid); + return -ENOMEM; + } + + ihid->bufsize = report_size; + + return 0; +} + +static int i2c_hid_get_raw_report(struct hid_device *hid, + u8 report_type, u8 report_id, + u8 *buf, size_t count) +{ + struct i2c_client *client = hid->driver_data; + struct i2c_hid *ihid = i2c_get_clientdata(client); + int ret_count; + + if (report_type == HID_OUTPUT_REPORT) + return -EINVAL; + + /* + * In case of unnumbered reports the response from the device will + * not have the report ID that the upper layers expect, so we need + * to stash it the buffer ourselves and adjust the data size. + */ + if (!report_id) { + buf[0] = 0; + buf++; + count--; + } + + ret_count = i2c_hid_get_report(ihid, + report_type == HID_FEATURE_REPORT ? 0x03 : 0x01, + report_id, buf, count); + + if (ret_count > 0 && !report_id) + ret_count++; + + return ret_count; +} + +static int i2c_hid_output_raw_report(struct hid_device *hid, u8 report_type, + const u8 *buf, size_t count, bool do_set) +{ + struct i2c_client *client = hid->driver_data; + struct i2c_hid *ihid = i2c_get_clientdata(client); + int report_id = buf[0]; + int ret; + + if (report_type == HID_INPUT_REPORT) + return -EINVAL; + + mutex_lock(&ihid->reset_lock); + + /* + * Note that both numbered and unnumbered reports passed here + * are supposed to have report ID stored in the 1st byte of the + * buffer, so we strip it off unconditionally before passing payload + * to i2c_hid_set_or_send_report which takes care of encoding + * everything properly. + */ + ret = i2c_hid_set_or_send_report(ihid, + report_type == HID_FEATURE_REPORT ? 0x03 : 0x02, + report_id, buf + 1, count - 1, do_set); + + if (ret >= 0) + ret++; /* add report_id to the number of transferred bytes */ + + mutex_unlock(&ihid->reset_lock); + + return ret; +} + +static int i2c_hid_output_report(struct hid_device *hid, u8 *buf, size_t count) +{ + return i2c_hid_output_raw_report(hid, HID_OUTPUT_REPORT, buf, count, + false); +} + +static int i2c_hid_raw_request(struct hid_device *hid, unsigned char reportnum, + __u8 *buf, size_t len, unsigned char rtype, + int reqtype) +{ + switch (reqtype) { + case HID_REQ_GET_REPORT: + return i2c_hid_get_raw_report(hid, rtype, reportnum, buf, len); + case HID_REQ_SET_REPORT: + if (buf[0] != reportnum) + return -EINVAL; + return i2c_hid_output_raw_report(hid, rtype, buf, len, true); + default: + return -EIO; + } +} + +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; + unsigned int rsize; + char *rdesc; + int ret; + int tries = 3; + char *use_override; + + i2c_hid_dbg(ihid, "entering %s\n", __func__); + + rsize = le16_to_cpu(hdesc->wReportDescLength); + if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) { + dbg_hid("weird size of report descriptor (%u)\n", rsize); + return -EINVAL; + } + + do { + ret = i2c_hid_hwreset(ihid); + if (ret) + msleep(1000); + } while (tries-- > 0 && ret); + + if (ret) + return ret; + + use_override = i2c_hid_get_dmi_hid_report_desc_override(client->name, + &rsize); + + if (use_override) { + rdesc = use_override; + 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"); + return -ENOMEM; + } + + i2c_hid_dbg(ihid, "asking HID report descriptor\n"); + + ret = i2c_hid_read_register(ihid, + ihid->hdesc.wReportDescRegister, + rdesc, rsize); + if (ret) { + hid_err(hid, "reading report descriptor failed\n"); + kfree(rdesc); + return -EIO; + } + } + + i2c_hid_dbg(ihid, "Report Descriptor: %*ph\n", rsize, rdesc); + + ret = hid_parse_report(hid, rdesc, rsize); + if (!use_override) + kfree(rdesc); + + if (ret) { + dbg_hid("parsing report descriptor failed\n"); + return ret; + } + + return 0; +} + +static int i2c_hid_start(struct hid_device *hid) +{ + struct i2c_client *client = hid->driver_data; + struct i2c_hid *ihid = i2c_get_clientdata(client); + int ret; + unsigned int bufsize = HID_MIN_BUFFER_SIZE; + + i2c_hid_find_max_report(hid, HID_INPUT_REPORT, &bufsize); + i2c_hid_find_max_report(hid, HID_OUTPUT_REPORT, &bufsize); + 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; + } + + return 0; +} + +static void i2c_hid_stop(struct hid_device *hid) +{ + hid->claimed = 0; +} + +static int i2c_hid_open(struct hid_device *hid) +{ + struct i2c_client *client = hid->driver_data; + struct i2c_hid *ihid = i2c_get_clientdata(client); + + set_bit(I2C_HID_STARTED, &ihid->flags); + return 0; +} + +static void i2c_hid_close(struct hid_device *hid) +{ + struct i2c_client *client = hid->driver_data; + struct i2c_hid *ihid = i2c_get_clientdata(client); + + clear_bit(I2C_HID_STARTED, &ihid->flags); +} + +struct hid_ll_driver i2c_hid_ll_driver = { + .parse = i2c_hid_parse, + .start = i2c_hid_start, + .stop = i2c_hid_stop, + .open = i2c_hid_open, + .close = i2c_hid_close, + .output_report = i2c_hid_output_report, + .raw_request = i2c_hid_raw_request, +}; +EXPORT_SYMBOL_GPL(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", client->irq); + + 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, client->irq, ret); + + return ret; + } + + return 0; +} + +static int i2c_hid_fetch_hid_descriptor(struct i2c_hid *ihid) +{ + struct i2c_client *client = ihid->client; + struct i2c_hid_desc *hdesc = &ihid->hdesc; + unsigned int dsize; + int error; + + /* i2c hid fetch using a fixed descriptor size (30 bytes) */ + if (i2c_hid_get_dmi_i2c_hid_desc_override(client->name)) { + i2c_hid_dbg(ihid, "Using a HID descriptor override\n"); + ihid->hdesc = + *i2c_hid_get_dmi_i2c_hid_desc_override(client->name); + } else { + i2c_hid_dbg(ihid, "Fetching the HID descriptor\n"); + error = i2c_hid_read_register(ihid, + ihid->wHIDDescRegister, + &ihid->hdesc, + sizeof(ihid->hdesc)); + if (error) { + dev_err(&ihid->client->dev, + "failed to fetch HID descriptor: %d\n", + error); + return -ENODEV; + } + } + + /* Validate the length of HID descriptor, the 4 first bytes: + * bytes 0-1 -> length + * bytes 2-3 -> bcdVersion (has to be 1.00) */ + /* check bcdVersion == 1.0 */ + if (le16_to_cpu(hdesc->bcdVersion) != 0x0100) { + dev_err(&ihid->client->dev, + "unexpected HID descriptor bcdVersion (0x%04hx)\n", + le16_to_cpu(hdesc->bcdVersion)); + return -ENODEV; + } + + /* Descriptor length should be 30 bytes as per the specification */ + dsize = le16_to_cpu(hdesc->wHIDDescLength); + if (dsize != sizeof(struct i2c_hid_desc)) { + dev_err(&ihid->client->dev, + "weird size of HID descriptor (%u)\n", dsize); + return -ENODEV; + } + i2c_hid_dbg(ihid, "HID Descriptor: %*ph\n", dsize, &ihid->hdesc); + return 0; +} + +static int i2c_hid_core_power_up(struct i2c_hid *ihid) +{ + if (!ihid->ops->power_up) + return 0; + + return ihid->ops->power_up(ihid->ops); +} + +static void i2c_hid_core_power_down(struct i2c_hid *ihid) +{ + if (!ihid->ops->power_down) + return; + + ihid->ops->power_down(ihid->ops); +} + +static void i2c_hid_core_shutdown_tail(struct i2c_hid *ihid) +{ + if (!ihid->ops->shutdown_tail) + return; + + ihid->ops->shutdown_tail(ihid->ops); +} + +int i2c_hid_core_probe(struct i2c_client *client, struct i2chid_ops *ops, + u16 hid_descriptor_address, u32 quirks) +{ + int ret; + struct i2c_hid *ihid; + struct hid_device *hid; + + 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 = devm_kzalloc(&client->dev, sizeof(*ihid), GFP_KERNEL); + if (!ihid) + return -ENOMEM; + + ihid->ops = ops; + + ret = i2c_hid_core_power_up(ihid); + if (ret) + return ret; + + i2c_set_clientdata(client, ihid); + + ihid->client = client; + + ihid->wHIDDescRegister = cpu_to_le16(hid_descriptor_address); + + init_waitqueue_head(&ihid->wait); + mutex_init(&ihid->reset_lock); + + /* we need to allocate the command buffer without knowing the maximum + * size of the reports. Let's use HID_MIN_BUFFER_SIZE, then we do the + * real computation later. */ + ret = i2c_hid_alloc_buffers(ihid, HID_MIN_BUFFER_SIZE); + if (ret < 0) + goto err_powered; + + device_enable_async_suspend(&client->dev); + + /* Make sure there is something at this address */ + ret = i2c_smbus_read_byte(client); + if (ret < 0) { + dev_dbg(&client->dev, "nothing at this address: %d\n", ret); + ret = -ENXIO; + goto err_powered; + } + + ret = i2c_hid_fetch_hid_descriptor(ihid); + if (ret < 0) { + dev_err(&client->dev, + "Failed to fetch the HID Descriptor\n"); + goto err_powered; + } + + ret = i2c_hid_init_irq(client); + if (ret < 0) + goto err_powered; + + hid = hid_allocate_device(); + if (IS_ERR(hid)) { + ret = PTR_ERR(hid); + goto err_irq; + } + + ihid->hid = hid; + + hid->driver_data = client; + hid->ll_driver = &i2c_hid_ll_driver; + hid->dev.parent = &client->dev; + hid->bus = BUS_I2C; + hid->version = le16_to_cpu(ihid->hdesc.bcdVersion); + hid->vendor = le16_to_cpu(ihid->hdesc.wVendorID); + hid->product = le16_to_cpu(ihid->hdesc.wProductID); + + hid->initial_quirks = quirks; + hid->initial_quirks |= i2c_hid_get_dmi_quirks(hid->vendor, + hid->product); + + snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X", + client->name, (u16)hid->vendor, (u16)hid->product); + strscpy(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) + hid_err(client, "can't add hid device: %d\n", ret); + goto err_mem_free; + } + + return 0; + +err_mem_free: + hid_destroy_device(hid); + +err_irq: + free_irq(client->irq, ihid); + +err_powered: + i2c_hid_core_power_down(ihid); + i2c_hid_free_buffers(ihid); + return ret; +} +EXPORT_SYMBOL_GPL(i2c_hid_core_probe); + +void i2c_hid_core_remove(struct i2c_client *client) +{ + struct i2c_hid *ihid = i2c_get_clientdata(client); + struct hid_device *hid; + + hid = ihid->hid; + hid_destroy_device(hid); + + free_irq(client->irq, ihid); + + if (ihid->bufsize) + i2c_hid_free_buffers(ihid); + + i2c_hid_core_power_down(ihid); +} +EXPORT_SYMBOL_GPL(i2c_hid_core_remove); + +void i2c_hid_core_shutdown(struct i2c_client *client) +{ + struct i2c_hid *ihid = i2c_get_clientdata(client); + + i2c_hid_set_power(ihid, I2C_HID_PWR_SLEEP); + free_irq(client->irq, ihid); + + i2c_hid_core_shutdown_tail(ihid); +} +EXPORT_SYMBOL_GPL(i2c_hid_core_shutdown); + +#ifdef CONFIG_PM_SLEEP +static int i2c_hid_core_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct i2c_hid *ihid = i2c_get_clientdata(client); + struct hid_device *hid = ihid->hid; + int ret; + int wake_status; + + ret = hid_driver_suspend(hid, PMSG_SUSPEND); + if (ret < 0) + return ret; + + /* Save some power */ + i2c_hid_set_power(ihid, I2C_HID_PWR_SLEEP); + + disable_irq(client->irq); + + if (device_may_wakeup(&client->dev)) { + wake_status = enable_irq_wake(client->irq); + if (!wake_status) + ihid->irq_wake_enabled = true; + else + hid_warn(hid, "Failed to enable irq wake: %d\n", + wake_status); + } else { + i2c_hid_core_power_down(ihid); + } + + return 0; +} + +static int i2c_hid_core_resume(struct device *dev) +{ + int ret; + struct i2c_client *client = to_i2c_client(dev); + struct i2c_hid *ihid = i2c_get_clientdata(client); + struct hid_device *hid = ihid->hid; + int wake_status; + + if (!device_may_wakeup(&client->dev)) { + i2c_hid_core_power_up(ihid); + } else if (ihid->irq_wake_enabled) { + wake_status = disable_irq_wake(client->irq); + if (!wake_status) + ihid->irq_wake_enabled = false; + else + hid_warn(hid, "Failed to disable irq wake: %d\n", + wake_status); + } + + enable_irq(client->irq); + + /* Instead of resetting device, simply powers the device on. This + * solves "incomplete reports" on Raydium devices 2386:3118 and + * 2386:4B33 and fixes various SIS touchscreens no longer sending + * data after a suspend/resume. + * + * 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 + ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON); + + if (ret) + return ret; + + return hid_driver_reset_resume(hid); +} +#endif + +const struct dev_pm_ops i2c_hid_core_pm = { + SET_SYSTEM_SLEEP_PM_OPS(i2c_hid_core_suspend, i2c_hid_core_resume) +}; +EXPORT_SYMBOL_GPL(i2c_hid_core_pm); + +MODULE_DESCRIPTION("HID over I2C core driver"); +MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c b/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c new file mode 100644 index 000000000..210f17c3a --- /dev/null +++ b/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c @@ -0,0 +1,494 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* + * Quirks for I2C-HID devices that do not supply proper descriptors + * + * Copyright (c) 2018 Julian Sax <jsbc@gmx.de> + * + */ + +#include <linux/types.h> +#include <linux/dmi.h> +#include <linux/mod_devicetable.h> +#include <linux/hid.h> + +#include "i2c-hid.h" +#include "../hid-ids.h" + + +struct i2c_hid_desc_override { + union { + struct i2c_hid_desc *i2c_hid_desc; + uint8_t *i2c_hid_desc_buffer; + }; + uint8_t *hid_report_desc; + unsigned int hid_report_desc_size; + uint8_t *i2c_name; +}; + + +/* + * descriptors for the SIPODEV SP1064 touchpad + * + * This device does not supply any descriptors and on windows a filter + * driver operates between the i2c-hid layer and the device and injects + * these descriptors when the device is prompted. The descriptors were + * extracted by listening to the i2c-hid traffic that occurs between the + * windows filter driver and the windows i2c-hid driver. + */ + +static const struct i2c_hid_desc_override sipodev_desc = { + .i2c_hid_desc_buffer = (uint8_t []) + {0x1e, 0x00, /* Length of descriptor */ + 0x00, 0x01, /* Version of descriptor */ + 0xdb, 0x01, /* Length of report descriptor */ + 0x21, 0x00, /* Location of report descriptor */ + 0x24, 0x00, /* Location of input report */ + 0x1b, 0x00, /* Max input report length */ + 0x25, 0x00, /* Location of output report */ + 0x11, 0x00, /* Max output report length */ + 0x22, 0x00, /* Location of command register */ + 0x23, 0x00, /* Location of data register */ + 0x11, 0x09, /* Vendor ID */ + 0x88, 0x52, /* Product ID */ + 0x06, 0x00, /* Version ID */ + 0x00, 0x00, 0x00, 0x00 /* Reserved */ + }, + + .hid_report_desc = (uint8_t []) + {0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x02, /* Usage (Mouse), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, 0x01, /* Report ID (1), */ + 0x09, 0x01, /* Usage (Pointer), */ + 0xA1, 0x00, /* Collection (Physical), */ + 0x05, 0x09, /* Usage Page (Button), */ + 0x19, 0x01, /* Usage Minimum (01h), */ + 0x29, 0x02, /* Usage Maximum (02h), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x02, /* Report Count (2), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x06, /* Report Count (6), */ + 0x81, 0x01, /* Input (Constant), */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x30, /* Usage (X), */ + 0x09, 0x31, /* Usage (Y), */ + 0x15, 0x81, /* Logical Minimum (-127), */ + 0x25, 0x7F, /* Logical Maximum (127), */ + 0x75, 0x08, /* Report Size (8), */ + 0x95, 0x02, /* Report Count (2), */ + 0x81, 0x06, /* Input (Variable, Relative), */ + 0xC0, /* End Collection, */ + 0xC0, /* End Collection, */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x09, 0x05, /* Usage (Touchpad), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, 0x04, /* Report ID (4), */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x09, 0x22, /* Usage (Finger), */ + 0xA1, 0x02, /* Collection (Logical), */ + 0x15, 0x00, /* Logical Minimum (0), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x09, 0x47, /* Usage (Touch Valid), */ + 0x09, 0x42, /* Usage (Tip Switch), */ + 0x95, 0x02, /* Report Count (2), */ + 0x75, 0x01, /* Report Size (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x01, /* Report Count (1), */ + 0x75, 0x03, /* Report Size (3), */ + 0x25, 0x05, /* Logical Maximum (5), */ + 0x09, 0x51, /* Usage (Contact Identifier), */ + 0x81, 0x02, /* Input (Variable), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x03, /* Report Count (3), */ + 0x81, 0x03, /* Input (Constant, Variable), */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x26, 0x44, 0x0A, /* Logical Maximum (2628), */ + 0x75, 0x10, /* Report Size (16), */ + 0x55, 0x0E, /* Unit Exponent (14), */ + 0x65, 0x11, /* Unit (Centimeter), */ + 0x09, 0x30, /* Usage (X), */ + 0x46, 0x1A, 0x04, /* Physical Maximum (1050), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x46, 0xBC, 0x02, /* Physical Maximum (700), */ + 0x26, 0x34, 0x05, /* Logical Maximum (1332), */ + 0x09, 0x31, /* Usage (Y), */ + 0x81, 0x02, /* Input (Variable), */ + 0xC0, /* End Collection, */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x09, 0x22, /* Usage (Finger), */ + 0xA1, 0x02, /* Collection (Logical), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x09, 0x47, /* Usage (Touch Valid), */ + 0x09, 0x42, /* Usage (Tip Switch), */ + 0x95, 0x02, /* Report Count (2), */ + 0x75, 0x01, /* Report Size (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x01, /* Report Count (1), */ + 0x75, 0x03, /* Report Size (3), */ + 0x25, 0x05, /* Logical Maximum (5), */ + 0x09, 0x51, /* Usage (Contact Identifier), */ + 0x81, 0x02, /* Input (Variable), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x03, /* Report Count (3), */ + 0x81, 0x03, /* Input (Constant, Variable), */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x26, 0x44, 0x0A, /* Logical Maximum (2628), */ + 0x75, 0x10, /* Report Size (16), */ + 0x09, 0x30, /* Usage (X), */ + 0x46, 0x1A, 0x04, /* Physical Maximum (1050), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x46, 0xBC, 0x02, /* Physical Maximum (700), */ + 0x26, 0x34, 0x05, /* Logical Maximum (1332), */ + 0x09, 0x31, /* Usage (Y), */ + 0x81, 0x02, /* Input (Variable), */ + 0xC0, /* End Collection, */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x09, 0x22, /* Usage (Finger), */ + 0xA1, 0x02, /* Collection (Logical), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x09, 0x47, /* Usage (Touch Valid), */ + 0x09, 0x42, /* Usage (Tip Switch), */ + 0x95, 0x02, /* Report Count (2), */ + 0x75, 0x01, /* Report Size (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x01, /* Report Count (1), */ + 0x75, 0x03, /* Report Size (3), */ + 0x25, 0x05, /* Logical Maximum (5), */ + 0x09, 0x51, /* Usage (Contact Identifier), */ + 0x81, 0x02, /* Input (Variable), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x03, /* Report Count (3), */ + 0x81, 0x03, /* Input (Constant, Variable), */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x26, 0x44, 0x0A, /* Logical Maximum (2628), */ + 0x75, 0x10, /* Report Size (16), */ + 0x09, 0x30, /* Usage (X), */ + 0x46, 0x1A, 0x04, /* Physical Maximum (1050), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x46, 0xBC, 0x02, /* Physical Maximum (700), */ + 0x26, 0x34, 0x05, /* Logical Maximum (1332), */ + 0x09, 0x31, /* Usage (Y), */ + 0x81, 0x02, /* Input (Variable), */ + 0xC0, /* End Collection, */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x09, 0x22, /* Usage (Finger), */ + 0xA1, 0x02, /* Collection (Logical), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x09, 0x47, /* Usage (Touch Valid), */ + 0x09, 0x42, /* Usage (Tip Switch), */ + 0x95, 0x02, /* Report Count (2), */ + 0x75, 0x01, /* Report Size (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x01, /* Report Count (1), */ + 0x75, 0x03, /* Report Size (3), */ + 0x25, 0x05, /* Logical Maximum (5), */ + 0x09, 0x51, /* Usage (Contact Identifier), */ + 0x81, 0x02, /* Input (Variable), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x03, /* Report Count (3), */ + 0x81, 0x03, /* Input (Constant, Variable), */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x26, 0x44, 0x0A, /* Logical Maximum (2628), */ + 0x75, 0x10, /* Report Size (16), */ + 0x09, 0x30, /* Usage (X), */ + 0x46, 0x1A, 0x04, /* Physical Maximum (1050), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x46, 0xBC, 0x02, /* Physical Maximum (700), */ + 0x26, 0x34, 0x05, /* Logical Maximum (1332), */ + 0x09, 0x31, /* Usage (Y), */ + 0x81, 0x02, /* Input (Variable), */ + 0xC0, /* End Collection, */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x55, 0x0C, /* Unit Exponent (12), */ + 0x66, 0x01, 0x10, /* Unit (Seconds), */ + 0x47, 0xFF, 0xFF, 0x00, 0x00,/* Physical Maximum (65535), */ + 0x27, 0xFF, 0xFF, 0x00, 0x00,/* Logical Maximum (65535), */ + 0x75, 0x10, /* Report Size (16), */ + 0x95, 0x01, /* Report Count (1), */ + 0x09, 0x56, /* Usage (Scan Time), */ + 0x81, 0x02, /* Input (Variable), */ + 0x09, 0x54, /* Usage (Contact Count), */ + 0x25, 0x7F, /* Logical Maximum (127), */ + 0x75, 0x08, /* Report Size (8), */ + 0x81, 0x02, /* Input (Variable), */ + 0x05, 0x09, /* Usage Page (Button), */ + 0x09, 0x01, /* Usage (01h), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x07, /* Report Count (7), */ + 0x81, 0x03, /* Input (Constant, Variable), */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x85, 0x02, /* Report ID (2), */ + 0x09, 0x55, /* Usage (Contact Count Maximum), */ + 0x09, 0x59, /* Usage (59h), */ + 0x75, 0x04, /* Report Size (4), */ + 0x95, 0x02, /* Report Count (2), */ + 0x25, 0x0F, /* Logical Maximum (15), */ + 0xB1, 0x02, /* Feature (Variable), */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x85, 0x07, /* Report ID (7), */ + 0x09, 0x60, /* Usage (60h), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x01, /* Report Count (1), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0xB1, 0x02, /* Feature (Variable), */ + 0x95, 0x07, /* Report Count (7), */ + 0xB1, 0x03, /* Feature (Constant, Variable), */ + 0x85, 0x06, /* Report ID (6), */ + 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ + 0x09, 0xC5, /* Usage (C5h), */ + 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ + 0x75, 0x08, /* Report Size (8), */ + 0x96, 0x00, 0x01, /* Report Count (256), */ + 0xB1, 0x02, /* Feature (Variable), */ + 0xC0, /* End Collection, */ + 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ + 0x09, 0x01, /* Usage (01h), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, 0x0D, /* Report ID (13), */ + 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ + 0x19, 0x01, /* Usage Minimum (01h), */ + 0x29, 0x02, /* Usage Maximum (02h), */ + 0x75, 0x08, /* Report Size (8), */ + 0x95, 0x02, /* Report Count (2), */ + 0xB1, 0x02, /* Feature (Variable), */ + 0xC0, /* End Collection, */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x09, 0x0E, /* Usage (Configuration), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, 0x03, /* Report ID (3), */ + 0x09, 0x22, /* Usage (Finger), */ + 0xA1, 0x02, /* Collection (Logical), */ + 0x09, 0x52, /* Usage (Device Mode), */ + 0x25, 0x0A, /* Logical Maximum (10), */ + 0x95, 0x01, /* Report Count (1), */ + 0xB1, 0x02, /* Feature (Variable), */ + 0xC0, /* End Collection, */ + 0x09, 0x22, /* Usage (Finger), */ + 0xA1, 0x00, /* Collection (Physical), */ + 0x85, 0x05, /* Report ID (5), */ + 0x09, 0x57, /* Usage (57h), */ + 0x09, 0x58, /* Usage (58h), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x02, /* Report Count (2), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0xB1, 0x02, /* Feature (Variable), */ + 0x95, 0x06, /* Report Count (6), */ + 0xB1, 0x03, /* Feature (Constant, Variable),*/ + 0xC0, /* End Collection, */ + 0xC0 /* End Collection */ + }, + .hid_report_desc_size = 475, + .i2c_name = "SYNA3602:00" +}; + + +static const struct dmi_system_id i2c_hid_dmi_desc_override_table[] = { + { + .ident = "Teclast F6 Pro", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TECLAST"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "F6 Pro"), + }, + .driver_data = (void *)&sipodev_desc + }, + { + .ident = "Teclast F7", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TECLAST"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "F7"), + }, + .driver_data = (void *)&sipodev_desc + }, + { + .ident = "Trekstor Primebook C13", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TREKSTOR"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Primebook C13"), + }, + .driver_data = (void *)&sipodev_desc + }, + { + .ident = "Trekstor Primebook C11", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TREKSTOR"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Primebook C11"), + }, + .driver_data = (void *)&sipodev_desc + }, + { + /* + * There are at least 2 Primebook C11B versions, the older + * version has a product-name of "Primebook C11B", and a + * bios version / release / firmware revision of: + * V2.1.2 / 05/03/2018 / 18.2 + * The new version has "PRIMEBOOK C11B" as product-name and a + * bios version / release / firmware revision of: + * CFALKSW05_BIOS_V1.1.2 / 11/19/2018 / 19.2 + * Only the older version needs this quirk, note the newer + * version will not match as it has a different product-name. + */ + .ident = "Trekstor Primebook C11B", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TREKSTOR"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Primebook C11B"), + }, + .driver_data = (void *)&sipodev_desc + }, + { + .ident = "Trekstor SURFBOOK E11B", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TREKSTOR"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "SURFBOOK E11B"), + }, + .driver_data = (void *)&sipodev_desc + }, + { + .ident = "Direkt-Tek DTLAPY116-2", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Direkt-Tek"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "DTLAPY116-2"), + }, + .driver_data = (void *)&sipodev_desc + }, + { + .ident = "Direkt-Tek DTLAPY133-1", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Direkt-Tek"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "DTLAPY133-1"), + }, + .driver_data = (void *)&sipodev_desc + }, + { + .ident = "Mediacom Flexbook Edge 11", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "MEDIACOM"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "FlexBook edge11 - M-FBE11"), + }, + .driver_data = (void *)&sipodev_desc + }, + { + .ident = "Mediacom FlexBook edge 13", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "MEDIACOM"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "FlexBook_edge13-M-FBE13"), + }, + .driver_data = (void *)&sipodev_desc + }, + { + .ident = "Odys Winbook 13", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "AXDIA International GmbH"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "WINBOOK 13"), + }, + .driver_data = (void *)&sipodev_desc + }, + { + .ident = "iBall Aer3", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "iBall"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Aer3"), + }, + .driver_data = (void *)&sipodev_desc + }, + { + .ident = "Schneider SCL142ALM", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "SCHNEIDER"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "SCL142ALM"), + }, + .driver_data = (void *)&sipodev_desc + }, + { + .ident = "Vero K147", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "VERO"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "K147"), + }, + .driver_data = (void *)&sipodev_desc + }, + { } /* Terminate list */ +}; + +static const struct hid_device_id i2c_hid_elan_flipped_quirks = { + HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8, USB_VENDOR_ID_ELAN, 0x2dcd), + HID_QUIRK_X_INVERT | HID_QUIRK_Y_INVERT +}; + +/* + * This list contains devices which have specific issues based on the system + * they're on and not just the device itself. The driver_data will have a + * specific hid device to match against. + */ +static const struct dmi_system_id i2c_hid_dmi_quirk_table[] = { + { + .ident = "DynaBook K50/FR", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dynabook Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "dynabook K50/FR"), + }, + .driver_data = (void *)&i2c_hid_elan_flipped_quirks, + }, + { } /* Terminate list */ +}; + + +struct i2c_hid_desc *i2c_hid_get_dmi_i2c_hid_desc_override(uint8_t *i2c_name) +{ + struct i2c_hid_desc_override *override; + const struct dmi_system_id *system_id; + + system_id = dmi_first_match(i2c_hid_dmi_desc_override_table); + if (!system_id) + return NULL; + + override = system_id->driver_data; + if (strcmp(override->i2c_name, i2c_name)) + return NULL; + + return override->i2c_hid_desc; +} + +char *i2c_hid_get_dmi_hid_report_desc_override(uint8_t *i2c_name, + unsigned int *size) +{ + struct i2c_hid_desc_override *override; + const struct dmi_system_id *system_id; + + system_id = dmi_first_match(i2c_hid_dmi_desc_override_table); + if (!system_id) + return NULL; + + override = system_id->driver_data; + if (strcmp(override->i2c_name, i2c_name)) + return NULL; + + *size = override->hid_report_desc_size; + return override->hid_report_desc; +} + +u32 i2c_hid_get_dmi_quirks(const u16 vendor, const u16 product) +{ + u32 quirks = 0; + const struct dmi_system_id *system_id = + dmi_first_match(i2c_hid_dmi_quirk_table); + + if (system_id) { + const struct hid_device_id *device_id = + (struct hid_device_id *)(system_id->driver_data); + + if (device_id && device_id->vendor == vendor && + device_id->product == product) + quirks = device_id->driver_data; + } + + return quirks; +} diff --git a/drivers/hid/i2c-hid/i2c-hid-of-elan.c b/drivers/hid/i2c-hid/i2c-hid-of-elan.c new file mode 100644 index 000000000..2d991325e --- /dev/null +++ b/drivers/hid/i2c-hid/i2c-hid-of-elan.c @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Elan touchscreens that use the i2c-hid protocol. + * + * Copyright 2020 Google LLC + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/pm.h> +#include <linux/regulator/consumer.h> + +#include "i2c-hid.h" + +struct elan_i2c_hid_chip_data { + unsigned int post_gpio_reset_delay_ms; + unsigned int post_power_delay_ms; + u16 hid_descriptor_address; +}; + +struct i2c_hid_of_elan { + struct i2chid_ops ops; + + struct regulator *vcc33; + struct regulator *vccio; + struct gpio_desc *reset_gpio; + const struct elan_i2c_hid_chip_data *chip_data; +}; + +static int elan_i2c_hid_power_up(struct i2chid_ops *ops) +{ + struct i2c_hid_of_elan *ihid_elan = + container_of(ops, struct i2c_hid_of_elan, ops); + int ret; + + ret = regulator_enable(ihid_elan->vcc33); + if (ret) + return ret; + + ret = regulator_enable(ihid_elan->vccio); + if (ret) { + regulator_disable(ihid_elan->vcc33); + return ret; + } + + if (ihid_elan->chip_data->post_power_delay_ms) + msleep(ihid_elan->chip_data->post_power_delay_ms); + + gpiod_set_value_cansleep(ihid_elan->reset_gpio, 0); + if (ihid_elan->chip_data->post_gpio_reset_delay_ms) + msleep(ihid_elan->chip_data->post_gpio_reset_delay_ms); + + return 0; +} + +static void elan_i2c_hid_power_down(struct i2chid_ops *ops) +{ + struct i2c_hid_of_elan *ihid_elan = + container_of(ops, struct i2c_hid_of_elan, ops); + + gpiod_set_value_cansleep(ihid_elan->reset_gpio, 1); + regulator_disable(ihid_elan->vccio); + regulator_disable(ihid_elan->vcc33); +} + +static int i2c_hid_of_elan_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_hid_of_elan *ihid_elan; + + ihid_elan = devm_kzalloc(&client->dev, sizeof(*ihid_elan), GFP_KERNEL); + if (!ihid_elan) + return -ENOMEM; + + ihid_elan->ops.power_up = elan_i2c_hid_power_up; + ihid_elan->ops.power_down = elan_i2c_hid_power_down; + + /* Start out with reset asserted */ + ihid_elan->reset_gpio = + devm_gpiod_get_optional(&client->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ihid_elan->reset_gpio)) + return PTR_ERR(ihid_elan->reset_gpio); + + ihid_elan->vccio = devm_regulator_get(&client->dev, "vccio"); + if (IS_ERR(ihid_elan->vccio)) + return PTR_ERR(ihid_elan->vccio); + + ihid_elan->vcc33 = devm_regulator_get(&client->dev, "vcc33"); + if (IS_ERR(ihid_elan->vcc33)) + return PTR_ERR(ihid_elan->vcc33); + + ihid_elan->chip_data = device_get_match_data(&client->dev); + + return i2c_hid_core_probe(client, &ihid_elan->ops, + ihid_elan->chip_data->hid_descriptor_address, 0); +} + +static const struct elan_i2c_hid_chip_data elan_ekth6915_chip_data = { + .post_power_delay_ms = 1, + .post_gpio_reset_delay_ms = 300, + .hid_descriptor_address = 0x0001, +}; + +static const struct of_device_id elan_i2c_hid_of_match[] = { + { .compatible = "elan,ekth6915", .data = &elan_ekth6915_chip_data }, + { } +}; +MODULE_DEVICE_TABLE(of, elan_i2c_hid_of_match); + +static struct i2c_driver elan_i2c_hid_ts_driver = { + .driver = { + .name = "i2c_hid_of_elan", + .pm = &i2c_hid_core_pm, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .of_match_table = of_match_ptr(elan_i2c_hid_of_match), + }, + .probe = i2c_hid_of_elan_probe, + .remove = i2c_hid_core_remove, + .shutdown = i2c_hid_core_shutdown, +}; +module_i2c_driver(elan_i2c_hid_ts_driver); + +MODULE_AUTHOR("Douglas Anderson <dianders@chromium.org>"); +MODULE_DESCRIPTION("Elan i2c-hid touchscreen driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/i2c-hid/i2c-hid-of-goodix.c b/drivers/hid/i2c-hid/i2c-hid-of-goodix.c new file mode 100644 index 000000000..ec6c73f75 --- /dev/null +++ b/drivers/hid/i2c-hid/i2c-hid-of-goodix.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Goodix touchscreens that use the i2c-hid protocol. + * + * Copyright 2020 Google LLC + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/pm.h> +#include <linux/regulator/consumer.h> + +#include "i2c-hid.h" + +struct goodix_i2c_hid_timing_data { + unsigned int post_gpio_reset_delay_ms; + unsigned int post_power_delay_ms; +}; + +struct i2c_hid_of_goodix { + struct i2chid_ops ops; + + struct regulator *vdd; + struct notifier_block nb; + struct gpio_desc *reset_gpio; + const struct goodix_i2c_hid_timing_data *timings; +}; + +static void goodix_i2c_hid_deassert_reset(struct i2c_hid_of_goodix *ihid_goodix, + bool regulator_just_turned_on) +{ + if (regulator_just_turned_on && ihid_goodix->timings->post_power_delay_ms) + msleep(ihid_goodix->timings->post_power_delay_ms); + + gpiod_set_value_cansleep(ihid_goodix->reset_gpio, 0); + if (ihid_goodix->timings->post_gpio_reset_delay_ms) + msleep(ihid_goodix->timings->post_gpio_reset_delay_ms); +} + +static int goodix_i2c_hid_power_up(struct i2chid_ops *ops) +{ + struct i2c_hid_of_goodix *ihid_goodix = + container_of(ops, struct i2c_hid_of_goodix, ops); + + return regulator_enable(ihid_goodix->vdd); +} + +static void goodix_i2c_hid_power_down(struct i2chid_ops *ops) +{ + struct i2c_hid_of_goodix *ihid_goodix = + container_of(ops, struct i2c_hid_of_goodix, ops); + + regulator_disable(ihid_goodix->vdd); +} + +static int ihid_goodix_vdd_notify(struct notifier_block *nb, + unsigned long event, + void *ignored) +{ + struct i2c_hid_of_goodix *ihid_goodix = + container_of(nb, struct i2c_hid_of_goodix, nb); + int ret = NOTIFY_OK; + + switch (event) { + case REGULATOR_EVENT_PRE_DISABLE: + gpiod_set_value_cansleep(ihid_goodix->reset_gpio, 1); + break; + + case REGULATOR_EVENT_ENABLE: + goodix_i2c_hid_deassert_reset(ihid_goodix, true); + break; + + case REGULATOR_EVENT_ABORT_DISABLE: + goodix_i2c_hid_deassert_reset(ihid_goodix, false); + break; + + default: + ret = NOTIFY_DONE; + break; + } + + return ret; +} + +static int i2c_hid_of_goodix_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_hid_of_goodix *ihid_goodix; + int ret; + ihid_goodix = devm_kzalloc(&client->dev, sizeof(*ihid_goodix), + GFP_KERNEL); + if (!ihid_goodix) + return -ENOMEM; + + ihid_goodix->ops.power_up = goodix_i2c_hid_power_up; + ihid_goodix->ops.power_down = goodix_i2c_hid_power_down; + + /* Start out with reset asserted */ + ihid_goodix->reset_gpio = + devm_gpiod_get_optional(&client->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ihid_goodix->reset_gpio)) + return PTR_ERR(ihid_goodix->reset_gpio); + + ihid_goodix->vdd = devm_regulator_get(&client->dev, "vdd"); + if (IS_ERR(ihid_goodix->vdd)) + return PTR_ERR(ihid_goodix->vdd); + + ihid_goodix->timings = device_get_match_data(&client->dev); + + /* + * We need to control the "reset" line in lockstep with the regulator + * actually turning on an off instead of just when we make the request. + * This matters if the regulator is shared with another consumer. + * - If the regulator is off then we must assert reset. The reset + * line is active low and on some boards it could cause a current + * leak if left high. + * - If the regulator is on then we don't want reset asserted for very + * long. Holding the controller in reset apparently draws extra + * power. + */ + ihid_goodix->nb.notifier_call = ihid_goodix_vdd_notify; + ret = devm_regulator_register_notifier(ihid_goodix->vdd, &ihid_goodix->nb); + if (ret) + return dev_err_probe(&client->dev, ret, + "regulator notifier request failed\n"); + + /* + * If someone else is holding the regulator on (or the regulator is + * an always-on one) we might never be told to deassert reset. Do it + * now... and temporarily bump the regulator reference count just to + * make sure it is impossible for this to race with our own notifier! + * We also assume that someone else might have _just barely_ turned + * the regulator on so we'll do the full "post_power_delay" just in + * case. + */ + if (ihid_goodix->reset_gpio && regulator_is_enabled(ihid_goodix->vdd)) { + ret = regulator_enable(ihid_goodix->vdd); + if (ret) + return ret; + goodix_i2c_hid_deassert_reset(ihid_goodix, true); + regulator_disable(ihid_goodix->vdd); + } + + return i2c_hid_core_probe(client, &ihid_goodix->ops, 0x0001, 0); +} + +static const struct goodix_i2c_hid_timing_data goodix_gt7375p_timing_data = { + .post_power_delay_ms = 10, + .post_gpio_reset_delay_ms = 180, +}; + +static const struct of_device_id goodix_i2c_hid_of_match[] = { + { .compatible = "goodix,gt7375p", .data = &goodix_gt7375p_timing_data }, + { } +}; +MODULE_DEVICE_TABLE(of, goodix_i2c_hid_of_match); + +static struct i2c_driver goodix_i2c_hid_ts_driver = { + .driver = { + .name = "i2c_hid_of_goodix", + .pm = &i2c_hid_core_pm, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .of_match_table = of_match_ptr(goodix_i2c_hid_of_match), + }, + .probe = i2c_hid_of_goodix_probe, + .remove = i2c_hid_core_remove, + .shutdown = i2c_hid_core_shutdown, +}; +module_i2c_driver(goodix_i2c_hid_ts_driver); + +MODULE_AUTHOR("Douglas Anderson <dianders@chromium.org>"); +MODULE_DESCRIPTION("Goodix i2c-hid touchscreen driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hid/i2c-hid/i2c-hid-of.c b/drivers/hid/i2c-hid/i2c-hid-of.c new file mode 100644 index 000000000..97a27a803 --- /dev/null +++ b/drivers/hid/i2c-hid/i2c-hid-of.c @@ -0,0 +1,151 @@ +/* + * HID over I2C Open Firmware Subclass + * + * Copyright (c) 2012 Benjamin Tissoires <benjamin.tissoires@gmail.com> + * Copyright (c) 2012 Ecole Nationale de l'Aviation Civile, France + * Copyright (c) 2012 Red Hat, Inc + * + * This code was forked out of the core code, which was partly based on + * "USB HID support for Linux": + * + * Copyright (c) 1999 Andreas Gal + * Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz> + * Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc + * Copyright (c) 2007-2008 Oliver Neukum + * Copyright (c) 2006-2010 Jiri Kosina + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/hid.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/pm.h> +#include <linux/regulator/consumer.h> + +#include "i2c-hid.h" + +struct i2c_hid_of { + struct i2chid_ops ops; + + struct i2c_client *client; + struct regulator_bulk_data supplies[2]; + int post_power_delay_ms; +}; + +static int i2c_hid_of_power_up(struct i2chid_ops *ops) +{ + struct i2c_hid_of *ihid_of = container_of(ops, struct i2c_hid_of, ops); + struct device *dev = &ihid_of->client->dev; + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(ihid_of->supplies), + ihid_of->supplies); + if (ret) { + dev_warn(dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + if (ihid_of->post_power_delay_ms) + msleep(ihid_of->post_power_delay_ms); + + return 0; +} + +static void i2c_hid_of_power_down(struct i2chid_ops *ops) +{ + struct i2c_hid_of *ihid_of = container_of(ops, struct i2c_hid_of, ops); + + regulator_bulk_disable(ARRAY_SIZE(ihid_of->supplies), + ihid_of->supplies); +} + +static int i2c_hid_of_probe(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + struct device *dev = &client->dev; + struct i2c_hid_of *ihid_of; + u16 hid_descriptor_address; + u32 quirks = 0; + int ret; + u32 val; + + ihid_of = devm_kzalloc(&client->dev, sizeof(*ihid_of), GFP_KERNEL); + if (!ihid_of) + return -ENOMEM; + + ihid_of->ops.power_up = i2c_hid_of_power_up; + ihid_of->ops.power_down = i2c_hid_of_power_down; + + ret = of_property_read_u32(dev->of_node, "hid-descr-addr", &val); + if (ret) { + dev_err(&client->dev, "HID register address not provided\n"); + return -ENODEV; + } + if (val >> 16) { + dev_err(&client->dev, "Bad HID register address: 0x%08x\n", + val); + return -EINVAL; + } + hid_descriptor_address = val; + + if (!device_property_read_u32(&client->dev, "post-power-on-delay-ms", + &val)) + ihid_of->post_power_delay_ms = val; + + ihid_of->supplies[0].supply = "vdd"; + ihid_of->supplies[1].supply = "vddl"; + ret = devm_regulator_bulk_get(&client->dev, + ARRAY_SIZE(ihid_of->supplies), + ihid_of->supplies); + if (ret) + return ret; + + if (device_property_read_bool(dev, "touchscreen-inverted-x")) + quirks |= HID_QUIRK_X_INVERT; + + if (device_property_read_bool(dev, "touchscreen-inverted-y")) + quirks |= HID_QUIRK_Y_INVERT; + + return i2c_hid_core_probe(client, &ihid_of->ops, + hid_descriptor_address, quirks); +} + +static const struct of_device_id i2c_hid_of_match[] = { + { .compatible = "hid-over-i2c" }, + {}, +}; +MODULE_DEVICE_TABLE(of, i2c_hid_of_match); + +static const struct i2c_device_id i2c_hid_of_id_table[] = { + { "hid", 0 }, + { "hid-over-i2c", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, i2c_hid_of_id_table); + +static struct i2c_driver i2c_hid_of_driver = { + .driver = { + .name = "i2c_hid_of", + .pm = &i2c_hid_core_pm, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .of_match_table = of_match_ptr(i2c_hid_of_match), + }, + + .probe = i2c_hid_of_probe, + .remove = i2c_hid_core_remove, + .shutdown = i2c_hid_core_shutdown, + .id_table = i2c_hid_of_id_table, +}; + +module_i2c_driver(i2c_hid_of_driver); + +MODULE_DESCRIPTION("HID over I2C OF driver"); +MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/i2c-hid/i2c-hid.h b/drivers/hid/i2c-hid/i2c-hid.h new file mode 100644 index 000000000..2c7b66d5c --- /dev/null +++ b/drivers/hid/i2c-hid/i2c-hid.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +#ifndef I2C_HID_H +#define I2C_HID_H + +#include <linux/i2c.h> + +#ifdef CONFIG_DMI +struct i2c_hid_desc *i2c_hid_get_dmi_i2c_hid_desc_override(uint8_t *i2c_name); +char *i2c_hid_get_dmi_hid_report_desc_override(uint8_t *i2c_name, + unsigned int *size); +u32 i2c_hid_get_dmi_quirks(const u16 vendor, const u16 product); +#else +static inline struct i2c_hid_desc + *i2c_hid_get_dmi_i2c_hid_desc_override(uint8_t *i2c_name) +{ return NULL; } +static inline char *i2c_hid_get_dmi_hid_report_desc_override(uint8_t *i2c_name, + unsigned int *size) +{ return NULL; } +static inline u32 i2c_hid_get_dmi_quirks(const u16 vendor, const u16 product) +{ return 0; } +#endif + +/** + * struct i2chid_ops - Ops provided to the core. + * + * @power_up: do sequencing to power up the device. + * @power_down: do sequencing to power down the device. + * @shutdown_tail: called at the end of shutdown. + */ +struct i2chid_ops { + int (*power_up)(struct i2chid_ops *ops); + void (*power_down)(struct i2chid_ops *ops); + void (*shutdown_tail)(struct i2chid_ops *ops); +}; + +int i2c_hid_core_probe(struct i2c_client *client, struct i2chid_ops *ops, + u16 hid_descriptor_address, u32 quirks); +void i2c_hid_core_remove(struct i2c_client *client); + +void i2c_hid_core_shutdown(struct i2c_client *client); + +extern const struct dev_pm_ops i2c_hid_core_pm; + +#endif |