From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from omzsmtpe01.verizonbusiness.com ([199.249.25.210]:63682 "EHLO omzsmtpe01.verizonbusiness.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754631AbdDDTdF (ORCPT ); Tue, 4 Apr 2017 15:33:05 -0400 From: alexander.levin@verizon.com To: "gregkh@linuxfoundation.org" CC: "stable@vger.kernel.org" Subject: [PATCH for 4.9 35/98] HID: asus: Add i2c touchpad support Date: Tue, 4 Apr 2017 19:32:15 +0000 Message-ID: <20170404193158.19041-36-alexander.levin@verizon.com> References: <20170404193158.19041-1-alexander.levin@verizon.com> In-Reply-To: <20170404193158.19041-1-alexander.levin@verizon.com> Content-Language: en-US Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 Sender: stable-owner@vger.kernel.org List-ID: From: Brendan McGrath [ Upstream commit 9ce12d8be12c94334634dd57050444910415e45f ] Update the hid-asus module to add multitouch support for the Asus i2c touch= pad. This patch aims to resolve the issue raised here: https://bugzilla.kernel.org/show_bug.cgi?id=3D120181 The issue is in relation to an Asus touchpad device which currently does no= t have multitouch support. The device currently falls through to the hid-generic driver which treats the device as a mouse. This patch aims to add the multitouch support. [jkosina@suse.cz: move most of the 'patch comment' into actual changelog] [jkosina@suse.cz: drop hunk that changes ->name of the driver] Reviewed-by: Benjamin Tissoires Signed-off-by: Brendan McGrath Signed-off-by: Victor Vlasenko Signed-off-by: Frederik Wenigwieser Signed-off-by: Jiri Kosina Signed-off-by: Sasha Levin --- drivers/hid/Kconfig | 2 +- drivers/hid/hid-asus.c | 299 +++++++++++++++++++++++++++++++++++++++++++++= +++- drivers/hid/hid-core.c | 1 + drivers/hid/hid-ids.h | 1 + 4 files changed, 296 insertions(+), 7 deletions(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index cd4599c..f8f251c2 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -138,7 +138,7 @@ config HID_ASUS tristate "Asus" depends on I2C_HID ---help--- - Support for Asus notebook built-in keyboard via i2c. + Support for Asus notebook built-in keyboard and touchpad via i2c. =20 Supported devices: - EeeBook X205TA diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c index 7a811ec..d40ed9f 100644 --- a/drivers/hid/hid-asus.c +++ b/drivers/hid/hid-asus.c @@ -11,6 +11,12 @@ * This module based on hid-ortek by * Copyright (c) 2010 Johnathon Harris * Copyright (c) 2011 Jiri Kosina + * + * This module has been updated to add support for Asus i2c touchpad. + * + * Copyright (c) 2016 Brendan McGrath + * Copyright (c) 2016 Victor Vlasenko + * Copyright (c) 2016 Frederik Wenigwieser */ =20 /* @@ -20,16 +26,287 @@ * any later version. */ =20 -#include #include #include +#include =20 #include "hid-ids.h" =20 +MODULE_AUTHOR("Yusuke Fujimaki "); +MODULE_AUTHOR("Brendan McGrath "); +MODULE_AUTHOR("Victor Vlasenko "); +MODULE_AUTHOR("Frederik Wenigwieser "); +MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); + +#define FEATURE_REPORT_ID 0x0d +#define INPUT_REPORT_ID 0x5d + +#define INPUT_REPORT_SIZE 28 + +#define MAX_CONTACTS 5 + +#define MAX_X 2794 +#define MAX_Y 1758 +#define MAX_TOUCH_MAJOR 8 +#define MAX_PRESSURE 128 + +#define CONTACT_DATA_SIZE 5 + +#define BTN_LEFT_MASK 0x01 +#define CONTACT_TOOL_TYPE_MASK 0x80 +#define CONTACT_X_MSB_MASK 0xf0 +#define CONTACT_Y_MSB_MASK 0x0f +#define CONTACT_TOUCH_MAJOR_MASK 0x07 +#define CONTACT_PRESSURE_MASK 0x7f + +#define QUIRK_FIX_NOTEBOOK_REPORT BIT(0) +#define QUIRK_NO_INIT_REPORTS BIT(1) +#define QUIRK_SKIP_INPUT_MAPPING BIT(2) +#define QUIRK_IS_MULTITOUCH BIT(3) + +#define NOTEBOOK_QUIRKS QUIRK_FIX_NOTEBOOK_REPORT +#define TOUCHPAD_QUIRKS (QUIRK_NO_INIT_REPORTS | \ + QUIRK_SKIP_INPUT_MAPPING | \ + QUIRK_IS_MULTITOUCH) + +#define TRKID_SGN ((TRKID_MAX + 1) >> 1) + +struct asus_drvdata { + unsigned long quirks; + struct input_dev *input; +}; + +static void asus_report_contact_down(struct input_dev *input, + int toolType, u8 *data) +{ + int touch_major, pressure; + int x =3D (data[0] & CONTACT_X_MSB_MASK) << 4 | data[1]; + int y =3D MAX_Y - ((data[0] & CONTACT_Y_MSB_MASK) << 8 | data[2]); + + if (toolType =3D=3D MT_TOOL_PALM) { + touch_major =3D MAX_TOUCH_MAJOR; + pressure =3D MAX_PRESSURE; + } else { + touch_major =3D (data[3] >> 4) & CONTACT_TOUCH_MAJOR_MASK; + pressure =3D data[4] & CONTACT_PRESSURE_MASK; + } + + input_report_abs(input, ABS_MT_POSITION_X, x); + input_report_abs(input, ABS_MT_POSITION_Y, y); + input_report_abs(input, ABS_MT_TOUCH_MAJOR, touch_major); + input_report_abs(input, ABS_MT_PRESSURE, pressure); +} + +/* Required for Synaptics Palm Detection */ +static void asus_report_tool_width(struct input_dev *input) +{ + struct input_mt *mt =3D input->mt; + struct input_mt_slot *oldest; + int oldid, count, i; + + oldest =3D NULL; + oldid =3D mt->trkid; + count =3D 0; + + for (i =3D 0; i < mt->num_slots; ++i) { + struct input_mt_slot *ps =3D &mt->slots[i]; + int id =3D input_mt_get_value(ps, ABS_MT_TRACKING_ID); + + if (id < 0) + continue; + if ((id - oldid) & TRKID_SGN) { + oldest =3D ps; + oldid =3D id; + } + count++; + } + + if (oldest) { + input_report_abs(input, ABS_TOOL_WIDTH, + input_mt_get_value(oldest, ABS_MT_TOUCH_MAJOR)); + } +} + +static void asus_report_input(struct input_dev *input, u8 *data) +{ + int i; + u8 *contactData =3D data + 2; + + for (i =3D 0; i < MAX_CONTACTS; i++) { + bool down =3D !!(data[1] & BIT(i+3)); + int toolType =3D contactData[3] & CONTACT_TOOL_TYPE_MASK ? + MT_TOOL_PALM : MT_TOOL_FINGER; + + input_mt_slot(input, i); + input_mt_report_slot_state(input, toolType, down); + + if (down) { + asus_report_contact_down(input, toolType, contactData); + contactData +=3D CONTACT_DATA_SIZE; + } + } + + input_report_key(input, BTN_LEFT, data[1] & BTN_LEFT_MASK); + asus_report_tool_width(input); + + input_mt_sync_frame(input); + input_sync(input); +} + +static int asus_raw_event(struct hid_device *hdev, + struct hid_report *report, u8 *data, int size) +{ + struct asus_drvdata *drvdata =3D hid_get_drvdata(hdev); + + if (drvdata->quirks & QUIRK_IS_MULTITOUCH && + data[0] =3D=3D INPUT_REPORT_ID && + size =3D=3D INPUT_REPORT_SIZE) { + asus_report_input(drvdata->input, data); + return 1; + } + + return 0; +} + +static int asus_input_configured(struct hid_device *hdev, struct hid_input= *hi) +{ + struct asus_drvdata *drvdata =3D hid_get_drvdata(hdev); + + if (drvdata->quirks & QUIRK_IS_MULTITOUCH) { + int ret; + struct input_dev *input =3D hi->input; + + input_set_abs_params(input, ABS_MT_POSITION_X, 0, MAX_X, 0, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, 0, MAX_Y, 0, 0); + input_set_abs_params(input, ABS_TOOL_WIDTH, 0, MAX_TOUCH_MAJOR, 0, 0); + input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, MAX_TOUCH_MAJOR, 0, 0= ); + input_set_abs_params(input, ABS_MT_PRESSURE, 0, MAX_PRESSURE, 0, 0); + + __set_bit(BTN_LEFT, input->keybit); + __set_bit(INPUT_PROP_BUTTONPAD, input->propbit); + + ret =3D input_mt_init_slots(input, MAX_CONTACTS, INPUT_MT_POINTER); + + if (ret) { + hid_err(hdev, "Asus input mt init slots failed: %d\n", ret); + return ret; + } + + drvdata->input =3D input; + } + + return 0; +} + +static int asus_input_mapping(struct hid_device *hdev, + struct hid_input *hi, struct hid_field *field, + struct hid_usage *usage, unsigned long **bit, + int *max) +{ + struct asus_drvdata *drvdata =3D hid_get_drvdata(hdev); + + if (drvdata->quirks & QUIRK_SKIP_INPUT_MAPPING) { + /* Don't map anything from the HID report. + * We do it all manually in asus_input_configured + */ + return -1; + } + + return 0; +} + +static int asus_start_multitouch(struct hid_device *hdev) +{ + int ret; + const unsigned char buf[] =3D { FEATURE_REPORT_ID, 0x00, 0x03, 0x01, 0x00= }; + unsigned char *dmabuf =3D kmemdup(buf, sizeof(buf), GFP_KERNEL); + + if (!dmabuf) { + ret =3D -ENOMEM; + hid_err(hdev, "Asus failed to alloc dma buf: %d\n", ret); + return ret; + } + + ret =3D hid_hw_raw_request(hdev, dmabuf[0], dmabuf, sizeof(buf), + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + + kfree(dmabuf); + + if (ret !=3D sizeof(buf)) { + hid_err(hdev, "Asus failed to start multitouch: %d\n", ret); + return ret; + } + + return 0; +} + +static int __maybe_unused asus_reset_resume(struct hid_device *hdev) +{ + struct asus_drvdata *drvdata =3D hid_get_drvdata(hdev); + + if (drvdata->quirks & QUIRK_IS_MULTITOUCH) + return asus_start_multitouch(hdev); + + return 0; +} + +static int asus_probe(struct hid_device *hdev, const struct hid_device_id = *id) +{ + int ret; + struct asus_drvdata *drvdata; + + drvdata =3D devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL); + if (drvdata =3D=3D NULL) { + hid_err(hdev, "Can't alloc Asus descriptor\n"); + return -ENOMEM; + } + + hid_set_drvdata(hdev, drvdata); + + drvdata->quirks =3D id->driver_data; + + if (drvdata->quirks & QUIRK_NO_INIT_REPORTS) + hdev->quirks |=3D HID_QUIRK_NO_INIT_REPORTS; + + ret =3D hid_parse(hdev); + if (ret) { + hid_err(hdev, "Asus hid parse failed: %d\n", ret); + return ret; + } + + ret =3D hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) { + hid_err(hdev, "Asus hw start failed: %d\n", ret); + return ret; + } + + if (!drvdata->input) { + hid_err(hdev, "Asus input not registered\n"); + ret =3D -ENOMEM; + goto err_stop_hw; + } + + drvdata->input->name =3D "Asus TouchPad"; + + if (drvdata->quirks & QUIRK_IS_MULTITOUCH) { + ret =3D asus_start_multitouch(hdev); + if (ret) + goto err_stop_hw; + } + + return 0; +err_stop_hw: + hid_hw_stop(hdev); + return ret; +} + static __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize) { - if (*rsize >=3D 56 && rdesc[54] =3D=3D 0x25 && rdesc[55] =3D=3D 0x65) { + struct asus_drvdata *drvdata =3D hid_get_drvdata(hdev); + + if (drvdata->quirks & QUIRK_FIX_NOTEBOOK_REPORT && + *rsize >=3D 56 && rdesc[54] =3D=3D 0x25 && rdesc[55] =3D=3D 0x65) { hid_info(hdev, "Fixing up Asus notebook report descriptor\n"); rdesc[55] =3D 0xdd; } @@ -37,15 +314,25 @@ static __u8 *asus_report_fixup(struct hid_device *hdev= , __u8 *rdesc, } =20 static const struct hid_device_id asus_devices[] =3D { - { HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_NOTEBOOK_KE= YBOARD) }, + { HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, + USB_DEVICE_ID_ASUSTEK_NOTEBOOK_KEYBOARD), NOTEBOOK_QUIRKS}, + { HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, + USB_DEVICE_ID_ASUSTEK_TOUCHPAD), TOUCHPAD_QUIRKS }, { } }; MODULE_DEVICE_TABLE(hid, asus_devices); =20 static struct hid_driver asus_driver =3D { - .name =3D "asus", - .id_table =3D asus_devices, - .report_fixup =3D asus_report_fixup + .name =3D "asus", + .id_table =3D asus_devices, + .report_fixup =3D asus_report_fixup, + .probe =3D asus_probe, + .input_mapping =3D asus_input_mapping, + .input_configured =3D asus_input_configured, +#ifdef CONFIG_PM + .reset_resume =3D asus_reset_resume, +#endif + .raw_event =3D asus_raw_event }; module_hid_driver(asus_driver); =20 diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index a5dd7e6..18981ee 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1856,6 +1856,7 @@ static const struct hid_device_id hid_have_special_dr= iver[] =3D { { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONL= Y) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY= ) }, { HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_NOTEBOOK_KE= YBOARD) }, + { HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_TOUCHPAD) }= , { HID_USB_DEVICE(USB_VENDOR_ID_AUREAL, USB_DEVICE_ID_AUREAL_W01RN) }, { HID_USB_DEVICE(USB_VENDOR_ID_BELKIN, USB_DEVICE_ID_FLIP_KVM) }, { HID_USB_DEVICE(USB_VENDOR_ID_BETOP_2185BFM, 0x2208) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index dd6377e..af8e2cd 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -174,6 +174,7 @@ #define USB_DEVICE_ID_ASUSTEK_LCM 0x1726 #define USB_DEVICE_ID_ASUSTEK_LCM2 0x175b #define USB_DEVICE_ID_ASUSTEK_NOTEBOOK_KEYBOARD 0x8585 +#define USB_DEVICE_ID_ASUSTEK_TOUCHPAD 0x0101 =20 #define USB_VENDOR_ID_ATEN 0x0557 #define USB_DEVICE_ID_ATEN_UC100KM 0x2004 --=20 2.9.3