summaryrefslogtreecommitdiffstats
path: root/drivers/hid
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hid')
-rw-r--r--drivers/hid/Kconfig14
-rw-r--r--drivers/hid/Makefile5
-rw-r--r--drivers/hid/hid-asus.c275
-rw-r--r--drivers/hid/hid-core.c185
-rw-r--r--drivers/hid/hid-cougar.c2
-rw-r--r--drivers/hid/hid-cp2112.c2
-rw-r--r--drivers/hid/hid-debug.c137
-rw-r--r--drivers/hid/hid-elan.c2
-rw-r--r--drivers/hid/hid-ids.h27
-rw-r--r--drivers/hid/hid-input.c112
-rw-r--r--drivers/hid/hid-kye.c83
-rw-r--r--drivers/hid/hid-lenovo.c10
-rw-r--r--drivers/hid/hid-lg.c60
-rw-r--r--drivers/hid/hid-lg4ff.c6
-rw-r--r--drivers/hid/hid-logitech-hidpp.c384
-rw-r--r--drivers/hid/hid-maltron.c165
-rw-r--r--drivers/hid/hid-multitouch.c6
-rw-r--r--drivers/hid/hid-quirks.c44
-rw-r--r--drivers/hid/hid-roccat-kone.c1
-rw-r--r--drivers/hid/hid-sony.c31
-rw-r--r--drivers/hid/hid-steam.c60
-rw-r--r--drivers/hid/hid-topseed.c3
-rw-r--r--drivers/hid/hid-uclogic-core.c418
-rw-r--r--drivers/hid/hid-uclogic-params.c1118
-rw-r--r--drivers/hid/hid-uclogic-params.h207
-rw-r--r--drivers/hid/hid-uclogic-rdesc.c (renamed from drivers/hid/hid-uclogic.c)822
-rw-r--r--drivers/hid/hid-uclogic-rdesc.h155
-rw-r--r--drivers/hid/hid-viewsonic.c105
-rw-r--r--drivers/hid/hidraw.c8
-rw-r--r--drivers/hid/i2c-hid/i2c-hid-core.c13
-rw-r--r--drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c8
-rw-r--r--drivers/hid/intel-ish-hid/ipc/ipc.c32
-rw-r--r--drivers/hid/intel-ish-hid/ipc/pci-ish.c7
-rw-r--r--drivers/hid/intel-ish-hid/ishtp-hid-client.c4
-rw-r--r--drivers/hid/intel-ish-hid/ishtp-hid.c6
-rw-r--r--drivers/hid/intel-ish-hid/ishtp-hid.h6
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/bus.c27
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/bus.h6
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/client.h2
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/hbm.c97
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/hbm.h2
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h2
-rw-r--r--drivers/hid/wacom_sys.c32
-rw-r--r--drivers/hid/wacom_wac.c24
44 files changed, 3828 insertions, 887 deletions
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 41e9935fc584..4ca0cdfa6b33 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -150,6 +150,7 @@ config HID_ASUS
tristate "Asus"
depends on LEDS_CLASS
depends on ASUS_WMI || ASUS_WMI=n
+ select POWER_SUPPLY
---help---
Support for Asus notebook built-in keyboard and touchpad via i2c, and
the Asus Republic of Gamers laptop keyboard special keys.
@@ -412,6 +413,12 @@ config HID_WALTOP
---help---
Support for Waltop tablets.
+config HID_VIEWSONIC
+ tristate "ViewSonic/Signotec"
+ depends on HID
+ help
+ Support for ViewSonic/Signotec PD1011 signature pad.
+
config HID_GYRATION
tristate "Gyration remote control"
depends on HID
@@ -590,6 +597,13 @@ config HID_MAGICMOUSE
Say Y here if you want support for the multi-touch features of the
Apple Wireless "Magic" Mouse and the Apple Wireless "Magic" Trackpad.
+config HID_MALTRON
+ tristate "Maltron L90 keyboard"
+ depends on HID
+ ---help---
+ Adds support for the volume up, volume down, mute, and play/pause buttons
+ of the Maltron L90 keyboard.
+
config HID_MAYFLASH
tristate "Mayflash game controller adapter force feedback"
depends on HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 896a51ce7ce0..170163b41303 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -66,6 +66,7 @@ obj-$(CONFIG_HID_LOGITECH) += hid-logitech.o
obj-$(CONFIG_HID_LOGITECH_DJ) += hid-logitech-dj.o
obj-$(CONFIG_HID_LOGITECH_HIDPP) += hid-logitech-hidpp.o
obj-$(CONFIG_HID_MAGICMOUSE) += hid-magicmouse.o
+obj-$(CONFIG_HID_MALTRON) += hid-maltron.o
obj-$(CONFIG_HID_MAYFLASH) += hid-mf.o
obj-$(CONFIG_HID_MICROSOFT) += hid-microsoft.o
obj-$(CONFIG_HID_MONTEREY) += hid-monterey.o
@@ -108,12 +109,16 @@ obj-$(CONFIG_HID_THRUSTMASTER) += hid-tmff.o
obj-$(CONFIG_HID_TIVO) += hid-tivo.o
obj-$(CONFIG_HID_TOPSEED) += hid-topseed.o
obj-$(CONFIG_HID_TWINHAN) += hid-twinhan.o
+hid-uclogic-objs := hid-uclogic-core.o \
+ hid-uclogic-rdesc.o \
+ hid-uclogic-params.o
obj-$(CONFIG_HID_UCLOGIC) += hid-uclogic.o
obj-$(CONFIG_HID_UDRAW_PS3) += hid-udraw-ps3.o
obj-$(CONFIG_HID_LED) += hid-led.o
obj-$(CONFIG_HID_XINMO) += hid-xinmo.o
obj-$(CONFIG_HID_ZEROPLUS) += hid-zpff.o
obj-$(CONFIG_HID_ZYDACRON) += hid-zydacron.o
+obj-$(CONFIG_HID_VIEWSONIC) += hid-viewsonic.o
wacom-objs := wacom_wac.o wacom_sys.o
obj-$(CONFIG_HID_WACOM) += wacom.o
diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index a1fa2fc8c9b5..336aeaed1159 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -32,6 +32,7 @@
#include <linux/platform_data/x86/asus-wmi.h>
#include <linux/input/mt.h>
#include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
+#include <linux/power_supply.h>
#include "hid-ids.h"
@@ -61,6 +62,13 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
#define CONTACT_TOUCH_MAJOR_MASK 0x07
#define CONTACT_PRESSURE_MASK 0x7f
+#define BATTERY_REPORT_ID (0x03)
+#define BATTERY_REPORT_SIZE (1 + 8)
+#define BATTERY_LEVEL_MAX ((u8)255)
+#define BATTERY_STAT_DISCONNECT (0)
+#define BATTERY_STAT_CHARGING (1)
+#define BATTERY_STAT_FULL (2)
+
#define QUIRK_FIX_NOTEBOOK_REPORT BIT(0)
#define QUIRK_NO_INIT_REPORTS BIT(1)
#define QUIRK_SKIP_INPUT_MAPPING BIT(2)
@@ -70,6 +78,8 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
#define QUIRK_T100_KEYBOARD BIT(6)
#define QUIRK_T100CHI BIT(7)
#define QUIRK_G752_KEYBOARD BIT(8)
+#define QUIRK_T101HA_DOCK BIT(9)
+#define QUIRK_T90CHI BIT(10)
#define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \
QUIRK_NO_INIT_REPORTS | \
@@ -99,12 +109,21 @@ struct asus_touchpad_info {
struct asus_drvdata {
unsigned long quirks;
+ struct hid_device *hdev;
struct input_dev *input;
struct asus_kbd_leds *kbd_backlight;
const struct asus_touchpad_info *tp;
bool enable_backlight;
+ struct power_supply *battery;
+ struct power_supply_desc battery_desc;
+ int battery_capacity;
+ int battery_stat;
+ bool battery_in_query;
+ unsigned long battery_next_query;
};
+static int asus_report_battery(struct asus_drvdata *, u8 *, int);
+
static const struct asus_touchpad_info asus_i2c_tp = {
.max_x = 2794,
.max_y = 1758,
@@ -241,11 +260,26 @@ static int asus_report_input(struct asus_drvdata *drvdat, u8 *data, int size)
return 1;
}
+static int asus_event(struct hid_device *hdev, struct hid_field *field,
+ struct hid_usage *usage, __s32 value)
+{
+ if ((usage->hid & HID_USAGE_PAGE) == 0xff310000 &&
+ (usage->hid & HID_USAGE) != 0x00 && !usage->type) {
+ hid_warn(hdev, "Unmapped Asus vendor usagepage code 0x%02x\n",
+ usage->hid & HID_USAGE);
+ }
+
+ return 0;
+}
+
static int asus_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *data, int size)
{
struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
+ if (drvdata->battery && data[0] == BATTERY_REPORT_ID)
+ return asus_report_battery(drvdata, data, size);
+
if (drvdata->tp && data[0] == INPUT_REPORT_ID)
return asus_report_input(drvdata, data, size);
@@ -415,6 +449,164 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
return ret;
}
+/*
+ * [0] REPORT_ID (same value defined in report descriptor)
+ * [1] rest battery level. range [0..255]
+ * [2]..[7] Bluetooth hardware address (MAC address)
+ * [8] charging status
+ * = 0 : AC offline / discharging
+ * = 1 : AC online / charging
+ * = 2 : AC online / fully charged
+ */
+static int asus_parse_battery(struct asus_drvdata *drvdata, u8 *data, int size)
+{
+ u8 sts;
+ u8 lvl;
+ int val;
+
+ lvl = data[1];
+ sts = data[8];
+
+ drvdata->battery_capacity = ((int)lvl * 100) / (int)BATTERY_LEVEL_MAX;
+
+ switch (sts) {
+ case BATTERY_STAT_CHARGING:
+ val = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case BATTERY_STAT_FULL:
+ val = POWER_SUPPLY_STATUS_FULL;
+ break;
+ case BATTERY_STAT_DISCONNECT:
+ default:
+ val = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ }
+ drvdata->battery_stat = val;
+
+ return 0;
+}
+
+static int asus_report_battery(struct asus_drvdata *drvdata, u8 *data, int size)
+{
+ /* notify only the autonomous event by device */
+ if ((drvdata->battery_in_query == false) &&
+ (size == BATTERY_REPORT_SIZE))
+ power_supply_changed(drvdata->battery);
+
+ return 0;
+}
+
+static int asus_battery_query(struct asus_drvdata *drvdata)
+{
+ u8 *buf;
+ int ret = 0;
+
+ buf = kmalloc(BATTERY_REPORT_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ drvdata->battery_in_query = true;
+ ret = hid_hw_raw_request(drvdata->hdev, BATTERY_REPORT_ID,
+ buf, BATTERY_REPORT_SIZE,
+ HID_INPUT_REPORT, HID_REQ_GET_REPORT);
+ drvdata->battery_in_query = false;
+ if (ret == BATTERY_REPORT_SIZE)
+ ret = asus_parse_battery(drvdata, buf, BATTERY_REPORT_SIZE);
+ else
+ ret = -ENODATA;
+
+ kfree(buf);
+
+ return ret;
+}
+
+static enum power_supply_property asus_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_SCOPE,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+};
+
+#define QUERY_MIN_INTERVAL (60 * HZ) /* 60[sec] */
+
+static int asus_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct asus_drvdata *drvdata = power_supply_get_drvdata(psy);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ case POWER_SUPPLY_PROP_CAPACITY:
+ if (time_before(drvdata->battery_next_query, jiffies)) {
+ drvdata->battery_next_query =
+ jiffies + QUERY_MIN_INTERVAL;
+ ret = asus_battery_query(drvdata);
+ if (ret)
+ return ret;
+ }
+ if (psp == POWER_SUPPLY_PROP_STATUS)
+ val->intval = drvdata->battery_stat;
+ else
+ val->intval = drvdata->battery_capacity;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_SCOPE:
+ val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = drvdata->hdev->name;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int asus_battery_probe(struct hid_device *hdev)
+{
+ struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
+ struct power_supply_config pscfg = { .drv_data = drvdata };
+ int ret = 0;
+
+ drvdata->battery_capacity = 0;
+ drvdata->battery_stat = POWER_SUPPLY_STATUS_UNKNOWN;
+ drvdata->battery_in_query = false;
+
+ drvdata->battery_desc.properties = asus_battery_props;
+ drvdata->battery_desc.num_properties = ARRAY_SIZE(asus_battery_props);
+ drvdata->battery_desc.get_property = asus_battery_get_property;
+ drvdata->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+ drvdata->battery_desc.use_for_apm = 0;
+ drvdata->battery_desc.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
+ "asus-keyboard-%s-battery",
+ strlen(hdev->uniq) ?
+ hdev->uniq : dev_name(&hdev->dev));
+ if (!drvdata->battery_desc.name)
+ return -ENOMEM;
+
+ drvdata->battery_next_query = jiffies;
+
+ drvdata->battery = devm_power_supply_register(&hdev->dev,
+ &(drvdata->battery_desc), &pscfg);
+ if (IS_ERR(drvdata->battery)) {
+ ret = PTR_ERR(drvdata->battery);
+ drvdata->battery = NULL;
+ hid_err(hdev, "Unable to register battery device\n");
+ return ret;
+ }
+
+ power_supply_powers(drvdata->battery, &hdev->dev);
+
+ return ret;
+}
+
static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi)
{
struct input_dev *input = hi->input;
@@ -487,7 +679,7 @@ static int asus_input_mapping(struct hid_device *hdev,
* This avoids a bunch of non-functional hid_input devices getting
* created because of the T100CHI using HID_QUIRK_MULTI_INPUT.
*/
- if (drvdata->quirks & QUIRK_T100CHI) {
+ if (drvdata->quirks & (QUIRK_T100CHI | QUIRK_T90CHI)) {
if (field->application == (HID_UP_GENDESK | 0x0080) ||
usage->hid == (HID_UP_GENDEVCTRLS | 0x0024) ||
usage->hid == (HID_UP_GENDEVCTRLS | 0x0025) ||
@@ -510,6 +702,7 @@ static int asus_input_mapping(struct hid_device *hdev,
case 0x20: asus_map_key_clear(KEY_BRIGHTNESSUP); break;
case 0x35: asus_map_key_clear(KEY_DISPLAY_OFF); break;
case 0x6c: asus_map_key_clear(KEY_SLEEP); break;
+ case 0x7c: asus_map_key_clear(KEY_MICMUTE); break;
case 0x82: asus_map_key_clear(KEY_CAMERA); break;
case 0x88: asus_map_key_clear(KEY_RFKILL); break;
case 0xb5: asus_map_key_clear(KEY_CALC); break;
@@ -528,6 +721,9 @@ static int asus_input_mapping(struct hid_device *hdev,
/* Fn+Space Power4Gear Hybrid */
case 0x5c: asus_map_key_clear(KEY_PROG3); break;
+ /* Fn+F5 "fan" symbol on FX503VD */
+ case 0x99: asus_map_key_clear(KEY_PROG4); break;
+
default:
/* ASUS lazily declares 256 usages, ignore the rest,
* as some make the keyboard appear as a pointer device. */
@@ -643,6 +839,15 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
drvdata->quirks = id->driver_data;
+ /*
+ * T90CHI's keyboard dock returns same ID values as T100CHI's dock.
+ * Thus, identify T90CHI dock with product name string.
+ */
+ if (strstr(hdev->name, "T90CHI")) {
+ drvdata->quirks &= ~QUIRK_T100CHI;
+ drvdata->quirks |= QUIRK_T90CHI;
+ }
+
if (drvdata->quirks & QUIRK_IS_MULTITOUCH)
drvdata->tp = &asus_i2c_tp;
@@ -677,12 +882,28 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
if (drvdata->quirks & QUIRK_NO_INIT_REPORTS)
hdev->quirks |= HID_QUIRK_NO_INIT_REPORTS;
+ drvdata->hdev = hdev;
+
+ if (drvdata->quirks & (QUIRK_T100CHI | QUIRK_T90CHI)) {
+ ret = asus_battery_probe(hdev);
+ if (ret) {
+ hid_err(hdev,
+ "Asus hid battery_probe failed: %d\n", ret);
+ return ret;
+ }
+ }
+
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "Asus hid parse failed: %d\n", ret);
return ret;
}
+ /* use hid-multitouch for T101HA touchpad */
+ if (id->driver_data & QUIRK_T101HA_DOCK &&
+ hdev->collection->usage == HID_GD_MOUSE)
+ return -ENODEV;
+
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "Asus hw start failed: %d\n", ret);
@@ -747,28 +968,44 @@ static __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
hid_info(hdev, "Fixing up Asus T100 keyb report descriptor\n");
rdesc[74] &= ~HID_MAIN_ITEM_CONSTANT;
}
- /* For the T100CHI keyboard dock */
- if (drvdata->quirks & QUIRK_T100CHI &&
- *rsize == 403 && rdesc[388] == 0x09 && rdesc[389] == 0x76) {
+ /* For the T100CHI/T90CHI keyboard dock */
+ if (drvdata->quirks & (QUIRK_T100CHI | QUIRK_T90CHI)) {
+ int rsize_orig;
+ int offs;
+
+ if (drvdata->quirks & QUIRK_T100CHI) {
+ rsize_orig = 403;
+ offs = 388;
+ } else {
+ rsize_orig = 306;
+ offs = 291;
+ }
+
/*
* Change Usage (76h) to Usage Minimum (00h), Usage Maximum
* (FFh) and clear the flags in the Input() byte.
* Note the descriptor has a bogus 0 byte at the end so we
* only need 1 extra byte.
*/
- *rsize = 404;
- rdesc = kmemdup(rdesc, *rsize, GFP_KERNEL);
- if (!rdesc)
- return NULL;
-
- hid_info(hdev, "Fixing up T100CHI keyb report descriptor\n");
- memmove(rdesc + 392, rdesc + 390, 12);
- rdesc[388] = 0x19;
- rdesc[389] = 0x00;
- rdesc[390] = 0x29;
- rdesc[391] = 0xff;
- rdesc[402] = 0x00;
+ if (*rsize == rsize_orig &&
+ rdesc[offs] == 0x09 && rdesc[offs + 1] == 0x76) {
+ *rsize = rsize_orig + 1;
+ rdesc = kmemdup(rdesc, *rsize, GFP_KERNEL);
+ if (!rdesc)
+ return NULL;
+
+ hid_info(hdev, "Fixing up %s keyb report descriptor\n",
+ drvdata->quirks & QUIRK_T100CHI ?
+ "T100CHI" : "T90CHI");
+ memmove(rdesc + offs + 4, rdesc + offs + 2, 12);
+ rdesc[offs] = 0x19;
+ rdesc[offs + 1] = 0x00;
+ rdesc[offs + 2] = 0x29;
+ rdesc[offs + 3] = 0xff;
+ rdesc[offs + 14] = 0x00;
+ }
}
+
if (drvdata->quirks & QUIRK_G752_KEYBOARD &&
*rsize == 75 && rdesc[61] == 0x15 && rdesc[62] == 0x00) {
/* report is missing usage mninum and maximum */
@@ -806,11 +1043,16 @@ static const struct hid_device_id asus_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD3), QUIRK_G752_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
+ USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD),
+ QUIRK_USE_KBD_BACKLIGHT },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_T100TA_KEYBOARD),
QUIRK_T100_KEYBOARD | QUIRK_NO_CONSUMER_USAGES },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_T100TAF_KEYBOARD),
QUIRK_T100_KEYBOARD | QUIRK_NO_CONSUMER_USAGES },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
+ USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD), QUIRK_T101HA_DOCK },
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_ASUS_AK1D) },
{ HID_USB_DEVICE(USB_VENDOR_ID_TURBOX, USB_DEVICE_ID_ASUS_MD_5110) },
{ HID_USB_DEVICE(USB_VENDOR_ID_JESS, USB_DEVICE_ID_ASUS_MD_5112) },
@@ -832,6 +1074,7 @@ static struct hid_driver asus_driver = {
#ifdef CONFIG_PM
.reset_resume = asus_reset_resume,
#endif
+ .event = asus_event,
.raw_event = asus_raw_event
};
module_hid_driver(asus_driver);
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 5bec9244c45b..860e21ec6a49 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -125,6 +125,7 @@ static int open_collection(struct hid_parser *parser, unsigned type)
{
struct hid_collection *collection;
unsigned usage;
+ int collection_index;
usage = parser->local.usage[0];
@@ -167,11 +168,13 @@ static int open_collection(struct hid_parser *parser, unsigned type)
parser->collection_stack[parser->collection_stack_ptr++] =
parser->device->maxcollection;
- collection = parser->device->collection +
- parser->device->maxcollection++;
+ collection_index = parser->device->maxcollection++;
+ collection = parser->device->collection + collection_index;
collection->type = type;
collection->usage = usage;
collection->level = parser->collection_stack_ptr - 1;
+ collection->parent_idx = (collection->level == 0) ? -1 :
+ parser->collection_stack[collection->level - 1];
if (type == HID_COLLECTION_APPLICATION)
parser->device->maxapplication++;
@@ -290,6 +293,7 @@ static int hid_add_field(struct hid_parser *parser, unsigned report_type, unsign
field->usage[i].collection_index =
parser->local.collection_index[j];
field->usage[i].usage_index = i;
+ field->usage[i].resolution_multiplier = 1;
}
field->maxusage = usages;
@@ -943,6 +947,169 @@ struct hid_report *hid_validate_values(struct hid_device *hid,
}
EXPORT_SYMBOL_GPL(hid_validate_values);
+static int hid_calculate_multiplier(struct hid_device *hid,
+ struct hid_field *multiplier)
+{
+ int m;
+ __s32 v = *multiplier->value;
+ __s32 lmin = multiplier->logical_minimum;
+ __s32 lmax = multiplier->logical_maximum;
+ __s32 pmin = multiplier->physical_minimum;
+ __s32 pmax = multiplier->physical_maximum;
+
+ /*
+ * "Because OS implementations will generally divide the control's
+ * reported count by the Effective Resolution Multiplier, designers
+ * should take care not to establish a potential Effective
+ * Resolution Multiplier of zero."
+ * HID Usage Table, v1.12, Section 4.3.1, p31
+ */
+ if (lmax - lmin == 0)
+ return 1;
+ /*
+ * Handling the unit exponent is left as an exercise to whoever
+ * finds a device where that exponent is not 0.
+ */
+ m = ((v - lmin)/(lmax - lmin) * (pmax - pmin) + pmin);
+ if (unlikely(multiplier->unit_exponent != 0)) {
+ hid_warn(hid,
+ "unsupported Resolution Multiplier unit exponent %d\n",
+ multiplier->unit_exponent);
+ }
+
+ /* There are no devices with an effective multiplier > 255 */
+ if (unlikely(m == 0 || m > 255 || m < -255)) {
+ hid_warn(hid, "unsupported Resolution Multiplier %d\n", m);
+ m = 1;
+ }
+
+ return m;
+}
+
+static void hid_apply_multiplier_to_field(struct hid_device *hid,
+ struct hid_field *field,
+ struct hid_collection *multiplier_collection,
+ int effective_multiplier)
+{
+ struct hid_collection *collection;
+ struct hid_usage *usage;
+ int i;
+
+ /*
+ * If multiplier_collection is NULL, the multiplier applies
+ * to all fields in the report.
+ * Otherwise, it is the Logical Collection the multiplier applies to
+ * but our field may be in a subcollection of that collection.
+ */
+ for (i = 0; i < field->maxusage; i++) {
+ usage = &field->usage[i];
+
+ collection = &hid->collection[usage->collection_index];
+ while (collection->parent_idx != -1 &&
+ collection != multiplier_collection)
+ collection = &hid->collection[collection->parent_idx];
+
+ if (collection->parent_idx != -1 ||
+ multiplier_collection == NULL)
+ usage->resolution_multiplier = effective_multiplier;
+
+ }
+}
+
+static void hid_apply_multiplier(struct hid_device *hid,
+ struct hid_field *multiplier)
+{
+ struct hid_report_enum *rep_enum;
+ struct hid_report *rep;
+ struct hid_field *field;
+ struct hid_collection *multiplier_collection;
+ int effective_multiplier;
+ int i;
+
+ /*
+ * "The Resolution Multiplier control must be contained in the same
+ * Logical Collection as the control(s) to which it is to be applied.
+ * If no Resolution Multiplier is defined, then the Resolution
+ * Multiplier defaults to 1. If more than one control exists in a
+ * Logical Collection, the Resolution Multiplier is associated with
+ * all controls in the collection. If no Logical Collection is
+ * defined, the Resolution Multiplier is associated with all
+ * controls in the report."
+ * HID Usage Table, v1.12, Section 4.3.1, p30
+ *
+ * Thus, search from the current collection upwards until we find a
+ * logical collection. Then search all fields for that same parent
+ * collection. Those are the fields the multiplier applies to.
+ *
+ * If we have more than one multiplier, it will overwrite the
+ * applicable fields later.
+ */
+ multiplier_collection = &hid->collection[multiplier->usage->collection_index];
+ while (multiplier_collection->parent_idx != -1 &&
+ multiplier_collection->type != HID_COLLECTION_LOGICAL)
+ multiplier_collection = &hid->collection[multiplier_collection->parent_idx];
+
+ effective_multiplier = hid_calculate_multiplier(hid, multiplier);
+
+ rep_enum = &hid->report_enum[HID_INPUT_REPORT];
+ list_for_each_entry(rep, &rep_enum->report_list, list) {
+ for (i = 0; i < rep->maxfield; i++) {
+ field = rep->field[i];
+ hid_apply_multiplier_to_field(hid, field,
+ multiplier_collection,
+ effective_multiplier);
+ }
+ }
+}
+
+/*
+ * hid_setup_resolution_multiplier - set up all resolution multipliers
+ *
+ * @device: hid device
+ *
+ * Search for all Resolution Multiplier Feature Reports and apply their
+ * value to all matching Input items. This only updates the internal struct
+ * fields.
+ *
+ * The Resolution Multiplier is applied by the hardware. If the multiplier
+ * is anything other than 1, the hardware will send pre-multiplied events
+ * so that the same physical interaction generates an accumulated
+ * accumulated_value = value * * multiplier
+ * This may be achieved by sending
+ * - "value * multiplier" for each event, or
+ * - "value" but "multiplier" times as frequently, or
+ * - a combination of the above
+ * The only guarantee is that the same physical interaction always generates
+ * an accumulated 'value * multiplier'.
+ *
+ * This function must be called before any event processing and after
+ * any SetRequest to the Resolution Multiplier.
+ */
+void hid_setup_resolution_multiplier(struct hid_device *hid)
+{
+ struct hid_report_enum *rep_enum;
+ struct hid_report *rep;
+ struct hid_usage *usage;
+ int i, j;
+
+ rep_enum = &hid->report_enum[HID_FEATURE_REPORT];
+ list_for_each_entry(rep, &rep_enum->report_list, list) {
+ for (i = 0; i < rep->maxfield; i++) {
+ /* Ignore if report count is out of bounds. */
+ if (rep->field[i]->report_count < 1)
+ continue;
+
+ for (j = 0; j < rep->field[i]->maxusage; j++) {
+ usage = &rep->field[i]->usage[j];
+ if (usage->hid == HID_GD_RESOLUTION_MULTIPLIER)
+ hid_apply_multiplier(hid,
+ rep->field[i]);
+ }
+ }
+ }
+}
+EXPORT_SYMBOL_GPL(hid_setup_resolution_multiplier);
+
/**
* hid_open_report - open a driver-specific device report
*
@@ -1039,9 +1206,17 @@ int hid_open_report(struct hid_device *device)
hid_err(device, "unbalanced delimiter at end of report description\n");
goto err;
}
+
+ /*
+ * fetch initial values in case the device's
+ * default multiplier isn't the recommended 1
+ */
+ hid_setup_resolution_multiplier(device);
+
kfree(parser->collection_stack);
vfree(parser);
device->status |= HID_STAT_PARSED;
+
return 0;
}
}
@@ -1126,10 +1301,10 @@ static u32 __extract(u8 *report, unsigned offset, int n)
u32 hid_field_extract(const struct hid_device *hid, u8 *report,
unsigned offset, unsigned n)
{
- if (n > 32) {
- hid_warn(hid, "hid_field_extract() called with n (%d) > 32! (%s)\n",
+ if (n > 256) {
+ hid_warn(hid, "hid_field_extract() called with n (%d) > 256! (%s)\n",
n, current->comm);
- n = 32;
+ n = 256;
}
return __extract(report, offset, n);
diff --git a/drivers/hid/hid-cougar.c b/drivers/hid/hid-cougar.c
index 3f0916b64c60..e0bb7b34f3a4 100644
--- a/drivers/hid/hid-cougar.c
+++ b/drivers/hid/hid-cougar.c
@@ -326,6 +326,8 @@ module_param_cb(g6_is_space, &cougar_g6_is_space_ops, &g6_is_space, 0644);
static struct hid_device_id cougar_id_table[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_SOLID_YEAR,
USB_DEVICE_ID_COUGAR_500K_GAMING_KEYBOARD) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SOLID_YEAR,
+ USB_DEVICE_ID_COUGAR_700K_GAMING_KEYBOARD) },
{}
};
MODULE_DEVICE_TABLE(hid, cougar_id_table);
diff --git a/drivers/hid/hid-cp2112.c b/drivers/hid/hid-cp2112.c
index 271f31461da4..47f65857408d 100644
--- a/drivers/hid/hid-cp2112.c
+++ b/drivers/hid/hid-cp2112.c
@@ -1203,7 +1203,7 @@ static int __maybe_unused cp2112_allocate_irq(struct cp2112_device *dev,
return -EINVAL;
dev->desc[pin] = gpiochip_request_own_desc(&dev->gc, pin,
- "HID/I2C:Event");
+ "HID/I2C:Event", 0);
if (IS_ERR(dev->desc[pin])) {
dev_err(dev->gc.parent, "Failed to request GPIO\n");
return PTR_ERR(dev->desc[pin]);
diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c
index b48100236df8..1384e57182af 100644
--- a/drivers/hid/hid-debug.c
+++ b/drivers/hid/hid-debug.c
@@ -30,6 +30,7 @@
#include <linux/debugfs.h>
#include <linux/seq_file.h>
+#include <linux/kfifo.h>
#include <linux/sched/signal.h>
#include <linux/export.h>
#include <linux/slab.h>
@@ -661,17 +662,12 @@ EXPORT_SYMBOL_GPL(hid_dump_device);
/* enqueue string to 'events' ring buffer */
void hid_debug_event(struct hid_device *hdev, char *buf)
{
- unsigned i;
struct hid_debug_list *list;
unsigned long flags;
spin_lock_irqsave(&hdev->debug_list_lock, flags);
- list_for_each_entry(list, &hdev->debug_list, node) {
- for (i = 0; buf[i]; i++)
- list->hid_debug_buf[(list->tail + i) % HID_DEBUG_BUFSIZE] =
- buf[i];
- list->tail = (list->tail + i) % HID_DEBUG_BUFSIZE;
- }
+ list_for_each_entry(list, &hdev->debug_list, node)
+ kfifo_in(&list->hid_debug_fifo, buf, strlen(buf));
spin_unlock_irqrestore(&hdev->debug_list_lock, flags);
wake_up_interruptible(&hdev->debug_wait);
@@ -722,8 +718,7 @@ void hid_dump_input(struct hid_device *hdev, struct hid_usage *usage, __s32 valu
hid_debug_event(hdev, buf);
kfree(buf);
- wake_up_interruptible(&hdev->debug_wait);
-
+ wake_up_interruptible(&hdev->debug_wait);
}
EXPORT_SYMBOL_GPL(hid_dump_input);
@@ -1065,16 +1060,16 @@ static int hid_debug_rdesc_show(struct seq_file *f, void *p)
seq_printf(f, "\n\n");
/* dump parsed data and input mappings */
+ if (down_interruptible(&hdev->driver_input_lock))
+ return 0;
+
hid_dump_device(hdev, f);
seq_printf(f, "\n");
hid_dump_input_mapping(hdev, f);
- return 0;
-}
+ up(&hdev->driver_input_lock);
-static int hid_debug_rdesc_open(struct inode *inode, struct file *file)
-{
- return single_open(file, hid_debug_rdesc_show, inode->i_private);
+ return 0;
}
static int hid_debug_events_open(struct inode *inode, struct file *file)
@@ -1088,8 +1083,8 @@ static int hid_debug_events_open(struct inode *inode, struct file *file)
goto out;
}
- if (!(list->hid_debug_buf = kzalloc(HID_DEBUG_BUFSIZE, GFP_KERNEL))) {
- err = -ENOMEM;
+ err = kfifo_alloc(&list->hid_debug_fifo, HID_DEBUG_FIFOSIZE, GFP_KERNEL);
+ if (err) {
kfree(list);
goto out;
}
@@ -1109,77 +1104,57 @@ static ssize_t hid_debug_events_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
{
struct hid_debug_list *list = file->private_data;
- int ret = 0, len;
+ int ret = 0, copied;
DECLARE_WAITQUEUE(wait, current);
mutex_lock(&list->read_mutex);
- while (ret == 0) {
- if (list->head == list->tail) {
- add_wait_queue(&list->hdev->debug_wait, &wait);
- set_current_state(TASK_INTERRUPTIBLE);
-
- while (list->head == list->tail) {
- if (file->f_flags & O_NONBLOCK) {
- ret = -EAGAIN;
- break;
- }
- if (signal_pending(current)) {
- ret = -ERESTARTSYS;
- break;
- }
+ if (kfifo_is_empty(&list->hid_debug_fifo)) {
+ add_wait_queue(&list->hdev->debug_wait, &wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ while (kfifo_is_empty(&list->hid_debug_fifo)) {
+ if (file->f_flags & O_NONBLOCK) {
+ ret = -EAGAIN;
+ break;
+ }
- if (!list->hdev || !list->hdev->debug) {
- ret = -EIO;
- set_current_state(TASK_RUNNING);
- goto out;
- }
+ if (signal_pending(current)) {
+ ret = -ERESTARTSYS;
+ break;
+ }
- /* allow O_NONBLOCK from other threads */
- mutex_unlock(&list->read_mutex);
- schedule();
- mutex_lock(&list->read_mutex);
- set_current_state(TASK_INTERRUPTIBLE);
+ /* if list->hdev is NULL we cannot remove_wait_queue().
+ * if list->hdev->debug is 0 then hid_debug_unregister()
+ * was already called and list->hdev is being destroyed.
+ * if we add remove_wait_queue() here we can hit a race.
+ */
+ if (!list->hdev || !list->hdev->debug) {
+ ret = -EIO;
+ set_current_state(TASK_RUNNING);
+ goto out;
}
- set_current_state(TASK_RUNNING);
- remove_wait_queue(&list->hdev->debug_wait, &wait);
+ /* allow O_NONBLOCK from other threads */
+ mutex_unlock(&list->read_mutex);
+ schedule();
+ mutex_lock(&list->read_mutex);
+ set_current_state(TASK_INTERRUPTIBLE);
}
- if (ret)
- goto out;
+ __set_current_state(TASK_RUNNING);
+ remove_wait_queue(&list->hdev->debug_wait, &wait);
- /* pass the ringbuffer contents to userspace */
-copy_rest:
- if (list->tail == list->head)
+ if (ret)
goto out;
- if (list->tail > list->head) {
- len = list->tail - list->head;
- if (len > count)
- len = count;
-
- if (copy_to_user(buffer + ret, &list->hid_debug_buf[list->head], len)) {
- ret = -EFAULT;
- goto out;
- }
- ret += len;
- list->head += len;
- } else {
- len = HID_DEBUG_BUFSIZE - list->head;
- if (len > count)
- len = count;
-
- if (copy_to_user(buffer, &list->hid_debug_buf[list->head], len)) {
- ret = -EFAULT;
- goto out;
- }
- list->head = 0;
- ret += len;
- count -= len;
- if (count > 0)
- goto copy_rest;
- }
-
}
+
+ /* pass the fifo content to userspace, locking is not needed with only
+ * one concurrent reader and one concurrent writer
+ */
+ ret = kfifo_to_user(&list->hid_debug_fifo, buffer, count, &copied);
+ if (ret)
+ goto out;
+ ret = copied;
out:
mutex_unlock(&list->read_mutex);
return ret;
@@ -1190,7 +1165,7 @@ static __poll_t hid_debug_events_poll(struct file *file, poll_table *wait)
struct hid_debug_list *list = file->private_data;
poll_wait(file, &list->hdev->debug_wait, wait);
- if (list->head != list->tail)
+ if (!kfifo_is_empty(&list->hid_debug_fifo))
return EPOLLIN | EPOLLRDNORM;
if (!list->hdev->debug)
return EPOLLERR | EPOLLHUP;
@@ -1205,18 +1180,13 @@ static int hid_debug_events_release(struct inode *inode, struct file *file)
spin_lock_irqsave(&list->hdev->debug_list_lock, flags);
list_del(&list->node);
spin_unlock_irqrestore(&list->hdev->debug_list_lock, flags);
- kfree(list->hid_debug_buf);
+ kfifo_free(&list->hid_debug_fifo);
kfree(list);
return 0;
}
-static const struct file_operations hid_debug_rdesc_fops = {
- .open = hid_debug_rdesc_open,
- .read = seq_read,
- .llseek = seq_lseek,
- .release = single_release,
-};
+DEFINE_SHOW_ATTRIBUTE(hid_debug_rdesc);
static const struct file_operations hid_debug_events_fops = {
.owner = THIS_MODULE,
@@ -1256,4 +1226,3 @@ void hid_debug_exit(void)
{
debugfs_remove_recursive(hid_debug_root);
}
-
diff --git a/drivers/hid/hid-elan.c b/drivers/hid/hid-elan.c
index 0bfd6d1b44c1..1c62095cee99 100644
--- a/drivers/hid/hid-elan.c
+++ b/drivers/hid/hid-elan.c
@@ -393,7 +393,7 @@ static int elan_start_multitouch(struct hid_device *hdev)
* This byte sequence will enable multitouch mode and disable
* mouse emulation
*/
- const unsigned char buf[] = { 0x0D, 0x00, 0x03, 0x21, 0x00 };
+ static const unsigned char buf[] = { 0x0D, 0x00, 0x03, 0x21, 0x00 };
unsigned char *dmabuf = kmemdup(buf, sizeof(buf), GFP_KERNEL);
if (!dmabuf)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 27519eb8ee63..adce58f24f76 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -72,6 +72,7 @@
#define USB_VENDOR_ID_ALCOR 0x058f
#define USB_DEVICE_ID_ALCOR_USBRS232 0x9720
+#define USB_DEVICE_ID_ALCOR_MALTRON_KB 0x9410
#define USB_VENDOR_ID_ALPS 0x0433
#define USB_DEVICE_ID_IBM_GAMEPAD 0x1101
@@ -187,12 +188,14 @@
#define USB_DEVICE_ID_ASUSTEK_T100TA_KEYBOARD 0x17e0
#define USB_DEVICE_ID_ASUSTEK_T100TAF_KEYBOARD 0x1807
#define USB_DEVICE_ID_ASUSTEK_T100CHI_KEYBOARD 0x8502
+#define USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD 0x183d
#define USB_DEVICE_ID_ASUSTEK_T304_KEYBOARD 0x184a
#define USB_DEVICE_ID_ASUSTEK_I2C_KEYBOARD 0x8585
#define USB_DEVICE_ID_ASUSTEK_I2C_TOUCHPAD 0x0101
#define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1 0x1854
#define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2 0x1837
#define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD3 0x1822
+#define USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD 0x1869
#define USB_VENDOR_ID_ATEN 0x0557
#define USB_DEVICE_ID_ATEN_UC100KM 0x2004
@@ -271,6 +274,7 @@
#define USB_DEVICE_ID_CHICONY_PIXART_USB_OPTICAL_MOUSE 0x1053
#define USB_DEVICE_ID_CHICONY_WIRELESS2 0x1123
#define USB_DEVICE_ID_ASUS_AK1D 0x1125
+#define USB_DEVICE_ID_CHICONY_TOSHIBA_WT10A 0x1408
#define USB_DEVICE_ID_CHICONY_ACER_SWITCH12 0x1421
#define USB_VENDOR_ID_CHUNGHWAT 0x2247
@@ -459,6 +463,9 @@
#define USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_010A 0x010a
#define USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_E100 0xe100
+#define I2C_VENDOR_ID_GOODIX 0x27c6
+#define I2C_DEVICE_ID_GOODIX_01F0 0x01f0
+
#define USB_VENDOR_ID_GOODTOUCH 0x1aad
#define USB_DEVICE_ID_GOODTOUCH_000f 0x000f
@@ -656,6 +663,7 @@
#define USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2 0x501a
#define USB_DEVICE_ID_KYE_EASYPEN_M610X 0x5013
#define USB_DEVICE_ID_KYE_PENSKETCH_M912 0x5015
+#define USB_DEVICE_ID_KYE_EASYPEN_M406XE 0x5019
#define USB_VENDOR_ID_LABTEC 0x1020
#define USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD 0x0006
@@ -709,6 +717,7 @@
#define USB_DEVICE_ID_LENOVO_TPPRODOCK 0x6067
#define USB_DEVICE_ID_LENOVO_X1_COVER 0x6085
#define USB_DEVICE_ID_LENOVO_X1_TAB 0x60a3
+#define USB_DEVICE_ID_LENOVO_X1_TAB3 0x60b5
#define USB_VENDOR_ID_LG 0x1fd2
#define USB_DEVICE_ID_LG_MULTITOUCH 0x0064
@@ -739,6 +748,7 @@
#define USB_DEVICE_ID_LOGITECH_WINGMAN_F3D 0xc283
#define USB_DEVICE_ID_LOGITECH_FORCE3D_PRO 0xc286
#define USB_DEVICE_ID_LOGITECH_FLIGHT_SYSTEM_G940 0xc287
+#define USB_DEVICE_ID_LOGITECH_WINGMAN_FG 0xc20e
#define USB_DEVICE_ID_LOGITECH_WINGMAN_FFG 0xc293
#define USB_DEVICE_ID_LOGITECH_WHEEL 0xc294
#define USB_DEVICE_ID_LOGITECH_MOMO_WHEEL 0xc295
@@ -1025,6 +1035,7 @@
#define USB_VENDOR_ID_SOLID_YEAR 0x060b
#define USB_DEVICE_ID_COUGAR_500K_GAMING_KEYBOARD 0x500a
+#define USB_DEVICE_ID_COUGAR_700K_GAMING_KEYBOARD 0x700a
#define USB_VENDOR_ID_SOUNDGRAPH 0x15c2
#define USB_DEVICE_ID_SOUNDGRAPH_IMON_FIRST 0x0034
@@ -1072,6 +1083,7 @@
#define USB_DEVICE_ID_SYNAPTICS_HD 0x0ac3
#define USB_DEVICE_ID_SYNAPTICS_QUAD_HD 0x1ac3
#define USB_DEVICE_ID_SYNAPTICS_TP_V103 0x5710
+#define I2C_DEVICE_ID_SYNAPTICS_7E7E 0x7e7e
#define USB_VENDOR_ID_TEXAS_INSTRUMENTS 0x2047
#define USB_DEVICE_ID_TEXAS_INSTRUMENTS_LENOVO_YOGA 0x0855
@@ -1128,11 +1140,16 @@
#define USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850 0x0522
#define USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60 0x0781
#define USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3 0x3031
-#define USB_DEVICE_ID_UGEE_TABLET_81 0x0081
-#define USB_DEVICE_ID_UGEE_TABLET_45 0x0045
+#define USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_81 0x0081
+#define USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_45 0x0045
+#define USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_47 0x0047
#define USB_DEVICE_ID_YIYNOVA_TABLET 0x004d
#define USB_VENDOR_ID_UGEE 0x28bd
+#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_G540 0x0075
+#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_G640 0x0094
+#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01 0x0042
+#define USB_DEVICE_ID_UGEE_TABLET_G5 0x0074
#define USB_DEVICE_ID_UGEE_TABLET_EX07S 0x0071
#define USB_VENDOR_ID_UNITEC 0x227d
@@ -1234,4 +1251,10 @@
#define USB_VENDOR_ID_UGTIZER 0x2179
#define USB_DEVICE_ID_UGTIZER_TABLET_GP0610 0x0053
+#define USB_VENDOR_ID_VIEWSONIC 0x0543
+#define USB_DEVICE_ID_VIEWSONIC_PD1011 0xe621
+
+#define USB_VENDOR_ID_SIGNOTEC 0x2133
+#define USB_DEVICE_ID_SIGNOTEC_VIEWSONIC_PD1011 0x0018
+
#endif
diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index da76358cde06..b607286a0bc8 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -328,6 +328,9 @@ static const struct hid_device_id hid_battery_quirks[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_SYMBOL,
USB_DEVICE_ID_SYMBOL_SCANNER_3),
HID_BATTERY_QUIRK_IGNORE },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ASUSTEK,
+ USB_DEVICE_ID_ASUSTEK_T100CHI_KEYBOARD),
+ HID_BATTERY_QUIRK_IGNORE },
{}
};
@@ -720,7 +723,15 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
map_abs_clear(usage->hid & 0xf);
break;
- case HID_GD_SLIDER: case HID_GD_DIAL: case HID_GD_WHEEL:
+ case HID_GD_WHEEL:
+ if (field->flags & HID_MAIN_ITEM_RELATIVE) {
+ set_bit(REL_WHEEL, input->relbit);
+ map_rel(REL_WHEEL_HI_RES);
+ } else {
+ map_abs(usage->hid & 0xf);
+ }
+ break;
+ case HID_GD_SLIDER: case HID_GD_DIAL:
if (field->flags & HID_MAIN_ITEM_RELATIVE)
map_rel(usage->hid & 0xf);
else
@@ -999,6 +1010,7 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
case 0x1b8: map_key_clear(KEY_VIDEO); break;
case 0x1bc: map_key_clear(KEY_MESSENGER); break;
case 0x1bd: map_key_clear(KEY_INFO); break;
+ case 0x1cb: map_key_clear(KEY_ASSISTANT); break;
case 0x201: map_key_clear(KEY_NEW); break;
case 0x202: map_key_clear(KEY_OPEN); break;
case 0x203: map_key_clear(KEY_CLOSE); break;
@@ -1025,7 +1037,10 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
case 0x232: map_key_clear(KEY_FULL_SCREEN); break;
case 0x233: map_key_clear(KEY_SCROLLUP); break;
case 0x234: map_key_clear(KEY_SCROLLDOWN); break;
- case 0x238: map_rel(REL_HWHEEL); break;
+ case 0x238: /* AC Pan */
+ set_bit(REL_HWHEEL, input->relbit);
+ map_rel(REL_HWHEEL_HI_RES);
+ break;
case 0x23d: map_key_clear(KEY_EDIT); break;
case 0x25f: map_key_clear(KEY_CANCEL); break;
case 0x269: map_key_clear(KEY_INSERT); break;
@@ -1215,6 +1230,38 @@ ignore:
}
+static void hidinput_handle_scroll(struct hid_usage *usage,
+ struct input_dev *input,
+ __s32 value)
+{
+ int code;
+ int hi_res, lo_res;
+
+ if (value == 0)
+ return;
+
+ if (usage->code == REL_WHEEL_HI_RES)
+ code = REL_WHEEL;
+ else
+ code = REL_HWHEEL;
+
+ /*
+ * Windows reports one wheel click as value 120. Where a high-res
+ * scroll wheel is present, a fraction of 120 is reported instead.
+ * Our REL_WHEEL_HI_RES axis does the same because all HW must
+ * adhere to the 120 expectation.
+ */
+ hi_res = value * 120/usage->resolution_multiplier;
+
+ usage->wheel_accumulated += hi_res;
+ lo_res = usage->wheel_accumulated/120;
+ if (lo_res)
+ usage->wheel_accumulated -= lo_res * 120;
+
+ input_event(input, EV_REL, code, lo_res);
+ input_event(input, EV_REL, usage->code, hi_res);
+}
+
void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct hid_usage *usage, __s32 value)
{
struct input_dev *input;
@@ -1277,6 +1324,12 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct
if ((usage->type == EV_KEY) && (usage->code == 0)) /* Key 0 is "unassigned", not KEY_UNKNOWN */
return;
+ if ((usage->type == EV_REL) && (usage->code == REL_WHEEL_HI_RES ||
+ usage->code == REL_HWHEEL_HI_RES)) {
+ hidinput_handle_scroll(usage, input, value);
+ return;
+ }
+
if ((usage->type == EV_ABS) && (field->flags & HID_MAIN_ITEM_RELATIVE) &&
(usage->code == ABS_VOLUME)) {
int count = abs(value);
@@ -1504,6 +1557,58 @@ static void hidinput_close(struct input_dev *dev)
hid_hw_close(hid);
}
+static void hidinput_change_resolution_multipliers(struct hid_device *hid)
+{
+ struct hid_report_enum *rep_enum;
+ struct hid_report *rep;
+ struct hid_usage *usage;
+ int i, j;
+
+ rep_enum = &hid->report_enum[HID_FEATURE_REPORT];
+ list_for_each_entry(rep, &rep_enum->report_list, list) {
+ bool update_needed = false;
+
+ if (rep->maxfield == 0)
+ continue;
+
+ /*
+ * If we have more than one feature within this report we
+ * need to fill in the bits from the others before we can
+ * overwrite the ones for the Resolution Multiplier.
+ */
+ if (rep->maxfield > 1) {
+ hid_hw_request(hid, rep, HID_REQ_GET_REPORT);
+ hid_hw_wait(hid);
+ }
+
+ for (i = 0; i < rep->maxfield; i++) {
+ __s32 logical_max = rep->field[i]->logical_maximum;
+
+ /* There is no good reason for a Resolution
+ * Multiplier to have a count other than 1.
+ * Ignore that case.
+ */
+ if (rep->field[i]->report_count != 1)
+ continue;
+
+ for (j = 0; j < rep->field[i]->maxusage; j++) {
+ usage = &rep->field[i]->usage[j];
+
+ if (usage->hid != HID_GD_RESOLUTION_MULTIPLIER)
+ continue;
+
+ *rep->field[i]->value = logical_max;
+ update_needed = true;
+ }
+ }
+ if (update_needed)
+ hid_hw_request(hid, rep, HID_REQ_SET_REPORT);
+ }
+
+ /* refresh our structs */
+ hid_setup_resolution_multiplier(hid);
+}
+
static void report_features(struct hid_device *hid)
{
struct hid_driver *drv = hid->driver;
@@ -1797,6 +1902,8 @@ int hidinput_connect(struct hid_device *hid, unsigned int force)
}
}
+ hidinput_change_resolution_multipliers(hid);
+
list_for_each_entry_safe(hidinput, next, &hid->inputs, list) {
if (drv->input_configured &&
drv->input_configured(hid, hidinput))
@@ -1855,4 +1962,3 @@ void hidinput_disconnect(struct hid_device *hid)
cancel_work_sync(&hid->led_work);
}
EXPORT_SYMBOL_GPL(hidinput_disconnect);
-
diff --git a/drivers/hid/hid-kye.c b/drivers/hid/hid-kye.c
index 9c113f62472d..679d422b885a 100644
--- a/drivers/hid/hid-kye.c
+++ b/drivers/hid/hid-kye.c
@@ -483,6 +483,80 @@ static __u8 pensketch_m912_rdesc_fixed[] = {
0xC0 /* End Collection */
};
+/* Original EasyPen M406XE report descriptor size */
+#define EASYPEN_M406XE_RDESC_ORIG_SIZE 476
+
+/* Fixed EasyPen M406XE report descriptor */
+static __u8 easypen_m406xe_rdesc_fixed[] = {
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x09, 0x01, /* Usage (01h), */
+ 0xA1, 0x01, /* Collection (Application), */
+ 0x85, 0x05, /* Report ID (5), */
+ 0x09, 0x01, /* Usage (01h), */
+ 0x15, 0x80, /* Logical Minimum (-128), */
+ 0x25, 0x7F, /* Logical Maximum (127), */
+ 0x75, 0x08, /* Report Size (8), */
+ 0x95, 0x07, /* Report Count (7), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0xC0, /* End Collection, */
+ 0x05, 0x0D, /* Usage Page (Digitizer), */
+ 0x09, 0x02, /* Usage (Pen), */
+ 0xA1, 0x01, /* Collection (Application), */
+ 0x85, 0x10, /* Report ID (16), */
+ 0x09, 0x20, /* Usage (Stylus), */
+ 0xA0, /* Collection (Physical), */
+ 0x14, /* Logical Minimum (0), */
+ 0x25, 0x01, /* Logical Maximum (1), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x09, 0x42, /* Usage (Tip Switch), */
+ 0x09, 0x44, /* Usage (Barrel Switch), */
+ 0x09, 0x46, /* Usage (Tablet Pick), */
+ 0x95, 0x03, /* Report Count (3), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x95, 0x04, /* Report Count (4), */
+ 0x81, 0x03, /* Input (Constant, Variable), */
+ 0x09, 0x32, /* Usage (In Range), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x75, 0x10, /* Report Size (16), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0xA4, /* Push, */
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x55, 0xFD, /* Unit Exponent (-3), */
+ 0x65, 0x13, /* Unit (Inch), */
+ 0x34, /* Physical Minimum (0), */
+ 0x09, 0x30, /* Usage (X), */
+ 0x46, 0x70, 0x17, /* Physical Maximum (6000), */
+ 0x26, 0x00, 0x3C, /* Logical Maximum (15360), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x09, 0x31, /* Usage (Y), */
+ 0x46, 0xA0, 0x0F, /* Physical Maximum (4000), */
+ 0x26, 0x00, 0x28, /* Logical Maximum (10240), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0xB4, /* Pop, */
+ 0x09, 0x30, /* Usage (Tip Pressure), */
+ 0x26, 0xFF, 0x03, /* Logical Maximum (1023), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0xC0, /* End Collection, */
+ 0xC0, /* End Collection */
+ 0x05, 0x0C, /* Usage Page (Consumer), */
+ 0x09, 0x01, /* Usage (Consumer Control), */
+ 0xA1, 0x01, /* Collection (Application), */
+ 0x85, 0x12, /* Report ID (18), */
+ 0x14, /* Logical Minimum (0), */
+ 0x25, 0x01, /* Logical Maximum (1), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x95, 0x04, /* Report Count (4), */
+ 0x0A, 0x79, 0x02, /* Usage (AC Redo Or Repeat), */
+ 0x0A, 0x1A, 0x02, /* Usage (AC Undo), */
+ 0x0A, 0x2D, 0x02, /* Usage (AC Zoom In), */
+ 0x0A, 0x2E, 0x02, /* Usage (AC Zoom Out), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x95, 0x34, /* Report Count (52), */
+ 0x81, 0x03, /* Input (Constant, Variable), */
+ 0xC0 /* End Collection */
+};
+
static __u8 *kye_consumer_control_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize, int offset, const char *device_name) {
/*
@@ -555,6 +629,12 @@ static __u8 *kye_report_fixup(struct hid_device *hdev, __u8 *rdesc,
*rsize = sizeof(easypen_m610x_rdesc_fixed);
}
break;
+ case USB_DEVICE_ID_KYE_EASYPEN_M406XE:
+ if (*rsize == EASYPEN_M406XE_RDESC_ORIG_SIZE) {
+ rdesc = easypen_m406xe_rdesc_fixed;
+ *rsize = sizeof(easypen_m406xe_rdesc_fixed);
+ }
+ break;
case USB_DEVICE_ID_KYE_PENSKETCH_M912:
if (*rsize == PENSKETCH_M912_RDESC_ORIG_SIZE) {
rdesc = pensketch_m912_rdesc_fixed;
@@ -644,6 +724,7 @@ static int kye_probe(struct hid_device *hdev, const struct hid_device_id *id)
case USB_DEVICE_ID_KYE_MOUSEPEN_I608X:
case USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2:
case USB_DEVICE_ID_KYE_EASYPEN_M610X:
+ case USB_DEVICE_ID_KYE_EASYPEN_M406XE:
case USB_DEVICE_ID_KYE_PENSKETCH_M912:
ret = kye_tablet_enable(hdev);
if (ret) {
@@ -679,6 +760,8 @@ static const struct hid_device_id kye_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE,
USB_DEVICE_ID_KYE_EASYPEN_M610X) },
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE,
+ USB_DEVICE_ID_KYE_EASYPEN_M406XE) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_KYE,
USB_DEVICE_ID_GENIUS_GILA_GAMING_MOUSE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE,
USB_DEVICE_ID_GENIUS_GX_IMPERATOR) },
diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c
index 643b6eb54442..eacc76d2ab96 100644
--- a/drivers/hid/hid-lenovo.c
+++ b/drivers/hid/hid-lenovo.c
@@ -743,7 +743,9 @@ static int lenovo_probe_tpkbd(struct hid_device *hdev)
data_pointer->led_mute.brightness_get = lenovo_led_brightness_get_tpkbd;
data_pointer->led_mute.brightness_set = lenovo_led_brightness_set_tpkbd;
data_pointer->led_mute.dev = dev;
- led_classdev_register(dev, &data_pointer->led_mute);
+ ret = led_classdev_register(dev, &data_pointer->led_mute);
+ if (ret < 0)
+ goto err;
data_pointer->led_micmute.name = name_micmute;
data_pointer->led_micmute.brightness_get =
@@ -751,7 +753,11 @@ static int lenovo_probe_tpkbd(struct hid_device *hdev)
data_pointer->led_micmute.brightness_set =
lenovo_led_brightness_set_tpkbd;
data_pointer->led_micmute.dev = dev;
- led_classdev_register(dev, &data_pointer->led_micmute);
+ ret = led_classdev_register(dev, &data_pointer->led_micmute);
+ if (ret < 0) {
+ led_classdev_unregister(&data_pointer->led_mute);
+ goto err;
+ }
lenovo_features_set_tpkbd(hdev);
diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c
index 596227ddb6e0..5d419a95b6c2 100644
--- a/drivers/hid/hid-lg.c
+++ b/drivers/hid/hid-lg.c
@@ -50,6 +50,7 @@
#define MOMO_RDESC_ORIG_SIZE 87
#define MOMO2_RDESC_ORIG_SIZE 87
#define FFG_RDESC_ORIG_SIZE 85
+#define FG_RDESC_ORIG_SIZE 82
/* Fixed report descriptors for Logitech Driving Force (and Pro)
* wheel controllers
@@ -381,6 +382,49 @@ static __u8 ffg_rdesc_fixed[] = {
0xC0 /* End Collection */
};
+static __u8 fg_rdesc_fixed[] = {
+0x05, 0x01, /* Usage Page (Desktop), */
+0x09, 0x04, /* Usage (Joystik), */
+0xA1, 0x01, /* Collection (Application), */
+0xA1, 0x02, /* Collection (Logical), */
+0x15, 0x00, /* Logical Minimum (0), */
+0x26, 0xFF, 0x00, /* Logical Maximum (255), */
+0x35, 0x00, /* Physical Minimum (0), */
+0x46, 0xFF, 0x00, /* Physical Maximum (255), */
+0x75, 0x08, /* Report Size (8), */
+0x95, 0x01, /* Report Count (1), */
+0x09, 0x30, /* Usage (X), */
+0x81, 0x02, /* Input (Variable), */
+0xA4, /* Push, */
+0x25, 0x01, /* Logical Maximum (1), */
+0x45, 0x01, /* Physical Maximum (1), */
+0x75, 0x01, /* Report Size (1), */
+0x95, 0x02, /* Report Count (2), */
+0x81, 0x01, /* Input (Constant), */
+0x95, 0x06, /* Report Count (6), */
+0x05, 0x09, /* Usage Page (Button), */
+0x19, 0x01, /* Usage Minimum (01h), */
+0x29, 0x06, /* Usage Maximum (06h), */
+0x81, 0x02, /* Input (Variable), */
+0x05, 0x01, /* Usage Page (Desktop), */
+0xB4, /* Pop, */
+0x81, 0x02, /* Input (Constant), */
+0x09, 0x31, /* Usage (Y), */
+0x81, 0x02, /* Input (Variable), */
+0x09, 0x32, /* Usage (Z), */
+0x81, 0x02, /* Input (Variable), */
+0xC0, /* End Collection, */
+0xA1, 0x02, /* Collection (Logical), */
+0x26, 0xFF, 0x00, /* Logical Maximum (255), */
+0x46, 0xFF, 0x00, /* Physical Maximum (255), */
+0x75, 0x08, /* Report Size (8), */
+0x95, 0x04, /* Report Count (4), */
+0x09, 0x02, /* Usage (02h), */
+0xB1, 0x02, /* Feature (Variable), */
+0xC0, /* End Collection, */
+0xC0 /* End Collection, */
+};
+
/*
* Certain Logitech keyboards send in report #3 keys which are far
* above the logical maximum described in descriptor. This extends
@@ -408,6 +452,19 @@ static __u8 *lg_report_fixup(struct hid_device *hdev, __u8 *rdesc,
switch (hdev->product) {
+ case USB_DEVICE_ID_LOGITECH_WINGMAN_FG:
+ if (*rsize == FG_RDESC_ORIG_SIZE) {
+ hid_info(hdev,
+ "fixing up Logitech Wingman Formula GP report descriptor\n");
+ rdesc = fg_rdesc_fixed;
+ *rsize = sizeof(fg_rdesc_fixed);
+ } else {
+ hid_info(hdev,
+ "rdesc size test failed for formula gp\n");
+ }
+ break;
+
+
case USB_DEVICE_ID_LOGITECH_WINGMAN_FFG:
if (*rsize == FFG_RDESC_ORIG_SIZE) {
hid_info(hdev,
@@ -664,6 +721,7 @@ static int lg_input_mapped(struct hid_device *hdev, struct hid_input *hi,
usage->code == ABS_RZ)) {
switch (hdev->product) {
case USB_DEVICE_ID_LOGITECH_G29_WHEEL:
+ case USB_DEVICE_ID_LOGITECH_WINGMAN_FG:
case USB_DEVICE_ID_LOGITECH_WINGMAN_FFG:
case USB_DEVICE_ID_LOGITECH_WHEEL:
case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL:
@@ -871,6 +929,8 @@ static const struct hid_device_id lg_devices[] = {
.driver_data = LG_NOGET | LG_FF4 },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WII_WHEEL),
.driver_data = LG_FF4 },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_FG),
+ .driver_data = LG_NOGET },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_FFG),
.driver_data = LG_NOGET | LG_FF4 },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2),
diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c
index 512d67e1aae3..a299c9d1605f 100644
--- a/drivers/hid/hid-lg4ff.c
+++ b/drivers/hid/hid-lg4ff.c
@@ -103,6 +103,10 @@ static const signed short lg4ff_wheel_effects[] = {
-1
};
+static const signed short no_wheel_effects[] = {
+ -1
+};
+
struct lg4ff_wheel {
const u32 product_id;
const signed short *ff_effects;
@@ -137,6 +141,7 @@ struct lg4ff_alternate_mode {
};
static const struct lg4ff_wheel lg4ff_devices[] = {
+ {USB_DEVICE_ID_LOGITECH_WINGMAN_FG, no_wheel_effects, 40, 180, NULL},
{USB_DEVICE_ID_LOGITECH_WINGMAN_FFG, lg4ff_wheel_effects, 40, 180, NULL},
{USB_DEVICE_ID_LOGITECH_WHEEL, lg4ff_wheel_effects, 40, 270, NULL},
{USB_DEVICE_ID_LOGITECH_MOMO_WHEEL, lg4ff_wheel_effects, 40, 270, NULL},
@@ -346,6 +351,7 @@ int lg4ff_raw_event(struct hid_device *hdev, struct hid_report *report,
rd[5] = rd[3];
rd[6] = 0x7F;
return 1;
+ case USB_DEVICE_ID_LOGITECH_WINGMAN_FG:
case USB_DEVICE_ID_LOGITECH_WINGMAN_FFG:
case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL:
case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2:
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 19cc980eebce..199cc256e9d9 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -21,6 +21,7 @@
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/sched.h>
+#include <linux/sched/clock.h>
#include <linux/kfifo.h>
#include <linux/input/mt.h>
#include <linux/workqueue.h>
@@ -64,6 +65,14 @@ MODULE_PARM_DESC(disable_tap_to_click,
#define HIDPP_QUIRK_NO_HIDINPUT BIT(23)
#define HIDPP_QUIRK_FORCE_OUTPUT_REPORTS BIT(24)
#define HIDPP_QUIRK_UNIFYING BIT(25)
+#define HIDPP_QUIRK_HI_RES_SCROLL_1P0 BIT(26)
+#define HIDPP_QUIRK_HI_RES_SCROLL_X2120 BIT(27)
+#define HIDPP_QUIRK_HI_RES_SCROLL_X2121 BIT(28)
+
+/* Convenience constant to check for any high-res support. */
+#define HIDPP_QUIRK_HI_RES_SCROLL (HIDPP_QUIRK_HI_RES_SCROLL_1P0 | \
+ HIDPP_QUIRK_HI_RES_SCROLL_X2120 | \
+ HIDPP_QUIRK_HI_RES_SCROLL_X2121)
#define HIDPP_QUIRK_DELAYED_INIT HIDPP_QUIRK_NO_HIDINPUT
@@ -128,6 +137,25 @@ struct hidpp_battery {
bool online;
};
+/**
+ * struct hidpp_scroll_counter - Utility class for processing high-resolution
+ * scroll events.
+ * @dev: the input device for which events should be reported.
+ * @wheel_multiplier: the scalar multiplier to be applied to each wheel event
+ * @remainder: counts the number of high-resolution units moved since the last
+ * low-resolution event (REL_WHEEL or REL_HWHEEL) was sent. Should
+ * only be used by class methods.
+ * @direction: direction of last movement (1 or -1)
+ * @last_time: last event time, used to reset remainder after inactivity
+ */
+struct hidpp_scroll_counter {
+ struct input_dev *dev;
+ int wheel_multiplier;
+ int remainder;
+ int direction;
+ unsigned long long last_time;
+};
+
struct hidpp_device {
struct hid_device *hid_dev;
struct mutex send_mutex;
@@ -149,6 +177,7 @@ struct hidpp_device {
unsigned long capabilities;
struct hidpp_battery battery;
+ struct hidpp_scroll_counter vertical_wheel_counter;
};
/* HID++ 1.0 error codes */
@@ -391,6 +420,67 @@ static void hidpp_prefix_name(char **name, int name_length)
*name = new_name;
}
+/**
+ * hidpp_scroll_counter_handle_scroll() - Send high- and low-resolution scroll
+ * events given a high-resolution wheel
+ * movement.
+ * @counter: a hid_scroll_counter struct describing the wheel.
+ * @hi_res_value: the movement of the wheel, in the mouse's high-resolution
+ * units.
+ *
+ * Given a high-resolution movement, this function converts the movement into
+ * fractions of 120 and emits high-resolution scroll events for the input
+ * device. It also uses the multiplier from &struct hid_scroll_counter to
+ * emit low-resolution scroll events when appropriate for
+ * backwards-compatibility with userspace input libraries.
+ */
+static void hidpp_scroll_counter_handle_scroll(struct hidpp_scroll_counter *counter,
+ int hi_res_value)
+{
+ int low_res_value, remainder, direction;
+ unsigned long long now, previous;
+
+ hi_res_value = hi_res_value * 120/counter->wheel_multiplier;
+ input_report_rel(counter->dev, REL_WHEEL_HI_RES, hi_res_value);
+
+ remainder = counter->remainder;
+ direction = hi_res_value > 0 ? 1 : -1;
+
+ now = sched_clock();
+ previous = counter->last_time;
+ counter->last_time = now;
+ /*
+ * Reset the remainder after a period of inactivity or when the
+ * direction changes. This prevents the REL_WHEEL emulation point
+ * from sliding for devices that don't always provide the same
+ * number of movements per detent.
+ */
+ if (now - previous > 1000000000 || direction != counter->direction)
+ remainder = 0;
+
+ counter->direction = direction;
+ remainder += hi_res_value;
+
+ /* Some wheels will rest 7/8ths of a detent from the previous detent
+ * after slow movement, so we want the threshold for low-res events to
+ * be in the middle between two detents (e.g. after 4/8ths) as
+ * opposed to on the detents themselves (8/8ths).
+ */
+ if (abs(remainder) >= 60) {
+ /* Add (or subtract) 1 because we want to trigger when the wheel
+ * is half-way to the next detent (i.e. scroll 1 detent after a
+ * 1/2 detent movement, 2 detents after a 1 1/2 detent movement,
+ * etc.).
+ */
+ low_res_value = remainder / 120;
+ if (low_res_value == 0)
+ low_res_value = (hi_res_value > 0 ? 1 : -1);
+ input_report_rel(counter->dev, REL_WHEEL, low_res_value);
+ remainder -= low_res_value * 120;
+ }
+ counter->remainder = remainder;
+}
+
/* -------------------------------------------------------------------------- */
/* HIDP++ 1.0 commands */
/* -------------------------------------------------------------------------- */
@@ -400,32 +490,53 @@ static void hidpp_prefix_name(char **name, int name_length)
#define HIDPP_SET_LONG_REGISTER 0x82
#define HIDPP_GET_LONG_REGISTER 0x83
-#define HIDPP_REG_GENERAL 0x00
-
-static int hidpp10_enable_battery_reporting(struct hidpp_device *hidpp_dev)
+/**
+ * hidpp10_set_register_bit() - Sets a single bit in a HID++ 1.0 register.
+ * @hidpp_dev: the device to set the register on.
+ * @register_address: the address of the register to modify.
+ * @byte: the byte of the register to modify. Should be less than 3.
+ * Return: 0 if successful, otherwise a negative error code.
+ */
+static int hidpp10_set_register_bit(struct hidpp_device *hidpp_dev,
+ u8 register_address, u8 byte, u8 bit)
{
struct hidpp_report response;
int ret;
u8 params[3] = { 0 };
ret = hidpp_send_rap_command_sync(hidpp_dev,
- REPORT_ID_HIDPP_SHORT,
- HIDPP_GET_REGISTER,
- HIDPP_REG_GENERAL,
- NULL, 0, &response);
+ REPORT_ID_HIDPP_SHORT,
+ HIDPP_GET_REGISTER,
+ register_address,
+ NULL, 0, &response);
if (ret)
return ret;
memcpy(params, response.rap.params, 3);
- /* Set the battery bit */
- params[0] |= BIT(4);
+ params[byte] |= BIT(bit);
return hidpp_send_rap_command_sync(hidpp_dev,
- REPORT_ID_HIDPP_SHORT,
- HIDPP_SET_REGISTER,
- HIDPP_REG_GENERAL,
- params, 3, &response);
+ REPORT_ID_HIDPP_SHORT,
+ HIDPP_SET_REGISTER,
+ register_address,
+ params, 3, &response);
+}
+
+
+#define HIDPP_REG_GENERAL 0x00
+
+static int hidpp10_enable_battery_reporting(struct hidpp_device *hidpp_dev)
+{
+ return hidpp10_set_register_bit(hidpp_dev, HIDPP_REG_GENERAL, 0, 4);
+}
+
+#define HIDPP_REG_FEATURES 0x01
+
+/* On HID++ 1.0 devices, high-res scroll was called "scrolling acceleration". */
+static int hidpp10_enable_scrolling_acceleration(struct hidpp_device *hidpp_dev)
+{
+ return hidpp10_set_register_bit(hidpp_dev, HIDPP_REG_FEATURES, 0, 6);
}
#define HIDPP_REG_BATTERY_STATUS 0x07
@@ -1137,6 +1248,99 @@ static int hidpp_battery_get_property(struct power_supply *psy,
}
/* -------------------------------------------------------------------------- */
+/* 0x2120: Hi-resolution scrolling */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_HI_RESOLUTION_SCROLLING 0x2120
+
+#define CMD_HI_RESOLUTION_SCROLLING_SET_HIGHRES_SCROLLING_MODE 0x10
+
+static int hidpp_hrs_set_highres_scrolling_mode(struct hidpp_device *hidpp,
+ bool enabled, u8 *multiplier)
+{
+ u8 feature_index;
+ u8 feature_type;
+ int ret;
+ u8 params[1];
+ struct hidpp_report response;
+
+ ret = hidpp_root_get_feature(hidpp,
+ HIDPP_PAGE_HI_RESOLUTION_SCROLLING,
+ &feature_index,
+ &feature_type);
+ if (ret)
+ return ret;
+
+ params[0] = enabled ? BIT(0) : 0;
+ ret = hidpp_send_fap_command_sync(hidpp, feature_index,
+ CMD_HI_RESOLUTION_SCROLLING_SET_HIGHRES_SCROLLING_MODE,
+ params, sizeof(params), &response);
+ if (ret)
+ return ret;
+ *multiplier = response.fap.params[1];
+ return 0;
+}
+
+/* -------------------------------------------------------------------------- */
+/* 0x2121: HiRes Wheel */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_HIRES_WHEEL 0x2121
+
+#define CMD_HIRES_WHEEL_GET_WHEEL_CAPABILITY 0x00
+#define CMD_HIRES_WHEEL_SET_WHEEL_MODE 0x20
+
+static int hidpp_hrw_get_wheel_capability(struct hidpp_device *hidpp,
+ u8 *multiplier)
+{
+ u8 feature_index;
+ u8 feature_type;
+ int ret;
+ struct hidpp_report response;
+
+ ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL,
+ &feature_index, &feature_type);
+ if (ret)
+ goto return_default;
+
+ ret = hidpp_send_fap_command_sync(hidpp, feature_index,
+ CMD_HIRES_WHEEL_GET_WHEEL_CAPABILITY,
+ NULL, 0, &response);
+ if (ret)
+ goto return_default;
+
+ *multiplier = response.fap.params[0];
+ return 0;
+return_default:
+ hid_warn(hidpp->hid_dev,
+ "Couldn't get wheel multiplier (error %d)\n", ret);
+ return ret;
+}
+
+static int hidpp_hrw_set_wheel_mode(struct hidpp_device *hidpp, bool invert,
+ bool high_resolution, bool use_hidpp)
+{
+ u8 feature_index;
+ u8 feature_type;
+ int ret;
+ u8 params[1];
+ struct hidpp_report response;
+
+ ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL,
+ &feature_index, &feature_type);
+ if (ret)
+ return ret;
+
+ params[0] = (invert ? BIT(2) : 0) |
+ (high_resolution ? BIT(1) : 0) |
+ (use_hidpp ? BIT(0) : 0);
+
+ return hidpp_send_fap_command_sync(hidpp, feature_index,
+ CMD_HIRES_WHEEL_SET_WHEEL_MODE,
+ params, sizeof(params), &response);
+}
+
+/* -------------------------------------------------------------------------- */
/* 0x4301: Solar Keyboard */
/* -------------------------------------------------------------------------- */
@@ -1465,7 +1669,7 @@ struct hidpp_ff_work_data {
u8 size;
};
-static const signed short hiddpp_ff_effects[] = {
+static const signed short hidpp_ff_effects[] = {
FF_CONSTANT,
FF_PERIODIC,
FF_SINE,
@@ -1480,7 +1684,7 @@ static const signed short hiddpp_ff_effects[] = {
-1
};
-static const signed short hiddpp_ff_effects_v2[] = {
+static const signed short hidpp_ff_effects_v2[] = {
FF_RAMP,
FF_FRICTION,
FF_INERTIA,
@@ -1873,11 +2077,11 @@ static int hidpp_ff_init(struct hidpp_device *hidpp, u8 feature_index)
version = bcdDevice & 255;
/* Set supported force feedback capabilities */
- for (j = 0; hiddpp_ff_effects[j] >= 0; j++)
- set_bit(hiddpp_ff_effects[j], dev->ffbit);
+ for (j = 0; hidpp_ff_effects[j] >= 0; j++)
+ set_bit(hidpp_ff_effects[j], dev->ffbit);
if (version > 1)
- for (j = 0; hiddpp_ff_effects_v2[j] >= 0; j++)
- set_bit(hiddpp_ff_effects_v2[j], dev->ffbit);
+ for (j = 0; hidpp_ff_effects_v2[j] >= 0; j++)
+ set_bit(hidpp_ff_effects_v2[j], dev->ffbit);
/* Read number of slots available in device */
error = hidpp_send_fap_command_sync(hidpp, feature_index,
@@ -1907,6 +2111,13 @@ static int hidpp_ff_init(struct hidpp_device *hidpp, u8 feature_index)
kfree(data);
return -ENOMEM;
}
+ data->wq = create_singlethread_workqueue("hidpp-ff-sendqueue");
+ if (!data->wq) {
+ kfree(data->effect_ids);
+ kfree(data);
+ return -ENOMEM;
+ }
+
data->hidpp = hidpp;
data->feature_index = feature_index;
data->version = version;
@@ -1951,7 +2162,6 @@ static int hidpp_ff_init(struct hidpp_device *hidpp, u8 feature_index)
/* ignore boost value at response.fap.params[2] */
/* init the hardware command queue */
- data->wq = create_singlethread_workqueue("hidpp-ff-sendqueue");
atomic_set(&data->workqueue_size, 0);
/* initialize with zero autocenter to get wheel in usable state */
@@ -2387,10 +2597,15 @@ static int m560_raw_event(struct hid_device *hdev, u8 *data, int size)
input_report_key(mydata->input, BTN_RIGHT,
!!(data[1] & M560_MOUSE_BTN_RIGHT));
- if (data[1] & M560_MOUSE_BTN_WHEEL_LEFT)
+ if (data[1] & M560_MOUSE_BTN_WHEEL_LEFT) {
input_report_rel(mydata->input, REL_HWHEEL, -1);
- else if (data[1] & M560_MOUSE_BTN_WHEEL_RIGHT)
+ input_report_rel(mydata->input, REL_HWHEEL_HI_RES,
+ -120);
+ } else if (data[1] & M560_MOUSE_BTN_WHEEL_RIGHT) {
input_report_rel(mydata->input, REL_HWHEEL, 1);
+ input_report_rel(mydata->input, REL_HWHEEL_HI_RES,
+ 120);
+ }
v = hid_snto32(hid_field_extract(hdev, data+3, 0, 12), 12);
input_report_rel(mydata->input, REL_X, v);
@@ -2399,7 +2614,9 @@ static int m560_raw_event(struct hid_device *hdev, u8 *data, int size)
input_report_rel(mydata->input, REL_Y, v);
v = hid_snto32(data[6], 8);
- input_report_rel(mydata->input, REL_WHEEL, v);
+ if (v != 0)
+ hidpp_scroll_counter_handle_scroll(
+ &hidpp->vertical_wheel_counter, v);
input_sync(mydata->input);
}
@@ -2426,6 +2643,8 @@ static void m560_populate_input(struct hidpp_device *hidpp,
__set_bit(REL_Y, mydata->input->relbit);
__set_bit(REL_WHEEL, mydata->input->relbit);
__set_bit(REL_HWHEEL, mydata->input->relbit);
+ __set_bit(REL_WHEEL_HI_RES, mydata->input->relbit);
+ __set_bit(REL_HWHEEL_HI_RES, mydata->input->relbit);
}
static int m560_input_mapping(struct hid_device *hdev, struct hid_input *hi,
@@ -2528,6 +2747,37 @@ static int g920_get_config(struct hidpp_device *hidpp)
}
/* -------------------------------------------------------------------------- */
+/* High-resolution scroll wheels */
+/* -------------------------------------------------------------------------- */
+
+static int hi_res_scroll_enable(struct hidpp_device *hidpp)
+{
+ int ret;
+ u8 multiplier = 1;
+
+ if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL_X2121) {
+ ret = hidpp_hrw_set_wheel_mode(hidpp, false, true, false);
+ if (ret == 0)
+ ret = hidpp_hrw_get_wheel_capability(hidpp, &multiplier);
+ } else if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL_X2120) {
+ ret = hidpp_hrs_set_highres_scrolling_mode(hidpp, true,
+ &multiplier);
+ } else /* if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL_1P0) */ {
+ ret = hidpp10_enable_scrolling_acceleration(hidpp);
+ multiplier = 8;
+ }
+ if (ret)
+ return ret;
+
+ if (multiplier == 0)
+ multiplier = 1;
+
+ hidpp->vertical_wheel_counter.wheel_multiplier = multiplier;
+ hid_info(hidpp->hid_dev, "multiplier = %d\n", multiplier);
+ return 0;
+}
+
+/* -------------------------------------------------------------------------- */
/* Generic HID++ devices */
/* -------------------------------------------------------------------------- */
@@ -2572,6 +2822,9 @@ static void hidpp_populate_input(struct hidpp_device *hidpp,
wtp_populate_input(hidpp, input, origin_is_hid_core);
else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
m560_populate_input(hidpp, input, origin_is_hid_core);
+
+ if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL)
+ hidpp->vertical_wheel_counter.dev = input;
}
static int hidpp_input_configured(struct hid_device *hdev,
@@ -2690,6 +2943,27 @@ static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report,
return 0;
}
+static int hidpp_event(struct hid_device *hdev, struct hid_field *field,
+ struct hid_usage *usage, __s32 value)
+{
+ /* This function will only be called for scroll events, due to the
+ * restriction imposed in hidpp_usages.
+ */
+ struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+ struct hidpp_scroll_counter *counter = &hidpp->vertical_wheel_counter;
+ /* A scroll event may occur before the multiplier has been retrieved or
+ * the input device set, or high-res scroll enabling may fail. In such
+ * cases we must return early (falling back to default behaviour) to
+ * avoid a crash in hidpp_scroll_counter_handle_scroll.
+ */
+ if (!(hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL) || value == 0
+ || counter->dev == NULL || counter->wheel_multiplier == 0)
+ return 0;
+
+ hidpp_scroll_counter_handle_scroll(counter, value);
+ return 1;
+}
+
static int hidpp_initialize_battery(struct hidpp_device *hidpp)
{
static atomic_t battery_no = ATOMIC_INIT(0);
@@ -2901,6 +3175,9 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
if (hidpp->battery.ps)
power_supply_changed(hidpp->battery.ps);
+ if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL)
+ hi_res_scroll_enable(hidpp);
+
if (!(hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT) || hidpp->delayed_input)
/* if the input nodes are already created, we can stop now */
return;
@@ -3086,35 +3363,63 @@ static void hidpp_remove(struct hid_device *hdev)
mutex_destroy(&hidpp->send_mutex);
}
+#define LDJ_DEVICE(product) \
+ HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, \
+ USB_VENDOR_ID_LOGITECH, (product))
+
static const struct hid_device_id hidpp_devices[] = {
{ /* wireless touchpad */
- HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
- USB_VENDOR_ID_LOGITECH, 0x4011),
+ LDJ_DEVICE(0x4011),
.driver_data = HIDPP_QUIRK_CLASS_WTP | HIDPP_QUIRK_DELAYED_INIT |
HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS },
{ /* wireless touchpad T650 */
- HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
- USB_VENDOR_ID_LOGITECH, 0x4101),
+ LDJ_DEVICE(0x4101),
.driver_data = HIDPP_QUIRK_CLASS_WTP | HIDPP_QUIRK_DELAYED_INIT },
{ /* wireless touchpad T651 */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_T651),
.driver_data = HIDPP_QUIRK_CLASS_WTP },
+ { /* Mouse Logitech Anywhere MX */
+ LDJ_DEVICE(0x1017), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_1P0 },
+ { /* Mouse Logitech Cube */
+ LDJ_DEVICE(0x4010), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2120 },
+ { /* Mouse Logitech M335 */
+ LDJ_DEVICE(0x4050), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
+ { /* Mouse Logitech M515 */
+ LDJ_DEVICE(0x4007), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2120 },
{ /* Mouse logitech M560 */
- HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
- USB_VENDOR_ID_LOGITECH, 0x402d),
- .driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_CLASS_M560 },
+ LDJ_DEVICE(0x402d),
+ .driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_CLASS_M560
+ | HIDPP_QUIRK_HI_RES_SCROLL_X2120 },
+ { /* Mouse Logitech M705 (firmware RQM17) */
+ LDJ_DEVICE(0x101b), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_1P0 },
+ { /* Mouse Logitech M705 (firmware RQM67) */
+ LDJ_DEVICE(0x406d), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
+ { /* Mouse Logitech M720 */
+ LDJ_DEVICE(0x405e), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
+ { /* Mouse Logitech MX Anywhere 2 */
+ LDJ_DEVICE(0x404a), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
+ { LDJ_DEVICE(0xb013), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
+ { LDJ_DEVICE(0xb018), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
+ { LDJ_DEVICE(0xb01f), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
+ { /* Mouse Logitech MX Anywhere 2S */
+ LDJ_DEVICE(0x406a), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
+ { /* Mouse Logitech MX Master */
+ LDJ_DEVICE(0x4041), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
+ { LDJ_DEVICE(0x4060), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
+ { LDJ_DEVICE(0x4071), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
+ { /* Mouse Logitech MX Master 2S */
+ LDJ_DEVICE(0x4069), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
+ { /* Mouse Logitech Performance MX */
+ LDJ_DEVICE(0x101a), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_1P0 },
{ /* Keyboard logitech K400 */
- HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
- USB_VENDOR_ID_LOGITECH, 0x4024),
+ LDJ_DEVICE(0x4024),
.driver_data = HIDPP_QUIRK_CLASS_K400 },
{ /* Solar Keyboard Logitech K750 */
- HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
- USB_VENDOR_ID_LOGITECH, 0x4002),
+ LDJ_DEVICE(0x4002),
.driver_data = HIDPP_QUIRK_CLASS_K750 },
- { HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
- USB_VENDOR_ID_LOGITECH, HID_ANY_ID)},
+ { LDJ_DEVICE(HID_ANY_ID) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G920_WHEEL),
.driver_data = HIDPP_QUIRK_CLASS_G920 | HIDPP_QUIRK_FORCE_OUTPUT_REPORTS},
@@ -3123,12 +3428,19 @@ static const struct hid_device_id hidpp_devices[] = {
MODULE_DEVICE_TABLE(hid, hidpp_devices);
+static const struct hid_usage_id hidpp_usages[] = {
+ { HID_GD_WHEEL, EV_REL, REL_WHEEL_HI_RES },
+ { HID_ANY_ID - 1, HID_ANY_ID - 1, HID_ANY_ID - 1}
+};
+
static struct hid_driver hidpp_driver = {
.name = "logitech-hidpp-device",
.id_table = hidpp_devices,
.probe = hidpp_probe,
.remove = hidpp_remove,
.raw_event = hidpp_raw_event,
+ .usage_table = hidpp_usages,
+ .event = hidpp_event,
.input_configured = hidpp_input_configured,
.input_mapping = hidpp_input_mapping,
.input_mapped = hidpp_input_mapped,
diff --git a/drivers/hid/hid-maltron.c b/drivers/hid/hid-maltron.c
new file mode 100644
index 000000000000..dcd6db6a646e
--- /dev/null
+++ b/drivers/hid/hid-maltron.c
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * HID driver for Maltron L90
+ *
+ * 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) 2008 Jiri Slaby
+ * Copyright (c) 2012 David Dillow <dave@thedillows.org>
+ * Copyright (c) 2006-2013 Jiri Kosina
+ * Copyright (c) 2013 Colin Leitner <colin.leitner@gmail.com>
+ * Copyright (c) 2014-2016 Frank Praznik <frank.praznik@gmail.com>
+ * Copyright (c) 2010 Richard Nauber <Richard.Nauber@gmail.com>
+ * Copyright (c) 2016 Yuxuan Shui <yshuiv7@gmail.com>
+ * Copyright (c) 2018 William Whistler <wtbw@wtbw.co.uk>
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+/* The original buggy USB descriptor */
+static u8 maltron_rdesc_o[] = {
+ 0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */
+ 0x09, 0x80, /* Usage (Sys Control) */
+ 0xA1, 0x01, /* Collection (Application) */
+ 0x85, 0x02, /* Report ID (2) */
+ 0x75, 0x01, /* Report Size (1) */
+ 0x95, 0x01, /* Report Count (1) */
+ 0x15, 0x00, /* Logical Minimum (0) */
+ 0x25, 0x01, /* Logical Maximum (1) */
+ 0x09, 0x82, /* Usage (Sys Sleep) */
+ 0x81, 0x06, /* Input (Data,Var,Rel) */
+ 0x09, 0x82, /* Usage (Sys Sleep) */
+ 0x81, 0x06, /* Input (Data,Var,Rel) */
+ 0x09, 0x83, /* Usage (Sys Wake Up) */
+ 0x81, 0x06, /* Input (Data,Var,Rel) */
+ 0x75, 0x05, /* Report Size (5) */
+ 0x81, 0x01, /* Input (Const,Array,Abs) */
+ 0xC0, /* End Collection */
+ 0x05, 0x0C, /* Usage Page (Consumer) */
+ 0x09, 0x01, /* Usage (Consumer Control) */
+ 0xA1, 0x01, /* Collection (Application) */
+ 0x85, 0x03, /* Report ID (3) */
+ 0x95, 0x01, /* Report Count (1) */
+ 0x75, 0x10, /* Report Size (16) */
+ 0x19, 0x00, /* Usage Minimum (Unassigned) */
+ 0x2A, 0xFF, 0x7F, /* Usage Maximum (0x7FFF) */
+ 0x81, 0x00, /* Input (Data,Array,Abs) */
+ 0xC0, /* End Collection */
+ 0x06, 0x7F, 0xFF, /* Usage Page (Vendor Defined 0xFF7F) */
+ 0x09, 0x01, /* Usage (0x01) */
+ 0xA1, 0x01, /* Collection (Application) */
+ 0x85, 0x04, /* Report ID (4) */
+ 0x95, 0x01, /* Report Count (1) */
+ 0x75, 0x10, /* Report Size (16) */
+ 0x19, 0x00, /* Usage Minimum (0x00) */
+ 0x2A, 0xFF, 0x7F, /* Usage Maximum (0x7FFF) */
+ 0x81, 0x00, /* Input (Data,Array,Abs) */
+ 0x75, 0x02, /* Report Size (2) */
+ 0x25, 0x02, /* Logical Maximum (2) */
+ 0x09, 0x90, /* Usage (0x90) */
+ 0xB1, 0x02, /* Feature (Data,Var,Abs) */
+ 0x75, 0x06, /* Report Size (6) */
+ 0xB1, 0x01, /* Feature (Const,Array,Abs) */
+ 0x75, 0x01, /* Report Size (1) */
+ 0x25, 0x01, /* Logical Maximum (1) */
+ 0x05, 0x08, /* Usage Page (LEDs) */
+ 0x09, 0x2A, /* Usage (On-Line) */
+ 0x91, 0x02, /* Output (Data,Var,Abs) */
+ 0x09, 0x4B, /* Usage (Generic Indicator) */
+ 0x91, 0x02, /* Output (Data,Var,Abs) */
+ 0x75, 0x06, /* Report Size (6) */
+ 0x95, 0x01, /* Report Count (1) */
+ 0x91, 0x01, /* Output (Const,Array,Abs) */
+ 0xC0 /* End Collection */
+};
+
+/* The patched descriptor, allowing media key events to be accepted as valid */
+static u8 maltron_rdesc[] = {
+ 0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */
+ 0x09, 0x80, /* Usage (Sys Control) */
+ 0xA1, 0x01, /* Collection (Application) */
+ 0x85, 0x02, /* Report ID (2) */
+ 0x75, 0x01, /* Report Size (1) */
+ 0x95, 0x01, /* Report Count (1) */
+ 0x15, 0x00, /* Logical Minimum (0) */
+ 0x25, 0x01, /* Logical Maximum (1) */
+ 0x09, 0x82, /* Usage (Sys Sleep) */
+ 0x81, 0x06, /* Input (Data,Var,Rel) */
+ 0x09, 0x82, /* Usage (Sys Sleep) */
+ 0x81, 0x06, /* Input (Data,Var,Rel) */
+ 0x09, 0x83, /* Usage (Sys Wake Up) */
+ 0x81, 0x06, /* Input (Data,Var,Rel) */
+ 0x75, 0x05, /* Report Size (5) */
+ 0x81, 0x01, /* Input (Const,Array,Abs) */
+ 0xC0, /* End Collection */
+ 0x05, 0x0C, /* Usage Page (Consumer) */
+ 0x09, 0x01, /* Usage (Consumer Control) */
+ 0xA1, 0x01, /* Collection (Application) */
+ 0x85, 0x03, /* Report ID (3) */
+ 0x15, 0x00, /* Logical Minimum (0) - changed */
+ 0x26, 0xFF, 0x7F, /* Logical Maximum (32767) - changed */
+ 0x95, 0x01, /* Report Count (1) */
+ 0x75, 0x10, /* Report Size (16) */
+ 0x19, 0x00, /* Usage Minimum (Unassigned) */
+ 0x2A, 0xFF, 0x7F, /* Usage Maximum (0x7FFF) */
+ 0x81, 0x00, /* Input (Data,Array,Abs) */
+ 0xC0, /* End Collection */
+ 0x06, 0x7F, 0xFF, /* Usage Page (Vendor Defined 0xFF7F) */
+ 0x09, 0x01, /* Usage (0x01) */
+ 0xA1, 0x01, /* Collection (Application) */
+ 0x85, 0x04, /* Report ID (4) */
+ 0x95, 0x01, /* Report Count (1) */
+ 0x75, 0x10, /* Report Size (16) */
+ 0x19, 0x00, /* Usage Minimum (0x00) */
+ 0x2A, 0xFF, 0x7F, /* Usage Maximum (0x7FFF) */
+ 0x81, 0x00, /* Input (Data,Array,Abs) */
+ 0x75, 0x02, /* Report Size (2) */
+ 0x25, 0x02, /* Logical Maximum (2) */
+ 0x09, 0x90, /* Usage (0x90) */
+ 0xB1, 0x02, /* Feature (Data,Var,Abs) */
+ 0x75, 0x06, /* Report Size (6) */
+ 0xB1, 0x01, /* Feature (Const,Array,Abs) */
+ 0x75, 0x01, /* Report Size (1) */
+ 0x25, 0x01, /* Logical Maximum (1) */
+ 0x05, 0x08, /* Usage Page (LEDs) */
+ 0x09, 0x2A, /* Usage (On-Line) */
+ 0x91, 0x02, /* Output (Data,Var,Abs) */
+ 0x09, 0x4B, /* Usage (Generic Indicator) */
+ 0x91, 0x02, /* Output (Data,Var,Abs) */
+ 0x75, 0x06, /* Report Size (6) */
+ 0x95, 0x01, /* Report Count (1) */
+ 0x91, 0x01, /* Output (Const,Array,Abs) */
+ 0xC0 /* End Collection */
+};
+
+static __u8 *maltron_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int *rsize)
+{
+ if (*rsize == sizeof(maltron_rdesc_o) &&
+ !memcmp(maltron_rdesc_o, rdesc, sizeof(maltron_rdesc_o))) {
+ hid_info(hdev, "Replacing Maltron L90 keyboard report descriptor\n");
+ *rsize = sizeof(maltron_rdesc);
+ return maltron_rdesc;
+ }
+ return rdesc;
+}
+
+static const struct hid_device_id maltron_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_ALCOR, USB_DEVICE_ID_ALCOR_MALTRON_KB)},
+ { }
+};
+MODULE_DEVICE_TABLE(hid, maltron_devices);
+
+static struct hid_driver maltron_driver = {
+ .name = "maltron",
+ .id_table = maltron_devices,
+ .report_fixup = maltron_report_fixup
+};
+module_hid_driver(maltron_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
index dca0a3a90fb8..c02d4cad1893 100644
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -1780,6 +1780,12 @@ static const struct hid_device_id mt_devices[] = {
USB_VENDOR_ID_LENOVO,
USB_DEVICE_ID_LENOVO_X1_TAB) },
+ /* Lenovo X1 TAB Gen 3 */
+ { .driver_data = MT_CLS_WIN_8_DUAL,
+ HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8,
+ USB_VENDOR_ID_LENOVO,
+ USB_DEVICE_ID_LENOVO_X1_TAB3) },
+
/* Anton devices */
{ .driver_data = MT_CLS_EXPORT_ALL_INPUTS,
MT_USB_DEVICE(USB_VENDOR_ID_ANTON,
diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
index 94088c0ed68a..77ffba48cc73 100644
--- a/drivers/hid/hid-quirks.c
+++ b/drivers/hid/hid-quirks.c
@@ -99,6 +99,7 @@ static const struct hid_device_id hid_quirks[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X), HID_QUIRK_MULTI_INPUT },
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2), HID_QUIRK_MULTI_INPUT },
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_PENSKETCH_M912), HID_QUIRK_MULTI_INPUT },
+ { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M406XE), HID_QUIRK_MULTI_INPUT },
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_PIXART_USB_OPTICAL_MOUSE_ID2), HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_C007), HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_C077), HID_QUIRK_ALWAYS_POLL },
@@ -411,11 +412,6 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_GENIUS_MANTICORE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_GENIUS_GX_IMPERATOR) },
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_ERGO_525V) },
- { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_I405X) },
- { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X) },
- { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2) },
- { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M610X) },
- { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_PENSKETCH_M912) },
#endif
#if IS_ENABLED(CONFIG_HID_LCPOWER)
{ HID_USB_DEVICE(USB_VENDOR_ID_LCPOWER, USB_DEVICE_ID_LCPOWER_LC1000) },
@@ -451,6 +447,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2_2) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G29_WHEEL) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_F3D) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_FG) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_FFG) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_FORCE3D_PRO) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_FLIGHT_SYSTEM_G940) },
@@ -673,35 +670,9 @@ static const struct hid_device_id hid_have_special_driver[] = {
#if IS_ENABLED(CONFIG_HID_TWINHAN)
{ HID_USB_DEVICE(USB_VENDOR_ID_TWINHAN, USB_DEVICE_ID_TWINHAN_IR_REMOTE) },
#endif
-#if IS_ENABLED(CONFIG_HID_UCLOGIC)
- { HID_USB_DEVICE(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_TABLET) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_HUION_TABLET) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_PF1209) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_WP4030U) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_WP5540U) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_WP1062) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_YIYNOVA_TABLET) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UGEE_TABLET_81) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UGEE_TABLET_45) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_TABLET_EX07S) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UGTIZER, USB_DEVICE_ID_UGTIZER_TABLET_GP0610) },
-#endif
#if IS_ENABLED(CONFIG_HID_UDRAW_PS3)
{ HID_USB_DEVICE(USB_VENDOR_ID_THQ, USB_DEVICE_ID_THQ_PS3_UDRAW) },
#endif
-#if IS_ENABLED(CONFIG_HID_WALTOP)
- { HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_SLIM_TABLET_5_8_INCH) },
- { HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_SLIM_TABLET_12_1_INCH) },
- { HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_Q_PAD) },
- { HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_PID_0038) },
- { HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_MEDIA_TABLET_10_6_INCH) },
- { HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_MEDIA_TABLET_14_1_INCH) },
- { HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_SIRIUS_BATTERY_FREE_TABLET) },
-#endif
#if IS_ENABLED(CONFIG_HID_XINMO)
{ HID_USB_DEVICE(USB_VENDOR_ID_XIN_MO, USB_DEVICE_ID_XIN_MO_DUAL_ARCADE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_XIN_MO, USB_DEVICE_ID_THT_2P_ARCADE) },
@@ -744,7 +715,6 @@ static const struct hid_device_id hid_ignore_list[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_DEALEXTREAME, USB_DEVICE_ID_DEALEXTREAME_RADIO_SI4701) },
{ HID_USB_DEVICE(USB_VENDOR_ID_DELORME, USB_DEVICE_ID_DELORME_EARTHMATE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_DELORME, USB_DEVICE_ID_DELORME_EM_LT20) },
- { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, 0x0400) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ESSENTIAL_REALITY, USB_DEVICE_ID_ESSENTIAL_REALITY_P5) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ETT, USB_DEVICE_ID_TC5UH) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ETT, USB_DEVICE_ID_TC4UM) },
@@ -884,7 +854,7 @@ static const struct hid_device_id hid_ignore_list[] = {
{ }
};
-/**
+/*
* hid_mouse_ignore_list - mouse devices which should not be handled by the hid layer
*
* There are composite devices for which we want to ignore only a certain
@@ -1025,6 +995,10 @@ bool hid_ignore(struct hid_device *hdev)
if (hdev->product == 0x0401 &&
strncmp(hdev->name, "ELAN0800", 8) != 0)
return true;
+ /* Same with product id 0x0400 */
+ if (hdev->product == 0x0400 &&
+ strncmp(hdev->name, "QTEC0001", 8) != 0)
+ return true;
break;
}
@@ -1071,7 +1045,7 @@ static struct hid_device_id *hid_exists_dquirk(const struct hid_device *hdev)
}
if (bl_entry != NULL)
- dbg_hid("Found dynamic quirk 0x%lx for HID device 0x%hx:0x%hx\n",
+ dbg_hid("Found dynamic quirk 0x%lx for HID device 0x%04x:0x%04x\n",
bl_entry->driver_data, bl_entry->vendor,
bl_entry->product);
@@ -1238,7 +1212,7 @@ static unsigned long hid_gets_squirk(const struct hid_device *hdev)
quirks |= bl_entry->driver_data;
if (quirks)
- dbg_hid("Found squirk 0x%lx for HID device 0x%hx:0x%hx\n",
+ dbg_hid("Found squirk 0x%lx for HID device 0x%04x:0x%04x\n",
quirks, hdev->vendor, hdev->product);
return quirks;
}
diff --git a/drivers/hid/hid-roccat-kone.c b/drivers/hid/hid-roccat-kone.c
index bf4675a27396..c4dd6162c1d6 100644
--- a/drivers/hid/hid-roccat-kone.c
+++ b/drivers/hid/hid-roccat-kone.c
@@ -783,6 +783,7 @@ static void kone_keep_values_up_to_date(struct kone_device *kone,
case kone_mouse_event_switch_profile:
kone->actual_dpi = kone->profiles[event->value - 1].
startup_dpi;
+ /* fall through */
case kone_mouse_event_osd_profile:
kone->actual_profile = event->value;
break;
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
index 9671a4bad643..26fae90b931a 100644
--- a/drivers/hid/hid-sony.c
+++ b/drivers/hid/hid-sony.c
@@ -58,6 +58,7 @@
#define FUTUREMAX_DANCE_MAT BIT(13)
#define NSG_MR5U_REMOTE_BT BIT(14)
#define NSG_MR7U_REMOTE_BT BIT(15)
+#define SHANWAN_GAMEPAD BIT(16)
#define SIXAXIS_CONTROLLER (SIXAXIS_CONTROLLER_USB | SIXAXIS_CONTROLLER_BT)
#define MOTION_CONTROLLER (MOTION_CONTROLLER_USB | MOTION_CONTROLLER_BT)
@@ -1490,6 +1491,7 @@ static int sony_register_sensors(struct sony_sc *sc)
*/
static int sixaxis_set_operational_usb(struct hid_device *hdev)
{
+ struct sony_sc *sc = hid_get_drvdata(hdev);
const int buf_size =
max(SIXAXIS_REPORT_0xF2_SIZE, SIXAXIS_REPORT_0xF5_SIZE);
u8 *buf;
@@ -1519,14 +1521,15 @@ static int sixaxis_set_operational_usb(struct hid_device *hdev)
/*
* But the USB interrupt would cause SHANWAN controllers to
- * start rumbling non-stop.
+ * start rumbling non-stop, so skip step 3 for these controllers.
*/
- if (strcmp(hdev->name, "SHANWAN PS3 GamePad")) {
- ret = hid_hw_output_report(hdev, buf, 1);
- if (ret < 0) {
- hid_info(hdev, "can't set operational mode: step 3, ignoring\n");
- ret = 0;
- }
+ if (sc->quirks & SHANWAN_GAMEPAD)
+ goto out;
+
+ ret = hid_hw_output_report(hdev, buf, 1);
+ if (ret < 0) {
+ hid_info(hdev, "can't set operational mode: step 3, ignoring\n");
+ ret = 0;
}
out:
@@ -2097,9 +2100,14 @@ static void sixaxis_send_output_report(struct sony_sc *sc)
}
}
- hid_hw_raw_request(sc->hdev, report->report_id, (u8 *)report,
- sizeof(struct sixaxis_output_report),
- HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
+ /* SHANWAN controllers require output reports via intr channel */
+ if (sc->quirks & SHANWAN_GAMEPAD)
+ hid_hw_output_report(sc->hdev, (u8 *)report,
+ sizeof(struct sixaxis_output_report));
+ else
+ hid_hw_raw_request(sc->hdev, report->report_id, (u8 *)report,
+ sizeof(struct sixaxis_output_report),
+ HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
}
static void dualshock4_send_output_report(struct sony_sc *sc)
@@ -2811,6 +2819,9 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)
if (!strcmp(hdev->name, "FutureMax Dance Mat"))
quirks |= FUTUREMAX_DANCE_MAT;
+ if (!strcmp(hdev->name, "SHANWAN PS3 GamePad"))
+ quirks |= SHANWAN_GAMEPAD;
+
sc = devm_kzalloc(&hdev->dev, sizeof(*sc), GFP_KERNEL);
if (sc == NULL) {
hid_err(hdev, "can't alloc sony descriptor\n");
diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
index dc4128bfe2ca..8dae0f9b819e 100644
--- a/drivers/hid/hid-steam.c
+++ b/drivers/hid/hid-steam.c
@@ -283,11 +283,6 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
static int steam_input_open(struct input_dev *dev)
{
struct steam_device *steam = input_get_drvdata(dev);
- int ret;
-
- ret = hid_hw_open(steam->hdev);
- if (ret)
- return ret;
mutex_lock(&steam->mutex);
if (!steam->client_opened && lizard_mode)
@@ -304,8 +299,6 @@ static void steam_input_close(struct input_dev *dev)
if (!steam->client_opened && lizard_mode)
steam_set_lizard_mode(steam, true);
mutex_unlock(&steam->mutex);
-
- hid_hw_close(steam->hdev);
}
static enum power_supply_property steam_battery_props[] = {
@@ -506,6 +499,7 @@ static void steam_battery_unregister(struct steam_device *steam)
static int steam_register(struct steam_device *steam)
{
int ret;
+ bool client_opened;
/*
* This function can be called several times in a row with the
@@ -518,9 +512,11 @@ static int steam_register(struct steam_device *steam)
* Unlikely, but getting the serial could fail, and it is not so
* important, so make up a serial number and go on.
*/
+ mutex_lock(&steam->mutex);
if (steam_get_serial(steam) < 0)
strlcpy(steam->serial_no, "XXXXXXXXXX",
sizeof(steam->serial_no));
+ mutex_unlock(&steam->mutex);
hid_info(steam->hdev, "Steam Controller '%s' connected",
steam->serial_no);
@@ -535,13 +531,15 @@ static int steam_register(struct steam_device *steam)
}
mutex_lock(&steam->mutex);
- if (!steam->client_opened) {
+ client_opened = steam->client_opened;
+ if (!client_opened)
steam_set_lizard_mode(steam, lizard_mode);
+ mutex_unlock(&steam->mutex);
+
+ if (!client_opened)
ret = steam_input_register(steam);
- } else {
+ else
ret = 0;
- }
- mutex_unlock(&steam->mutex);
return ret;
}
@@ -623,11 +621,6 @@ static void steam_client_ll_stop(struct hid_device *hdev)
static int steam_client_ll_open(struct hid_device *hdev)
{
struct steam_device *steam = hdev->driver_data;
- int ret;
-
- ret = hid_hw_open(steam->hdev);
- if (ret)
- return ret;
mutex_lock(&steam->mutex);
steam->client_opened = true;
@@ -635,22 +628,28 @@ static int steam_client_ll_open(struct hid_device *hdev)
steam_input_unregister(steam);
- return ret;
+ return 0;
}
static void steam_client_ll_close(struct hid_device *hdev)
{
struct steam_device *steam = hdev->driver_data;
+ unsigned long flags;
+ bool connected;
+
+ spin_lock_irqsave(&steam->lock, flags);
+ connected = steam->connected;
+ spin_unlock_irqrestore(&steam->lock, flags);
+
mutex_lock(&steam->mutex);
steam->client_opened = false;
+ if (connected)
+ steam_set_lizard_mode(steam, lizard_mode);
mutex_unlock(&steam->mutex);
- hid_hw_close(steam->hdev);
- if (steam->connected) {
- steam_set_lizard_mode(steam, lizard_mode);
+ if (connected)
steam_input_register(steam);
- }
}
static int steam_client_ll_raw_request(struct hid_device *hdev,
@@ -759,14 +758,15 @@ static int steam_probe(struct hid_device *hdev,
if (ret)
goto client_hdev_add_fail;
+ ret = hid_hw_open(hdev);
+ if (ret) {
+ hid_err(hdev,
+ "%s:hid_hw_open\n",
+ __func__);
+ goto hid_hw_open_fail;
+ }
+
if (steam->quirks & STEAM_QUIRK_WIRELESS) {
- ret = hid_hw_open(hdev);
- if (ret) {
- hid_err(hdev,
- "%s:hid_hw_open for wireless\n",
- __func__);
- goto hid_hw_open_fail;
- }
hid_info(hdev, "Steam wireless receiver connected");
steam_request_conn_status(steam);
} else {
@@ -781,8 +781,8 @@ static int steam_probe(struct hid_device *hdev,
return 0;
-hid_hw_open_fail:
input_register_fail:
+hid_hw_open_fail:
client_hdev_add_fail:
hid_hw_stop(hdev);
hid_hw_start_fail:
@@ -809,8 +809,8 @@ static void steam_remove(struct hid_device *hdev)
cancel_work_sync(&steam->work_connect);
if (steam->quirks & STEAM_QUIRK_WIRELESS) {
hid_info(hdev, "Steam wireless receiver disconnected");
- hid_hw_close(hdev);
}
+ hid_hw_close(hdev);
hid_hw_stop(hdev);
steam_unregister(steam);
}
diff --git a/drivers/hid/hid-topseed.c b/drivers/hid/hid-topseed.c
index 8a5b843e9dd6..e9cdde840362 100644
--- a/drivers/hid/hid-topseed.c
+++ b/drivers/hid/hid-topseed.c
@@ -34,7 +34,9 @@ static int ts_input_mapping(struct hid_device *hdev, struct hid_input *hi,
return 0;
switch (usage->hid & HID_USAGE) {
+ case 0x00c: ts_map_key_clear(KEY_WLAN); break;
case 0x00d: ts_map_key_clear(KEY_MEDIA); break;
+ case 0x010: ts_map_key_clear(KEY_ZOOM); break;
case 0x024: ts_map_key_clear(KEY_MENU); break;
case 0x025: ts_map_key_clear(KEY_TV); break;
case 0x027: ts_map_key_clear(KEY_MODE); break;
@@ -67,6 +69,7 @@ static const struct hid_device_id ts_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_BTC, USB_DEVICE_ID_BTC_EMPREX_REMOTE_2) },
{ HID_USB_DEVICE(USB_VENDOR_ID_TOPSEED2, USB_DEVICE_ID_TOPSEED2_RF_COMBO) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_TOSHIBA_WT10A) },
{ }
};
MODULE_DEVICE_TABLE(hid, ts_devices);
diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c
new file mode 100644
index 000000000000..8fe02d81265d
--- /dev/null
+++ b/drivers/hid/hid-uclogic-core.c
@@ -0,0 +1,418 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * HID driver for UC-Logic devices not fully compliant with HID standard
+ *
+ * Copyright (c) 2010-2014 Nikolai Kondrashov
+ * Copyright (c) 2013 Martin Rusko
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/timer.h>
+#include "usbhid/usbhid.h"
+#include "hid-uclogic-params.h"
+
+#include "hid-ids.h"
+
+/* Driver data */
+struct uclogic_drvdata {
+ /* Interface parameters */
+ struct uclogic_params params;
+ /* Pointer to the replacement report descriptor. NULL if none. */
+ __u8 *desc_ptr;
+ /*
+ * Size of the replacement report descriptor.
+ * Only valid if desc_ptr is not NULL
+ */
+ unsigned int desc_size;
+ /* Pen input device */
+ struct input_dev *pen_input;
+ /* In-range timer */
+ struct timer_list inrange_timer;
+ /* Last rotary encoder state, or U8_MAX for none */
+ u8 re_state;
+};
+
+/**
+ * uclogic_inrange_timeout - handle pen in-range state timeout.
+ * Emulate input events normally generated when pen goes out of range for
+ * tablets which don't report that.
+ *
+ * @t: The timer the timeout handler is attached to, stored in a struct
+ * uclogic_drvdata.
+ */
+static void uclogic_inrange_timeout(struct timer_list *t)
+{
+ struct uclogic_drvdata *drvdata = from_timer(drvdata, t,
+ inrange_timer);
+ struct input_dev *input = drvdata->pen_input;
+
+ if (input == NULL)
+ return;
+ input_report_abs(input, ABS_PRESSURE, 0);
+ /* If BTN_TOUCH state is changing */
+ if (test_bit(BTN_TOUCH, input->key)) {
+ input_event(input, EV_MSC, MSC_SCAN,
+ /* Digitizer Tip Switch usage */
+ 0xd0042);
+ input_report_key(input, BTN_TOUCH, 0);
+ }
+ input_report_key(input, BTN_TOOL_PEN, 0);
+ input_sync(input);
+}
+
+static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int *rsize)
+{
+ struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
+
+ if (drvdata->desc_ptr != NULL) {
+ rdesc = drvdata->desc_ptr;
+ *rsize = drvdata->desc_size;
+ }
+ return rdesc;
+}
+
+static int uclogic_input_mapping(struct hid_device *hdev,
+ struct hid_input *hi,
+ struct hid_field *field,
+ struct hid_usage *usage,
+ unsigned long **bit,
+ int *max)
+{
+ struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
+ struct uclogic_params *params = &drvdata->params;
+
+ /* discard the unused pen interface */
+ if (params->pen_unused && (field->application == HID_DG_PEN))
+ return -1;
+
+ /* let hid-core decide what to do */
+ return 0;
+}
+
+static int uclogic_input_configured(struct hid_device *hdev,
+ struct hid_input *hi)
+{
+ struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
+ struct uclogic_params *params = &drvdata->params;
+ char *name;
+ const char *suffix = NULL;
+ struct hid_field *field;
+ size_t len;
+
+ /* no report associated (HID_QUIRK_MULTI_INPUT not set) */
+ if (!hi->report)
+ return 0;
+
+ /*
+ * If this is the input corresponding to the pen report
+ * in need of tweaking.
+ */
+ if (hi->report->id == params->pen.id) {
+ /* Remember the input device so we can simulate events */
+ drvdata->pen_input = hi->input;
+ }
+
+ field = hi->report->field[0];
+
+ switch (field->application) {
+ case HID_GD_KEYBOARD:
+ suffix = "Keyboard";
+ break;
+ case HID_GD_MOUSE:
+ suffix = "Mouse";
+ break;
+ case HID_GD_KEYPAD:
+ suffix = "Pad";
+ break;
+ case HID_DG_PEN:
+ suffix = "Pen";
+ break;
+ case HID_CP_CONSUMER_CONTROL:
+ suffix = "Consumer Control";
+ break;
+ case HID_GD_SYSTEM_CONTROL:
+ suffix = "System Control";
+ break;
+ }
+
+ if (suffix) {
+ len = strlen(hdev->name) + 2 + strlen(suffix);
+ name = devm_kzalloc(&hi->input->dev, len, GFP_KERNEL);
+ if (name) {
+ snprintf(name, len, "%s %s", hdev->name, suffix);
+ hi->input->name = name;
+ }
+ }
+
+ return 0;
+}
+
+static int uclogic_probe(struct hid_device *hdev,
+ const struct hid_device_id *id)
+{
+ int rc;
+ struct uclogic_drvdata *drvdata = NULL;
+ bool params_initialized = false;
+
+ /*
+ * libinput requires the pad interface to be on a different node
+ * than the pen, so use QUIRK_MULTI_INPUT for all tablets.
+ */
+ hdev->quirks |= HID_QUIRK_MULTI_INPUT;
+
+ /* Allocate and assign driver data */
+ drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
+ if (drvdata == NULL) {
+ rc = -ENOMEM;
+ goto failure;
+ }
+ timer_setup(&drvdata->inrange_timer, uclogic_inrange_timeout, 0);
+ drvdata->re_state = U8_MAX;
+ hid_set_drvdata(hdev, drvdata);
+
+ /* Initialize the device and retrieve interface parameters */
+ rc = uclogic_params_init(&drvdata->params, hdev);
+ if (rc != 0) {
+ hid_err(hdev, "failed probing parameters: %d\n", rc);
+ goto failure;
+ }
+ params_initialized = true;
+ hid_dbg(hdev, "parameters:\n" UCLOGIC_PARAMS_FMT_STR,
+ UCLOGIC_PARAMS_FMT_ARGS(&drvdata->params));
+ if (drvdata->params.invalid) {
+ hid_info(hdev, "interface is invalid, ignoring\n");
+ rc = -ENODEV;
+ goto failure;
+ }
+
+ /* Generate replacement report descriptor */
+ rc = uclogic_params_get_desc(&drvdata->params,
+ &drvdata->desc_ptr,
+ &drvdata->desc_size);
+ if (rc) {
+ hid_err(hdev,
+ "failed generating replacement report descriptor: %d\n",
+ rc);
+ goto failure;
+ }
+
+ rc = hid_parse(hdev);
+ if (rc) {
+ hid_err(hdev, "parse failed\n");
+ goto failure;
+ }
+
+ rc = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (rc) {
+ hid_err(hdev, "hw start failed\n");
+ goto failure;
+ }
+
+ return 0;
+failure:
+ /* Assume "remove" might not be called if "probe" failed */
+ if (params_initialized)
+ uclogic_params_cleanup(&drvdata->params);
+ return rc;
+}
+
+#ifdef CONFIG_PM
+static int uclogic_resume(struct hid_device *hdev)
+{
+ int rc;
+ struct uclogic_params params;
+
+ /* Re-initialize the device, but discard parameters */
+ rc = uclogic_params_init(&params, hdev);
+ if (rc != 0)
+ hid_err(hdev, "failed to re-initialize the device\n");
+ else
+ uclogic_params_cleanup(&params);
+
+ return rc;
+}
+#endif
+
+static int uclogic_raw_event(struct hid_device *hdev,
+ struct hid_report *report,
+ u8 *data, int size)
+{
+ struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
+ struct uclogic_params *params = &drvdata->params;
+
+ /* Tweak pen reports, if necessary */
+ if (!params->pen_unused &&
+ (report->type == HID_INPUT_REPORT) &&
+ (report->id == params->pen.id) &&
+ (size >= 2)) {
+ /* If it's the "virtual" frame controls report */
+ if (params->frame.id != 0 &&
+ data[1] & params->pen_frame_flag) {
+ /* Change to virtual frame controls report ID */
+ data[0] = params->frame.id;
+ return 0;
+ }
+ /* If in-range reports are inverted */
+ if (params->pen.inrange ==
+ UCLOGIC_PARAMS_PEN_INRANGE_INVERTED) {
+ /* Invert the in-range bit */
+ data[1] ^= 0x40;
+ }
+ /*
+ * If report contains fragmented high-resolution pen
+ * coordinates
+ */
+ if (size >= 10 && params->pen.fragmented_hires) {
+ u8 pressure_low_byte;
+ u8 pressure_high_byte;
+
+ /* Lift pressure bytes */
+ pressure_low_byte = data[6];
+ pressure_high_byte = data[7];
+ /*
+ * Move Y coord to make space for high-order X
+ * coord byte
+ */
+ data[6] = data[5];
+ data[5] = data[4];
+ /* Move high-order X coord byte */
+ data[4] = data[8];
+ /* Move high-order Y coord byte */
+ data[7] = data[9];
+ /* Place pressure bytes */
+ data[8] = pressure_low_byte;
+ data[9] = pressure_high_byte;
+ }
+ /* If we need to emulate in-range detection */
+ if (params->pen.inrange == UCLOGIC_PARAMS_PEN_INRANGE_NONE) {
+ /* Set in-range bit */
+ data[1] |= 0x40;
+ /* (Re-)start in-range timeout */
+ mod_timer(&drvdata->inrange_timer,
+ jiffies + msecs_to_jiffies(100));
+ }
+ }
+
+ /* Tweak frame control reports, if necessary */
+ if ((report->type == HID_INPUT_REPORT) &&
+ (report->id == params->frame.id)) {
+ /* If need to, and can, set pad device ID for Wacom drivers */
+ if (params->frame.dev_id_byte > 0 &&
+ params->frame.dev_id_byte < size) {
+ data[params->frame.dev_id_byte] = 0xf;
+ }
+ /* If need to, and can, read rotary encoder state change */
+ if (params->frame.re_lsb > 0 &&
+ params->frame.re_lsb / 8 < size) {
+ unsigned int byte = params->frame.re_lsb / 8;
+ unsigned int bit = params->frame.re_lsb % 8;
+
+ u8 change;
+ u8 prev_state = drvdata->re_state;
+ /* Read Gray-coded state */
+ u8 state = (data[byte] >> bit) & 0x3;
+ /* Encode state change into 2-bit signed integer */
+ if ((prev_state == 1 && state == 0) ||
+ (prev_state == 2 && state == 3)) {
+ change = 1;
+ } else if ((prev_state == 2 && state == 0) ||
+ (prev_state == 1 && state == 3)) {
+ change = 3;
+ } else {
+ change = 0;
+ }
+ /* Write change */
+ data[byte] = (data[byte] & ~((u8)3 << bit)) |
+ (change << bit);
+ /* Remember state */
+ drvdata->re_state = state;
+ }
+ }
+
+ return 0;
+}
+
+static void uclogic_remove(struct hid_device *hdev)
+{
+ struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
+
+ del_timer_sync(&drvdata->inrange_timer);
+ hid_hw_stop(hdev);
+ kfree(drvdata->desc_ptr);
+ uclogic_params_cleanup(&drvdata->params);
+}
+
+static const struct hid_device_id uclogic_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_TABLET_PF1209) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_TABLET_WP4030U) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_TABLET_WP5540U) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_TABLET_WP1062) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_HUION,
+ USB_DEVICE_ID_HUION_TABLET) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_HUION_TABLET) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_YIYNOVA_TABLET) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_81) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_45) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_47) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UGTIZER,
+ USB_DEVICE_ID_UGTIZER_TABLET_GP0610) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_TABLET_G5) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_TABLET_EX07S) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_XPPEN_TABLET_G540) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_XPPEN_TABLET_G640) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01) },
+ { }
+};
+MODULE_DEVICE_TABLE(hid, uclogic_devices);
+
+static struct hid_driver uclogic_driver = {
+ .name = "uclogic",
+ .id_table = uclogic_devices,
+ .probe = uclogic_probe,
+ .remove = uclogic_remove,
+ .report_fixup = uclogic_report_fixup,
+ .raw_event = uclogic_raw_event,
+ .input_mapping = uclogic_input_mapping,
+ .input_configured = uclogic_input_configured,
+#ifdef CONFIG_PM
+ .resume = uclogic_resume,
+ .reset_resume = uclogic_resume,
+#endif
+};
+module_hid_driver(uclogic_driver);
+
+MODULE_AUTHOR("Martin Rusko");
+MODULE_AUTHOR("Nikolai Kondrashov");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c
new file mode 100644
index 000000000000..0187c9f8fc22
--- /dev/null
+++ b/drivers/hid/hid-uclogic-params.c
@@ -0,0 +1,1118 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * HID driver for UC-Logic devices not fully compliant with HID standard
+ * - tablet initialization and parameter retrieval
+ *
+ * Copyright (c) 2018 Nikolai Kondrashov
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include "hid-uclogic-params.h"
+#include "hid-uclogic-rdesc.h"
+#include "usbhid/usbhid.h"
+#include "hid-ids.h"
+#include <linux/ctype.h>
+#include <asm/unaligned.h>
+
+/**
+ * Convert a pen in-range reporting type to a string.
+ *
+ * @inrange: The in-range reporting type to convert.
+ *
+ * Returns:
+ * The string representing the type, or NULL if the type is unknown.
+ */
+const char *uclogic_params_pen_inrange_to_str(
+ enum uclogic_params_pen_inrange inrange)
+{
+ switch (inrange) {
+ case UCLOGIC_PARAMS_PEN_INRANGE_NORMAL:
+ return "normal";
+ case UCLOGIC_PARAMS_PEN_INRANGE_INVERTED:
+ return "inverted";
+ case UCLOGIC_PARAMS_PEN_INRANGE_NONE:
+ return "none";
+ default:
+ return NULL;
+ }
+}
+
+/**
+ * uclogic_params_get_str_desc - retrieve a string descriptor from a HID
+ * device interface, putting it into a kmalloc-allocated buffer as is, without
+ * character encoding conversion.
+ *
+ * @pbuf: Location for the kmalloc-allocated buffer pointer containing
+ * the retrieved descriptor. Not modified in case of error.
+ * Can be NULL to have retrieved descriptor discarded.
+ * @hdev: The HID device of the tablet interface to retrieve the string
+ * descriptor from. Cannot be NULL.
+ * @idx: Index of the string descriptor to request from the device.
+ * @len: Length of the buffer to allocate and the data to retrieve.
+ *
+ * Returns:
+ * number of bytes retrieved (<= len),
+ * -EPIPE, if the descriptor was not found, or
+ * another negative errno code in case of other error.
+ */
+static int uclogic_params_get_str_desc(__u8 **pbuf, struct hid_device *hdev,
+ __u8 idx, size_t len)
+{
+ int rc;
+ struct usb_device *udev = hid_to_usb_dev(hdev);
+ __u8 *buf = NULL;
+
+ /* Check arguments */
+ if (hdev == NULL) {
+ rc = -EINVAL;
+ goto cleanup;
+ }
+
+ buf = kmalloc(len, GFP_KERNEL);
+ if (buf == NULL) {
+ rc = -ENOMEM;
+ goto cleanup;
+ }
+
+ rc = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+ USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
+ (USB_DT_STRING << 8) + idx,
+ 0x0409, buf, len,
+ USB_CTRL_GET_TIMEOUT);
+ if (rc == -EPIPE) {
+ hid_dbg(hdev, "string descriptor #%hhu not found\n", idx);
+ goto cleanup;
+ } else if (rc < 0) {
+ hid_err(hdev,
+ "failed retrieving string descriptor #%hhu: %d\n",
+ idx, rc);
+ goto cleanup;
+ }
+
+ if (pbuf != NULL) {
+ *pbuf = buf;
+ buf = NULL;
+ }
+
+cleanup:
+ kfree(buf);
+ return rc;
+}
+
+/**
+ * uclogic_params_pen_cleanup - free resources used by struct
+ * uclogic_params_pen (tablet interface's pen input parameters).
+ * Can be called repeatedly.
+ *
+ * @pen: Pen input parameters to cleanup. Cannot be NULL.
+ */
+static void uclogic_params_pen_cleanup(struct uclogic_params_pen *pen)
+{
+ kfree(pen->desc_ptr);
+ memset(pen, 0, sizeof(*pen));
+}
+
+/**
+ * uclogic_params_pen_init_v1() - initialize tablet interface pen
+ * input and retrieve its parameters from the device, using v1 protocol.
+ *
+ * @pen: Pointer to the pen parameters to initialize (to be
+ * cleaned up with uclogic_params_pen_cleanup()). Not modified in
+ * case of error, or if parameters are not found. Cannot be NULL.
+ * @pfound: Location for a flag which is set to true if the parameters
+ * were found, and to false if not (e.g. device was
+ * incompatible). Not modified in case of error. Cannot be NULL.
+ * @hdev: The HID device of the tablet interface to initialize and get
+ * parameters from. Cannot be NULL.
+ *
+ * Returns:
+ * Zero, if successful. A negative errno code on error.
+ */
+static int uclogic_params_pen_init_v1(struct uclogic_params_pen *pen,
+ bool *pfound,
+ struct hid_device *hdev)
+{
+ int rc;
+ bool found = false;
+ /* Buffer for (part of) the string descriptor */
+ __u8 *buf = NULL;
+ /* Minimum descriptor length required, maximum seen so far is 18 */
+ const int len = 12;
+ s32 resolution;
+ /* Pen report descriptor template parameters */
+ s32 desc_params[UCLOGIC_RDESC_PEN_PH_ID_NUM];
+ __u8 *desc_ptr = NULL;
+
+ /* Check arguments */
+ if (pen == NULL || pfound == NULL || hdev == NULL) {
+ rc = -EINVAL;
+ goto cleanup;
+ }
+
+ /*
+ * Read string descriptor containing pen input parameters.
+ * The specific string descriptor and data were discovered by sniffing
+ * the Windows driver traffic.
+ * NOTE: This enables fully-functional tablet mode.
+ */
+ rc = uclogic_params_get_str_desc(&buf, hdev, 100, len);
+ if (rc == -EPIPE) {
+ hid_dbg(hdev,
+ "string descriptor with pen parameters not found, assuming not compatible\n");
+ goto finish;
+ } else if (rc < 0) {
+ hid_err(hdev, "failed retrieving pen parameters: %d\n", rc);
+ goto cleanup;
+ } else if (rc != len) {
+ hid_dbg(hdev,
+ "string descriptor with pen parameters has invalid length (got %d, expected %d), assuming not compatible\n",
+ rc, len);
+ goto finish;
+ }
+
+ /*
+ * Fill report descriptor parameters from the string descriptor
+ */
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] =
+ get_unaligned_le16(buf + 2);
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] =
+ get_unaligned_le16(buf + 4);
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_PRESSURE_LM] =
+ get_unaligned_le16(buf + 8);
+ resolution = get_unaligned_le16(buf + 10);
+ if (resolution == 0) {
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] = 0;
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] = 0;
+ } else {
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] =
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] * 1000 /
+ resolution;
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] =
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] * 1000 /
+ resolution;
+ }
+ kfree(buf);
+ buf = NULL;
+
+ /*
+ * Generate pen report descriptor
+ */
+ desc_ptr = uclogic_rdesc_template_apply(
+ uclogic_rdesc_pen_v1_template_arr,
+ uclogic_rdesc_pen_v1_template_size,
+ desc_params, ARRAY_SIZE(desc_params));
+ if (desc_ptr == NULL) {
+ rc = -ENOMEM;
+ goto cleanup;
+ }
+
+ /*
+ * Fill-in the parameters
+ */
+ memset(pen, 0, sizeof(*pen));
+ pen->desc_ptr = desc_ptr;
+ desc_ptr = NULL;
+ pen->desc_size = uclogic_rdesc_pen_v1_template_size;
+ pen->id = UCLOGIC_RDESC_PEN_V1_ID;
+ pen->inrange = UCLOGIC_PARAMS_PEN_INRANGE_INVERTED;
+ found = true;
+finish:
+ *pfound = found;
+ rc = 0;
+cleanup:
+ kfree(desc_ptr);
+ kfree(buf);
+ return rc;
+}
+
+/**
+ * uclogic_params_get_le24() - get a 24-bit little-endian number from a
+ * buffer.
+ *
+ * @p: The pointer to the number buffer.
+ *
+ * Returns:
+ * The retrieved number
+ */
+static s32 uclogic_params_get_le24(const void *p)
+{
+ const __u8 *b = p;
+ return b[0] | (b[1] << 8UL) | (b[2] << 16UL);
+}
+
+/**
+ * uclogic_params_pen_init_v2() - initialize tablet interface pen
+ * input and retrieve its parameters from the device, using v2 protocol.
+ *
+ * @pen: Pointer to the pen parameters to initialize (to be
+ * cleaned up with uclogic_params_pen_cleanup()). Not modified in
+ * case of error, or if parameters are not found. Cannot be NULL.
+ * @pfound: Location for a flag which is set to true if the parameters
+ * were found, and to false if not (e.g. device was
+ * incompatible). Not modified in case of error. Cannot be NULL.
+ * @hdev: The HID device of the tablet interface to initialize and get
+ * parameters from. Cannot be NULL.
+ *
+ * Returns:
+ * Zero, if successful. A negative errno code on error.
+ */
+static int uclogic_params_pen_init_v2(struct uclogic_params_pen *pen,
+ bool *pfound,
+ struct hid_device *hdev)
+{
+ int rc;
+ bool found = false;
+ /* Buffer for (part of) the string descriptor */
+ __u8 *buf = NULL;
+ /* Descriptor length required */
+ const int len = 18;
+ s32 resolution;
+ /* Pen report descriptor template parameters */
+ s32 desc_params[UCLOGIC_RDESC_PEN_PH_ID_NUM];
+ __u8 *desc_ptr = NULL;
+
+ /* Check arguments */
+ if (pen == NULL || pfound == NULL || hdev == NULL) {
+ rc = -EINVAL;
+ goto cleanup;
+ }
+
+ /*
+ * Read string descriptor containing pen input parameters.
+ * The specific string descriptor and data were discovered by sniffing
+ * the Windows driver traffic.
+ * NOTE: This enables fully-functional tablet mode.
+ */
+ rc = uclogic_params_get_str_desc(&buf, hdev, 200, len);
+ if (rc == -EPIPE) {
+ hid_dbg(hdev,
+ "string descriptor with pen parameters not found, assuming not compatible\n");
+ goto finish;
+ } else if (rc < 0) {
+ hid_err(hdev, "failed retrieving pen parameters: %d\n", rc);
+ goto cleanup;
+ } else if (rc != len) {
+ hid_dbg(hdev,
+ "string descriptor with pen parameters has invalid length (got %d, expected %d), assuming not compatible\n",
+ rc, len);
+ goto finish;
+ } else {
+ size_t i;
+ /*
+ * Check it's not just a catch-all UTF-16LE-encoded ASCII
+ * string (such as the model name) some tablets put into all
+ * unknown string descriptors.
+ */
+ for (i = 2;
+ i < len &&
+ (buf[i] >= 0x20 && buf[i] < 0x7f && buf[i + 1] == 0);
+ i += 2);
+ if (i >= len) {
+ hid_dbg(hdev,
+ "string descriptor with pen parameters seems to contain only text, assuming not compatible\n");
+ goto finish;
+ }
+ }
+
+ /*
+ * Fill report descriptor parameters from the string descriptor
+ */
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] =
+ uclogic_params_get_le24(buf + 2);
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] =
+ uclogic_params_get_le24(buf + 5);
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_PRESSURE_LM] =
+ get_unaligned_le16(buf + 8);
+ resolution = get_unaligned_le16(buf + 10);
+ if (resolution == 0) {
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] = 0;
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] = 0;
+ } else {
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] =
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] * 1000 /
+ resolution;
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] =
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] * 1000 /
+ resolution;
+ }
+ kfree(buf);
+ buf = NULL;
+
+ /*
+ * Generate pen report descriptor
+ */
+ desc_ptr = uclogic_rdesc_template_apply(
+ uclogic_rdesc_pen_v2_template_arr,
+ uclogic_rdesc_pen_v2_template_size,
+ desc_params, ARRAY_SIZE(desc_params));
+ if (desc_ptr == NULL) {
+ rc = -ENOMEM;
+ goto cleanup;
+ }
+
+ /*
+ * Fill-in the parameters
+ */
+ memset(pen, 0, sizeof(*pen));
+ pen->desc_ptr = desc_ptr;
+ desc_ptr = NULL;
+ pen->desc_size = uclogic_rdesc_pen_v2_template_size;
+ pen->id = UCLOGIC_RDESC_PEN_V2_ID;
+ pen->inrange = UCLOGIC_PARAMS_PEN_INRANGE_NONE;
+ pen->fragmented_hires = true;
+ found = true;
+finish:
+ *pfound = found;
+ rc = 0;
+cleanup:
+ kfree(desc_ptr);
+ kfree(buf);
+ return rc;
+}
+
+/**
+ * uclogic_params_frame_cleanup - free resources used by struct
+ * uclogic_params_frame (tablet interface's frame controls input parameters).
+ * Can be called repeatedly.
+ *
+ * @frame: Frame controls input parameters to cleanup. Cannot be NULL.
+ */
+static void uclogic_params_frame_cleanup(struct uclogic_params_frame *frame)
+{
+ kfree(frame->desc_ptr);
+ memset(frame, 0, sizeof(*frame));
+}
+
+/**
+ * uclogic_params_frame_init_with_desc() - initialize tablet's frame control
+ * parameters with a static report descriptor.
+ *
+ * @frame: Pointer to the frame parameters to initialize (to be cleaned
+ * up with uclogic_params_frame_cleanup()). Not modified in case
+ * of error. Cannot be NULL.
+ * @desc_ptr: Report descriptor pointer. Can be NULL, if desc_size is zero.
+ * @desc_size: Report descriptor size.
+ * @id: Report ID used for frame reports, if they should be tweaked,
+ * zero if not.
+ *
+ * Returns:
+ * Zero, if successful. A negative errno code on error.
+ */
+static int uclogic_params_frame_init_with_desc(
+ struct uclogic_params_frame *frame,
+ const __u8 *desc_ptr,
+ size_t desc_size,
+ unsigned int id)
+{
+ __u8 *copy_desc_ptr;
+
+ if (frame == NULL || (desc_ptr == NULL && desc_size != 0))
+ return -EINVAL;
+
+ copy_desc_ptr = kmemdup(desc_ptr, desc_size, GFP_KERNEL);
+ if (copy_desc_ptr == NULL)
+ return -ENOMEM;
+
+ memset(frame, 0, sizeof(*frame));
+ frame->desc_ptr = copy_desc_ptr;
+ frame->desc_size = desc_size;
+ frame->id = id;
+ return 0;
+}
+
+/**
+ * uclogic_params_frame_init_v1_buttonpad() - initialize abstract buttonpad
+ * on a v1 tablet interface.
+ *
+ * @frame: Pointer to the frame parameters to initialize (to be cleaned
+ * up with uclogic_params_frame_cleanup()). Not modified in case
+ * of error, or if parameters are not found. Cannot be NULL.
+ * @pfound: Location for a flag which is set to true if the parameters
+ * were found, and to false if not (e.g. device was
+ * incompatible). Not modified in case of error. Cannot be NULL.
+ * @hdev: The HID device of the tablet interface to initialize and get
+ * parameters from. Cannot be NULL.
+ *
+ * Returns:
+ * Zero, if successful. A negative errno code on error.
+ */
+static int uclogic_params_frame_init_v1_buttonpad(
+ struct uclogic_params_frame *frame,
+ bool *pfound,
+ struct hid_device *hdev)
+{
+ int rc;
+ bool found = false;
+ struct usb_device *usb_dev = hid_to_usb_dev(hdev);
+ char *str_buf = NULL;
+ const size_t str_len = 16;
+
+ /* Check arguments */
+ if (frame == NULL || pfound == NULL || hdev == NULL) {
+ rc = -EINVAL;
+ goto cleanup;
+ }
+
+ /*
+ * Enable generic button mode
+ */
+ str_buf = kzalloc(str_len, GFP_KERNEL);
+ if (str_buf == NULL) {
+ rc = -ENOMEM;
+ goto cleanup;
+ }
+
+ rc = usb_string(usb_dev, 123, str_buf, str_len);
+ if (rc == -EPIPE) {
+ hid_dbg(hdev,
+ "generic button -enabling string descriptor not found\n");
+ } else if (rc < 0) {
+ goto cleanup;
+ } else if (strncmp(str_buf, "HK On", rc) != 0) {
+ hid_dbg(hdev,
+ "invalid response to enabling generic buttons: \"%s\"\n",
+ str_buf);
+ } else {
+ hid_dbg(hdev, "generic buttons enabled\n");
+ rc = uclogic_params_frame_init_with_desc(
+ frame,
+ uclogic_rdesc_buttonpad_v1_arr,
+ uclogic_rdesc_buttonpad_v1_size,
+ UCLOGIC_RDESC_BUTTONPAD_V1_ID);
+ if (rc != 0)
+ goto cleanup;
+ found = true;
+ }
+
+ *pfound = found;
+ rc = 0;
+cleanup:
+ kfree(str_buf);
+ return rc;
+}
+
+/**
+ * uclogic_params_cleanup - free resources used by struct uclogic_params
+ * (tablet interface's parameters).
+ * Can be called repeatedly.
+ *
+ * @params: Input parameters to cleanup. Cannot be NULL.
+ */
+void uclogic_params_cleanup(struct uclogic_params *params)
+{
+ if (!params->invalid) {
+ kfree(params->desc_ptr);
+ if (!params->pen_unused)
+ uclogic_params_pen_cleanup(&params->pen);
+ uclogic_params_frame_cleanup(&params->frame);
+ memset(params, 0, sizeof(*params));
+ }
+}
+
+/**
+ * Get a replacement report descriptor for a tablet's interface.
+ *
+ * @params: The parameters of a tablet interface to get report
+ * descriptor for. Cannot be NULL.
+ * @pdesc: Location for the resulting, kmalloc-allocated report
+ * descriptor pointer, or for NULL, if there's no replacement
+ * report descriptor. Not modified in case of error. Cannot be
+ * NULL.
+ * @psize: Location for the resulting report descriptor size, not set if
+ * there's no replacement report descriptor. Not modified in case
+ * of error. Cannot be NULL.
+ *
+ * Returns:
+ * Zero, if successful.
+ * -EINVAL, if invalid arguments are supplied.
+ * -ENOMEM, if failed to allocate memory.
+ */
+int uclogic_params_get_desc(const struct uclogic_params *params,
+ __u8 **pdesc,
+ unsigned int *psize)
+{
+ bool common_present;
+ bool pen_present;
+ bool frame_present;
+ unsigned int size;
+ __u8 *desc = NULL;
+
+ /* Check arguments */
+ if (params == NULL || pdesc == NULL || psize == NULL)
+ return -EINVAL;
+
+ size = 0;
+
+ common_present = (params->desc_ptr != NULL);
+ pen_present = (!params->pen_unused && params->pen.desc_ptr != NULL);
+ frame_present = (params->frame.desc_ptr != NULL);
+
+ if (common_present)
+ size += params->desc_size;
+ if (pen_present)
+ size += params->pen.desc_size;
+ if (frame_present)
+ size += params->frame.desc_size;
+
+ if (common_present || pen_present || frame_present) {
+ __u8 *p;
+
+ desc = kmalloc(size, GFP_KERNEL);
+ if (desc == NULL)
+ return -ENOMEM;
+ p = desc;
+
+ if (common_present) {
+ memcpy(p, params->desc_ptr,
+ params->desc_size);
+ p += params->desc_size;
+ }
+ if (pen_present) {
+ memcpy(p, params->pen.desc_ptr,
+ params->pen.desc_size);
+ p += params->pen.desc_size;
+ }
+ if (frame_present) {
+ memcpy(p, params->frame.desc_ptr,
+ params->frame.desc_size);
+ p += params->frame.desc_size;
+ }
+
+ WARN_ON(p != desc + size);
+
+ *psize = size;
+ }
+
+ *pdesc = desc;
+ return 0;
+}
+
+/**
+ * uclogic_params_init_invalid() - initialize tablet interface parameters,
+ * specifying the interface is invalid.
+ *
+ * @params: Parameters to initialize (to be cleaned with
+ * uclogic_params_cleanup()). Cannot be NULL.
+ */
+static void uclogic_params_init_invalid(struct uclogic_params *params)
+{
+ params->invalid = true;
+}
+
+/**
+ * uclogic_params_init_with_opt_desc() - initialize tablet interface
+ * parameters with an optional replacement report descriptor. Only modify
+ * report descriptor, if the original report descriptor matches the expected
+ * size.
+ *
+ * @params: Parameters to initialize (to be cleaned with
+ * uclogic_params_cleanup()). Not modified in case of
+ * error. Cannot be NULL.
+ * @hdev: The HID device of the tablet interface create the
+ * parameters for. Cannot be NULL.
+ * @orig_desc_size: Expected size of the original report descriptor to
+ * be replaced.
+ * @desc_ptr: Pointer to the replacement report descriptor.
+ * Can be NULL, if desc_size is zero.
+ * @desc_size: Size of the replacement report descriptor.
+ *
+ * Returns:
+ * Zero, if successful. -EINVAL if an invalid argument was passed.
+ * -ENOMEM, if failed to allocate memory.
+ */
+static int uclogic_params_init_with_opt_desc(struct uclogic_params *params,
+ struct hid_device *hdev,
+ unsigned int orig_desc_size,
+ __u8 *desc_ptr,
+ unsigned int desc_size)
+{
+ __u8 *desc_copy_ptr = NULL;
+ unsigned int desc_copy_size;
+ int rc;
+
+ /* Check arguments */
+ if (params == NULL || hdev == NULL ||
+ (desc_ptr == NULL && desc_size != 0)) {
+ rc = -EINVAL;
+ goto cleanup;
+ }
+
+ /* Replace report descriptor, if it matches */
+ if (hdev->dev_rsize == orig_desc_size) {
+ hid_dbg(hdev,
+ "device report descriptor matches the expected size, replacing\n");
+ desc_copy_ptr = kmemdup(desc_ptr, desc_size, GFP_KERNEL);
+ if (desc_copy_ptr == NULL) {
+ rc = -ENOMEM;
+ goto cleanup;
+ }
+ desc_copy_size = desc_size;
+ } else {
+ hid_dbg(hdev,
+ "device report descriptor doesn't match the expected size (%u != %u), preserving\n",
+ hdev->dev_rsize, orig_desc_size);
+ desc_copy_ptr = NULL;
+ desc_copy_size = 0;
+ }
+
+ /* Output parameters */
+ memset(params, 0, sizeof(*params));
+ params->desc_ptr = desc_copy_ptr;
+ desc_copy_ptr = NULL;
+ params->desc_size = desc_copy_size;
+
+ rc = 0;
+cleanup:
+ kfree(desc_copy_ptr);
+ return rc;
+}
+
+/**
+ * uclogic_params_init_with_pen_unused() - initialize tablet interface
+ * parameters preserving original reports and generic HID processing, but
+ * disabling pen usage.
+ *
+ * @params: Parameters to initialize (to be cleaned with
+ * uclogic_params_cleanup()). Not modified in case of
+ * error. Cannot be NULL.
+ */
+static void uclogic_params_init_with_pen_unused(struct uclogic_params *params)
+{
+ memset(params, 0, sizeof(*params));
+ params->pen_unused = true;
+}
+
+/**
+ * uclogic_params_init() - initialize a Huion tablet interface and discover
+ * its parameters.
+ *
+ * @params: Parameters to fill in (to be cleaned with
+ * uclogic_params_cleanup()). Not modified in case of error.
+ * Cannot be NULL.
+ * @hdev: The HID device of the tablet interface to initialize and get
+ * parameters from. Cannot be NULL.
+ *
+ * Returns:
+ * Zero, if successful. A negative errno code on error.
+ */
+static int uclogic_params_huion_init(struct uclogic_params *params,
+ struct hid_device *hdev)
+{
+ int rc;
+ struct usb_device *udev = hid_to_usb_dev(hdev);
+ struct usb_interface *iface = to_usb_interface(hdev->dev.parent);
+ __u8 bInterfaceNumber = iface->cur_altsetting->desc.bInterfaceNumber;
+ bool found;
+ /* The resulting parameters (noop) */
+ struct uclogic_params p = {0, };
+ static const char transition_ver[] = "HUION_T153_160607";
+ char *ver_ptr = NULL;
+ const size_t ver_len = sizeof(transition_ver) + 1;
+
+ /* Check arguments */
+ if (params == NULL || hdev == NULL) {
+ rc = -EINVAL;
+ goto cleanup;
+ }
+
+ /* If it's not a pen interface */
+ if (bInterfaceNumber != 0) {
+ /* TODO: Consider marking the interface invalid */
+ uclogic_params_init_with_pen_unused(&p);
+ goto output;
+ }
+
+ /* Try to get firmware version */
+ ver_ptr = kzalloc(ver_len, GFP_KERNEL);
+ if (ver_ptr == NULL) {
+ rc = -ENOMEM;
+ goto cleanup;
+ }
+ rc = usb_string(udev, 201, ver_ptr, ver_len);
+ if (rc == -EPIPE) {
+ *ver_ptr = '\0';
+ } else if (rc < 0) {
+ hid_err(hdev,
+ "failed retrieving Huion firmware version: %d\n", rc);
+ goto cleanup;
+ }
+
+ /* If this is a transition firmware */
+ if (strcmp(ver_ptr, transition_ver) == 0) {
+ hid_dbg(hdev,
+ "transition firmware detected, not probing pen v2 parameters\n");
+ } else {
+ /* Try to probe v2 pen parameters */
+ rc = uclogic_params_pen_init_v2(&p.pen, &found, hdev);
+ if (rc != 0) {
+ hid_err(hdev,
+ "failed probing pen v2 parameters: %d\n", rc);
+ goto cleanup;
+ } else if (found) {
+ hid_dbg(hdev, "pen v2 parameters found\n");
+ /* Create v2 buttonpad parameters */
+ rc = uclogic_params_frame_init_with_desc(
+ &p.frame,
+ uclogic_rdesc_buttonpad_v2_arr,
+ uclogic_rdesc_buttonpad_v2_size,
+ UCLOGIC_RDESC_BUTTONPAD_V2_ID);
+ if (rc != 0) {
+ hid_err(hdev,
+ "failed creating v2 buttonpad parameters: %d\n",
+ rc);
+ goto cleanup;
+ }
+ /* Set bitmask marking frame reports in pen reports */
+ p.pen_frame_flag = 0x20;
+ goto output;
+ }
+ hid_dbg(hdev, "pen v2 parameters not found\n");
+ }
+
+ /* Try to probe v1 pen parameters */
+ rc = uclogic_params_pen_init_v1(&p.pen, &found, hdev);
+ if (rc != 0) {
+ hid_err(hdev,
+ "failed probing pen v1 parameters: %d\n", rc);
+ goto cleanup;
+ } else if (found) {
+ hid_dbg(hdev, "pen v1 parameters found\n");
+ /* Try to probe v1 buttonpad */
+ rc = uclogic_params_frame_init_v1_buttonpad(
+ &p.frame,
+ &found, hdev);
+ if (rc != 0) {
+ hid_err(hdev, "v1 buttonpad probing failed: %d\n", rc);
+ goto cleanup;
+ }
+ hid_dbg(hdev, "buttonpad v1 parameters%s found\n",
+ (found ? "" : " not"));
+ if (found) {
+ /* Set bitmask marking frame reports */
+ p.pen_frame_flag = 0x20;
+ }
+ goto output;
+ }
+ hid_dbg(hdev, "pen v1 parameters not found\n");
+
+ uclogic_params_init_invalid(&p);
+
+output:
+ /* Output parameters */
+ memcpy(params, &p, sizeof(*params));
+ memset(&p, 0, sizeof(p));
+ rc = 0;
+cleanup:
+ kfree(ver_ptr);
+ uclogic_params_cleanup(&p);
+ return rc;
+}
+
+/**
+ * uclogic_params_init() - initialize a tablet interface and discover its
+ * parameters.
+ *
+ * @params: Parameters to fill in (to be cleaned with
+ * uclogic_params_cleanup()). Not modified in case of error.
+ * Cannot be NULL.
+ * @hdev: The HID device of the tablet interface to initialize and get
+ * parameters from. Cannot be NULL. Must be using the USB low-level
+ * driver, i.e. be an actual USB tablet.
+ *
+ * Returns:
+ * Zero, if successful. A negative errno code on error.
+ */
+int uclogic_params_init(struct uclogic_params *params,
+ struct hid_device *hdev)
+{
+ int rc;
+ struct usb_device *udev = hid_to_usb_dev(hdev);
+ __u8 bNumInterfaces = udev->config->desc.bNumInterfaces;
+ struct usb_interface *iface = to_usb_interface(hdev->dev.parent);
+ __u8 bInterfaceNumber = iface->cur_altsetting->desc.bInterfaceNumber;
+ bool found;
+ /* The resulting parameters (noop) */
+ struct uclogic_params p = {0, };
+
+ /* Check arguments */
+ if (params == NULL || hdev == NULL ||
+ !hid_is_using_ll_driver(hdev, &usb_hid_driver)) {
+ rc = -EINVAL;
+ goto cleanup;
+ }
+
+ /*
+ * Set replacement report descriptor if the original matches the
+ * specified size. Otherwise keep interface unchanged.
+ */
+#define WITH_OPT_DESC(_orig_desc_token, _new_desc_token) \
+ uclogic_params_init_with_opt_desc( \
+ &p, hdev, \
+ UCLOGIC_RDESC_##_orig_desc_token##_SIZE, \
+ uclogic_rdesc_##_new_desc_token##_arr, \
+ uclogic_rdesc_##_new_desc_token##_size)
+
+#define VID_PID(_vid, _pid) \
+ (((__u32)(_vid) << 16) | ((__u32)(_pid) & U16_MAX))
+
+ /*
+ * Handle specific interfaces for specific tablets.
+ *
+ * Observe the following logic:
+ *
+ * If the interface is recognized as producing certain useful input:
+ * Mark interface as valid.
+ * Output interface parameters.
+ * Else, if the interface is recognized as *not* producing any useful
+ * input:
+ * Mark interface as invalid.
+ * Else:
+ * Mark interface as valid.
+ * Output noop parameters.
+ *
+ * Rule of thumb: it is better to disable a broken interface than let
+ * it spew garbage input.
+ */
+
+ switch (VID_PID(hdev->vendor, hdev->product)) {
+ case VID_PID(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_TABLET_PF1209):
+ rc = WITH_OPT_DESC(PF1209_ORIG, pf1209_fixed);
+ if (rc != 0)
+ goto cleanup;
+ break;
+ case VID_PID(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_TABLET_WP4030U):
+ rc = WITH_OPT_DESC(WPXXXXU_ORIG, wp4030u_fixed);
+ if (rc != 0)
+ goto cleanup;
+ break;
+ case VID_PID(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_TABLET_WP5540U):
+ if (hdev->dev_rsize == UCLOGIC_RDESC_WP5540U_V2_ORIG_SIZE) {
+ if (bInterfaceNumber == 0) {
+ /* Try to probe v1 pen parameters */
+ rc = uclogic_params_pen_init_v1(&p.pen,
+ &found, hdev);
+ if (rc != 0) {
+ hid_err(hdev,
+ "pen probing failed: %d\n",
+ rc);
+ goto cleanup;
+ }
+ if (!found) {
+ hid_warn(hdev,
+ "pen parameters not found");
+ }
+ } else {
+ uclogic_params_init_invalid(&p);
+ }
+ } else {
+ rc = WITH_OPT_DESC(WPXXXXU_ORIG, wp5540u_fixed);
+ if (rc != 0)
+ goto cleanup;
+ }
+ break;
+ case VID_PID(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U):
+ rc = WITH_OPT_DESC(WPXXXXU_ORIG, wp8060u_fixed);
+ if (rc != 0)
+ goto cleanup;
+ break;
+ case VID_PID(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_TABLET_WP1062):
+ rc = WITH_OPT_DESC(WP1062_ORIG, wp1062_fixed);
+ if (rc != 0)
+ goto cleanup;
+ break;
+ case VID_PID(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850):
+ switch (bInterfaceNumber) {
+ case 0:
+ rc = WITH_OPT_DESC(TWHL850_ORIG0, twhl850_fixed0);
+ if (rc != 0)
+ goto cleanup;
+ break;
+ case 1:
+ rc = WITH_OPT_DESC(TWHL850_ORIG1, twhl850_fixed1);
+ if (rc != 0)
+ goto cleanup;
+ break;
+ case 2:
+ rc = WITH_OPT_DESC(TWHL850_ORIG2, twhl850_fixed2);
+ if (rc != 0)
+ goto cleanup;
+ break;
+ }
+ break;
+ case VID_PID(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60):
+ /*
+ * If it is not a three-interface version, which is known to
+ * respond to initialization.
+ */
+ if (bNumInterfaces != 3) {
+ switch (bInterfaceNumber) {
+ case 0:
+ rc = WITH_OPT_DESC(TWHA60_ORIG0,
+ twha60_fixed0);
+ if (rc != 0)
+ goto cleanup;
+ break;
+ case 1:
+ rc = WITH_OPT_DESC(TWHA60_ORIG1,
+ twha60_fixed1);
+ if (rc != 0)
+ goto cleanup;
+ break;
+ }
+ break;
+ }
+ /* FALL THROUGH */
+ case VID_PID(USB_VENDOR_ID_HUION,
+ USB_DEVICE_ID_HUION_TABLET):
+ case VID_PID(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_HUION_TABLET):
+ case VID_PID(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_YIYNOVA_TABLET):
+ case VID_PID(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_81):
+ case VID_PID(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3):
+ case VID_PID(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_45):
+ case VID_PID(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_47):
+ rc = uclogic_params_huion_init(&p, hdev);
+ if (rc != 0)
+ goto cleanup;
+ break;
+ case VID_PID(USB_VENDOR_ID_UGTIZER,
+ USB_DEVICE_ID_UGTIZER_TABLET_GP0610):
+ case VID_PID(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_XPPEN_TABLET_G540):
+ case VID_PID(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_XPPEN_TABLET_G640):
+ /* If this is the pen interface */
+ if (bInterfaceNumber == 1) {
+ /* Probe v1 pen parameters */
+ rc = uclogic_params_pen_init_v1(&p.pen, &found, hdev);
+ if (rc != 0) {
+ hid_err(hdev, "pen probing failed: %d\n", rc);
+ goto cleanup;
+ }
+ if (!found) {
+ hid_warn(hdev, "pen parameters not found");
+ uclogic_params_init_invalid(&p);
+ }
+ } else {
+ /* TODO: Consider marking the interface invalid */
+ uclogic_params_init_with_pen_unused(&p);
+ }
+ break;
+ case VID_PID(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01):
+ /* If this is the pen and frame interface */
+ if (bInterfaceNumber == 1) {
+ /* Probe v1 pen parameters */
+ rc = uclogic_params_pen_init_v1(&p.pen, &found, hdev);
+ if (rc != 0) {
+ hid_err(hdev, "pen probing failed: %d\n", rc);
+ goto cleanup;
+ }
+ /* Initialize frame parameters */
+ rc = uclogic_params_frame_init_with_desc(
+ &p.frame,
+ uclogic_rdesc_xppen_deco01_frame_arr,
+ uclogic_rdesc_xppen_deco01_frame_size,
+ 0);
+ if (rc != 0)
+ goto cleanup;
+ } else {
+ /* TODO: Consider marking the interface invalid */
+ uclogic_params_init_with_pen_unused(&p);
+ }
+ break;
+ case VID_PID(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_TABLET_G5):
+ /* Ignore non-pen interfaces */
+ if (bInterfaceNumber != 1) {
+ uclogic_params_init_invalid(&p);
+ break;
+ }
+
+ rc = uclogic_params_pen_init_v1(&p.pen, &found, hdev);
+ if (rc != 0) {
+ hid_err(hdev, "pen probing failed: %d\n", rc);
+ goto cleanup;
+ } else if (found) {
+ rc = uclogic_params_frame_init_with_desc(
+ &p.frame,
+ uclogic_rdesc_ugee_g5_frame_arr,
+ uclogic_rdesc_ugee_g5_frame_size,
+ UCLOGIC_RDESC_UGEE_G5_FRAME_ID);
+ if (rc != 0) {
+ hid_err(hdev,
+ "failed creating buttonpad parameters: %d\n",
+ rc);
+ goto cleanup;
+ }
+ p.frame.re_lsb =
+ UCLOGIC_RDESC_UGEE_G5_FRAME_RE_LSB;
+ p.frame.dev_id_byte =
+ UCLOGIC_RDESC_UGEE_G5_FRAME_DEV_ID_BYTE;
+ } else {
+ hid_warn(hdev, "pen parameters not found");
+ uclogic_params_init_invalid(&p);
+ }
+
+ break;
+ case VID_PID(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_TABLET_EX07S):
+ /* Ignore non-pen interfaces */
+ if (bInterfaceNumber != 1) {
+ uclogic_params_init_invalid(&p);
+ break;
+ }
+
+ rc = uclogic_params_pen_init_v1(&p.pen, &found, hdev);
+ if (rc != 0) {
+ hid_err(hdev, "pen probing failed: %d\n", rc);
+ goto cleanup;
+ } else if (found) {
+ rc = uclogic_params_frame_init_with_desc(
+ &p.frame,
+ uclogic_rdesc_ugee_ex07_buttonpad_arr,
+ uclogic_rdesc_ugee_ex07_buttonpad_size,
+ 0);
+ if (rc != 0) {
+ hid_err(hdev,
+ "failed creating buttonpad parameters: %d\n",
+ rc);
+ goto cleanup;
+ }
+ } else {
+ hid_warn(hdev, "pen parameters not found");
+ uclogic_params_init_invalid(&p);
+ }
+
+ break;
+ }
+
+#undef VID_PID
+#undef WITH_OPT_DESC
+
+ /* Output parameters */
+ memcpy(params, &p, sizeof(*params));
+ memset(&p, 0, sizeof(p));
+ rc = 0;
+cleanup:
+ uclogic_params_cleanup(&p);
+ return rc;
+}
diff --git a/drivers/hid/hid-uclogic-params.h b/drivers/hid/hid-uclogic-params.h
new file mode 100644
index 000000000000..ba48b1c7a0e5
--- /dev/null
+++ b/drivers/hid/hid-uclogic-params.h
@@ -0,0 +1,207 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * HID driver for UC-Logic devices not fully compliant with HID standard
+ * - tablet initialization and parameter retrieval
+ *
+ * Copyright (c) 2018 Nikolai Kondrashov
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#ifndef _HID_UCLOGIC_PARAMS_H
+#define _HID_UCLOGIC_PARAMS_H
+
+#include <linux/usb.h>
+#include <linux/hid.h>
+
+/* Types of pen in-range reporting */
+enum uclogic_params_pen_inrange {
+ /* Normal reports: zero - out of proximity, one - in proximity */
+ UCLOGIC_PARAMS_PEN_INRANGE_NORMAL = 0,
+ /* Inverted reports: zero - in proximity, one - out of proximity */
+ UCLOGIC_PARAMS_PEN_INRANGE_INVERTED,
+ /* No reports */
+ UCLOGIC_PARAMS_PEN_INRANGE_NONE,
+};
+
+/* Convert a pen in-range reporting type to a string */
+extern const char *uclogic_params_pen_inrange_to_str(
+ enum uclogic_params_pen_inrange inrange);
+
+/*
+ * Tablet interface's pen input parameters.
+ *
+ * Must use declarative (descriptive) language, not imperative, to simplify
+ * understanding and maintain consistency.
+ *
+ * Noop (preserving functionality) when filled with zeroes.
+ */
+struct uclogic_params_pen {
+ /*
+ * Pointer to report descriptor describing the inputs.
+ * Allocated with kmalloc.
+ */
+ __u8 *desc_ptr;
+ /*
+ * Size of the report descriptor.
+ * Only valid, if "desc_ptr" is not NULL.
+ */
+ unsigned int desc_size;
+ /* Report ID, if reports should be tweaked, zero if not */
+ unsigned int id;
+ /* Type of in-range reporting, only valid if "id" is not zero */
+ enum uclogic_params_pen_inrange inrange;
+ /*
+ * True, if reports include fragmented high resolution coords, with
+ * high-order X and then Y bytes following the pressure field.
+ * Only valid if "id" is not zero.
+ */
+ bool fragmented_hires;
+};
+
+/*
+ * Parameters of frame control inputs of a tablet interface.
+ *
+ * Must use declarative (descriptive) language, not imperative, to simplify
+ * understanding and maintain consistency.
+ *
+ * Noop (preserving functionality) when filled with zeroes.
+ */
+struct uclogic_params_frame {
+ /*
+ * Pointer to report descriptor describing the inputs.
+ * Allocated with kmalloc.
+ */
+ __u8 *desc_ptr;
+ /*
+ * Size of the report descriptor.
+ * Only valid, if "desc_ptr" is not NULL.
+ */
+ unsigned int desc_size;
+ /*
+ * Report ID, if reports should be tweaked, zero if not.
+ */
+ unsigned int id;
+ /*
+ * Number of the least-significant bit of the 2-bit state of a rotary
+ * encoder, in the report. Cannot point to a 2-bit field crossing a
+ * byte boundary. Zero if not present. Only valid if "id" is not zero.
+ */
+ unsigned int re_lsb;
+ /*
+ * Offset of the Wacom-style device ID byte in the report, to be set
+ * to pad device ID (0xf), for compatibility with Wacom drivers. Zero
+ * if no changes to the report should be made. Only valid if "id" is
+ * not zero.
+ */
+ unsigned int dev_id_byte;
+};
+
+/*
+ * Tablet interface report parameters.
+ *
+ * Must use declarative (descriptive) language, not imperative, to simplify
+ * understanding and maintain consistency.
+ *
+ * When filled with zeros represents a "noop" configuration - passes all
+ * reports unchanged and lets the generic HID driver handle everything.
+ *
+ * The resulting device report descriptor is assembled from all the report
+ * descriptor parts referenced by the structure. No order of assembly should
+ * be assumed. The structure represents original device report descriptor if
+ * all the parts are NULL.
+ */
+struct uclogic_params {
+ /*
+ * True if the whole interface is invalid, false otherwise.
+ */
+ bool invalid;
+ /*
+ * Pointer to the common part of the replacement report descriptor,
+ * allocated with kmalloc. NULL if no common part is needed.
+ * Only valid, if "invalid" is false.
+ */
+ __u8 *desc_ptr;
+ /*
+ * Size of the common part of the replacement report descriptor.
+ * Only valid, if "desc_ptr" is not NULL.
+ */
+ unsigned int desc_size;
+ /*
+ * True, if pen usage in report descriptor is invalid, when present.
+ * Only valid, if "invalid" is false.
+ */
+ bool pen_unused;
+ /*
+ * Pen parameters and optional report descriptor part.
+ * Only valid if "pen_unused" is valid and false.
+ */
+ struct uclogic_params_pen pen;
+ /*
+ * Frame control parameters and optional report descriptor part.
+ * Only valid, if "invalid" is false.
+ */
+ struct uclogic_params_frame frame;
+ /*
+ * Bitmask matching frame controls "sub-report" flag in the second
+ * byte of the pen report, or zero if it's not expected.
+ * Only valid if both "pen" and "frame" are valid, and "frame.id" is
+ * not zero.
+ */
+ __u8 pen_frame_flag;
+};
+
+/* Initialize a tablet interface and discover its parameters */
+extern int uclogic_params_init(struct uclogic_params *params,
+ struct hid_device *hdev);
+
+/* Tablet interface parameters *printf format string */
+#define UCLOGIC_PARAMS_FMT_STR \
+ ".invalid = %s\n" \
+ ".desc_ptr = %p\n" \
+ ".desc_size = %u\n" \
+ ".pen_unused = %s\n" \
+ ".pen.desc_ptr = %p\n" \
+ ".pen.desc_size = %u\n" \
+ ".pen.id = %u\n" \
+ ".pen.inrange = %s\n" \
+ ".pen.fragmented_hires = %s\n" \
+ ".frame.desc_ptr = %p\n" \
+ ".frame.desc_size = %u\n" \
+ ".frame.id = %u\n" \
+ ".frame.re_lsb = %u\n" \
+ ".frame.dev_id_byte = %u\n" \
+ ".pen_frame_flag = 0x%02x\n"
+
+/* Tablet interface parameters *printf format arguments */
+#define UCLOGIC_PARAMS_FMT_ARGS(_params) \
+ ((_params)->invalid ? "true" : "false"), \
+ (_params)->desc_ptr, \
+ (_params)->desc_size, \
+ ((_params)->pen_unused ? "true" : "false"), \
+ (_params)->pen.desc_ptr, \
+ (_params)->pen.desc_size, \
+ (_params)->pen.id, \
+ uclogic_params_pen_inrange_to_str((_params)->pen.inrange), \
+ ((_params)->pen.fragmented_hires ? "true" : "false"), \
+ (_params)->frame.desc_ptr, \
+ (_params)->frame.desc_size, \
+ (_params)->frame.id, \
+ (_params)->frame.re_lsb, \
+ (_params)->frame.dev_id_byte, \
+ (_params)->pen_frame_flag
+
+/* Get a replacement report descriptor for a tablet's interface. */
+extern int uclogic_params_get_desc(const struct uclogic_params *params,
+ __u8 **pdesc,
+ unsigned int *psize);
+
+/* Free resources used by tablet interface's parameters */
+extern void uclogic_params_cleanup(struct uclogic_params *params);
+
+#endif /* _HID_UCLOGIC_PARAMS_H */
diff --git a/drivers/hid/hid-uclogic.c b/drivers/hid/hid-uclogic-rdesc.c
index 56b196d60041..bf5da6de7bba 100644
--- a/drivers/hid/hid-uclogic.c
+++ b/drivers/hid/hid-uclogic-rdesc.c
@@ -1,7 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0+
/*
* HID driver for UC-Logic devices not fully compliant with HID standard
+ * - original and fixed report descriptors
*
- * Copyright (c) 2010-2014 Nikolai Kondrashov
+ * Copyright (c) 2010-2017 Nikolai Kondrashov
* Copyright (c) 2013 Martin Rusko
*/
@@ -12,20 +14,12 @@
* any later version.
*/
-#include <linux/device.h>
-#include <linux/hid.h>
-#include <linux/module.h>
-#include <linux/usb.h>
+#include "hid-uclogic-rdesc.h"
+#include <linux/slab.h>
#include <asm/unaligned.h>
-#include "usbhid/usbhid.h"
-
-#include "hid-ids.h"
-
-/* Size of the original descriptor of WPXXXXU tablets */
-#define WPXXXXU_RDESC_ORIG_SIZE 212
/* Fixed WP4030U report descriptor */
-static __u8 wp4030u_rdesc_fixed[] = {
+__u8 uclogic_rdesc_wp4030u_fixed_arr[] = {
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x02, /* Usage (Pen), */
0xA1, 0x01, /* Collection (Application), */
@@ -66,8 +60,11 @@ static __u8 wp4030u_rdesc_fixed[] = {
0xC0 /* End Collection */
};
+const size_t uclogic_rdesc_wp4030u_fixed_size =
+ sizeof(uclogic_rdesc_wp4030u_fixed_arr);
+
/* Fixed WP5540U report descriptor */
-static __u8 wp5540u_rdesc_fixed[] = {
+__u8 uclogic_rdesc_wp5540u_fixed_arr[] = {
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x02, /* Usage (Pen), */
0xA1, 0x01, /* Collection (Application), */
@@ -140,8 +137,11 @@ static __u8 wp5540u_rdesc_fixed[] = {
0xC0 /* End Collection */
};
+const size_t uclogic_rdesc_wp5540u_fixed_size =
+ sizeof(uclogic_rdesc_wp5540u_fixed_arr);
+
/* Fixed WP8060U report descriptor */
-static __u8 wp8060u_rdesc_fixed[] = {
+__u8 uclogic_rdesc_wp8060u_fixed_arr[] = {
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x02, /* Usage (Pen), */
0xA1, 0x01, /* Collection (Application), */
@@ -214,11 +214,11 @@ static __u8 wp8060u_rdesc_fixed[] = {
0xC0 /* End Collection */
};
-/* Size of the original descriptor of WP1062 tablet */
-#define WP1062_RDESC_ORIG_SIZE 254
+const size_t uclogic_rdesc_wp8060u_fixed_size =
+ sizeof(uclogic_rdesc_wp8060u_fixed_arr);
/* Fixed WP1062 report descriptor */
-static __u8 wp1062_rdesc_fixed[] = {
+__u8 uclogic_rdesc_wp1062_fixed_arr[] = {
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x02, /* Usage (Pen), */
0xA1, 0x01, /* Collection (Application), */
@@ -262,11 +262,11 @@ static __u8 wp1062_rdesc_fixed[] = {
0xC0 /* End Collection */
};
-/* Size of the original descriptor of PF1209 tablet */
-#define PF1209_RDESC_ORIG_SIZE 234
+const size_t uclogic_rdesc_wp1062_fixed_size =
+ sizeof(uclogic_rdesc_wp1062_fixed_arr);
/* Fixed PF1209 report descriptor */
-static __u8 pf1209_rdesc_fixed[] = {
+__u8 uclogic_rdesc_pf1209_fixed_arr[] = {
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x02, /* Usage (Pen), */
0xA1, 0x01, /* Collection (Application), */
@@ -339,13 +339,11 @@ static __u8 pf1209_rdesc_fixed[] = {
0xC0 /* End Collection */
};
-/* Size of the original descriptors of TWHL850 tablet */
-#define TWHL850_RDESC_ORIG_SIZE0 182
-#define TWHL850_RDESC_ORIG_SIZE1 161
-#define TWHL850_RDESC_ORIG_SIZE2 92
+const size_t uclogic_rdesc_pf1209_fixed_size =
+ sizeof(uclogic_rdesc_pf1209_fixed_arr);
/* Fixed PID 0522 tablet report descriptor, interface 0 (stylus) */
-static __u8 twhl850_rdesc_fixed0[] = {
+__u8 uclogic_rdesc_twhl850_fixed0_arr[] = {
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x02, /* Usage (Pen), */
0xA1, 0x01, /* Collection (Application), */
@@ -387,8 +385,11 @@ static __u8 twhl850_rdesc_fixed0[] = {
0xC0 /* End Collection */
};
+const size_t uclogic_rdesc_twhl850_fixed0_size =
+ sizeof(uclogic_rdesc_twhl850_fixed0_arr);
+
/* Fixed PID 0522 tablet report descriptor, interface 1 (mouse) */
-static __u8 twhl850_rdesc_fixed1[] = {
+__u8 uclogic_rdesc_twhl850_fixed1_arr[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x02, /* Usage (Mouse), */
0xA1, 0x01, /* Collection (Application), */
@@ -424,8 +425,11 @@ static __u8 twhl850_rdesc_fixed1[] = {
0xC0 /* End Collection */
};
+const size_t uclogic_rdesc_twhl850_fixed1_size =
+ sizeof(uclogic_rdesc_twhl850_fixed1_arr);
+
/* Fixed PID 0522 tablet report descriptor, interface 2 (frame buttons) */
-static __u8 twhl850_rdesc_fixed2[] = {
+__u8 uclogic_rdesc_twhl850_fixed2_arr[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x06, /* Usage (Keyboard), */
0xA1, 0x01, /* Collection (Application), */
@@ -447,12 +451,11 @@ static __u8 twhl850_rdesc_fixed2[] = {
0xC0 /* End Collection */
};
-/* Size of the original descriptors of TWHA60 tablet */
-#define TWHA60_RDESC_ORIG_SIZE0 254
-#define TWHA60_RDESC_ORIG_SIZE1 139
+const size_t uclogic_rdesc_twhl850_fixed2_size =
+ sizeof(uclogic_rdesc_twhl850_fixed2_arr);
/* Fixed TWHA60 report descriptor, interface 0 (stylus) */
-static __u8 twha60_rdesc_fixed0[] = {
+__u8 uclogic_rdesc_twha60_fixed0_arr[] = {
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x02, /* Usage (Pen), */
0xA1, 0x01, /* Collection (Application), */
@@ -497,8 +500,11 @@ static __u8 twha60_rdesc_fixed0[] = {
0xC0 /* End Collection */
};
+const size_t uclogic_rdesc_twha60_fixed0_size =
+ sizeof(uclogic_rdesc_twha60_fixed0_arr);
+
/* Fixed TWHA60 report descriptor, interface 1 (frame buttons) */
-static __u8 twha60_rdesc_fixed1[] = {
+__u8 uclogic_rdesc_twha60_fixed1_arr[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x06, /* Usage (Keyboard), */
0xA1, 0x01, /* Collection (Application), */
@@ -522,29 +528,69 @@ static __u8 twha60_rdesc_fixed1[] = {
0xC0 /* End Collection */
};
-/* Report descriptor template placeholder head */
-#define UCLOGIC_PH_HEAD 0xFE, 0xED, 0x1D
+const size_t uclogic_rdesc_twha60_fixed1_size =
+ sizeof(uclogic_rdesc_twha60_fixed1_arr);
-/* Report descriptor template placeholder IDs */
-enum uclogic_ph_id {
- UCLOGIC_PH_ID_X_LM,
- UCLOGIC_PH_ID_X_PM,
- UCLOGIC_PH_ID_Y_LM,
- UCLOGIC_PH_ID_Y_PM,
- UCLOGIC_PH_ID_PRESSURE_LM,
- UCLOGIC_PH_ID_NUM
+/* Fixed report descriptor template for (tweaked) v1 pen reports */
+const __u8 uclogic_rdesc_pen_v1_template_arr[] = {
+ 0x05, 0x0D, /* Usage Page (Digitizer), */
+ 0x09, 0x02, /* Usage (Pen), */
+ 0xA1, 0x01, /* Collection (Application), */
+ 0x85, 0x07, /* Report ID (7), */
+ 0x09, 0x20, /* Usage (Stylus), */
+ 0xA0, /* Collection (Physical), */
+ 0x14, /* Logical Minimum (0), */
+ 0x25, 0x01, /* Logical Maximum (1), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x09, 0x42, /* Usage (Tip Switch), */
+ 0x09, 0x44, /* Usage (Barrel Switch), */
+ 0x09, 0x46, /* Usage (Tablet Pick), */
+ 0x95, 0x03, /* Report Count (3), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x95, 0x03, /* Report Count (3), */
+ 0x81, 0x03, /* Input (Constant, Variable), */
+ 0x09, 0x32, /* Usage (In Range), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x81, 0x03, /* Input (Constant, Variable), */
+ 0x75, 0x10, /* Report Size (16), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0xA4, /* Push, */
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x65, 0x13, /* Unit (Inch), */
+ 0x55, 0xFD, /* Unit Exponent (-3), */
+ 0x34, /* Physical Minimum (0), */
+ 0x09, 0x30, /* Usage (X), */
+ 0x27, UCLOGIC_RDESC_PEN_PH(X_LM),
+ /* Logical Maximum (PLACEHOLDER), */
+ 0x47, UCLOGIC_RDESC_PEN_PH(X_PM),
+ /* Physical Maximum (PLACEHOLDER), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x09, 0x31, /* Usage (Y), */
+ 0x27, UCLOGIC_RDESC_PEN_PH(Y_LM),
+ /* Logical Maximum (PLACEHOLDER), */
+ 0x47, UCLOGIC_RDESC_PEN_PH(Y_PM),
+ /* Physical Maximum (PLACEHOLDER), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0xB4, /* Pop, */
+ 0x09, 0x30, /* Usage (Tip Pressure), */
+ 0x27, UCLOGIC_RDESC_PEN_PH(PRESSURE_LM),
+ /* Logical Maximum (PLACEHOLDER), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0xC0, /* End Collection, */
+ 0xC0 /* End Collection */
};
-/* Report descriptor template placeholder */
-#define UCLOGIC_PH(_ID) UCLOGIC_PH_HEAD, UCLOGIC_PH_ID_##_ID
-#define UCLOGIC_PEN_REPORT_ID 0x07
+const size_t uclogic_rdesc_pen_v1_template_size =
+ sizeof(uclogic_rdesc_pen_v1_template_arr);
-/* Fixed report descriptor template */
-static const __u8 uclogic_tablet_rdesc_template[] = {
+/* Fixed report descriptor template for (tweaked) v2 pen reports */
+const __u8 uclogic_rdesc_pen_v2_template_arr[] = {
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x02, /* Usage (Pen), */
0xA1, 0x01, /* Collection (Application), */
- 0x85, 0x07, /* Report ID (7), */
+ 0x85, 0x08, /* Report ID (8), */
0x09, 0x20, /* Usage (Stylus), */
0xA0, /* Collection (Physical), */
0x14, /* Logical Minimum (0), */
@@ -562,529 +608,255 @@ static const __u8 uclogic_tablet_rdesc_template[] = {
0x81, 0x02, /* Input (Variable), */
0x95, 0x01, /* Report Count (1), */
0x81, 0x03, /* Input (Constant, Variable), */
- 0x75, 0x10, /* Report Size (16), */
0x95, 0x01, /* Report Count (1), */
0xA4, /* Push, */
0x05, 0x01, /* Usage Page (Desktop), */
0x65, 0x13, /* Unit (Inch), */
0x55, 0xFD, /* Unit Exponent (-3), */
+ 0x75, 0x18, /* Report Size (24), */
0x34, /* Physical Minimum (0), */
0x09, 0x30, /* Usage (X), */
- 0x27, UCLOGIC_PH(X_LM), /* Logical Maximum (PLACEHOLDER), */
- 0x47, UCLOGIC_PH(X_PM), /* Physical Maximum (PLACEHOLDER), */
+ 0x27, UCLOGIC_RDESC_PEN_PH(X_LM),
+ /* Logical Maximum (PLACEHOLDER), */
+ 0x47, UCLOGIC_RDESC_PEN_PH(X_PM),
+ /* Physical Maximum (PLACEHOLDER), */
0x81, 0x02, /* Input (Variable), */
0x09, 0x31, /* Usage (Y), */
- 0x27, UCLOGIC_PH(Y_LM), /* Logical Maximum (PLACEHOLDER), */
- 0x47, UCLOGIC_PH(Y_PM), /* Physical Maximum (PLACEHOLDER), */
+ 0x27, UCLOGIC_RDESC_PEN_PH(Y_LM),
+ /* Logical Maximum (PLACEHOLDER), */
+ 0x47, UCLOGIC_RDESC_PEN_PH(Y_PM),
+ /* Physical Maximum (PLACEHOLDER), */
0x81, 0x02, /* Input (Variable), */
0xB4, /* Pop, */
0x09, 0x30, /* Usage (Tip Pressure), */
- 0x27,
- UCLOGIC_PH(PRESSURE_LM),/* Logical Maximum (PLACEHOLDER), */
+ 0x75, 0x10, /* Report Size (16), */
+ 0x27, UCLOGIC_RDESC_PEN_PH(PRESSURE_LM),
+ /* Logical Maximum (PLACEHOLDER), */
0x81, 0x02, /* Input (Variable), */
+ 0x81, 0x03, /* Input (Constant, Variable), */
0xC0, /* End Collection, */
0xC0 /* End Collection */
};
-/* Fixed virtual pad report descriptor */
-static const __u8 uclogic_buttonpad_rdesc[] = {
+const size_t uclogic_rdesc_pen_v2_template_size =
+ sizeof(uclogic_rdesc_pen_v2_template_arr);
+
+/**
+ * Expand to the contents of a generic buttonpad report descriptor.
+ *
+ * @_padding: Padding from the end of button bits at bit 44, until
+ * the end of the report, in bits.
+ */
+#define UCLOGIC_RDESC_BUTTONPAD_BYTES(_padding) \
+ 0x05, 0x01, /* Usage Page (Desktop), */ \
+ 0x09, 0x07, /* Usage (Keypad), */ \
+ 0xA1, 0x01, /* Collection (Application), */ \
+ 0x85, 0xF7, /* Report ID (247), */ \
+ 0x14, /* Logical Minimum (0), */ \
+ 0x25, 0x01, /* Logical Maximum (1), */ \
+ 0x75, 0x01, /* Report Size (1), */ \
+ 0x05, 0x0D, /* Usage Page (Digitizer), */ \
+ 0x09, 0x39, /* Usage (Tablet Function Keys), */ \
+ 0xA0, /* Collection (Physical), */ \
+ 0x09, 0x44, /* Usage (Barrel Switch), */ \
+ 0x95, 0x01, /* Report Count (1), */ \
+ 0x81, 0x02, /* Input (Variable), */ \
+ 0x05, 0x01, /* Usage Page (Desktop), */ \
+ 0x09, 0x30, /* Usage (X), */ \
+ 0x09, 0x31, /* Usage (Y), */ \
+ 0x95, 0x02, /* Report Count (2), */ \
+ 0x81, 0x02, /* Input (Variable), */ \
+ 0x95, 0x15, /* Report Count (21), */ \
+ 0x81, 0x01, /* Input (Constant), */ \
+ 0x05, 0x09, /* Usage Page (Button), */ \
+ 0x19, 0x01, /* Usage Minimum (01h), */ \
+ 0x29, 0x0A, /* Usage Maximum (0Ah), */ \
+ 0x95, 0x0A, /* Report Count (10), */ \
+ 0x81, 0x02, /* Input (Variable), */ \
+ 0xC0, /* End Collection, */ \
+ 0x05, 0x01, /* Usage Page (Desktop), */ \
+ 0x09, 0x05, /* Usage (Gamepad), */ \
+ 0xA0, /* Collection (Physical), */ \
+ 0x05, 0x09, /* Usage Page (Button), */ \
+ 0x19, 0x01, /* Usage Minimum (01h), */ \
+ 0x29, 0x02, /* Usage Maximum (02h), */ \
+ 0x95, 0x02, /* Report Count (2), */ \
+ 0x81, 0x02, /* Input (Variable), */ \
+ 0x95, _padding, /* Report Count (_padding), */ \
+ 0x81, 0x01, /* Input (Constant), */ \
+ 0xC0, /* End Collection, */ \
+ 0xC0 /* End Collection */
+
+/* Fixed report descriptor for (tweaked) v1 buttonpad reports */
+const __u8 uclogic_rdesc_buttonpad_v1_arr[] = {
+ UCLOGIC_RDESC_BUTTONPAD_BYTES(20)
+};
+const size_t uclogic_rdesc_buttonpad_v1_size =
+ sizeof(uclogic_rdesc_buttonpad_v1_arr);
+
+/* Fixed report descriptor for (tweaked) v2 buttonpad reports */
+const __u8 uclogic_rdesc_buttonpad_v2_arr[] = {
+ UCLOGIC_RDESC_BUTTONPAD_BYTES(52)
+};
+const size_t uclogic_rdesc_buttonpad_v2_size =
+ sizeof(uclogic_rdesc_buttonpad_v2_arr);
+
+/* Fixed report descriptor for Ugee EX07 buttonpad */
+const __u8 uclogic_rdesc_ugee_ex07_buttonpad_arr[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x07, /* Usage (Keypad), */
0xA1, 0x01, /* Collection (Application), */
- 0x85, 0xF7, /* Report ID (247), */
- 0x05, 0x0D, /* Usage Page (Digitizers), */
+ 0x85, 0x06, /* Report ID (6), */
+ 0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x39, /* Usage (Tablet Function Keys), */
0xA0, /* Collection (Physical), */
0x05, 0x09, /* Usage Page (Button), */
0x75, 0x01, /* Report Size (1), */
- 0x95, 0x18, /* Report Count (24), */
+ 0x19, 0x03, /* Usage Minimum (03h), */
+ 0x29, 0x06, /* Usage Maximum (06h), */
+ 0x95, 0x04, /* Report Count (4), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x95, 0x1A, /* Report Count (26), */
0x81, 0x03, /* Input (Constant, Variable), */
0x19, 0x01, /* Usage Minimum (01h), */
- 0x29, 0x08, /* Usage Maximum (08h), */
- 0x95, 0x08, /* Report Count (8), */
+ 0x29, 0x02, /* Usage Maximum (02h), */
+ 0x95, 0x02, /* Report Count (2), */
0x81, 0x02, /* Input (Variable), */
- 0xC0, /* End Collection */
+ 0xC0, /* End Collection, */
0xC0 /* End Collection */
};
+const size_t uclogic_rdesc_ugee_ex07_buttonpad_size =
+ sizeof(uclogic_rdesc_ugee_ex07_buttonpad_arr);
-/* Parameter indices */
-enum uclogic_prm {
- UCLOGIC_PRM_X_LM = 1,
- UCLOGIC_PRM_Y_LM = 2,
- UCLOGIC_PRM_PRESSURE_LM = 4,
- UCLOGIC_PRM_RESOLUTION = 5,
- UCLOGIC_PRM_NUM
+/* Fixed report descriptor for Ugee G5 frame controls */
+const __u8 uclogic_rdesc_ugee_g5_frame_arr[] = {
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x09, 0x07, /* Usage (Keypad), */
+ 0xA1, 0x01, /* Collection (Application), */
+ 0x85, 0x06, /* Report ID (6), */
+ 0x05, 0x0D, /* Usage Page (Digitizer), */
+ 0x09, 0x39, /* Usage (Tablet Function Keys), */
+ 0xA0, /* Collection (Physical), */
+ 0x14, /* Logical Minimum (0), */
+ 0x25, 0x01, /* Logical Maximum (1), */
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x05, 0x09, /* Usage Page (Button), */
+ 0x19, 0x01, /* Usage Minimum (01h), */
+ 0x29, 0x05, /* Usage Maximum (05h), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x95, 0x05, /* Report Count (5), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x95, 0x03, /* Report Count (3), */
+ 0x81, 0x01, /* Input (Constant), */
+ 0x05, 0x0D, /* Usage Page (Digitizer), */
+ 0x0A, 0xFF, 0xFF, /* Usage (FFFFh), */
+ 0x26, 0xFF, 0x00, /* Logical Maximum (255), */
+ 0x75, 0x08, /* Report Size (8), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x25, 0x01, /* Logical Maximum (1), */
+ 0x09, 0x44, /* Usage (Barrel Switch), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x09, 0x30, /* Usage (X), */
+ 0x09, 0x31, /* Usage (Y), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x95, 0x02, /* Report Count (2), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x95, 0x0B, /* Report Count (11), */
+ 0x81, 0x01, /* Input (Constant), */
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x09, 0x38, /* Usage (Wheel), */
+ 0x15, 0xFF, /* Logical Minimum (-1), */
+ 0x25, 0x01, /* Logical Maximum (1), */
+ 0x75, 0x02, /* Report Size (2), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x81, 0x06, /* Input (Variable, Relative), */
+ 0xC0, /* End Collection, */
+ 0xC0 /* End Collection */
};
-
-/* Driver data */
-struct uclogic_drvdata {
- __u8 *rdesc;
- unsigned int rsize;
- bool invert_pen_inrange;
- bool ignore_pen_usage;
- bool has_virtual_pad_interface;
+const size_t uclogic_rdesc_ugee_g5_frame_size =
+ sizeof(uclogic_rdesc_ugee_g5_frame_arr);
+
+/* Fixed report descriptor for XP-Pen Deco 01 frame controls */
+const __u8 uclogic_rdesc_xppen_deco01_frame_arr[] = {
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x09, 0x07, /* Usage (Keypad), */
+ 0xA1, 0x01, /* Collection (Application), */
+ 0x85, 0x06, /* Report ID (6), */
+ 0x14, /* Logical Minimum (0), */
+ 0x25, 0x01, /* Logical Maximum (1), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x05, 0x0D, /* Usage Page (Digitizer), */
+ 0x09, 0x39, /* Usage (Tablet Function Keys), */
+ 0xA0, /* Collection (Physical), */
+ 0x05, 0x09, /* Usage Page (Button), */
+ 0x19, 0x01, /* Usage Minimum (01h), */
+ 0x29, 0x08, /* Usage Maximum (08h), */
+ 0x95, 0x08, /* Report Count (8), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x05, 0x0D, /* Usage Page (Digitizer), */
+ 0x09, 0x44, /* Usage (Barrel Switch), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x09, 0x30, /* Usage (X), */
+ 0x09, 0x31, /* Usage (Y), */
+ 0x95, 0x02, /* Report Count (2), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x95, 0x15, /* Report Count (21), */
+ 0x81, 0x01, /* Input (Constant), */
+ 0xC0, /* End Collection, */
+ 0xC0 /* End Collection */
};
-static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc,
- unsigned int *rsize)
-{
- struct usb_interface *iface = to_usb_interface(hdev->dev.parent);
- __u8 iface_num = iface->cur_altsetting->desc.bInterfaceNumber;
- struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
-
- if (drvdata->rdesc != NULL) {
- rdesc = drvdata->rdesc;
- *rsize = drvdata->rsize;
- return rdesc;
- }
-
- switch (hdev->product) {
- case USB_DEVICE_ID_UCLOGIC_TABLET_PF1209:
- if (*rsize == PF1209_RDESC_ORIG_SIZE) {
- rdesc = pf1209_rdesc_fixed;
- *rsize = sizeof(pf1209_rdesc_fixed);
- }
- break;
- case USB_DEVICE_ID_UCLOGIC_TABLET_WP4030U:
- if (*rsize == WPXXXXU_RDESC_ORIG_SIZE) {
- rdesc = wp4030u_rdesc_fixed;
- *rsize = sizeof(wp4030u_rdesc_fixed);
- }
- break;
- case USB_DEVICE_ID_UCLOGIC_TABLET_WP5540U:
- if (*rsize == WPXXXXU_RDESC_ORIG_SIZE) {
- rdesc = wp5540u_rdesc_fixed;
- *rsize = sizeof(wp5540u_rdesc_fixed);
- }
- break;
- case USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U:
- if (*rsize == WPXXXXU_RDESC_ORIG_SIZE) {
- rdesc = wp8060u_rdesc_fixed;
- *rsize = sizeof(wp8060u_rdesc_fixed);
- }
- break;
- case USB_DEVICE_ID_UCLOGIC_TABLET_WP1062:
- if (*rsize == WP1062_RDESC_ORIG_SIZE) {
- rdesc = wp1062_rdesc_fixed;
- *rsize = sizeof(wp1062_rdesc_fixed);
- }
- break;
- case USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850:
- switch (iface_num) {
- case 0:
- if (*rsize == TWHL850_RDESC_ORIG_SIZE0) {
- rdesc = twhl850_rdesc_fixed0;
- *rsize = sizeof(twhl850_rdesc_fixed0);
- }
- break;
- case 1:
- if (*rsize == TWHL850_RDESC_ORIG_SIZE1) {
- rdesc = twhl850_rdesc_fixed1;
- *rsize = sizeof(twhl850_rdesc_fixed1);
- }
- break;
- case 2:
- if (*rsize == TWHL850_RDESC_ORIG_SIZE2) {
- rdesc = twhl850_rdesc_fixed2;
- *rsize = sizeof(twhl850_rdesc_fixed2);
- }
- break;
- }
- break;
- case USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60:
- switch (iface_num) {
- case 0:
- if (*rsize == TWHA60_RDESC_ORIG_SIZE0) {
- rdesc = twha60_rdesc_fixed0;
- *rsize = sizeof(twha60_rdesc_fixed0);
- }
- break;
- case 1:
- if (*rsize == TWHA60_RDESC_ORIG_SIZE1) {
- rdesc = twha60_rdesc_fixed1;
- *rsize = sizeof(twha60_rdesc_fixed1);
- }
- break;
- }
- break;
- }
-
- return rdesc;
-}
-
-static int uclogic_input_mapping(struct hid_device *hdev, struct hid_input *hi,
- struct hid_field *field, struct hid_usage *usage,
- unsigned long **bit, int *max)
-{
- struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
-
- /* discard the unused pen interface */
- if ((drvdata->ignore_pen_usage) &&
- (field->application == HID_DG_PEN))
- return -1;
-
- /* let hid-core decide what to do */
- return 0;
-}
-
-static int uclogic_input_configured(struct hid_device *hdev,
- struct hid_input *hi)
-{
- char *name;
- const char *suffix = NULL;
- struct hid_field *field;
- size_t len;
-
- /* no report associated (HID_QUIRK_MULTI_INPUT not set) */
- if (!hi->report)
- return 0;
-
- field = hi->report->field[0];
-
- switch (field->application) {
- case HID_GD_KEYBOARD:
- suffix = "Keyboard";
- break;
- case HID_GD_MOUSE:
- suffix = "Mouse";
- break;
- case HID_GD_KEYPAD:
- suffix = "Pad";
- break;
- case HID_DG_PEN:
- suffix = "Pen";
- break;
- case HID_CP_CONSUMER_CONTROL:
- suffix = "Consumer Control";
- break;
- case HID_GD_SYSTEM_CONTROL:
- suffix = "System Control";
- break;
- }
-
- if (suffix) {
- len = strlen(hdev->name) + 2 + strlen(suffix);
- name = devm_kzalloc(&hi->input->dev, len, GFP_KERNEL);
- if (name) {
- snprintf(name, len, "%s %s", hdev->name, suffix);
- hi->input->name = name;
- }
- }
-
- return 0;
-}
+const size_t uclogic_rdesc_xppen_deco01_frame_size =
+ sizeof(uclogic_rdesc_xppen_deco01_frame_arr);
/**
- * Enable fully-functional tablet mode and determine device parameters.
+ * uclogic_rdesc_template_apply() - apply report descriptor parameters to a
+ * report descriptor template, creating a report descriptor. Copies the
+ * template over to the new report descriptor and replaces every occurrence of
+ * UCLOGIC_RDESC_PH_HEAD, followed by an index byte, with the value from the
+ * parameter list at that index.
*
- * @hdev: HID device
+ * @template_ptr: Pointer to the template buffer.
+ * @template_size: Size of the template buffer.
+ * @param_list: List of template parameters.
+ * @param_num: Number of parameters in the list.
+ *
+ * Returns:
+ * Kmalloc-allocated pointer to the created report descriptor,
+ * or NULL if allocation failed.
*/
-static int uclogic_tablet_enable(struct hid_device *hdev)
+__u8 *uclogic_rdesc_template_apply(const __u8 *template_ptr,
+ size_t template_size,
+ const s32 *param_list,
+ size_t param_num)
{
- int rc;
- struct usb_device *usb_dev = hid_to_usb_dev(hdev);
- struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
- __le16 *buf = NULL;
- size_t len;
- s32 params[UCLOGIC_PH_ID_NUM];
- s32 resolution;
+ static const __u8 head[] = {UCLOGIC_RDESC_PH_HEAD};
+ __u8 *rdesc_ptr;
__u8 *p;
s32 v;
- /*
- * Read string descriptor containing tablet parameters. The specific
- * string descriptor and data were discovered by sniffing the Windows
- * driver traffic.
- * NOTE: This enables fully-functional tablet mode.
- */
- len = UCLOGIC_PRM_NUM * sizeof(*buf);
- buf = kmalloc(len, GFP_KERNEL);
- if (buf == NULL) {
- rc = -ENOMEM;
- goto cleanup;
- }
- rc = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0),
- USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
- (USB_DT_STRING << 8) + 0x64,
- 0x0409, buf, len,
- USB_CTRL_GET_TIMEOUT);
- if (rc == -EPIPE) {
- hid_err(hdev, "device parameters not found\n");
- rc = -ENODEV;
- goto cleanup;
- } else if (rc < 0) {
- hid_err(hdev, "failed to get device parameters: %d\n", rc);
- rc = -ENODEV;
- goto cleanup;
- } else if (rc != len) {
- hid_err(hdev, "invalid device parameters\n");
- rc = -ENODEV;
- goto cleanup;
- }
-
- /* Extract device parameters */
- params[UCLOGIC_PH_ID_X_LM] = le16_to_cpu(buf[UCLOGIC_PRM_X_LM]);
- params[UCLOGIC_PH_ID_Y_LM] = le16_to_cpu(buf[UCLOGIC_PRM_Y_LM]);
- params[UCLOGIC_PH_ID_PRESSURE_LM] =
- le16_to_cpu(buf[UCLOGIC_PRM_PRESSURE_LM]);
- resolution = le16_to_cpu(buf[UCLOGIC_PRM_RESOLUTION]);
- if (resolution == 0) {
- params[UCLOGIC_PH_ID_X_PM] = 0;
- params[UCLOGIC_PH_ID_Y_PM] = 0;
- } else {
- params[UCLOGIC_PH_ID_X_PM] = params[UCLOGIC_PH_ID_X_LM] *
- 1000 / resolution;
- params[UCLOGIC_PH_ID_Y_PM] = params[UCLOGIC_PH_ID_Y_LM] *
- 1000 / resolution;
- }
+ rdesc_ptr = kmemdup(template_ptr, template_size, GFP_KERNEL);
+ if (rdesc_ptr == NULL)
+ return NULL;
- /* Allocate fixed report descriptor */
- drvdata->rdesc = devm_kzalloc(&hdev->dev,
- sizeof(uclogic_tablet_rdesc_template),
- GFP_KERNEL);
- if (drvdata->rdesc == NULL) {
- rc = -ENOMEM;
- goto cleanup;
- }
- drvdata->rsize = sizeof(uclogic_tablet_rdesc_template);
-
- /* Format fixed report descriptor */
- memcpy(drvdata->rdesc, uclogic_tablet_rdesc_template,
- drvdata->rsize);
- for (p = drvdata->rdesc;
- p <= drvdata->rdesc + drvdata->rsize - 4;) {
- if (p[0] == 0xFE && p[1] == 0xED && p[2] == 0x1D &&
- p[3] < ARRAY_SIZE(params)) {
- v = params[p[3]];
+ for (p = rdesc_ptr; p + sizeof(head) < rdesc_ptr + template_size;) {
+ if (memcmp(p, head, sizeof(head)) == 0 &&
+ p[sizeof(head)] < param_num) {
+ v = param_list[p[sizeof(head)]];
put_unaligned(cpu_to_le32(v), (s32 *)p);
- p += 4;
+ p += sizeof(head) + 1;
} else {
p++;
}
}
- rc = 0;
-
-cleanup:
- kfree(buf);
- return rc;
+ return rdesc_ptr;
}
-
-/**
- * Enable actual button mode.
- *
- * @hdev: HID device
- */
-static int uclogic_button_enable(struct hid_device *hdev)
-{
- int rc;
- struct usb_device *usb_dev = hid_to_usb_dev(hdev);
- struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
- char *str_buf;
- size_t str_len = 16;
- unsigned char *rdesc;
- size_t rdesc_len;
-
- str_buf = kzalloc(str_len, GFP_KERNEL);
- if (str_buf == NULL) {
- rc = -ENOMEM;
- goto cleanup;
- }
-
- /* Enable abstract keyboard mode */
- rc = usb_string(usb_dev, 0x7b, str_buf, str_len);
- if (rc == -EPIPE) {
- hid_info(hdev, "button mode setting not found\n");
- rc = 0;
- goto cleanup;
- } else if (rc < 0) {
- hid_err(hdev, "failed to enable abstract keyboard\n");
- goto cleanup;
- } else if (strncmp(str_buf, "HK On", rc)) {
- hid_info(hdev, "invalid answer when requesting buttons: '%s'\n",
- str_buf);
- rc = -EINVAL;
- goto cleanup;
- }
-
- /* Re-allocate fixed report descriptor */
- rdesc_len = drvdata->rsize + sizeof(uclogic_buttonpad_rdesc);
- rdesc = devm_kzalloc(&hdev->dev, rdesc_len, GFP_KERNEL);
- if (!rdesc) {
- rc = -ENOMEM;
- goto cleanup;
- }
-
- memcpy(rdesc, drvdata->rdesc, drvdata->rsize);
-
- /* Append the buttonpad descriptor */
- memcpy(rdesc + drvdata->rsize, uclogic_buttonpad_rdesc,
- sizeof(uclogic_buttonpad_rdesc));
-
- /* clean up old rdesc and use the new one */
- drvdata->rsize = rdesc_len;
- devm_kfree(&hdev->dev, drvdata->rdesc);
- drvdata->rdesc = rdesc;
-
- rc = 0;
-
-cleanup:
- kfree(str_buf);
- return rc;
-}
-
-static int uclogic_probe(struct hid_device *hdev,
- const struct hid_device_id *id)
-{
- int rc;
- struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
- struct usb_device *udev = hid_to_usb_dev(hdev);
- struct uclogic_drvdata *drvdata;
-
- /*
- * libinput requires the pad interface to be on a different node
- * than the pen, so use QUIRK_MULTI_INPUT for all tablets.
- */
- hdev->quirks |= HID_QUIRK_MULTI_INPUT;
-
- /* Allocate and assign driver data */
- drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
- if (drvdata == NULL)
- return -ENOMEM;
-
- hid_set_drvdata(hdev, drvdata);
-
- switch (id->product) {
- case USB_DEVICE_ID_HUION_TABLET:
- case USB_DEVICE_ID_YIYNOVA_TABLET:
- case USB_DEVICE_ID_UGEE_TABLET_81:
- case USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3:
- case USB_DEVICE_ID_UGEE_TABLET_45:
- /* If this is the pen interface */
- if (intf->cur_altsetting->desc.bInterfaceNumber == 0) {
- rc = uclogic_tablet_enable(hdev);
- if (rc) {
- hid_err(hdev, "tablet enabling failed\n");
- return rc;
- }
- drvdata->invert_pen_inrange = true;
-
- rc = uclogic_button_enable(hdev);
- drvdata->has_virtual_pad_interface = !rc;
- } else {
- drvdata->ignore_pen_usage = true;
- }
- break;
- case USB_DEVICE_ID_UGTIZER_TABLET_GP0610:
- case USB_DEVICE_ID_UGEE_TABLET_EX07S:
- /* If this is the pen interface */
- if (intf->cur_altsetting->desc.bInterfaceNumber == 1) {
- rc = uclogic_tablet_enable(hdev);
- if (rc) {
- hid_err(hdev, "tablet enabling failed\n");
- return rc;
- }
- drvdata->invert_pen_inrange = true;
- } else {
- drvdata->ignore_pen_usage = true;
- }
- break;
- case USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60:
- /*
- * If it is the three-interface version, which is known to
- * respond to initialization.
- */
- if (udev->config->desc.bNumInterfaces == 3) {
- /* If it is the pen interface */
- if (intf->cur_altsetting->desc.bInterfaceNumber == 0) {
- rc = uclogic_tablet_enable(hdev);
- if (rc) {
- hid_err(hdev, "tablet enabling failed\n");
- return rc;
- }
- drvdata->invert_pen_inrange = true;
-
- rc = uclogic_button_enable(hdev);
- drvdata->has_virtual_pad_interface = !rc;
- } else {
- drvdata->ignore_pen_usage = true;
- }
- }
- break;
- }
-
- rc = hid_parse(hdev);
- if (rc) {
- hid_err(hdev, "parse failed\n");
- return rc;
- }
-
- rc = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
- if (rc) {
- hid_err(hdev, "hw start failed\n");
- return rc;
- }
-
- return 0;
-}
-
-static int uclogic_raw_event(struct hid_device *hdev, struct hid_report *report,
- u8 *data, int size)
-{
- struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
-
- if ((report->type == HID_INPUT_REPORT) &&
- (report->id == UCLOGIC_PEN_REPORT_ID) &&
- (size >= 2)) {
- if (drvdata->has_virtual_pad_interface && (data[1] & 0x20))
- /* Change to virtual frame button report ID */
- data[0] = 0xf7;
- else if (drvdata->invert_pen_inrange)
- /* Invert the in-range bit */
- data[1] ^= 0x40;
- }
-
- return 0;
-}
-
-static const struct hid_device_id uclogic_devices[] = {
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
- USB_DEVICE_ID_UCLOGIC_TABLET_PF1209) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
- USB_DEVICE_ID_UCLOGIC_TABLET_WP4030U) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
- USB_DEVICE_ID_UCLOGIC_TABLET_WP5540U) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
- USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
- USB_DEVICE_ID_UCLOGIC_TABLET_WP1062) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
- USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
- USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60) },
- { HID_USB_DEVICE(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_TABLET) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_HUION_TABLET) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_YIYNOVA_TABLET) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UGEE_TABLET_81) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UGEE_TABLET_45) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UGTIZER, USB_DEVICE_ID_UGTIZER_TABLET_GP0610) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_TABLET_EX07S) },
- { }
-};
-MODULE_DEVICE_TABLE(hid, uclogic_devices);
-
-static struct hid_driver uclogic_driver = {
- .name = "uclogic",
- .id_table = uclogic_devices,
- .probe = uclogic_probe,
- .report_fixup = uclogic_report_fixup,
- .raw_event = uclogic_raw_event,
- .input_mapping = uclogic_input_mapping,
- .input_configured = uclogic_input_configured,
-};
-module_hid_driver(uclogic_driver);
-
-MODULE_AUTHOR("Martin Rusko");
-MODULE_AUTHOR("Nikolai Kondrashov");
-MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-uclogic-rdesc.h b/drivers/hid/hid-uclogic-rdesc.h
new file mode 100644
index 000000000000..c5da51055af3
--- /dev/null
+++ b/drivers/hid/hid-uclogic-rdesc.h
@@ -0,0 +1,155 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * HID driver for UC-Logic devices not fully compliant with HID standard
+ * - original and fixed report descriptors
+ *
+ * Copyright (c) 2010-2018 Nikolai Kondrashov
+ * Copyright (c) 2013 Martin Rusko
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#ifndef _HID_UCLOGIC_RDESC_H
+#define _HID_UCLOGIC_RDESC_H
+
+#include <linux/usb.h>
+
+/* Size of the original descriptor of WPXXXXU tablets */
+#define UCLOGIC_RDESC_WPXXXXU_ORIG_SIZE 212
+
+/* Fixed WP4030U report descriptor */
+extern __u8 uclogic_rdesc_wp4030u_fixed_arr[];
+extern const size_t uclogic_rdesc_wp4030u_fixed_size;
+
+/* Fixed WP5540U report descriptor */
+extern __u8 uclogic_rdesc_wp5540u_fixed_arr[];
+extern const size_t uclogic_rdesc_wp5540u_fixed_size;
+
+/* Fixed WP8060U report descriptor */
+extern __u8 uclogic_rdesc_wp8060u_fixed_arr[];
+extern const size_t uclogic_rdesc_wp8060u_fixed_size;
+
+/* Size of the original descriptor of the new WP5540U tablet */
+#define UCLOGIC_RDESC_WP5540U_V2_ORIG_SIZE 232
+
+/* Size of the original descriptor of WP1062 tablet */
+#define UCLOGIC_RDESC_WP1062_ORIG_SIZE 254
+
+/* Fixed WP1062 report descriptor */
+extern __u8 uclogic_rdesc_wp1062_fixed_arr[];
+extern const size_t uclogic_rdesc_wp1062_fixed_size;
+
+/* Size of the original descriptor of PF1209 tablet */
+#define UCLOGIC_RDESC_PF1209_ORIG_SIZE 234
+
+/* Fixed PF1209 report descriptor */
+extern __u8 uclogic_rdesc_pf1209_fixed_arr[];
+extern const size_t uclogic_rdesc_pf1209_fixed_size;
+
+/* Size of the original descriptors of TWHL850 tablet */
+#define UCLOGIC_RDESC_TWHL850_ORIG0_SIZE 182
+#define UCLOGIC_RDESC_TWHL850_ORIG1_SIZE 161
+#define UCLOGIC_RDESC_TWHL850_ORIG2_SIZE 92
+
+/* Fixed PID 0522 tablet report descriptor, interface 0 (stylus) */
+extern __u8 uclogic_rdesc_twhl850_fixed0_arr[];
+extern const size_t uclogic_rdesc_twhl850_fixed0_size;
+
+/* Fixed PID 0522 tablet report descriptor, interface 1 (mouse) */
+extern __u8 uclogic_rdesc_twhl850_fixed1_arr[];
+extern const size_t uclogic_rdesc_twhl850_fixed1_size;
+
+/* Fixed PID 0522 tablet report descriptor, interface 2 (frame buttons) */
+extern __u8 uclogic_rdesc_twhl850_fixed2_arr[];
+extern const size_t uclogic_rdesc_twhl850_fixed2_size;
+
+/* Size of the original descriptors of TWHA60 tablet */
+#define UCLOGIC_RDESC_TWHA60_ORIG0_SIZE 254
+#define UCLOGIC_RDESC_TWHA60_ORIG1_SIZE 139
+
+/* Fixed TWHA60 report descriptor, interface 0 (stylus) */
+extern __u8 uclogic_rdesc_twha60_fixed0_arr[];
+extern const size_t uclogic_rdesc_twha60_fixed0_size;
+
+/* Fixed TWHA60 report descriptor, interface 1 (frame buttons) */
+extern __u8 uclogic_rdesc_twha60_fixed1_arr[];
+extern const size_t uclogic_rdesc_twha60_fixed1_size;
+
+/* Report descriptor template placeholder head */
+#define UCLOGIC_RDESC_PH_HEAD 0xFE, 0xED, 0x1D
+
+/* Apply report descriptor parameters to a report descriptor template */
+extern __u8 *uclogic_rdesc_template_apply(const __u8 *template_ptr,
+ size_t template_size,
+ const s32 *param_list,
+ size_t param_num);
+
+/* Pen report descriptor template placeholder IDs */
+enum uclogic_rdesc_pen_ph_id {
+ UCLOGIC_RDESC_PEN_PH_ID_X_LM,
+ UCLOGIC_RDESC_PEN_PH_ID_X_PM,
+ UCLOGIC_RDESC_PEN_PH_ID_Y_LM,
+ UCLOGIC_RDESC_PEN_PH_ID_Y_PM,
+ UCLOGIC_RDESC_PEN_PH_ID_PRESSURE_LM,
+ UCLOGIC_RDESC_PEN_PH_ID_NUM
+};
+
+/* Report descriptor pen template placeholder */
+#define UCLOGIC_RDESC_PEN_PH(_ID) \
+ UCLOGIC_RDESC_PH_HEAD, UCLOGIC_RDESC_PEN_PH_ID_##_ID
+
+/* Report ID for v1 pen reports */
+#define UCLOGIC_RDESC_PEN_V1_ID 0x07
+
+/* Fixed report descriptor template for (tweaked) v1 pen reports */
+extern const __u8 uclogic_rdesc_pen_v1_template_arr[];
+extern const size_t uclogic_rdesc_pen_v1_template_size;
+
+/* Report ID for v2 pen reports */
+#define UCLOGIC_RDESC_PEN_V2_ID 0x08
+
+/* Fixed report descriptor template for (tweaked) v2 pen reports */
+extern const __u8 uclogic_rdesc_pen_v2_template_arr[];
+extern const size_t uclogic_rdesc_pen_v2_template_size;
+
+/* Fixed report descriptor for (tweaked) v1 buttonpad reports */
+extern const __u8 uclogic_rdesc_buttonpad_v1_arr[];
+extern const size_t uclogic_rdesc_buttonpad_v1_size;
+
+/* Report ID for tweaked v1 buttonpad reports */
+#define UCLOGIC_RDESC_BUTTONPAD_V1_ID 0xf7
+
+/* Fixed report descriptor for (tweaked) v2 buttonpad reports */
+extern const __u8 uclogic_rdesc_buttonpad_v2_arr[];
+extern const size_t uclogic_rdesc_buttonpad_v2_size;
+
+/* Report ID for tweaked v2 buttonpad reports */
+#define UCLOGIC_RDESC_BUTTONPAD_V2_ID 0xf7
+
+/* Fixed report descriptor for Ugee EX07 buttonpad */
+extern const __u8 uclogic_rdesc_ugee_ex07_buttonpad_arr[];
+extern const size_t uclogic_rdesc_ugee_ex07_buttonpad_size;
+
+/* Fixed report descriptor for XP-Pen Deco 01 frame controls */
+extern const __u8 uclogic_rdesc_xppen_deco01_frame_arr[];
+extern const size_t uclogic_rdesc_xppen_deco01_frame_size;
+
+/* Fixed report descriptor for Ugee G5 frame controls */
+extern const __u8 uclogic_rdesc_ugee_g5_frame_arr[];
+extern const size_t uclogic_rdesc_ugee_g5_frame_size;
+
+/* Report ID of Ugee G5 frame control reports */
+#define UCLOGIC_RDESC_UGEE_G5_FRAME_ID 0x06
+
+/* Device ID byte offset in Ugee G5 frame report */
+#define UCLOGIC_RDESC_UGEE_G5_FRAME_DEV_ID_BYTE 0x2
+
+/* Least-significant bit of Ugee G5 frame rotary encoder state */
+#define UCLOGIC_RDESC_UGEE_G5_FRAME_RE_LSB 38
+
+#endif /* _HID_UCLOGIC_RDESC_H */
diff --git a/drivers/hid/hid-viewsonic.c b/drivers/hid/hid-viewsonic.c
new file mode 100644
index 000000000000..df60c8fc2efd
--- /dev/null
+++ b/drivers/hid/hid-viewsonic.c
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * HID driver for ViewSonic devices not fully compliant with HID standard
+ *
+ * Copyright (c) 2017 Nikolai Kondrashov
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+/* Size of the original descriptor of PD1011 signature pad */
+#define PD1011_RDESC_ORIG_SIZE 408
+
+/* Fixed report descriptor of PD1011 signature pad */
+static __u8 pd1011_rdesc_fixed[] = {
+ 0x05, 0x0D, /* Usage Page (Digitizer), */
+ 0x09, 0x02, /* Usage (Pen), */
+ 0xA1, 0x01, /* Collection (Application), */
+ 0x85, 0x02, /* Report ID (2), */
+ 0x09, 0x20, /* Usage (Stylus), */
+ 0xA0, /* Collection (Physical), */
+ 0x75, 0x10, /* Report Size (16), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0xA4, /* Push, */
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x65, 0x13, /* Unit (Inch), */
+ 0x55, 0xFD, /* Unit Exponent (-3), */
+ 0x34, /* Physical Minimum (0), */
+ 0x09, 0x30, /* Usage (X), */
+ 0x46, 0x5D, 0x21, /* Physical Maximum (8541), */
+ 0x27, 0x80, 0xA9,
+ 0x00, 0x00, /* Logical Maximum (43392), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x09, 0x31, /* Usage (Y), */
+ 0x46, 0xDA, 0x14, /* Physical Maximum (5338), */
+ 0x26, 0xF0, 0x69, /* Logical Maximum (27120), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0xB4, /* Pop, */
+ 0x14, /* Logical Minimum (0), */
+ 0x25, 0x01, /* Logical Maximum (1), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x81, 0x03, /* Input (Constant, Variable), */
+ 0x09, 0x32, /* Usage (In Range), */
+ 0x09, 0x42, /* Usage (Tip Switch), */
+ 0x95, 0x02, /* Report Count (2), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x95, 0x05, /* Report Count (5), */
+ 0x81, 0x03, /* Input (Constant, Variable), */
+ 0x75, 0x10, /* Report Size (16), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x09, 0x30, /* Usage (Tip Pressure), */
+ 0x15, 0x05, /* Logical Minimum (5), */
+ 0x26, 0xFF, 0x07, /* Logical Maximum (2047), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x75, 0x10, /* Report Size (16), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x81, 0x03, /* Input (Constant, Variable), */
+ 0xC0, /* End Collection, */
+ 0xC0 /* End Collection */
+};
+
+static __u8 *viewsonic_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int *rsize)
+{
+ switch (hdev->product) {
+ case USB_DEVICE_ID_VIEWSONIC_PD1011:
+ case USB_DEVICE_ID_SIGNOTEC_VIEWSONIC_PD1011:
+ if (*rsize == PD1011_RDESC_ORIG_SIZE) {
+ rdesc = pd1011_rdesc_fixed;
+ *rsize = sizeof(pd1011_rdesc_fixed);
+ }
+ break;
+ }
+
+ return rdesc;
+}
+
+static const struct hid_device_id viewsonic_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_VIEWSONIC,
+ USB_DEVICE_ID_VIEWSONIC_PD1011) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SIGNOTEC,
+ USB_DEVICE_ID_SIGNOTEC_VIEWSONIC_PD1011) },
+ { }
+};
+MODULE_DEVICE_TABLE(hid, viewsonic_devices);
+
+static struct hid_driver viewsonic_driver = {
+ .name = "viewsonic",
+ .id_table = viewsonic_devices,
+ .report_fixup = viewsonic_report_fixup,
+};
+module_hid_driver(viewsonic_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c
index 4a44e48e08b2..9fc51eff1079 100644
--- a/drivers/hid/hidraw.c
+++ b/drivers/hid/hidraw.c
@@ -107,8 +107,6 @@ out:
/*
* The first byte of the report buffer is expected to be a report number.
- *
- * This function is to be called with the minors_lock mutex held.
*/
static ssize_t hidraw_send_report(struct file *file, const char __user *buffer, size_t count, unsigned char report_type)
{
@@ -117,6 +115,8 @@ static ssize_t hidraw_send_report(struct file *file, const char __user *buffer,
__u8 *buf;
int ret = 0;
+ lockdep_assert_held(&minors_lock);
+
if (!hidraw_table[minor] || !hidraw_table[minor]->exist) {
ret = -ENODEV;
goto out;
@@ -181,8 +181,6 @@ static ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t
* of buffer is the report number to request, or 0x0 if the defice does not
* use numbered reports. The report_type parameter can be HID_FEATURE_REPORT
* or HID_INPUT_REPORT.
- *
- * This function is to be called with the minors_lock mutex held.
*/
static ssize_t hidraw_get_report(struct file *file, char __user *buffer, size_t count, unsigned char report_type)
{
@@ -192,6 +190,8 @@ static ssize_t hidraw_get_report(struct file *file, char __user *buffer, size_t
int ret = 0, len;
unsigned char report_number;
+ lockdep_assert_held(&minors_lock);
+
if (!hidraw_table[minor] || !hidraw_table[minor]->exist) {
ret = -ENODEV;
goto out;
diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c
index 8555ce7e737b..4d1f24ee249c 100644
--- a/drivers/hid/i2c-hid/i2c-hid-core.c
+++ b/drivers/hid/i2c-hid/i2c-hid-core.c
@@ -50,6 +50,7 @@
#define I2C_HID_QUIRK_NO_IRQ_AFTER_RESET BIT(1)
#define I2C_HID_QUIRK_NO_RUNTIME_PM BIT(2)
#define I2C_HID_QUIRK_DELAY_AFTER_SLEEP BIT(3)
+#define I2C_HID_QUIRK_BOGUS_IRQ BIT(4)
/* flags */
#define I2C_HID_STARTED 0
@@ -179,6 +180,12 @@ static const struct i2c_hid_quirks {
I2C_HID_QUIRK_DELAY_AFTER_SLEEP },
{ USB_VENDOR_ID_LG, I2C_DEVICE_ID_LG_8001,
I2C_HID_QUIRK_NO_RUNTIME_PM },
+ { I2C_VENDOR_ID_GOODIX, I2C_DEVICE_ID_GOODIX_01F0,
+ I2C_HID_QUIRK_NO_RUNTIME_PM },
+ { USB_VENDOR_ID_ELAN, HID_ANY_ID,
+ I2C_HID_QUIRK_BOGUS_IRQ },
+ { USB_VENDOR_ID_SYNAPTICS, I2C_DEVICE_ID_SYNAPTICS_7E7E,
+ I2C_HID_QUIRK_NO_RUNTIME_PM },
{ 0, 0 }
};
@@ -503,6 +510,12 @@ static void i2c_hid_get_input(struct i2c_hid *ihid)
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 < 2)) {
dev_err(&ihid->client->dev, "%s: incomplete report (%d/%d)\n",
__func__, size, ret_size);
diff --git a/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c b/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c
index 89f2976f9c53..fd1b6eea6d2f 100644
--- a/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c
+++ b/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c
@@ -346,6 +346,14 @@ static const struct dmi_system_id i2c_hid_dmi_desc_override_table[] = {
},
.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
+ },
{ } /* Terminate list */
};
diff --git a/drivers/hid/intel-ish-hid/ipc/ipc.c b/drivers/hid/intel-ish-hid/ipc/ipc.c
index 742191bb24c6..96e869118db3 100644
--- a/drivers/hid/intel-ish-hid/ipc/ipc.c
+++ b/drivers/hid/intel-ish-hid/ipc/ipc.c
@@ -91,7 +91,10 @@ static bool check_generated_interrupt(struct ishtp_device *dev)
IPC_INT_FROM_ISH_TO_HOST_CHV_AB(pisr_val);
} else {
pisr_val = ish_reg_read(dev, IPC_REG_PISR_BXT);
- interrupt_generated = IPC_INT_FROM_ISH_TO_HOST_BXT(pisr_val);
+ interrupt_generated = !!pisr_val;
+ /* only busy-clear bit is RW, others are RO */
+ if (pisr_val)
+ ish_reg_write(dev, IPC_REG_PISR_BXT, pisr_val);
}
return interrupt_generated;
@@ -256,33 +259,22 @@ static int write_ipc_from_queue(struct ishtp_device *dev)
int i;
void (*ipc_send_compl)(void *);
void *ipc_send_compl_prm;
- static int out_ipc_locked;
- unsigned long out_ipc_flags;
if (dev->dev_state == ISHTP_DEV_DISABLED)
- return -EINVAL;
+ return -EINVAL;
- spin_lock_irqsave(&dev->out_ipc_spinlock, out_ipc_flags);
- if (out_ipc_locked) {
- spin_unlock_irqrestore(&dev->out_ipc_spinlock, out_ipc_flags);
- return -EBUSY;
- }
- out_ipc_locked = 1;
+ spin_lock_irqsave(&dev->wr_processing_spinlock, flags);
if (!ish_is_input_ready(dev)) {
- out_ipc_locked = 0;
- spin_unlock_irqrestore(&dev->out_ipc_spinlock, out_ipc_flags);
+ spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags);
return -EBUSY;
}
- spin_unlock_irqrestore(&dev->out_ipc_spinlock, out_ipc_flags);
- spin_lock_irqsave(&dev->wr_processing_spinlock, flags);
/*
* if tx send list is empty - return 0;
* may happen, as RX_COMPLETE handler doesn't check list emptiness.
*/
if (list_empty(&dev->wr_processing_list)) {
spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags);
- out_ipc_locked = 0;
return 0;
}
@@ -325,6 +317,8 @@ static int write_ipc_from_queue(struct ishtp_device *dev)
memcpy(&reg, &r_buf[length >> 2], rem);
ish_reg_write(dev, reg_addr, reg);
}
+ ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, doorbell_val);
+
/* Flush writes to msg registers and doorbell */
ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS);
@@ -332,9 +326,6 @@ static int write_ipc_from_queue(struct ishtp_device *dev)
++dev->ipc_tx_cnt;
dev->ipc_tx_bytes_cnt += IPC_HEADER_GET_LENGTH(doorbell_val);
- ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, doorbell_val);
- out_ipc_locked = 0;
-
ipc_send_compl = ipc_link->ipc_send_compl;
ipc_send_compl_prm = ipc_link->ipc_send_compl_prm;
list_del_init(&ipc_link->link);
@@ -839,11 +830,11 @@ int ish_hw_start(struct ishtp_device *dev)
{
ish_set_host_rdy(dev);
+ set_host_ready(dev);
+
/* After that we can enable ISH DMA operation and wakeup ISHFW */
ish_wakeup(dev);
- set_host_ready(dev);
-
/* wait for FW-initiated reset flow */
if (!dev->recvd_hw_ready)
wait_event_interruptible_timeout(dev->wait_hw_ready,
@@ -914,7 +905,6 @@ struct ishtp_device *ish_dev_init(struct pci_dev *pdev)
init_waitqueue_head(&dev->wait_hw_ready);
spin_lock_init(&dev->wr_processing_spinlock);
- spin_lock_init(&dev->out_ipc_spinlock);
/* Init IPC processing and free lists */
INIT_LIST_HEAD(&dev->wr_processing_list);
diff --git a/drivers/hid/intel-ish-hid/ipc/pci-ish.c b/drivers/hid/intel-ish-hid/ipc/pci-ish.c
index 8793cc49f855..a6e1ee744f4d 100644
--- a/drivers/hid/intel-ish-hid/ipc/pci-ish.c
+++ b/drivers/hid/intel-ish-hid/ipc/pci-ish.c
@@ -117,6 +117,7 @@ static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
int ret;
struct ish_hw *hw;
+ unsigned long irq_flag = 0;
struct ishtp_device *ishtp;
struct device *dev = &pdev->dev;
@@ -156,8 +157,12 @@ static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
pdev->dev_flags |= PCI_DEV_FLAGS_NO_D3;
/* request and enable interrupt */
+ ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES);
+ if (!pdev->msi_enabled && !pdev->msix_enabled)
+ irq_flag = IRQF_SHARED;
+
ret = devm_request_irq(dev, pdev->irq, ish_irq_handler,
- IRQF_SHARED, KBUILD_MODNAME, ishtp);
+ irq_flag, KBUILD_MODNAME, ishtp);
if (ret) {
dev_err(dev, "ISH: request IRQ %d failed\n", pdev->irq);
return ret;
diff --git a/drivers/hid/intel-ish-hid/ishtp-hid-client.c b/drivers/hid/intel-ish-hid/ishtp-hid-client.c
index e64243bc9c96..30fe0c5e6fad 100644
--- a/drivers/hid/intel-ish-hid/ishtp-hid-client.c
+++ b/drivers/hid/intel-ish-hid/ishtp-hid-client.c
@@ -788,8 +788,8 @@ static int hid_ishtp_cl_probe(struct ishtp_cl_device *cl_device)
if (!cl_device)
return -ENODEV;
- if (uuid_le_cmp(hid_ishtp_guid,
- cl_device->fw_client->props.protocol_name) != 0)
+ if (!guid_equal(&hid_ishtp_guid,
+ &cl_device->fw_client->props.protocol_name))
return -ENODEV;
client_data = devm_kzalloc(&cl_device->dev, sizeof(*client_data),
diff --git a/drivers/hid/intel-ish-hid/ishtp-hid.c b/drivers/hid/intel-ish-hid/ishtp-hid.c
index cd23903ddcf1..bc4c536f3c0d 100644
--- a/drivers/hid/intel-ish-hid/ishtp-hid.c
+++ b/drivers/hid/intel-ish-hid/ishtp-hid.c
@@ -206,8 +206,8 @@ int ishtp_hid_probe(unsigned int cur_hid_dev,
hid->bus = BUS_INTEL_ISHTP;
hid->dev.parent = &client_data->cl_device->dev;
hid->version = le16_to_cpu(ISH_HID_VERSION);
- hid->vendor = le16_to_cpu(ISH_HID_VENDOR);
- hid->product = le16_to_cpu(ISH_HID_PRODUCT);
+ hid->vendor = le16_to_cpu(client_data->hid_devices[cur_hid_dev].vid);
+ hid->product = le16_to_cpu(client_data->hid_devices[cur_hid_dev].pid);
snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X", "hid-ishtp",
hid->vendor, hid->product);
@@ -222,7 +222,7 @@ int ishtp_hid_probe(unsigned int cur_hid_dev,
err_hid_device:
kfree(hid_data);
err_hid_data:
- kfree(hid);
+ hid_destroy_device(hid);
return rv;
}
diff --git a/drivers/hid/intel-ish-hid/ishtp-hid.h b/drivers/hid/intel-ish-hid/ishtp-hid.h
index f5c7eb79b7b5..1cd07a441cd4 100644
--- a/drivers/hid/intel-ish-hid/ishtp-hid.h
+++ b/drivers/hid/intel-ish-hid/ishtp-hid.h
@@ -29,9 +29,9 @@
client->cl_device->ishtp_dev, __VA_ARGS__)
/* ISH Transport protocol (ISHTP in short) GUID */
-static const uuid_le hid_ishtp_guid = UUID_LE(0x33AECD58, 0xB679, 0x4E54,
- 0x9B, 0xD9, 0xA0, 0x4D, 0x34,
- 0xF0, 0xC2, 0x26);
+static const guid_t hid_ishtp_guid =
+ GUID_INIT(0x33AECD58, 0xB679, 0x4E54,
+ 0x9B, 0xD9, 0xA0, 0x4D, 0x34, 0xF0, 0xC2, 0x26);
/* ISH HID message structure */
struct hostif_msg_hdr {
diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.c b/drivers/hid/intel-ish-hid/ishtp/bus.c
index 728dc6d4561a..d5f4b6438d86 100644
--- a/drivers/hid/intel-ish-hid/ishtp/bus.c
+++ b/drivers/hid/intel-ish-hid/ishtp/bus.c
@@ -119,7 +119,7 @@ int ishtp_send_msg(struct ishtp_device *dev, struct ishtp_msg_hdr *hdr,
* Return: This returns IPC send message status.
*/
int ishtp_write_message(struct ishtp_device *dev, struct ishtp_msg_hdr *hdr,
- unsigned char *buf)
+ void *buf)
{
return ishtp_send_msg(dev, hdr, buf, NULL, NULL);
}
@@ -133,18 +133,15 @@ int ishtp_write_message(struct ishtp_device *dev, struct ishtp_msg_hdr *hdr,
*
* Return: fw client index or -ENOENT if not found
*/
-int ishtp_fw_cl_by_uuid(struct ishtp_device *dev, const uuid_le *uuid)
+int ishtp_fw_cl_by_uuid(struct ishtp_device *dev, const guid_t *uuid)
{
- int i, res = -ENOENT;
+ unsigned int i;
for (i = 0; i < dev->fw_clients_num; ++i) {
- if (uuid_le_cmp(*uuid, dev->fw_clients[i].props.protocol_name)
- == 0) {
- res = i;
- break;
- }
+ if (guid_equal(uuid, &dev->fw_clients[i].props.protocol_name))
+ return i;
}
- return res;
+ return -ENOENT;
}
EXPORT_SYMBOL(ishtp_fw_cl_by_uuid);
@@ -158,7 +155,7 @@ EXPORT_SYMBOL(ishtp_fw_cl_by_uuid);
* Return: pointer of client information on success, NULL on failure.
*/
struct ishtp_fw_client *ishtp_fw_cl_get_client(struct ishtp_device *dev,
- const uuid_le *uuid)
+ const guid_t *uuid)
{
int i;
unsigned long flags;
@@ -401,7 +398,7 @@ static const struct device_type ishtp_cl_device_type = {
* Return: ishtp_cl_device pointer or NULL on failure
*/
static struct ishtp_cl_device *ishtp_bus_add_device(struct ishtp_device *dev,
- uuid_le uuid, char *name)
+ guid_t uuid, char *name)
{
struct ishtp_cl_device *device;
int status;
@@ -629,7 +626,7 @@ int ishtp_bus_new_client(struct ishtp_device *dev)
int i;
char *dev_name;
struct ishtp_cl_device *cl_device;
- uuid_le device_uuid;
+ guid_t device_uuid;
/*
* For all reported clients, create an unconnected client and add its
@@ -639,7 +636,7 @@ int ishtp_bus_new_client(struct ishtp_device *dev)
*/
i = dev->fw_client_presentation_num - 1;
device_uuid = dev->fw_clients[i].props.protocol_name;
- dev_name = kasprintf(GFP_KERNEL, "{%pUL}", device_uuid.b);
+ dev_name = kasprintf(GFP_KERNEL, "{%pUL}", &device_uuid);
if (!dev_name)
return -ENOMEM;
@@ -675,7 +672,8 @@ int ishtp_cl_device_bind(struct ishtp_cl *cl)
spin_lock_irqsave(&cl->dev->device_list_lock, flags);
list_for_each_entry(cl_device, &cl->dev->device_list,
device_link) {
- if (cl_device->fw_client->client_id == cl->fw_client_id) {
+ if (cl_device->fw_client &&
+ cl_device->fw_client->client_id == cl->fw_client_id) {
cl->device = cl_device;
rv = 0;
break;
@@ -735,6 +733,7 @@ void ishtp_bus_remove_all_clients(struct ishtp_device *ishtp_dev,
spin_lock_irqsave(&ishtp_dev->device_list_lock, flags);
list_for_each_entry_safe(cl_device, n, &ishtp_dev->device_list,
device_link) {
+ cl_device->fw_client = NULL;
if (warm_reset && cl_device->reference_count)
continue;
diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.h b/drivers/hid/intel-ish-hid/ishtp/bus.h
index b8a5bcc82536..4cf7ad586c37 100644
--- a/drivers/hid/intel-ish-hid/ishtp/bus.h
+++ b/drivers/hid/intel-ish-hid/ishtp/bus.h
@@ -85,7 +85,7 @@ int ishtp_send_msg(struct ishtp_device *dev,
/* Write a single-fragment message */
int ishtp_write_message(struct ishtp_device *dev,
struct ishtp_msg_hdr *hdr,
- unsigned char *buf);
+ void *buf);
/* Use DMA to send/receive messages */
int ishtp_use_dma_transfer(void);
@@ -112,8 +112,8 @@ void ishtp_cl_driver_unregister(struct ishtp_cl_driver *driver);
int ishtp_register_event_cb(struct ishtp_cl_device *device,
void (*read_cb)(struct ishtp_cl_device *));
-int ishtp_fw_cl_by_uuid(struct ishtp_device *dev, const uuid_le *cuuid);
+int ishtp_fw_cl_by_uuid(struct ishtp_device *dev, const guid_t *cuuid);
struct ishtp_fw_client *ishtp_fw_cl_get_client(struct ishtp_device *dev,
- const uuid_le *uuid);
+ const guid_t *uuid);
#endif /* _LINUX_ISHTP_CL_BUS_H */
diff --git a/drivers/hid/intel-ish-hid/ishtp/client.h b/drivers/hid/intel-ish-hid/ishtp/client.h
index 042f4c4853b1..e0df3eb611e6 100644
--- a/drivers/hid/intel-ish-hid/ishtp/client.h
+++ b/drivers/hid/intel-ish-hid/ishtp/client.h
@@ -126,7 +126,7 @@ struct ishtp_cl {
};
/* Client connection managenment internal functions */
-int ishtp_can_client_connect(struct ishtp_device *ishtp_dev, uuid_le *uuid);
+int ishtp_can_client_connect(struct ishtp_device *ishtp_dev, guid_t *uuid);
int ishtp_fw_cl_by_id(struct ishtp_device *dev, uint8_t client_id);
void ishtp_cl_send_msg(struct ishtp_device *dev, struct ishtp_cl *cl);
void recv_ishtp_cl_msg(struct ishtp_device *dev,
diff --git a/drivers/hid/intel-ish-hid/ishtp/hbm.c b/drivers/hid/intel-ish-hid/ishtp/hbm.c
index 8b5dd580ceec..d0b847c86935 100644
--- a/drivers/hid/intel-ish-hid/ishtp/hbm.c
+++ b/drivers/hid/intel-ish-hid/ishtp/hbm.c
@@ -136,19 +136,14 @@ int ishtp_hbm_start_wait(struct ishtp_device *dev)
int ishtp_hbm_start_req(struct ishtp_device *dev)
{
struct ishtp_msg_hdr hdr;
- unsigned char data[128];
- struct ishtp_msg_hdr *ishtp_hdr = &hdr;
- struct hbm_host_version_request *start_req;
- const size_t len = sizeof(struct hbm_host_version_request);
+ struct hbm_host_version_request start_req = { 0 };
- ishtp_hbm_hdr(ishtp_hdr, len);
+ ishtp_hbm_hdr(&hdr, sizeof(start_req));
/* host start message */
- start_req = (struct hbm_host_version_request *)data;
- memset(start_req, 0, len);
- start_req->hbm_cmd = HOST_START_REQ_CMD;
- start_req->host_version.major_version = HBM_MAJOR_VERSION;
- start_req->host_version.minor_version = HBM_MINOR_VERSION;
+ start_req.hbm_cmd = HOST_START_REQ_CMD;
+ start_req.host_version.major_version = HBM_MAJOR_VERSION;
+ start_req.host_version.minor_version = HBM_MINOR_VERSION;
/*
* (!) Response to HBM start may be so quick that this thread would get
@@ -156,7 +151,7 @@ int ishtp_hbm_start_req(struct ishtp_device *dev)
* So set it at first, change back to ISHTP_HBM_IDLE upon failure
*/
dev->hbm_state = ISHTP_HBM_START;
- if (ishtp_write_message(dev, ishtp_hdr, data)) {
+ if (ishtp_write_message(dev, &hdr, &start_req)) {
dev_err(dev->devc, "version message send failed\n");
dev->dev_state = ISHTP_DEV_RESETTING;
dev->hbm_state = ISHTP_HBM_IDLE;
@@ -178,19 +173,13 @@ int ishtp_hbm_start_req(struct ishtp_device *dev)
void ishtp_hbm_enum_clients_req(struct ishtp_device *dev)
{
struct ishtp_msg_hdr hdr;
- unsigned char data[128];
- struct ishtp_msg_hdr *ishtp_hdr = &hdr;
- struct hbm_host_enum_request *enum_req;
- const size_t len = sizeof(struct hbm_host_enum_request);
+ struct hbm_host_enum_request enum_req = { 0 };
/* enumerate clients */
- ishtp_hbm_hdr(ishtp_hdr, len);
+ ishtp_hbm_hdr(&hdr, sizeof(enum_req));
+ enum_req.hbm_cmd = HOST_ENUM_REQ_CMD;
- enum_req = (struct hbm_host_enum_request *)data;
- memset(enum_req, 0, len);
- enum_req->hbm_cmd = HOST_ENUM_REQ_CMD;
-
- if (ishtp_write_message(dev, ishtp_hdr, data)) {
+ if (ishtp_write_message(dev, &hdr, &enum_req)) {
dev->dev_state = ISHTP_DEV_RESETTING;
dev_err(dev->devc, "enumeration request send failed\n");
ish_hw_reset(dev);
@@ -208,12 +197,8 @@ void ishtp_hbm_enum_clients_req(struct ishtp_device *dev)
*/
static int ishtp_hbm_prop_req(struct ishtp_device *dev)
{
-
struct ishtp_msg_hdr hdr;
- unsigned char data[128];
- struct ishtp_msg_hdr *ishtp_hdr = &hdr;
- struct hbm_props_request *prop_req;
- const size_t len = sizeof(struct hbm_props_request);
+ struct hbm_props_request prop_req = { 0 };
unsigned long next_client_index;
uint8_t client_num;
@@ -237,15 +222,12 @@ static int ishtp_hbm_prop_req(struct ishtp_device *dev)
dev->fw_clients[client_num].client_id = next_client_index;
- ishtp_hbm_hdr(ishtp_hdr, len);
- prop_req = (struct hbm_props_request *)data;
+ ishtp_hbm_hdr(&hdr, sizeof(prop_req));
- memset(prop_req, 0, sizeof(struct hbm_props_request));
+ prop_req.hbm_cmd = HOST_CLIENT_PROPERTIES_REQ_CMD;
+ prop_req.address = next_client_index;
- prop_req->hbm_cmd = HOST_CLIENT_PROPERTIES_REQ_CMD;
- prop_req->address = next_client_index;
-
- if (ishtp_write_message(dev, ishtp_hdr, data)) {
+ if (ishtp_write_message(dev, &hdr, &prop_req)) {
dev->dev_state = ISHTP_DEV_RESETTING;
dev_err(dev->devc, "properties request send failed\n");
ish_hw_reset(dev);
@@ -266,19 +248,14 @@ static int ishtp_hbm_prop_req(struct ishtp_device *dev)
static void ishtp_hbm_stop_req(struct ishtp_device *dev)
{
struct ishtp_msg_hdr hdr;
- unsigned char data[128];
- struct ishtp_msg_hdr *ishtp_hdr = &hdr;
- struct hbm_host_stop_request *req;
- const size_t len = sizeof(struct hbm_host_stop_request);
+ struct hbm_host_stop_request stop_req = { 0 } ;
- ishtp_hbm_hdr(ishtp_hdr, len);
- req = (struct hbm_host_stop_request *)data;
+ ishtp_hbm_hdr(&hdr, sizeof(stop_req));
- memset(req, 0, sizeof(struct hbm_host_stop_request));
- req->hbm_cmd = HOST_STOP_REQ_CMD;
- req->reason = DRIVER_STOP_REQUEST;
+ stop_req.hbm_cmd = HOST_STOP_REQ_CMD;
+ stop_req.reason = DRIVER_STOP_REQUEST;
- ishtp_write_message(dev, ishtp_hdr, data);
+ ishtp_write_message(dev, &hdr, &stop_req);
}
/**
@@ -294,15 +271,15 @@ int ishtp_hbm_cl_flow_control_req(struct ishtp_device *dev,
struct ishtp_cl *cl)
{
struct ishtp_msg_hdr hdr;
- unsigned char data[128];
- struct ishtp_msg_hdr *ishtp_hdr = &hdr;
- const size_t len = sizeof(struct hbm_flow_control);
+ struct hbm_flow_control flow_ctrl;
+ const size_t len = sizeof(flow_ctrl);
int rv;
unsigned long flags;
spin_lock_irqsave(&cl->fc_spinlock, flags);
- ishtp_hbm_hdr(ishtp_hdr, len);
- ishtp_hbm_cl_hdr(cl, ISHTP_FLOW_CONTROL_CMD, data, len);
+
+ ishtp_hbm_hdr(&hdr, len);
+ ishtp_hbm_cl_hdr(cl, ISHTP_FLOW_CONTROL_CMD, &flow_ctrl, len);
/*
* Sync possible race when RB recycle and packet receive paths
@@ -315,7 +292,7 @@ int ishtp_hbm_cl_flow_control_req(struct ishtp_device *dev,
cl->recv_msg_num_frags = 0;
- rv = ishtp_write_message(dev, ishtp_hdr, data);
+ rv = ishtp_write_message(dev, &hdr, &flow_ctrl);
if (!rv) {
++cl->out_flow_ctrl_creds;
++cl->out_flow_ctrl_cnt;
@@ -345,14 +322,13 @@ int ishtp_hbm_cl_flow_control_req(struct ishtp_device *dev,
int ishtp_hbm_cl_disconnect_req(struct ishtp_device *dev, struct ishtp_cl *cl)
{
struct ishtp_msg_hdr hdr;
- unsigned char data[128];
- struct ishtp_msg_hdr *ishtp_hdr = &hdr;
- const size_t len = sizeof(struct hbm_client_connect_request);
+ struct hbm_client_connect_request disconn_req;
+ const size_t len = sizeof(disconn_req);
- ishtp_hbm_hdr(ishtp_hdr, len);
- ishtp_hbm_cl_hdr(cl, CLIENT_DISCONNECT_REQ_CMD, data, len);
+ ishtp_hbm_hdr(&hdr, len);
+ ishtp_hbm_cl_hdr(cl, CLIENT_DISCONNECT_REQ_CMD, &disconn_req, len);
- return ishtp_write_message(dev, ishtp_hdr, data);
+ return ishtp_write_message(dev, &hdr, &disconn_req);
}
/**
@@ -391,14 +367,13 @@ static void ishtp_hbm_cl_disconnect_res(struct ishtp_device *dev,
int ishtp_hbm_cl_connect_req(struct ishtp_device *dev, struct ishtp_cl *cl)
{
struct ishtp_msg_hdr hdr;
- unsigned char data[128];
- struct ishtp_msg_hdr *ishtp_hdr = &hdr;
- const size_t len = sizeof(struct hbm_client_connect_request);
+ struct hbm_client_connect_request conn_req;
+ const size_t len = sizeof(conn_req);
- ishtp_hbm_hdr(ishtp_hdr, len);
- ishtp_hbm_cl_hdr(cl, CLIENT_CONNECT_REQ_CMD, data, len);
+ ishtp_hbm_hdr(&hdr, len);
+ ishtp_hbm_cl_hdr(cl, CLIENT_CONNECT_REQ_CMD, &conn_req, len);
- return ishtp_write_message(dev, ishtp_hdr, data);
+ return ishtp_write_message(dev, &hdr, &conn_req);
}
/**
diff --git a/drivers/hid/intel-ish-hid/ishtp/hbm.h b/drivers/hid/intel-ish-hid/ishtp/hbm.h
index d96111cef7f8..7286e3600140 100644
--- a/drivers/hid/intel-ish-hid/ishtp/hbm.h
+++ b/drivers/hid/intel-ish-hid/ishtp/hbm.h
@@ -149,7 +149,7 @@ struct hbm_host_enum_response {
} __packed;
struct ishtp_client_properties {
- uuid_le protocol_name;
+ guid_t protocol_name;
uint8_t protocol_version;
uint8_t max_number_of_connections;
uint8_t fixed_address;
diff --git a/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h b/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h
index e7c6bfefaf9e..e54ce1ef27dd 100644
--- a/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h
+++ b/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h
@@ -211,8 +211,6 @@ struct ishtp_device {
/* For both processing list and free list */
spinlock_t wr_processing_spinlock;
- spinlock_t out_ipc_spinlock;
-
struct ishtp_fw_client *fw_clients; /*Note:memory has to be allocated*/
DECLARE_BITMAP(fw_clients_map, ISHTP_CLIENTS_MAX);
DECLARE_BITMAP(host_clients_map, ISHTP_CLIENTS_MAX);
diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c
index 0bdd85d486fe..a8633b1437b2 100644
--- a/drivers/hid/wacom_sys.c
+++ b/drivers/hid/wacom_sys.c
@@ -252,6 +252,38 @@ static void wacom_hid_usage_quirk(struct hid_device *hdev,
}
}
+ /*
+ * Wacom's AES devices use different vendor-defined usages to
+ * report serial number information compared to their branded
+ * hardware. The usages are also sometimes ill-defined and do
+ * not have the correct logical min/max values set. Lets patch
+ * the descriptor to use the branded usage convention and fix
+ * the errors.
+ */
+ if (usage->hid == WACOM_HID_WT_SERIALNUMBER &&
+ field->report_size == 16 &&
+ field->index + 2 < field->report->maxfield) {
+ struct hid_field *a = field->report->field[field->index + 1];
+ struct hid_field *b = field->report->field[field->index + 2];
+
+ if (a->maxusage > 0 &&
+ a->usage[0].hid == HID_DG_TOOLSERIALNUMBER &&
+ a->report_size == 32 &&
+ b->maxusage > 0 &&
+ b->usage[0].hid == 0xFF000000 &&
+ b->report_size == 8) {
+ features->quirks |= WACOM_QUIRK_AESPEN;
+ usage->hid = WACOM_HID_WD_TOOLTYPE;
+ field->logical_minimum = S16_MIN;
+ field->logical_maximum = S16_MAX;
+ a->logical_minimum = S32_MIN;
+ a->logical_maximum = S32_MAX;
+ b->usage[0].hid = WACOM_HID_WD_SERIALHI;
+ b->logical_minimum = 0;
+ b->logical_maximum = U8_MAX;
+ }
+ }
+
/* 2nd-generation Intuos Pro Large has incorrect Y maximum */
if (hdev->vendor == USB_VENDOR_ID_WACOM &&
hdev->product == 0x0358 &&
diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c
index 5dd3a8245f0f..747730d32ab6 100644
--- a/drivers/hid/wacom_wac.c
+++ b/drivers/hid/wacom_wac.c
@@ -626,6 +626,7 @@ static int wacom_intuos_get_tool_type(int tool_id)
case 0x8e2: /* IntuosHT2 pen */
case 0x022:
case 0x10804: /* Intuos4/5 13HD/24HD Art Pen */
+ case 0x10842: /* MobileStudio Pro Pro Pen slim */
case 0x14802: /* Intuos4/5 13HD/24HD Classic Pen */
case 0x16802: /* Cintiq 13HD Pro Pen */
case 0x18802: /* DTH2242 Pen */
@@ -667,6 +668,7 @@ static int wacom_intuos_get_tool_type(int tool_id)
case 0x1480a: /* Intuos4/5 13HD/24HD Classic Pen Eraser */
case 0x1090a: /* Intuos4/5 13HD/24HD Airbrush Eraser */
case 0x1080c: /* Intuos4/5 13HD/24HD Art Pen Eraser */
+ case 0x1084a: /* MobileStudio Pro Pro Pen slim Eraser */
case 0x1680a: /* Cintiq 13HD Pro Pen Eraser */
case 0x1880a: /* DTH2242 Eraser */
case 0x1080a: /* Intuos4/5 13HD/24HD General Pen Eraser */
@@ -2159,27 +2161,6 @@ static void wacom_wac_pen_usage_mapping(struct hid_device *hdev,
case HID_DG_TOOLSERIALNUMBER:
features->quirks |= WACOM_QUIRK_TOOLSERIAL;
wacom_map_usage(input, usage, field, EV_MSC, MSC_SERIAL, 0);
-
- /* Adjust AES usages to match modern convention */
- if (usage->hid == WACOM_HID_WT_SERIALNUMBER && field->report_size == 16) {
- if (field->index + 2 < field->report->maxfield) {
- struct hid_field *a = field->report->field[field->index + 1];
- struct hid_field *b = field->report->field[field->index + 2];
-
- if (a->maxusage > 0 && a->usage[0].hid == HID_DG_TOOLSERIALNUMBER && a->report_size == 32 &&
- b->maxusage > 0 && b->usage[0].hid == 0xFF000000 && b->report_size == 8) {
- features->quirks |= WACOM_QUIRK_AESPEN;
- usage->hid = WACOM_HID_WD_TOOLTYPE;
- field->logical_minimum = S16_MIN;
- field->logical_maximum = S16_MAX;
- a->logical_minimum = S32_MIN;
- a->logical_maximum = S32_MAX;
- b->usage[0].hid = WACOM_HID_WD_SERIALHI;
- b->logical_minimum = 0;
- b->logical_maximum = U8_MAX;
- }
- }
- }
break;
case WACOM_HID_WD_SENSE:
features->quirks |= WACOM_QUIRK_SENSE;
@@ -3525,6 +3506,7 @@ int wacom_setup_pen_input_capabilities(struct input_dev *input_dev,
switch (features->type) {
case GRAPHIRE_BT:
__clear_bit(ABS_MISC, input_dev->absbit);
+ /* fall through */
case WACOM_MO:
case WACOM_G4: