summaryrefslogtreecommitdiff
path: root/drivers/hid
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hid')
-rw-r--r--drivers/hid/Kconfig33
-rw-r--r--drivers/hid/Makefile1
-rw-r--r--drivers/hid/hid-core.c22
-rw-r--r--drivers/hid/hid-debug.c3
-rw-r--r--drivers/hid/hid-huion.c290
-rw-r--r--drivers/hid/hid-ids.h12
-rw-r--r--drivers/hid/hid-input.c23
-rw-r--r--drivers/hid/hid-kye.c140
-rw-r--r--drivers/hid/hid-lg.c7
-rw-r--r--drivers/hid/hid-lg.h12
-rw-r--r--drivers/hid/hid-lg4ff.c600
-rw-r--r--drivers/hid/hid-lg4ff.h18
-rw-r--r--drivers/hid/hid-logitech-hidpp.c11
-rw-r--r--drivers/hid/hid-multitouch.c1
-rw-r--r--drivers/hid/hid-rmi.c177
-rw-r--r--drivers/hid/hid-sony.c31
-rw-r--r--drivers/hid/hid-steelseries.c1
-rw-r--r--drivers/hid/hid-tivo.c1
-rw-r--r--drivers/hid/hid-uclogic.c325
-rw-r--r--drivers/hid/i2c-hid/i2c-hid.c68
-rw-r--r--drivers/hid/usbhid/hid-pidff.c6
-rw-r--r--drivers/hid/usbhid/hid-quirks.c13
-rw-r--r--drivers/hid/wacom.h8
-rw-r--r--drivers/hid/wacom_sys.c97
-rw-r--r--drivers/hid/wacom_wac.c416
-rw-r--r--drivers/hid/wacom_wac.h17
26 files changed, 1744 insertions, 589 deletions
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 152b006833cd..15901065727f 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -92,7 +92,7 @@ menu "Special HID drivers"
depends on HID
config HID_A4TECH
- tristate "A4 tech mice" if EXPERT
+ tristate "A4 tech mice"
depends on HID
default !EXPERT
---help---
@@ -113,7 +113,7 @@ config HID_ACRUX_FF
game controllers.
config HID_APPLE
- tristate "Apple {i,Power,Mac}Books" if EXPERT
+ tristate "Apple {i,Power,Mac}Books"
depends on HID
default !EXPERT
---help---
@@ -141,7 +141,7 @@ config HID_AUREAL
Support for Aureal Cy se W-01RN Remote Controller and other Aureal derived remotes.
config HID_BELKIN
- tristate "Belkin Flip KVM and Wireless keyboard" if EXPERT
+ tristate "Belkin Flip KVM and Wireless keyboard"
depends on HID
default !EXPERT
---help---
@@ -158,14 +158,14 @@ config HID_BETOP_FF
- BETOP 2185 PC & BFM MODE
config HID_CHERRY
- tristate "Cherry Cymotion keyboard" if EXPERT
+ tristate "Cherry Cymotion keyboard"
depends on HID
default !EXPERT
---help---
Support for Cherry Cymotion keyboard.
config HID_CHICONY
- tristate "Chicony Tactical pad" if EXPERT
+ tristate "Chicony Tactical pad"
depends on HID
default !EXPERT
---help---
@@ -196,7 +196,7 @@ config HID_CP2112
customizable USB descriptor fields are exposed as sysfs attributes.
config HID_CYPRESS
- tristate "Cypress mouse and barcode readers" if EXPERT
+ tristate "Cypress mouse and barcode readers"
depends on HID
default !EXPERT
---help---
@@ -245,7 +245,7 @@ config HID_ELO
different devices than those handled by CONFIG_TOUCHSCREEN_USB_ELO.
config HID_EZKEY
- tristate "Ezkey BTC 8193 keyboard" if EXPERT
+ tristate "Ezkey BTC 8193 keyboard"
depends on HID
default !EXPERT
---help---
@@ -286,12 +286,6 @@ config HID_GT683R
Currently the following devices are know to be supported:
- MSI GT683R
-config HID_HUION
- tristate "Huion tablets"
- depends on USB_HID
- ---help---
- Support for Huion 580 tablet.
-
config HID_KEYTOUCH
tristate "Keytouch HID devices"
depends on HID
@@ -312,9 +306,9 @@ config HID_KYE
config HID_UCLOGIC
tristate "UC-Logic"
- depends on HID
+ depends on USB_HID
---help---
- Support for UC-Logic tablets.
+ Support for UC-Logic and Huion tablets.
config HID_WALTOP
tristate "Waltop"
@@ -344,7 +338,7 @@ config HID_TWINHAN
Support for Twinhan IR remote control.
config HID_KENSINGTON
- tristate "Kensington Slimblade Trackball" if EXPERT
+ tristate "Kensington Slimblade Trackball"
depends on HID
default !EXPERT
---help---
@@ -372,7 +366,7 @@ config HID_LENOVO
- ThinkPad Compact USB Keyboard with TrackPoint (supports Fn keys)
config HID_LOGITECH
- tristate "Logitech devices" if EXPERT
+ tristate "Logitech devices"
depends on HID
default !EXPERT
---help---
@@ -461,14 +455,14 @@ config HID_MAGICMOUSE
Apple Wireless "Magic" Mouse and the Apple Wireless "Magic" Trackpad.
config HID_MICROSOFT
- tristate "Microsoft non-fully HID-compliant devices" if EXPERT
+ tristate "Microsoft non-fully HID-compliant devices"
depends on HID
default !EXPERT
---help---
Support for Microsoft devices that are not fully compliant with HID standard.
config HID_MONTEREY
- tristate "Monterey Genius KB29E keyboard" if EXPERT
+ tristate "Monterey Genius KB29E keyboard"
depends on HID
default !EXPERT
---help---
@@ -638,7 +632,6 @@ config HID_PICOLCD_CIR
config HID_PLANTRONICS
tristate "Plantronics USB HID Driver"
- default !EXPERT
depends on HID
---help---
Provides HID support for Plantronics telephony devices.
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 6f19958dfc38..9c399fe394fa 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -41,7 +41,6 @@ obj-$(CONFIG_HID_GYRATION) += hid-gyration.o
obj-$(CONFIG_HID_HOLTEK) += hid-holtek-kbd.o
obj-$(CONFIG_HID_HOLTEK) += hid-holtek-mouse.o
obj-$(CONFIG_HID_HOLTEK) += hid-holtekff.o
-obj-$(CONFIG_HID_HUION) += hid-huion.o
obj-$(CONFIG_HID_HYPERV_MOUSE) += hid-hyperv.o
obj-$(CONFIG_HID_ICADE) += hid-icade.o
obj-$(CONFIG_HID_KENSINGTON) += hid-kensington.o
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 7c669c328c4c..722a925795a2 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -1562,12 +1562,26 @@ read_report_descriptor(struct file *filp, struct kobject *kobj,
return count;
}
+static ssize_t
+show_country(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+
+ return sprintf(buf, "%02x\n", hdev->country & 0xff);
+}
+
static struct bin_attribute dev_bin_attr_report_desc = {
.attr = { .name = "report_descriptor", .mode = 0444 },
.read = read_report_descriptor,
.size = HID_MAX_DESCRIPTOR_SIZE,
};
+static struct device_attribute dev_attr_country = {
+ .attr = { .name = "country", .mode = 0444 },
+ .show = show_country,
+};
+
int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
{
static const char *types[] = { "Device", "Pointer", "Mouse", "Device",
@@ -1646,6 +1660,11 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
bus = "<UNKNOWN>";
}
+ ret = device_create_file(&hdev->dev, &dev_attr_country);
+ if (ret)
+ hid_warn(hdev,
+ "can't create sysfs country code attribute err: %d\n", ret);
+
ret = device_create_bin_file(&hdev->dev, &dev_bin_attr_report_desc);
if (ret)
hid_warn(hdev,
@@ -1661,6 +1680,7 @@ EXPORT_SYMBOL_GPL(hid_connect);
void hid_disconnect(struct hid_device *hdev)
{
+ device_remove_file(&hdev->dev, &dev_attr_country);
device_remove_bin_file(&hdev->dev, &dev_bin_attr_report_desc);
if (hdev->claimed & HID_CLAIMED_INPUT)
hidinput_disconnect(hdev);
@@ -1824,6 +1844,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ 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_2) },
{ 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) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LABTEC, USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LCPOWER, USB_DEVICE_ID_LCPOWER_LC1000 ) },
#if IS_ENABLED(CONFIG_HID_LENOVO)
@@ -1959,6 +1980,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb65a) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE_BT) },
{ HID_USB_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE_PRO) },
{ HID_USB_DEVICE(USB_VENDOR_ID_TOPSEED, USB_DEVICE_ID_TOPSEED_CYBERLINK) },
{ HID_USB_DEVICE(USB_VENDOR_ID_TOPSEED2, USB_DEVICE_ID_TOPSEED2_RF_COMBO) },
{ HID_USB_DEVICE(USB_VENDOR_ID_TWINHAN, USB_DEVICE_ID_TWINHAN_IR_REMOTE) },
diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c
index 4b2a18a8b7ec..c785095f9f2c 100644
--- a/drivers/hid/hid-debug.c
+++ b/drivers/hid/hid-debug.c
@@ -1128,7 +1128,8 @@ static ssize_t hid_debug_events_read(struct file *file, char __user *buffer,
if (!list->hdev || !list->hdev->debug) {
ret = -EIO;
- break;
+ set_current_state(TASK_RUNNING);
+ goto out;
}
/* allow O_NONBLOCK from other threads */
diff --git a/drivers/hid/hid-huion.c b/drivers/hid/hid-huion.c
deleted file mode 100644
index 61b68ca27790..000000000000
--- a/drivers/hid/hid-huion.c
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * HID driver for Huion devices not fully compliant with HID standard
- *
- * Copyright (c) 2013 Martin Rusko
- * Copyright (c) 2014 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 <linux/usb.h>
-#include <asm/unaligned.h>
-#include "usbhid/usbhid.h"
-
-#include "hid-ids.h"
-
-/* Report descriptor template placeholder head */
-#define HUION_PH_HEAD 0xFE, 0xED, 0x1D
-
-/* Report descriptor template placeholder IDs */
-enum huion_ph_id {
- HUION_PH_ID_X_LM,
- HUION_PH_ID_X_PM,
- HUION_PH_ID_Y_LM,
- HUION_PH_ID_Y_PM,
- HUION_PH_ID_PRESSURE_LM,
- HUION_PH_ID_NUM
-};
-
-/* Report descriptor template placeholder */
-#define HUION_PH(_ID) HUION_PH_HEAD, HUION_PH_ID_##_ID
-
-/* Fixed report descriptor template */
-static const __u8 huion_tablet_rdesc_template[] = {
- 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, HUION_PH(X_LM), /* Logical Maximum (PLACEHOLDER), */
- 0x47, HUION_PH(X_PM), /* Physical Maximum (PLACEHOLDER), */
- 0x81, 0x02, /* Input (Variable), */
- 0x09, 0x31, /* Usage (Y), */
- 0x27, HUION_PH(Y_LM), /* Logical Maximum (PLACEHOLDER), */
- 0x47, HUION_PH(Y_PM), /* Physical Maximum (PLACEHOLDER), */
- 0x81, 0x02, /* Input (Variable), */
- 0xB4, /* Pop, */
- 0x09, 0x30, /* Usage (Tip Pressure), */
- 0x27,
- HUION_PH(PRESSURE_LM), /* Logical Maximum (PLACEHOLDER), */
- 0x81, 0x02, /* Input (Variable), */
- 0xC0, /* End Collection, */
- 0xC0 /* End Collection */
-};
-
-/* Parameter indices */
-enum huion_prm {
- HUION_PRM_X_LM = 1,
- HUION_PRM_Y_LM = 2,
- HUION_PRM_PRESSURE_LM = 4,
- HUION_PRM_RESOLUTION = 5,
- HUION_PRM_NUM
-};
-
-/* Driver data */
-struct huion_drvdata {
- __u8 *rdesc;
- unsigned int rsize;
-};
-
-static __u8 *huion_report_fixup(struct hid_device *hdev, __u8 *rdesc,
- unsigned int *rsize)
-{
- struct huion_drvdata *drvdata = hid_get_drvdata(hdev);
- switch (hdev->product) {
- case USB_DEVICE_ID_HUION_TABLET:
- if (drvdata->rdesc != NULL) {
- rdesc = drvdata->rdesc;
- *rsize = drvdata->rsize;
- }
- break;
- }
- return rdesc;
-}
-
-/**
- * Enable fully-functional tablet mode and determine device parameters.
- *
- * @hdev: HID device
- */
-static int huion_tablet_enable(struct hid_device *hdev)
-{
- int rc;
- struct usb_device *usb_dev = hid_to_usb_dev(hdev);
- struct huion_drvdata *drvdata = hid_get_drvdata(hdev);
- __le16 *buf = NULL;
- size_t len;
- s32 params[HUION_PH_ID_NUM];
- s32 resolution;
- __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 = HUION_PRM_NUM * sizeof(*buf);
- buf = kmalloc(len, GFP_KERNEL);
- if (buf == NULL) {
- hid_err(hdev, "failed to allocate parameter buffer\n");
- 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[HUION_PH_ID_X_LM] = le16_to_cpu(buf[HUION_PRM_X_LM]);
- params[HUION_PH_ID_Y_LM] = le16_to_cpu(buf[HUION_PRM_Y_LM]);
- params[HUION_PH_ID_PRESSURE_LM] =
- le16_to_cpu(buf[HUION_PRM_PRESSURE_LM]);
- resolution = le16_to_cpu(buf[HUION_PRM_RESOLUTION]);
- if (resolution == 0) {
- params[HUION_PH_ID_X_PM] = 0;
- params[HUION_PH_ID_Y_PM] = 0;
- } else {
- params[HUION_PH_ID_X_PM] = params[HUION_PH_ID_X_LM] *
- 1000 / resolution;
- params[HUION_PH_ID_Y_PM] = params[HUION_PH_ID_Y_LM] *
- 1000 / resolution;
- }
-
- /* Allocate fixed report descriptor */
- drvdata->rdesc = devm_kmalloc(&hdev->dev,
- sizeof(huion_tablet_rdesc_template),
- GFP_KERNEL);
- if (drvdata->rdesc == NULL) {
- hid_err(hdev, "failed to allocate fixed rdesc\n");
- rc = -ENOMEM;
- goto cleanup;
- }
- drvdata->rsize = sizeof(huion_tablet_rdesc_template);
-
- /* Format fixed report descriptor */
- memcpy(drvdata->rdesc, huion_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] < sizeof(params)) {
- v = params[p[3]];
- put_unaligned(cpu_to_le32(v), (s32 *)p);
- p += 4;
- } else {
- p++;
- }
- }
-
- rc = 0;
-
-cleanup:
- kfree(buf);
- return rc;
-}
-
-static int huion_probe(struct hid_device *hdev, const struct hid_device_id *id)
-{
- int rc;
- struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
- struct huion_drvdata *drvdata;
-
- /* Allocate and assign driver data */
- drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
- if (drvdata == NULL) {
- hid_err(hdev, "failed to allocate driver data\n");
- return -ENOMEM;
- }
- hid_set_drvdata(hdev, drvdata);
-
- switch (id->product) {
- case USB_DEVICE_ID_HUION_TABLET:
- /* If this is the pen interface */
- if (intf->cur_altsetting->desc.bInterfaceNumber == 0) {
- rc = huion_tablet_enable(hdev);
- if (rc) {
- hid_err(hdev, "tablet enabling failed\n");
- return rc;
- }
- }
- 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 huion_raw_event(struct hid_device *hdev, struct hid_report *report,
- u8 *data, int size)
-{
- struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
-
- /* If this is a pen input report */
- if (intf->cur_altsetting->desc.bInterfaceNumber == 0 &&
- report->type == HID_INPUT_REPORT &&
- report->id == 0x07 && size >= 2)
- /* Invert the in-range bit */
- data[1] ^= 0x40;
-
- return 0;
-}
-
-static const struct hid_device_id huion_devices[] = {
- { HID_USB_DEVICE(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_TABLET) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_HUION_TABLET) },
- { }
-};
-MODULE_DEVICE_TABLE(hid, huion_devices);
-
-static struct hid_driver huion_driver = {
- .name = "huion",
- .id_table = huion_devices,
- .probe = huion_probe,
- .report_fixup = huion_report_fixup,
- .raw_event = huion_raw_event,
-};
-module_hid_driver(huion_driver);
-
-MODULE_AUTHOR("Martin Rusko");
-MODULE_DESCRIPTION("Huion HID driver");
-MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 204312bfab2c..41f167e4d75f 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -459,6 +459,11 @@
#define USB_DEVICE_ID_UGCI_FLYING 0x0020
#define USB_DEVICE_ID_UGCI_FIGHTING 0x0030
+#define USB_VENDOR_ID_HP 0x03f0
+#define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0A4A 0x0a4a
+#define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0B4A 0x0b4a
+#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE 0x134a
+
#define USB_VENDOR_ID_HUION 0x256c
#define USB_DEVICE_ID_HUION_TABLET 0x006e
@@ -533,6 +538,7 @@
#define USB_DEVICE_ID_KYE_MOUSEPEN_I608X 0x5011
#define USB_DEVICE_ID_KYE_MOUSEPEN_I608X_2 0x501a
#define USB_DEVICE_ID_KYE_EASYPEN_M610X 0x5013
+#define USB_DEVICE_ID_KYE_PENSKETCH_M912 0x5015
#define USB_VENDOR_ID_LABTEC 0x1020
#define USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD 0x0006
@@ -586,10 +592,14 @@
#define USB_VENDOR_ID_LOGITECH 0x046d
#define USB_DEVICE_ID_LOGITECH_AUDIOHUB 0x0a0e
#define USB_DEVICE_ID_LOGITECH_T651 0xb00c
+#define USB_DEVICE_ID_LOGITECH_C077 0xc007
#define USB_DEVICE_ID_LOGITECH_RECEIVER 0xc101
#define USB_DEVICE_ID_LOGITECH_HARMONY_FIRST 0xc110
#define USB_DEVICE_ID_LOGITECH_HARMONY_LAST 0xc14f
#define USB_DEVICE_ID_LOGITECH_HARMONY_PS3 0x0306
+#define USB_DEVICE_ID_LOGITECH_MOUSE_C01A 0xc01a
+#define USB_DEVICE_ID_LOGITECH_MOUSE_C05A 0xc05a
+#define USB_DEVICE_ID_LOGITECH_MOUSE_C06A 0xc06a
#define USB_DEVICE_ID_LOGITECH_RUMBLEPAD_CORD 0xc20a
#define USB_DEVICE_ID_LOGITECH_RUMBLEPAD 0xc211
#define USB_DEVICE_ID_LOGITECH_EXTREME_3D 0xc215
@@ -898,6 +908,7 @@
#define USB_VENDOR_ID_TIVO 0x150a
#define USB_DEVICE_ID_TIVO_SLIDE_BT 0x1200
#define USB_DEVICE_ID_TIVO_SLIDE 0x1201
+#define USB_DEVICE_ID_TIVO_SLIDE_PRO 0x1203
#define USB_VENDOR_ID_TOPSEED 0x0766
#define USB_DEVICE_ID_TOPSEED_CYBERLINK 0x0204
@@ -1020,6 +1031,7 @@
#define USB_DEVICE_ID_ZYTRONIC_ZXY100 0x0005
#define USB_VENDOR_ID_PRIMAX 0x0461
+#define USB_DEVICE_ID_PRIMAX_MOUSE_4D22 0x4d22
#define USB_DEVICE_ID_PRIMAX_KEYBOARD 0x4e05
diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index 052869d0ab78..19603efc8fa2 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -711,6 +711,29 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
}
break;
+ case HID_UP_TELEPHONY:
+ switch (usage->hid & HID_USAGE) {
+ case 0x2f: map_key_clear(KEY_MICMUTE); break;
+ case 0xb0: map_key_clear(KEY_NUMERIC_0); break;
+ case 0xb1: map_key_clear(KEY_NUMERIC_1); break;
+ case 0xb2: map_key_clear(KEY_NUMERIC_2); break;
+ case 0xb3: map_key_clear(KEY_NUMERIC_3); break;
+ case 0xb4: map_key_clear(KEY_NUMERIC_4); break;
+ case 0xb5: map_key_clear(KEY_NUMERIC_5); break;
+ case 0xb6: map_key_clear(KEY_NUMERIC_6); break;
+ case 0xb7: map_key_clear(KEY_NUMERIC_7); break;
+ case 0xb8: map_key_clear(KEY_NUMERIC_8); break;
+ case 0xb9: map_key_clear(KEY_NUMERIC_9); break;
+ case 0xba: map_key_clear(KEY_NUMERIC_STAR); break;
+ case 0xbb: map_key_clear(KEY_NUMERIC_POUND); break;
+ case 0xbc: map_key_clear(KEY_NUMERIC_A); break;
+ case 0xbd: map_key_clear(KEY_NUMERIC_B); break;
+ case 0xbe: map_key_clear(KEY_NUMERIC_C); break;
+ case 0xbf: map_key_clear(KEY_NUMERIC_D); break;
+ default: goto ignore;
+ }
+ break;
+
case HID_UP_CONSUMER: /* USB HUT v1.12, pages 75-84 */
switch (usage->hid & HID_USAGE) {
case 0x000: goto ignore;
diff --git a/drivers/hid/hid-kye.c b/drivers/hid/hid-kye.c
index 158fcf577fae..32e6d8d9ded0 100644
--- a/drivers/hid/hid-kye.c
+++ b/drivers/hid/hid-kye.c
@@ -268,6 +268,137 @@ static __u8 easypen_m610x_rdesc_fixed[] = {
0xC0 /* End Collection */
};
+
+/* Original PenSketch M912 report descriptor size */
+#define PENSKETCH_M912_RDESC_ORIG_SIZE 482
+
+/* Fixed PenSketch M912 report descriptor */
+static __u8 pensketch_m912_rdesc_fixed[] = {
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x08, /* Usage (00h), */
+ 0xA1, 0x01, /* Collection (Application), */
+ 0x85, 0x05, /* Report ID (5), */
+ 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
+ 0x09, 0x01, /* Usage (01h), */
+ 0x15, 0x81, /* Logical Minimum (-127), */
+ 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), */
+ 0x09, 0x42, /* Usage (Tip Switch), */
+ 0x09, 0x44, /* Usage (Barrel Switch), */
+ 0x09, 0x46, /* Usage (Tablet Pick), */
+ 0x14, /* Logical Minimum (0), */
+ 0x25, 0x01, /* Logical Maximum (1), */
+ 0x75, 0x01, /* Report Size (1), */
+ 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), */
+ 0x14, /* Logical Minimum (0), */
+ 0x34, /* Physical Minimum (0), */
+ 0x09, 0x30, /* Usage (X), */
+ 0x27, 0x00, 0xF0, 0x00, 0x00, /* Logical Maximum (61440), */
+ 0x46, 0xE0, 0x2E, /* Physical Maximum (12000), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x09, 0x31, /* Usage (Y), */
+ 0x27, 0x00, 0xB4, 0x00, 0x00, /* Logical Maximum (46080), */
+ 0x46, 0x28, 0x23, /* Physical Maximum (9000), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0xB4, /* Pop, */
+ 0x09, 0x30, /* Usage (Tip Pressure), */
+ 0x14, /* Logical Minimum (0), */
+ 0x26, 0xFF, 0x07, /* Logical Maximum (2047), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0xC0, /* End Collection, */
+ 0xC0, /* End Collection, */
+ 0x05, 0x0D, /* Usage Page (Digitizer), */
+ 0x09, 0x21, /* Usage (Puck), */
+ 0xA1, 0x01, /* Collection (Application), */
+ 0x85, 0x11, /* Report ID (17), */
+ 0x09, 0x21, /* Usage (Puck), */
+ 0xA0, /* Collection (Physical), */
+ 0x05, 0x09, /* Usage Page (Button), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x19, 0x01, /* Usage Minimum (01h), */
+ 0x29, 0x03, /* Usage Maximum (03h), */
+ 0x14, /* Logical Minimum (0), */
+ 0x25, 0x01, /* Logical Maximum (1), */
+ 0x95, 0x03, /* Report Count (3), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x95, 0x04, /* Report Count (4), */
+ 0x81, 0x01, /* Input (Constant), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x0B, 0x32, 0x00, 0x0D, 0x00, /* Usage (Digitizer In Range), */
+ 0x14, /* Logical Minimum (0), */
+ 0x25, 0x01, /* Logical Maximum (1), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0xA4, /* Push, */
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x75, 0x10, /* Report Size (16), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x55, 0xFD, /* Unit Exponent (-3), */
+ 0x65, 0x13, /* Unit (Inch), */
+ 0x14, /* Logical Minimum (0), */
+ 0x34, /* Physical Minimum (0), */
+ 0x09, 0x30, /* Usage (X), */
+ 0x27, 0x00, 0xF0, 0x00, 0x00, /* Logical Maximum (61440), */
+ 0x46, 0xE0, 0x2E, /* Physical Maximum (12000), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x09, 0x31, /* Usage (Y), */
+ 0x27, 0x00, 0xB4, 0x00, 0x00, /* Logical Maximum (46080), */
+ 0x46, 0x28, 0x23, /* Physical Maximum (9000), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x09, 0x38, /* Usage (Wheel), */
+ 0x75, 0x08, /* Report Size (8), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x15, 0xFF, /* Logical Minimum (-1), */
+ 0x25, 0x01, /* Logical Maximum (1), */
+ 0x34, /* Physical Minimum (0), */
+ 0x44, /* Physical Maximum (0), */
+ 0x81, 0x06, /* Input (Variable, Relative), */
+ 0xB4, /* Pop, */
+ 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, 0x08, /* Report Count (8), */
+ 0x05, 0x0C, /* Usage Page (Consumer), */
+ 0x0A, 0x6A, 0x02, /* Usage (AC Delete), */
+ 0x0A, 0x1A, 0x02, /* Usage (AC Undo), */
+ 0x0A, 0x01, 0x02, /* Usage (AC New), */
+ 0x0A, 0x2F, 0x02, /* Usage (AC Zoom), */
+ 0x0A, 0x25, 0x02, /* Usage (AC Forward), */
+ 0x0A, 0x24, 0x02, /* Usage (AC Back), */
+ 0x0A, 0x2D, 0x02, /* Usage (AC Zoom In), */
+ 0x0A, 0x2E, 0x02, /* Usage (AC Zoom Out), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x95, 0x30, /* Report Count (48), */
+ 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) {
/*
@@ -335,6 +466,12 @@ static __u8 *kye_report_fixup(struct hid_device *hdev, __u8 *rdesc,
*rsize = sizeof(easypen_m610x_rdesc_fixed);
}
break;
+ case USB_DEVICE_ID_KYE_PENSKETCH_M912:
+ if (*rsize == PENSKETCH_M912_RDESC_ORIG_SIZE) {
+ rdesc = pensketch_m912_rdesc_fixed;
+ *rsize = sizeof(pensketch_m912_rdesc_fixed);
+ }
+ break;
case USB_DEVICE_ID_GENIUS_GILA_GAMING_MOUSE:
rdesc = kye_consumer_control_fixup(hdev, rdesc, rsize, 104,
"Genius Gila Gaming Mouse");
@@ -418,6 +555,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_2:
case USB_DEVICE_ID_KYE_EASYPEN_M610X:
+ case USB_DEVICE_ID_KYE_PENSKETCH_M912:
ret = kye_tablet_enable(hdev);
if (ret) {
hid_err(hdev, "tablet enabling failed\n");
@@ -457,6 +595,8 @@ static const struct hid_device_id kye_devices[] = {
USB_DEVICE_ID_GENIUS_GX_IMPERATOR) },
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE,
USB_DEVICE_ID_GENIUS_MANTICORE) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_KYE,
+ USB_DEVICE_ID_KYE_PENSKETCH_M912) },
{ }
};
MODULE_DEVICE_TABLE(hid, kye_devices);
diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c
index f91ff145db9a..b86c18e651ed 100644
--- a/drivers/hid/hid-lg.c
+++ b/drivers/hid/hid-lg.c
@@ -27,6 +27,7 @@
#include "usbhid/usbhid.h"
#include "hid-ids.h"
#include "hid-lg.h"
+#include "hid-lg4ff.h"
#define LG_RDESC 0x001
#define LG_BAD_RELATIVE_KEYS 0x002
@@ -818,4 +819,10 @@ static struct hid_driver lg_driver = {
};
module_hid_driver(lg_driver);
+#ifdef CONFIG_LOGIWHEELS_FF
+int lg4ff_no_autoswitch = 0;
+module_param_named(lg4ff_no_autoswitch, lg4ff_no_autoswitch, int, S_IRUGO);
+MODULE_PARM_DESC(lg4ff_no_autoswitch, "Do not switch multimode wheels to their native mode automatically");
+#endif
+
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-lg.h b/drivers/hid/hid-lg.h
index 142ce3f5f055..10dd8f024135 100644
--- a/drivers/hid/hid-lg.h
+++ b/drivers/hid/hid-lg.h
@@ -24,16 +24,4 @@ int lg3ff_init(struct hid_device *hdev);
static inline int lg3ff_init(struct hid_device *hdev) { return -1; }
#endif
-#ifdef CONFIG_LOGIWHEELS_FF
-int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field,
- struct hid_usage *usage, __s32 value, struct lg_drv_data *drv_data);
-int lg4ff_init(struct hid_device *hdev);
-int lg4ff_deinit(struct hid_device *hdev);
-#else
-static inline int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field,
- struct hid_usage *usage, __s32 value, struct lg_drv_data *drv_data) { return 0; }
-static inline int lg4ff_init(struct hid_device *hdev) { return -1; }
-static inline int lg4ff_deinit(struct hid_device *hdev) { return -1; }
-#endif
-
#endif
diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c
index db0dd9b17e53..1232210b1cc5 100644
--- a/drivers/hid/hid-lg4ff.c
+++ b/drivers/hid/hid-lg4ff.c
@@ -30,23 +30,44 @@
#include "usbhid/usbhid.h"
#include "hid-lg.h"
+#include "hid-lg4ff.h"
#include "hid-ids.h"
-#define DFGT_REV_MAJ 0x13
-#define DFGT_REV_MIN 0x22
-#define DFGT2_REV_MIN 0x26
-#define DFP_REV_MAJ 0x11
-#define DFP_REV_MIN 0x06
-#define FFEX_REV_MAJ 0x21
-#define FFEX_REV_MIN 0x00
-#define G25_REV_MAJ 0x12
-#define G25_REV_MIN 0x22
-#define G27_REV_MAJ 0x12
-#define G27_REV_MIN 0x38
-#define G27_2_REV_MIN 0x39
-
#define to_hid_device(pdev) container_of(pdev, struct hid_device, dev)
+#define LG4FF_MMODE_IS_MULTIMODE 0
+#define LG4FF_MMODE_SWITCHED 1
+#define LG4FF_MMODE_NOT_MULTIMODE 2
+
+#define LG4FF_MODE_NATIVE_IDX 0
+#define LG4FF_MODE_DFEX_IDX 1
+#define LG4FF_MODE_DFP_IDX 2
+#define LG4FF_MODE_G25_IDX 3
+#define LG4FF_MODE_DFGT_IDX 4
+#define LG4FF_MODE_G27_IDX 5
+#define LG4FF_MODE_MAX_IDX 6
+
+#define LG4FF_MODE_NATIVE BIT(LG4FF_MODE_NATIVE_IDX)
+#define LG4FF_MODE_DFEX BIT(LG4FF_MODE_DFEX_IDX)
+#define LG4FF_MODE_DFP BIT(LG4FF_MODE_DFP_IDX)
+#define LG4FF_MODE_G25 BIT(LG4FF_MODE_G25_IDX)
+#define LG4FF_MODE_DFGT BIT(LG4FF_MODE_DFGT_IDX)
+#define LG4FF_MODE_G27 BIT(LG4FF_MODE_G27_IDX)
+
+#define LG4FF_DFEX_TAG "DF-EX"
+#define LG4FF_DFEX_NAME "Driving Force / Formula EX"
+#define LG4FF_DFP_TAG "DFP"
+#define LG4FF_DFP_NAME "Driving Force Pro"
+#define LG4FF_G25_TAG "G25"
+#define LG4FF_G25_NAME "G25 Racing Wheel"
+#define LG4FF_G27_TAG "G27"
+#define LG4FF_G27_NAME "G27 Racing Wheel"
+#define LG4FF_DFGT_TAG "DFGT"
+#define LG4FF_DFGT_NAME "Driving Force GT"
+
+#define LG4FF_FFEX_REV_MAJ 0x21
+#define LG4FF_FFEX_REV_MIN 0x00
+
static void hid_lg4ff_set_range_dfp(struct hid_device *hid, u16 range);
static void hid_lg4ff_set_range_g25(struct hid_device *hid, u16 range);
@@ -59,6 +80,10 @@ struct lg4ff_device_entry {
__u8 led_state;
struct led_classdev *led[5];
#endif
+ u32 alternate_modes;
+ const char *real_tag;
+ const char *real_name;
+ u16 real_product_id;
struct list_head list;
void (*set_range)(struct hid_device *hid, u16 range);
};
@@ -77,6 +102,35 @@ struct lg4ff_wheel {
void (*set_range)(struct hid_device *hid, u16 range);
};
+struct lg4ff_compat_mode_switch {
+ const __u8 cmd_count; /* Number of commands to send */
+ const __u8 cmd[];
+};
+
+struct lg4ff_wheel_ident_info {
+ const u16 mask;
+ const u16 result;
+ const u16 real_product_id;
+};
+
+struct lg4ff_wheel_ident_checklist {
+ const u32 count;
+ const struct lg4ff_wheel_ident_info *models[];
+};
+
+struct lg4ff_multimode_wheel {
+ const u16 product_id;
+ const u32 alternate_modes;
+ const char *real_tag;
+ const char *real_name;
+};
+
+struct lg4ff_alternate_mode {
+ const u16 product_id;
+ const char *tag;
+ const char *name;
+};
+
static const struct lg4ff_wheel lg4ff_devices[] = {
{USB_DEVICE_ID_LOGITECH_WHEEL, lg4ff_wheel_effects, 40, 270, NULL},
{USB_DEVICE_ID_LOGITECH_MOMO_WHEEL, lg4ff_wheel_effects, 40, 270, NULL},
@@ -88,46 +142,106 @@ static const struct lg4ff_wheel lg4ff_devices[] = {
{USB_DEVICE_ID_LOGITECH_WII_WHEEL, lg4ff_wheel_effects, 40, 270, NULL}
};
-struct lg4ff_native_cmd {
- const __u8 cmd_num; /* Number of commands to send */
- const __u8 cmd[];
+static const struct lg4ff_multimode_wheel lg4ff_multimode_wheels[] = {
+ {USB_DEVICE_ID_LOGITECH_DFP_WHEEL,
+ LG4FF_MODE_NATIVE | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+ LG4FF_DFP_TAG, LG4FF_DFP_NAME},
+ {USB_DEVICE_ID_LOGITECH_G25_WHEEL,
+ LG4FF_MODE_NATIVE | LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+ LG4FF_G25_TAG, LG4FF_G25_NAME},
+ {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL,
+ LG4FF_MODE_NATIVE | LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+ LG4FF_DFGT_TAG, LG4FF_DFGT_NAME},
+ {USB_DEVICE_ID_LOGITECH_G27_WHEEL,
+ LG4FF_MODE_NATIVE | LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+ LG4FF_G27_TAG, LG4FF_G27_NAME},
};
-struct lg4ff_usb_revision {
- const __u16 rev_maj;
- const __u16 rev_min;
- const struct lg4ff_native_cmd *command;
+static const struct lg4ff_alternate_mode lg4ff_alternate_modes[] = {
+ [LG4FF_MODE_NATIVE_IDX] = {0, "native", ""},
+ [LG4FF_MODE_DFEX_IDX] = {USB_DEVICE_ID_LOGITECH_WHEEL, LG4FF_DFEX_TAG, LG4FF_DFEX_NAME},
+ [LG4FF_MODE_DFP_IDX] = {USB_DEVICE_ID_LOGITECH_DFP_WHEEL, LG4FF_DFP_TAG, LG4FF_DFP_NAME},
+ [LG4FF_MODE_G25_IDX] = {USB_DEVICE_ID_LOGITECH_G25_WHEEL, LG4FF_G25_TAG, LG4FF_G25_NAME},
+ [LG4FF_MODE_DFGT_IDX] = {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, LG4FF_DFGT_TAG, LG4FF_DFGT_NAME},
+ [LG4FF_MODE_G27_IDX] = {USB_DEVICE_ID_LOGITECH_G27_WHEEL, LG4FF_G27_TAG, LG4FF_G27_NAME}
};
-static const struct lg4ff_native_cmd native_dfp = {
- 1,
- {0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}
+/* Multimode wheel identificators */
+static const struct lg4ff_wheel_ident_info lg4ff_dfp_ident_info = {
+ 0xf000,
+ 0x1000,
+ USB_DEVICE_ID_LOGITECH_DFP_WHEEL
+};
+
+static const struct lg4ff_wheel_ident_info lg4ff_g25_ident_info = {
+ 0xff00,
+ 0x1200,
+ USB_DEVICE_ID_LOGITECH_G25_WHEEL
+};
+
+static const struct lg4ff_wheel_ident_info lg4ff_g27_ident_info = {
+ 0xfff0,
+ 0x1230,
+ USB_DEVICE_ID_LOGITECH_G27_WHEEL
};
-static const struct lg4ff_native_cmd native_dfgt = {
+static const struct lg4ff_wheel_ident_info lg4ff_dfgt_ident_info = {
+ 0xff00,
+ 0x1300,
+ USB_DEVICE_ID_LOGITECH_DFGT_WHEEL
+};
+
+/* Multimode wheel identification checklists */
+static const struct lg4ff_wheel_ident_checklist lg4ff_main_checklist = {
+ 4,
+ {&lg4ff_dfgt_ident_info,
+ &lg4ff_g27_ident_info,
+ &lg4ff_g25_ident_info,
+ &lg4ff_dfp_ident_info}
+};
+
+/* Compatibility mode switching commands */
+/* EXT_CMD9 - Understood by G27 and DFGT */
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfex = {
2,
- {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1st command */
- 0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00} /* 2nd command */
+ {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
+ 0xf8, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00} /* Switch mode to DF-EX with detach */
};
-static const struct lg4ff_native_cmd native_g25 = {
- 1,
- {0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00}
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfp = {
+ 2,
+ {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
+ 0xf8, 0x09, 0x01, 0x01, 0x00, 0x00, 0x00} /* Switch mode to DFP with detach */
+};
+
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g25 = {
+ 2,
+ {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
+ 0xf8, 0x09, 0x02, 0x01, 0x00, 0x00, 0x00} /* Switch mode to G25 with detach */
+};
+
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfgt = {
+ 2,
+ {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
+ 0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00} /* Switch mode to DFGT with detach */
};
-static const struct lg4ff_native_cmd native_g27 = {
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g27 = {
2,
- {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1st command */
- 0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00} /* 2nd command */
+ {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
+ 0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00} /* Switch mode to G27 with detach */
};
-static const struct lg4ff_usb_revision lg4ff_revs[] = {
- {DFGT_REV_MAJ, DFGT_REV_MIN, &native_dfgt}, /* Driving Force GT */
- {DFGT_REV_MAJ, DFGT2_REV_MIN, &native_dfgt}, /* Driving Force GT v2 */
- {DFP_REV_MAJ, DFP_REV_MIN, &native_dfp}, /* Driving Force Pro */
- {G25_REV_MAJ, G25_REV_MIN, &native_g25}, /* G25 */
- {G27_REV_MAJ, G27_REV_MIN, &native_g27}, /* G27 */
- {G27_REV_MAJ, G27_2_REV_MIN, &native_g27}, /* G27 v2 */
+/* EXT_CMD1 - Understood by DFP, G25, G27 and DFGT */
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext01_dfp = {
+ 1,
+ {0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}
+};
+
+/* EXT_CMD16 - Understood by G25 and G27 */
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext16_g25 = {
+ 1,
+ {0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00}
};
/* Recalculates X axis value accordingly to currently selected range */
@@ -396,20 +510,216 @@ static void hid_lg4ff_set_range_dfp(struct hid_device *hid, __u16 range)
hid_hw_request(hid, report, HID_REQ_SET_REPORT);
}
-static void hid_lg4ff_switch_native(struct hid_device *hid, const struct lg4ff_native_cmd *cmd)
+static const struct lg4ff_compat_mode_switch *lg4ff_get_mode_switch_command(const u16 real_product_id, const u16 target_product_id)
+{
+ switch (real_product_id) {
+ case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+ switch (target_product_id) {
+ case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+ return &lg4ff_mode_switch_ext01_dfp;
+ /* DFP can only be switched to its native mode */
+ default:
+ return NULL;
+ }
+ break;
+ case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
+ switch (target_product_id) {
+ case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+ return &lg4ff_mode_switch_ext01_dfp;
+ case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
+ return &lg4ff_mode_switch_ext16_g25;
+ /* G25 can only be switched to DFP mode or its native mode */
+ default:
+ return NULL;
+ }
+ break;
+ case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
+ switch (target_product_id) {
+ case USB_DEVICE_ID_LOGITECH_WHEEL:
+ return &lg4ff_mode_switch_ext09_dfex;
+ case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+ return &lg4ff_mode_switch_ext09_dfp;
+ case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
+ return &lg4ff_mode_switch_ext09_g25;
+ case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
+ return &lg4ff_mode_switch_ext09_g27;
+ /* G27 can only be switched to DF-EX, DFP, G25 or its native mode */
+ default:
+ return NULL;
+ }
+ break;
+ case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
+ switch (target_product_id) {
+ case USB_DEVICE_ID_LOGITECH_WHEEL:
+ return &lg4ff_mode_switch_ext09_dfex;
+ case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+ return &lg4ff_mode_switch_ext09_dfp;
+ case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
+ return &lg4ff_mode_switch_ext09_dfgt;
+ /* DFGT can only be switched to DF-EX, DFP or its native mode */
+ default:
+ return NULL;
+ }
+ break;
+ /* No other wheels have multiple modes */
+ default:
+ return NULL;
+ }
+}
+
+static int lg4ff_switch_compatibility_mode(struct hid_device *hid, const struct lg4ff_compat_mode_switch *s)
{
struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
- __u8 i, j;
+ __s32 *value = report->field[0]->value;
+ u8 i;
- j = 0;
- while (j < 7*cmd->cmd_num) {
- for (i = 0; i < 7; i++)
- report->field[0]->value[i] = cmd->cmd[j++];
+ for (i = 0; i < s->cmd_count; i++) {
+ u8 j;
+
+ for (j = 0; j < 7; j++)
+ value[j] = s->cmd[j + (7*i)];
hid_hw_request(hid, report, HID_REQ_SET_REPORT);
}
+ hid_hw_wait(hid);
+ return 0;
+}
+
+static ssize_t lg4ff_alternate_modes_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct hid_device *hid = to_hid_device(dev);
+ struct lg4ff_device_entry *entry;
+ struct lg_drv_data *drv_data;
+ ssize_t count = 0;
+ int i;
+
+ drv_data = hid_get_drvdata(hid);
+ if (!drv_data) {
+ hid_err(hid, "Private driver data not found!\n");
+ return 0;
+ }
+
+ entry = drv_data->device_props;
+ if (!entry) {
+ hid_err(hid, "Device properties not found!\n");
+ return 0;
+ }
+
+ if (!entry->real_name) {
+ hid_err(hid, "NULL pointer to string\n");
+ return 0;
+ }
+
+ for (i = 0; i < LG4FF_MODE_MAX_IDX; i++) {
+ if (entry->alternate_modes & BIT(i)) {
+ /* Print tag and full name */
+ count += scnprintf(buf + count, PAGE_SIZE - count, "%s: %s",
+ lg4ff_alternate_modes[i].tag,
+ !lg4ff_alternate_modes[i].product_id ? entry->real_name : lg4ff_alternate_modes[i].name);
+ if (count >= PAGE_SIZE - 1)
+ return count;
+
+ /* Mark the currently active mode with an asterisk */
+ if (lg4ff_alternate_modes[i].product_id == entry->product_id ||
+ (lg4ff_alternate_modes[i].product_id == 0 && entry->product_id == entry->real_product_id))
+ count += scnprintf(buf + count, PAGE_SIZE - count, " *\n");
+ else
+ count += scnprintf(buf + count, PAGE_SIZE - count, "\n");
+
+ if (count >= PAGE_SIZE - 1)
+ return count;
+ }
+ }
+
+ return count;
+}
+
+static ssize_t lg4ff_alternate_modes_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct hid_device *hid = to_hid_device(dev);
+ struct lg4ff_device_entry *entry;
+ struct lg_drv_data *drv_data;
+ const struct lg4ff_compat_mode_switch *s;
+ u16 target_product_id = 0;
+ int i, ret;
+ char *lbuf;
+
+ drv_data = hid_get_drvdata(hid);
+ if (!drv_data) {
+ hid_err(hid, "Private driver data not found!\n");
+ return -EINVAL;
+ }
+
+ entry = drv_data->device_props;
+ if (!entry) {
+ hid_err(hid, "Device properties not found!\n");
+ return -EINVAL;
+ }
+
+ /* Allow \n at the end of the input parameter */
+ lbuf = kasprintf(GFP_KERNEL, "%s", buf);
+ if (!lbuf)
+ return -ENOMEM;
+
+ i = strlen(lbuf);
+ if (lbuf[i-1] == '\n') {
+ if (i == 1) {
+ kfree(lbuf);
+ return -EINVAL;
+ }
+ lbuf[i-1] = '\0';
+ }
+
+ for (i = 0; i < LG4FF_MODE_MAX_IDX; i++) {
+ const u16 mode_product_id = lg4ff_alternate_modes[i].product_id;
+ const char *tag = lg4ff_alternate_modes[i].tag;
+
+ if (entry->alternate_modes & BIT(i)) {
+ if (!strcmp(tag, lbuf)) {
+ if (!mode_product_id)
+ target_product_id = entry->real_product_id;
+ else
+ target_product_id = mode_product_id;
+ break;
+ }
+ }
+ }
+
+ if (i == LG4FF_MODE_MAX_IDX) {
+ hid_info(hid, "Requested mode \"%s\" is not supported by the device\n", lbuf);
+ kfree(lbuf);
+ return -EINVAL;
+ }
+ kfree(lbuf); /* Not needed anymore */
+
+ if (target_product_id == entry->product_id) /* Nothing to do */
+ return count;
+
+ /* Automatic switching has to be disabled for the switch to DF-EX mode to work correctly */
+ if (target_product_id == USB_DEVICE_ID_LOGITECH_WHEEL && !lg4ff_no_autoswitch) {
+ hid_info(hid, "\"%s\" cannot be switched to \"DF-EX\" mode. Load the \"hid_logitech\" module with \"lg4ff_no_autoswitch=1\" parameter set and try again\n",
+ entry->real_name);
+ return -EINVAL;
+ }
+
+ /* Take care of hardware limitations */
+ if ((entry->real_product_id == USB_DEVICE_ID_LOGITECH_DFP_WHEEL || entry->real_product_id == USB_DEVICE_ID_LOGITECH_G25_WHEEL) &&
+ entry->product_id > target_product_id) {
+ hid_info(hid, "\"%s\" cannot be switched back into \"%s\" mode\n", entry->real_name, lg4ff_alternate_modes[i].name);
+ return -EINVAL;
+ }
+
+ s = lg4ff_get_mode_switch_command(entry->real_product_id, target_product_id);
+ if (!s) {
+ hid_err(hid, "Invalid target product ID %X\n", target_product_id);
+ return -EINVAL;
+ }
+
+ ret = lg4ff_switch_compatibility_mode(hid, s);
+ return (ret == 0 ? count : ret);
}
+static DEVICE_ATTR(alternate_modes, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_alternate_modes_show, lg4ff_alternate_modes_store);
/* Read current range and display it in terminal */
static ssize_t range_show(struct device *dev, struct device_attribute *attr,
@@ -472,6 +782,41 @@ static ssize_t range_store(struct device *dev, struct device_attribute *attr,
}
static DEVICE_ATTR_RW(range);
+static ssize_t lg4ff_real_id_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct hid_device *hid = to_hid_device(dev);
+ struct lg4ff_device_entry *entry;
+ struct lg_drv_data *drv_data;
+ size_t count;
+
+ drv_data = hid_get_drvdata(hid);
+ if (!drv_data) {
+ hid_err(hid, "Private driver data not found!\n");
+ return 0;
+ }
+
+ entry = drv_data->device_props;
+ if (!entry) {
+ hid_err(hid, "Device properties not found!\n");
+ return 0;
+ }
+
+ if (!entry->real_tag || !entry->real_name) {
+ hid_err(hid, "NULL pointer to string\n");
+ return 0;
+ }
+
+ count = scnprintf(buf, PAGE_SIZE, "%s: %s\n", entry->real_tag, entry->real_name);
+ return count;
+}
+
+static ssize_t lg4ff_real_id_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+ /* Real ID is a read-only value */
+ return -EPERM;
+}
+static DEVICE_ATTR(real_id, S_IRUGO, lg4ff_real_id_show, lg4ff_real_id_store);
+
#ifdef CONFIG_LEDS_CLASS
static void lg4ff_set_leds(struct hid_device *hid, __u8 leds)
{
@@ -555,20 +900,119 @@ static enum led_brightness lg4ff_led_get_brightness(struct led_classdev *led_cde
}
#endif
+static u16 lg4ff_identify_multimode_wheel(struct hid_device *hid, const u16 reported_product_id, const u16 bcdDevice)
+{
+ const struct lg4ff_wheel_ident_checklist *checklist;
+ int i, from_idx, to_idx;
+
+ switch (reported_product_id) {
+ case USB_DEVICE_ID_LOGITECH_WHEEL:
+ case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+ checklist = &lg4ff_main_checklist;
+ from_idx = 0;
+ to_idx = checklist->count - 1;
+ break;
+ case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
+ checklist = &lg4ff_main_checklist;
+ from_idx = 0;
+ to_idx = checklist->count - 2; /* End identity check at G25 */
+ break;
+ case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
+ checklist = &lg4ff_main_checklist;
+ from_idx = 1; /* Start identity check at G27 */
+ to_idx = checklist->count - 3; /* End identity check at G27 */
+ break;
+ case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
+ checklist = &lg4ff_main_checklist;
+ from_idx = 0;
+ to_idx = checklist->count - 4; /* End identity check at DFGT */
+ break;
+ default:
+ return 0;
+ }
+
+ for (i = from_idx; i <= to_idx; i++) {
+ const u16 mask = checklist->models[i]->mask;
+ const u16 result = checklist->models[i]->result;
+ const u16 real_product_id = checklist->models[i]->real_product_id;
+
+ if ((bcdDevice & mask) == result) {
+ dbg_hid("Found wheel with real PID %X whose reported PID is %X\n", real_product_id, reported_product_id);
+ return real_product_id;
+ }
+ }
+
+ /* No match found. This is either Driving Force or an unknown
+ * wheel model, do not touch it */
+ dbg_hid("Wheel with bcdDevice %X was not recognized as multimode wheel, leaving in its current mode\n", bcdDevice);
+ return 0;
+}
+
+static int lg4ff_handle_multimode_wheel(struct hid_device *hid, u16 *real_product_id, const u16 bcdDevice)
+{
+ const u16 reported_product_id = hid->product;
+ int ret;
+
+ *real_product_id = lg4ff_identify_multimode_wheel(hid, reported_product_id, bcdDevice);
+ /* Probed wheel is not a multimode wheel */
+ if (!*real_product_id) {
+ *real_product_id = reported_product_id;
+ dbg_hid("Wheel is not a multimode wheel\n");
+ return LG4FF_MMODE_NOT_MULTIMODE;
+ }
+
+ /* Switch from "Driving Force" mode to native mode automatically.
+ * Otherwise keep the wheel in its current mode */
+ if (reported_product_id == USB_DEVICE_ID_LOGITECH_WHEEL &&
+ reported_product_id != *real_product_id &&
+ !lg4ff_no_autoswitch) {
+ const struct lg4ff_compat_mode_switch *s = lg4ff_get_mode_switch_command(*real_product_id, *real_product_id);
+
+ if (!s) {
+ hid_err(hid, "Invalid product id %X\n", *real_product_id);
+ return LG4FF_MMODE_NOT_MULTIMODE;
+ }
+
+ ret = lg4ff_switch_compatibility_mode(hid, s);
+ if (ret) {
+ /* Wheel could not have been switched to native mode,
+ * leave it in "Driving Force" mode and continue */
+ hid_err(hid, "Unable to switch wheel mode, errno %d\n", ret);
+ return LG4FF_MMODE_IS_MULTIMODE;
+ }
+ return LG4FF_MMODE_SWITCHED;
+ }
+
+ return LG4FF_MMODE_IS_MULTIMODE;
+}
+
+
int lg4ff_init(struct hid_device *hid)
{
struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
struct input_dev *dev = hidinput->input;
+ const struct usb_device_descriptor *udesc = &(hid_to_usb_dev(hid)->descriptor);
+ const u16 bcdDevice = le16_to_cpu(udesc->bcdDevice);
struct lg4ff_device_entry *entry;
struct lg_drv_data *drv_data;
- struct usb_device_descriptor *udesc;
int error, i, j;
- __u16 bcdDevice, rev_maj, rev_min;
+ int mmode_ret, mmode_idx = -1;
+ u16 real_product_id;
/* Check that the report looks ok */
if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7))
return -1;
+ /* Check if a multimode wheel has been connected and
+ * handle it appropriately */
+ mmode_ret = lg4ff_handle_multimode_wheel(hid, &real_product_id, bcdDevice);
+
+ /* Wheel has been told to switch to native mode. There is no point in going on
+ * with the initialization as the wheel will do a USB reset when it switches mode
+ */
+ if (mmode_ret == LG4FF_MMODE_SWITCHED)
+ return 0;
+
/* Check what wheel has been connected */
for (i = 0; i < ARRAY_SIZE(lg4ff_devices); i++) {
if (hid->product == lg4ff_devices[i].product_id) {
@@ -583,25 +1027,15 @@ int lg4ff_init(struct hid_device *hid)
return -1;
}
- /* Attempt to switch wheel to native mode when applicable */
- udesc = &(hid_to_usb_dev(hid)->descriptor);
- if (!udesc) {
- hid_err(hid, "NULL USB device descriptor\n");
- return -1;
- }
- bcdDevice = le16_to_cpu(udesc->bcdDevice);
- rev_maj = bcdDevice >> 8;
- rev_min = bcdDevice & 0xff;
-
- if (lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_WHEEL) {
- dbg_hid("Generic wheel detected, can it do native?\n");
- dbg_hid("USB revision: %2x.%02x\n", rev_maj, rev_min);
+ if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
+ for (mmode_idx = 0; mmode_idx < ARRAY_SIZE(lg4ff_multimode_wheels); mmode_idx++) {
+ if (real_product_id == lg4ff_multimode_wheels[mmode_idx].product_id)
+ break;
+ }
- for (j = 0; j < ARRAY_SIZE(lg4ff_revs); j++) {
- if (lg4ff_revs[j].rev_maj == rev_maj && lg4ff_revs[j].rev_min == rev_min) {
- hid_lg4ff_switch_native(hid, lg4ff_revs[j].command);
- hid_info(hid, "Switched to native mode\n");
- }
+ if (mmode_idx == ARRAY_SIZE(lg4ff_multimode_wheels)) {
+ hid_err(hid, "Device product ID %X is not listed as a multimode wheel", real_product_id);
+ return -1;
}
}
@@ -630,14 +1064,23 @@ int lg4ff_init(struct hid_device *hid)
drv_data->device_props = entry;
entry->product_id = lg4ff_devices[i].product_id;
+ entry->real_product_id = real_product_id;
entry->min_range = lg4ff_devices[i].min_range;
entry->max_range = lg4ff_devices[i].max_range;
entry->set_range = lg4ff_devices[i].set_range;
+ if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
+ BUG_ON(mmode_idx == -1);
+ entry->alternate_modes = lg4ff_multimode_wheels[mmode_idx].alternate_modes;
+ entry->real_tag = lg4ff_multimode_wheels[mmode_idx].real_tag;
+ entry->real_name = lg4ff_multimode_wheels[mmode_idx].real_name;
+ }
/* Check if autocentering is available and
* set the centering force to zero by default */
if (test_bit(FF_AUTOCENTER, dev->ffbit)) {
- if (rev_maj == FFEX_REV_MAJ && rev_min == FFEX_REV_MIN) /* Formula Force EX expects different autocentering command */
+ /* Formula Force EX expects different autocentering command */
+ if ((bcdDevice >> 8) == LG4FF_FFEX_REV_MAJ &&
+ (bcdDevice & 0xff) == LG4FF_FFEX_REV_MIN)
dev->ff->set_autocenter = hid_lg4ff_set_autocenter_ffex;
else
dev->ff->set_autocenter = hid_lg4ff_set_autocenter_default;
@@ -649,6 +1092,14 @@ int lg4ff_init(struct hid_device *hid)
error = device_create_file(&hid->dev, &dev_attr_range);
if (error)
return error;
+ if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
+ error = device_create_file(&hid->dev, &dev_attr_real_id);
+ if (error)
+ return error;
+ error = device_create_file(&hid->dev, &dev_attr_alternate_modes);
+ if (error)
+ return error;
+ }
dbg_hid("sysfs interface created\n");
/* Set the maximum range to start with */
@@ -711,24 +1162,26 @@ out:
return 0;
}
-
-
int lg4ff_deinit(struct hid_device *hid)
{
struct lg4ff_device_entry *entry;
struct lg_drv_data *drv_data;
- device_remove_file(&hid->dev, &dev_attr_range);
-
drv_data = hid_get_drvdata(hid);
if (!drv_data) {
hid_err(hid, "Error while deinitializing device, no private driver data.\n");
return -1;
}
entry = drv_data->device_props;
- if (!entry) {
- hid_err(hid, "Error while deinitializing device, no device properties data.\n");
- return -1;
+ if (!entry)
+ goto out; /* Nothing more to do */
+
+ device_remove_file(&hid->dev, &dev_attr_range);
+
+ /* Multimode devices will have at least the "MODE_NATIVE" bit set */
+ if (entry->alternate_modes) {
+ device_remove_file(&hid->dev, &dev_attr_real_id);
+ device_remove_file(&hid->dev, &dev_attr_alternate_modes);
}
#ifdef CONFIG_LEDS_CLASS
@@ -752,6 +1205,7 @@ int lg4ff_deinit(struct hid_device *hid)
/* Deallocate memory */
kfree(entry);
+out:
dbg_hid("Device successfully unregistered\n");
return 0;
}
diff --git a/drivers/hid/hid-lg4ff.h b/drivers/hid/hid-lg4ff.h
new file mode 100644
index 000000000000..5b6a5086c47f
--- /dev/null
+++ b/drivers/hid/hid-lg4ff.h
@@ -0,0 +1,18 @@
+#ifndef __HID_LG4FF_H
+#define __HID_LG4FF_H
+
+#ifdef CONFIG_LOGIWHEELS_FF
+extern int lg4ff_no_autoswitch; /* From hid-lg.c */
+
+int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field,
+ struct hid_usage *usage, __s32 value, struct lg_drv_data *drv_data);
+int lg4ff_init(struct hid_device *hdev);
+int lg4ff_deinit(struct hid_device *hdev);
+#else
+static inline int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field,
+ struct hid_usage *usage, __s32 value, struct lg_drv_data *drv_data) { return 0; }
+static inline int lg4ff_init(struct hid_device *hdev) { return -1; }
+static inline int lg4ff_deinit(struct hid_device *hdev) { return -1; }
+#endif
+
+#endif
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index e77658cd037c..b3cf6fd4be96 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -28,6 +28,11 @@ MODULE_LICENSE("GPL");
MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
MODULE_AUTHOR("Nestor Lopez Casado <nlopezcasad@logitech.com>");
+static bool disable_raw_mode;
+module_param(disable_raw_mode, bool, 0644);
+MODULE_PARM_DESC(disable_raw_mode,
+ "Disable Raw mode reporting for touchpads and keep firmware gestures.");
+
#define REPORT_ID_HIDPP_SHORT 0x10
#define REPORT_ID_HIDPP_LONG 0x11
@@ -1188,6 +1193,11 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
hidpp->quirks = id->driver_data;
+ if (disable_raw_mode) {
+ hidpp->quirks &= ~HIDPP_QUIRK_CLASS_WTP;
+ hidpp->quirks &= ~HIDPP_QUIRK_DELAYED_INIT;
+ }
+
if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) {
ret = wtp_allocate(hdev, id);
if (ret)
@@ -1210,6 +1220,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
connected = hidpp_is_connected(hidpp);
if (id->group != HID_GROUP_LOGITECH_DJ_DEVICE) {
if (!connected) {
+ ret = -ENODEV;
hid_err(hdev, "Device not connected");
hid_device_io_stop(hdev);
goto hid_parse_fail;
diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
index 55e89b16b3da..6a9b05b328a9 100644
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -42,7 +42,6 @@
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/slab.h>
-#include <linux/usb.h>
#include <linux/input/mt.h>
#include <linux/string.h>
diff --git a/drivers/hid/hid-rmi.c b/drivers/hid/hid-rmi.c
index 49d4fe4f5987..368ffdf2c0a3 100644
--- a/drivers/hid/hid-rmi.c
+++ b/drivers/hid/hid-rmi.c
@@ -104,6 +104,7 @@ struct rmi_data {
unsigned long flags;
+ struct rmi_function f01;
struct rmi_function f11;
struct rmi_function f30;
@@ -124,6 +125,7 @@ struct rmi_data {
struct hid_device *hdev;
unsigned long device_flags;
+ unsigned long firmware_id;
};
#define RMI_PAGE(addr) (((addr) >> 8) & 0xff)
@@ -272,6 +274,46 @@ static inline int rmi_read(struct hid_device *hdev, u16 addr, void *buf)
return rmi_read_block(hdev, addr, buf, 1);
}
+static int rmi_write_block(struct hid_device *hdev, u16 addr, void *buf,
+ const int len)
+{
+ struct rmi_data *data = hid_get_drvdata(hdev);
+ int ret;
+
+ mutex_lock(&data->page_mutex);
+
+ if (RMI_PAGE(addr) != data->page) {
+ ret = rmi_set_page(hdev, RMI_PAGE(addr));
+ if (ret < 0)
+ goto exit;
+ }
+
+ data->writeReport[0] = RMI_WRITE_REPORT_ID;
+ data->writeReport[1] = len;
+ data->writeReport[2] = addr & 0xFF;
+ data->writeReport[3] = (addr >> 8) & 0xFF;
+ memcpy(&data->writeReport[4], buf, len);
+
+ ret = rmi_write_report(hdev, data->writeReport,
+ data->output_report_size);
+ if (ret < 0) {
+ dev_err(&hdev->dev,
+ "failed to write request output report (%d)\n",
+ ret);
+ goto exit;
+ }
+ ret = 0;
+
+exit:
+ mutex_unlock(&data->page_mutex);
+ return ret;
+}
+
+static inline int rmi_write(struct hid_device *hdev, u16 addr, void *buf)
+{
+ return rmi_write_block(hdev, addr, buf, 1);
+}
+
static void rmi_f11_process_touch(struct rmi_data *hdata, int slot,
u8 finger_state, u8 *touch_data)
{
@@ -532,6 +574,9 @@ static void rmi_register_function(struct rmi_data *data,
u16 page_base = page << 8;
switch (pdt_entry->function_number) {
+ case 0x01:
+ f = &data->f01;
+ break;
case 0x11:
f = &data->f11;
break;
@@ -604,6 +649,92 @@ error_exit:
return retval;
}
+#define RMI_DEVICE_F01_BASIC_QUERY_LEN 11
+
+static int rmi_populate_f01(struct hid_device *hdev)
+{
+ struct rmi_data *data = hid_get_drvdata(hdev);
+ u8 basic_queries[RMI_DEVICE_F01_BASIC_QUERY_LEN];
+ u8 info[3];
+ int ret;
+ bool has_query42;
+ bool has_lts;
+ bool has_sensor_id;
+ bool has_ds4_queries = false;
+ bool has_build_id_query = false;
+ bool has_package_id_query = false;
+ u16 query_offset = data->f01.query_base_addr;
+ u16 prod_info_addr;
+ u8 ds4_query_len;
+
+ ret = rmi_read_block(hdev, query_offset, basic_queries,
+ RMI_DEVICE_F01_BASIC_QUERY_LEN);
+ if (ret) {
+ hid_err(hdev, "Can not read basic queries from Function 0x1.\n");
+ return ret;
+ }
+
+ has_lts = !!(basic_queries[0] & BIT(2));
+ has_sensor_id = !!(basic_queries[1] & BIT(3));
+ has_query42 = !!(basic_queries[1] & BIT(7));
+
+ query_offset += 11;
+ prod_info_addr = query_offset + 6;
+ query_offset += 10;
+
+ if (has_lts)
+ query_offset += 20;
+
+ if (has_sensor_id)
+ query_offset++;
+
+ if (has_query42) {
+ ret = rmi_read(hdev, query_offset, info);
+ if (ret) {
+ hid_err(hdev, "Can not read query42.\n");
+ return ret;
+ }
+ has_ds4_queries = !!(info[0] & BIT(0));
+ query_offset++;
+ }
+
+ if (has_ds4_queries) {
+ ret = rmi_read(hdev, query_offset, &ds4_query_len);
+ if (ret) {
+ hid_err(hdev, "Can not read DS4 Query length.\n");
+ return ret;
+ }
+ query_offset++;
+
+ if (ds4_query_len > 0) {
+ ret = rmi_read(hdev, query_offset, info);
+ if (ret) {
+ hid_err(hdev, "Can not read DS4 query.\n");
+ return ret;
+ }
+
+ has_package_id_query = !!(info[0] & BIT(0));
+ has_build_id_query = !!(info[0] & BIT(1));
+ }
+ }
+
+ if (has_package_id_query)
+ prod_info_addr++;
+
+ if (has_build_id_query) {
+ ret = rmi_read_block(hdev, prod_info_addr, info, 3);
+ if (ret) {
+ hid_err(hdev, "Can not read product info.\n");
+ return ret;
+ }
+
+ data->firmware_id = info[1] << 8 | info[0];
+ data->firmware_id += info[2] * 65536;
+ }
+
+ return 0;
+}
+
static int rmi_populate_f11(struct hid_device *hdev)
{
struct rmi_data *data = hid_get_drvdata(hdev);
@@ -620,6 +751,8 @@ static int rmi_populate_f11(struct hid_device *hdev)
bool has_gestures;
bool has_rel;
bool has_data40 = false;
+ bool has_dribble = false;
+ bool has_palm_detect = false;
unsigned x_size, y_size;
u16 query_offset;
@@ -661,6 +794,14 @@ static int rmi_populate_f11(struct hid_device *hdev)
has_rel = !!(buf[0] & BIT(3));
has_gestures = !!(buf[0] & BIT(5));
+ ret = rmi_read(hdev, data->f11.query_base_addr + 5, buf);
+ if (ret) {
+ hid_err(hdev, "can not get absolute data sources: %d.\n", ret);
+ return ret;
+ }
+
+ has_dribble = !!(buf[0] & BIT(4));
+
/*
* At least 4 queries are guaranteed to be present in F11
* +1 for query 5 which is present since absolute events are
@@ -680,6 +821,7 @@ static int rmi_populate_f11(struct hid_device *hdev)
ret);
return ret;
}
+ has_palm_detect = !!(buf[0] & BIT(0));
has_query10 = !!(buf[0] & BIT(2));
query_offset += 2; /* query 7 and 8 are present */
@@ -766,17 +908,38 @@ static int rmi_populate_f11(struct hid_device *hdev)
* retrieve the ctrl registers
* the ctrl register has a size of 20 but a fw bug split it into 16 + 4,
* and there is no way to know if the first 20 bytes are here or not.
- * We use only the first 10 bytes, so get only them.
+ * We use only the first 12 bytes, so get only them.
*/
- ret = rmi_read_block(hdev, data->f11.control_base_addr, buf, 10);
+ ret = rmi_read_block(hdev, data->f11.control_base_addr, buf, 12);
if (ret) {
- hid_err(hdev, "can not read ctrl block of size 10: %d.\n", ret);
+ hid_err(hdev, "can not read ctrl block of size 11: %d.\n", ret);
return ret;
}
data->max_x = buf[6] | (buf[7] << 8);
data->max_y = buf[8] | (buf[9] << 8);
+ if (has_dribble) {
+ buf[0] = buf[0] & ~BIT(6);
+ ret = rmi_write(hdev, data->f11.control_base_addr, buf);
+ if (ret) {
+ hid_err(hdev, "can not write to control reg 0: %d.\n",
+ ret);
+ return ret;
+ }
+ }
+
+ if (has_palm_detect) {
+ buf[11] = buf[11] & ~BIT(0);
+ ret = rmi_write(hdev, data->f11.control_base_addr + 11,
+ &buf[11]);
+ if (ret) {
+ hid_err(hdev, "can not write to control reg 11: %d.\n",
+ ret);
+ return ret;
+ }
+ }
+
return 0;
}
@@ -858,6 +1021,12 @@ static int rmi_populate(struct hid_device *hdev)
return ret;
}
+ ret = rmi_populate_f01(hdev);
+ if (ret) {
+ hid_err(hdev, "Error while initializing F01 (%d).\n", ret);
+ return ret;
+ }
+
ret = rmi_populate_f11(hdev);
if (ret) {
hid_err(hdev, "Error while initializing F11 (%d).\n", ret);
@@ -907,6 +1076,8 @@ static void rmi_input_configured(struct hid_device *hdev, struct hid_input *hi)
if (ret)
goto exit;
+ hid_info(hdev, "firmware id: %ld\n", data->firmware_id);
+
__set_bit(EV_ABS, input->evbit);
input_set_abs_params(input, ABS_MT_POSITION_X, 1, data->max_x, 0, 0);
input_set_abs_params(input, ABS_MT_POSITION_Y, 1, data->max_y, 0, 0);
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
index 1896c019e302..27f67e7ebf10 100644
--- a/drivers/hid/hid-sony.c
+++ b/drivers/hid/hid-sony.c
@@ -802,7 +802,8 @@ union sixaxis_output_report_01 {
#define DS4_REPORT_0x05_SIZE 32
#define DS4_REPORT_0x11_SIZE 78
#define DS4_REPORT_0x81_SIZE 7
-#define SIXAXIS_REPORT_0xF2_SIZE 18
+#define SIXAXIS_REPORT_0xF2_SIZE 17
+#define SIXAXIS_REPORT_0xF5_SIZE 8
static DEFINE_SPINLOCK(sony_dev_list_lock);
static LIST_HEAD(sony_device_list);
@@ -1130,18 +1131,38 @@ static void sony_input_configured(struct hid_device *hdev,
*/
static int sixaxis_set_operational_usb(struct hid_device *hdev)
{
+ const int buf_size =
+ max(SIXAXIS_REPORT_0xF2_SIZE, SIXAXIS_REPORT_0xF5_SIZE);
+ __u8 *buf;
int ret;
- char *buf = kmalloc(18, GFP_KERNEL);
+ buf = kmalloc(buf_size, GFP_KERNEL);
if (!buf)
return -ENOMEM;
- ret = hid_hw_raw_request(hdev, 0xf2, buf, 17, HID_FEATURE_REPORT,
- HID_REQ_GET_REPORT);
+ ret = hid_hw_raw_request(hdev, 0xf2, buf, SIXAXIS_REPORT_0xF2_SIZE,
+ HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
+ if (ret < 0) {
+ hid_err(hdev, "can't set operational mode: step 1\n");
+ goto out;
+ }
+
+ /*
+ * Some compatible controllers like the Speedlink Strike FX and
+ * Gasia need another query plus an USB interrupt to get operational.
+ */
+ ret = hid_hw_raw_request(hdev, 0xf5, buf, SIXAXIS_REPORT_0xF5_SIZE,
+ HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
+ if (ret < 0) {
+ hid_err(hdev, "can't set operational mode: step 2\n");
+ goto out;
+ }
+ ret = hid_hw_output_report(hdev, buf, 1);
if (ret < 0)
- hid_err(hdev, "can't set operational mode\n");
+ hid_err(hdev, "can't set operational mode: step 3\n");
+out:
kfree(buf);
return ret;
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
index 29f328f411fb..3edd4ac36494 100644
--- a/drivers/hid/hid-steelseries.c
+++ b/drivers/hid/hid-steelseries.c
@@ -12,7 +12,6 @@
*/
#include <linux/device.h>
-#include <linux/usb.h>
#include <linux/hid.h>
#include <linux/module.h>
diff --git a/drivers/hid/hid-tivo.c b/drivers/hid/hid-tivo.c
index d790d8d71f7f..d98696927453 100644
--- a/drivers/hid/hid-tivo.c
+++ b/drivers/hid/hid-tivo.c
@@ -64,6 +64,7 @@ static const struct hid_device_id tivo_devices[] = {
/* TiVo Slide Bluetooth remote, pairs with a Broadcom dongle */
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE_BT) },
{ HID_USB_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE_PRO) },
{ }
};
MODULE_DEVICE_TABLE(hid, tivo_devices);
diff --git a/drivers/hid/hid-uclogic.c b/drivers/hid/hid-uclogic.c
index fb8b516ff0ed..94167310e15a 100644
--- a/drivers/hid/hid-uclogic.c
+++ b/drivers/hid/hid-uclogic.c
@@ -1,7 +1,8 @@
/*
* HID driver for UC-Logic devices not fully compliant with HID standard
*
- * Copyright (c) 2010 Nikolai Kondrashov
+ * Copyright (c) 2010-2014 Nikolai Kondrashov
+ * Copyright (c) 2013 Martin Rusko
*/
/*
@@ -15,6 +16,8 @@
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/usb.h>
+#include <asm/unaligned.h>
+#include "usbhid/usbhid.h"
#include "hid-ids.h"
@@ -546,11 +549,93 @@ static __u8 twha60_rdesc_fixed1[] = {
0xC0 /* End Collection */
};
+/* Report descriptor template placeholder head */
+#define UCLOGIC_PH_HEAD 0xFE, 0xED, 0x1D
+
+/* 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
+};
+
+/* Report descriptor template placeholder */
+#define UCLOGIC_PH(_ID) UCLOGIC_PH_HEAD, UCLOGIC_PH_ID_##_ID
+#define UCLOGIC_PEN_REPORT_ID 0x07
+
+/* Fixed report descriptor template */
+static const __u8 uclogic_tablet_rdesc_template[] = {
+ 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_PH(X_LM), /* Logical Maximum (PLACEHOLDER), */
+ 0x47, UCLOGIC_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), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0xB4, /* Pop, */
+ 0x09, 0x30, /* Usage (Tip Pressure), */
+ 0x27,
+ UCLOGIC_PH(PRESSURE_LM),/* Logical Maximum (PLACEHOLDER), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0xC0, /* End Collection, */
+ 0xC0 /* End Collection */
+};
+
+/* 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
+};
+
+/* Driver data */
+struct uclogic_drvdata {
+ __u8 *rdesc;
+ unsigned int rsize;
+ bool invert_pen_inrange;
+ bool ignore_pen_usage;
+};
+
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);
switch (hdev->product) {
case USB_DEVICE_ID_UCLOGIC_TABLET_PF1209:
@@ -621,11 +706,241 @@ static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc,
break;
}
break;
+ default:
+ if (drvdata->rdesc != NULL) {
+ rdesc = drvdata->rdesc;
+ *rsize = drvdata->rsize;
+ }
}
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 void 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;
+
+ 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;
+ }
+ }
+}
+
+/**
+ * Enable fully-functional tablet mode and determine device parameters.
+ *
+ * @hdev: HID device
+ */
+static int uclogic_tablet_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);
+ __le16 *buf = NULL;
+ size_t len;
+ s32 params[UCLOGIC_PH_ID_NUM];
+ s32 resolution;
+ __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) {
+ hid_err(hdev, "failed to allocate parameter buffer\n");
+ 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;
+ }
+
+ /* Allocate fixed report descriptor */
+ drvdata->rdesc = devm_kzalloc(&hdev->dev,
+ sizeof(uclogic_tablet_rdesc_template),
+ GFP_KERNEL);
+ if (drvdata->rdesc == NULL) {
+ hid_err(hdev, "failed to allocate fixed rdesc\n");
+ 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] < sizeof(params)) {
+ v = params[p[3]];
+ put_unaligned(cpu_to_le32(v), (s32 *)p);
+ p += 4;
+ } else {
+ p++;
+ }
+ }
+
+ rc = 0;
+
+cleanup:
+ kfree(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 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;
+ hdev->quirks |= HID_QUIRK_NO_EMPTY_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:
+ /* 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;
+ } 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 ((drvdata->invert_pen_inrange) &&
+ (report->type == HID_INPUT_REPORT) &&
+ (report->id == UCLOGIC_PEN_REPORT_ID) &&
+ (size >= 2))
+ /* 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) },
@@ -641,6 +956,8 @@ static const struct hid_device_id uclogic_devices[] = {
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) },
{ }
};
MODULE_DEVICE_TABLE(hid, uclogic_devices);
@@ -648,8 +965,14 @@ 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/i2c-hid/i2c-hid.c b/drivers/hid/i2c-hid/i2c-hid.c
index 36053f33d6d9..ab4dd952b6ba 100644
--- a/drivers/hid/i2c-hid/i2c-hid.c
+++ b/drivers/hid/i2c-hid/i2c-hid.c
@@ -37,6 +37,7 @@
#include <linux/mutex.h>
#include <linux/acpi.h>
#include <linux/of.h>
+#include <linux/gpio/consumer.h>
#include <linux/i2c/i2c-hid.h>
@@ -144,6 +145,8 @@ struct i2c_hid {
unsigned long flags; /* device flags */
wait_queue_head_t wait; /* For waiting the interrupt */
+ struct gpio_desc *desc;
+ int irq;
struct i2c_hid_platform_data pdata;
};
@@ -785,16 +788,16 @@ static int i2c_hid_init_irq(struct i2c_client *client)
struct i2c_hid *ihid = i2c_get_clientdata(client);
int ret;
- dev_dbg(&client->dev, "Requesting IRQ: %d\n", client->irq);
+ dev_dbg(&client->dev, "Requesting IRQ: %d\n", ihid->irq);
- ret = request_threaded_irq(client->irq, NULL, i2c_hid_irq,
+ ret = request_threaded_irq(ihid->irq, NULL, i2c_hid_irq,
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
client->name, ihid);
if (ret < 0) {
dev_warn(&client->dev,
"Could not register for %s interrupt, irq = %d,"
" ret = %d\n",
- client->name, client->irq, ret);
+ client->name, ihid->irq, ret);
return ret;
}
@@ -841,6 +844,14 @@ static int i2c_hid_fetch_hid_descriptor(struct i2c_hid *ihid)
}
#ifdef CONFIG_ACPI
+
+/* Default GPIO mapping */
+static const struct acpi_gpio_params i2c_hid_irq_gpio = { 0, 0, true };
+static const struct acpi_gpio_mapping i2c_hid_acpi_gpios[] = {
+ { "gpios", &i2c_hid_irq_gpio, 1 },
+ { },
+};
+
static int i2c_hid_acpi_pdata(struct i2c_client *client,
struct i2c_hid_platform_data *pdata)
{
@@ -866,7 +877,7 @@ static int i2c_hid_acpi_pdata(struct i2c_client *client,
pdata->hid_descriptor_address = obj->integer.value;
ACPI_FREE(obj);
- return 0;
+ return acpi_dev_add_driver_gpios(adev, i2c_hid_acpi_gpios);
}
static const struct acpi_device_id i2c_hid_acpi_match[] = {
@@ -930,12 +941,6 @@ static int i2c_hid_probe(struct i2c_client *client,
dbg_hid("HID probe called for i2c 0x%02x\n", client->addr);
- if (!client->irq) {
- dev_err(&client->dev,
- "HID over i2c has not been provided an Int IRQ\n");
- return -EINVAL;
- }
-
ihid = kzalloc(sizeof(struct i2c_hid), GFP_KERNEL);
if (!ihid)
return -ENOMEM;
@@ -955,6 +960,23 @@ static int i2c_hid_probe(struct i2c_client *client,
ihid->pdata = *platform_data;
}
+ if (client->irq > 0) {
+ ihid->irq = client->irq;
+ } else if (ACPI_COMPANION(&client->dev)) {
+ ihid->desc = gpiod_get(&client->dev, NULL, GPIOD_IN);
+ if (IS_ERR(ihid->desc)) {
+ dev_err(&client->dev, "Failed to get GPIO interrupt\n");
+ return PTR_ERR(ihid->desc);
+ }
+
+ ihid->irq = gpiod_to_irq(ihid->desc);
+ if (ihid->irq < 0) {
+ gpiod_put(ihid->desc);
+ dev_err(&client->dev, "Failed to convert GPIO to IRQ\n");
+ return ihid->irq;
+ }
+ }
+
i2c_set_clientdata(client, ihid);
ihid->client = client;
@@ -1017,13 +1039,16 @@ err_mem_free:
hid_destroy_device(hid);
err_irq:
- free_irq(client->irq, ihid);
+ free_irq(ihid->irq, ihid);
err_pm:
pm_runtime_put_noidle(&client->dev);
pm_runtime_disable(&client->dev);
err:
+ if (ihid->desc)
+ gpiod_put(ihid->desc);
+
i2c_hid_free_buffers(ihid);
kfree(ihid);
return ret;
@@ -1042,13 +1067,18 @@ static int i2c_hid_remove(struct i2c_client *client)
hid = ihid->hid;
hid_destroy_device(hid);
- free_irq(client->irq, ihid);
+ free_irq(ihid->irq, ihid);
if (ihid->bufsize)
i2c_hid_free_buffers(ihid);
+ if (ihid->desc)
+ gpiod_put(ihid->desc);
+
kfree(ihid);
+ acpi_dev_remove_driver_gpios(ACPI_COMPANION(&client->dev));
+
return 0;
}
@@ -1060,9 +1090,9 @@ static int i2c_hid_suspend(struct device *dev)
struct hid_device *hid = ihid->hid;
int ret = 0;
- disable_irq(client->irq);
+ disable_irq(ihid->irq);
if (device_may_wakeup(&client->dev))
- enable_irq_wake(client->irq);
+ enable_irq_wake(ihid->irq);
if (hid->driver && hid->driver->suspend)
ret = hid->driver->suspend(hid, PMSG_SUSPEND);
@@ -1080,13 +1110,13 @@ static int i2c_hid_resume(struct device *dev)
struct i2c_hid *ihid = i2c_get_clientdata(client);
struct hid_device *hid = ihid->hid;
- enable_irq(client->irq);
+ enable_irq(ihid->irq);
ret = i2c_hid_hwreset(client);
if (ret)
return ret;
if (device_may_wakeup(&client->dev))
- disable_irq_wake(client->irq);
+ disable_irq_wake(ihid->irq);
if (hid->driver && hid->driver->reset_resume) {
ret = hid->driver->reset_resume(hid);
@@ -1101,17 +1131,19 @@ static int i2c_hid_resume(struct device *dev)
static int i2c_hid_runtime_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
+ struct i2c_hid *ihid = i2c_get_clientdata(client);
i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
- disable_irq(client->irq);
+ disable_irq(ihid->irq);
return 0;
}
static int i2c_hid_runtime_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
+ struct i2c_hid *ihid = i2c_get_clientdata(client);
- enable_irq(client->irq);
+ enable_irq(ihid->irq);
i2c_hid_set_power(client, I2C_HID_PWR_ON);
return 0;
}
diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c
index 0b531c6a76a5..08174d341f4a 100644
--- a/drivers/hid/usbhid/hid-pidff.c
+++ b/drivers/hid/usbhid/hid-pidff.c
@@ -568,6 +568,12 @@ static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *effect,
int type_id;
int error;
+ pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] = 0;
+ if (old) {
+ pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] =
+ pidff->pid_id[effect->id];
+ }
+
switch (effect->type) {
case FF_CONSTANT:
if (!old) {
diff --git a/drivers/hid/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c
index 9be99a67bfe2..a775143e6265 100644
--- a/drivers/hid/usbhid/hid-quirks.c
+++ b/drivers/hid/usbhid/hid-quirks.c
@@ -78,6 +78,13 @@ static const struct hid_blacklist {
{ USB_VENDOR_ID_ELO, USB_DEVICE_ID_ELO_TS2700, HID_QUIRK_NOGET },
{ USB_VENDOR_ID_FORMOSA, USB_DEVICE_ID_FORMOSA_IR_RECEIVER, HID_QUIRK_NO_INIT_REPORTS },
{ USB_VENDOR_ID_FREESCALE, USB_DEVICE_ID_FREESCALE_MX28, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0A4A, HID_QUIRK_ALWAYS_POLL },
+ { USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0B4A, HID_QUIRK_ALWAYS_POLL },
+ { USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE, HID_QUIRK_ALWAYS_POLL },
+ { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_C077, HID_QUIRK_ALWAYS_POLL },
+ { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOUSE_C01A, HID_QUIRK_ALWAYS_POLL },
+ { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOUSE_C05A, HID_QUIRK_ALWAYS_POLL },
+ { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOUSE_C06A, HID_QUIRK_ALWAYS_POLL },
{ USB_VENDOR_ID_MGE, USB_DEVICE_ID_MGE_UPS, HID_QUIRK_NOGET },
{ USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_3, HID_QUIRK_NO_INIT_REPORTS },
{ USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_3_JP, HID_QUIRK_NO_INIT_REPORTS },
@@ -91,6 +98,7 @@ static const struct hid_blacklist {
{ USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN, HID_QUIRK_NO_INIT_REPORTS },
{ USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN1, HID_QUIRK_NO_INIT_REPORTS },
{ USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN2, HID_QUIRK_NO_INIT_REPORTS },
+ { USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_MOUSE_4D22, HID_QUIRK_ALWAYS_POLL },
{ USB_VENDOR_ID_PRODIGE, USB_DEVICE_ID_PRODIGE_CORDLESS, HID_QUIRK_NOGET },
{ USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3001, HID_QUIRK_NOGET },
{ USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3008, HID_QUIRK_NOGET },
@@ -106,12 +114,8 @@ static const struct hid_blacklist {
{ USB_VENDOR_ID_SYMBOL, USB_DEVICE_ID_SYMBOL_SCANNER_2, HID_QUIRK_NOGET },
{ USB_VENDOR_ID_TPV, USB_DEVICE_ID_TPV_OPTICAL_TOUCHSCREEN, HID_QUIRK_NOGET },
{ USB_VENDOR_ID_TURBOX, USB_DEVICE_ID_TURBOX_KEYBOARD, HID_QUIRK_NOGET },
- { USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_PF1209, HID_QUIRK_MULTI_INPUT },
- { USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_WP4030U, HID_QUIRK_MULTI_INPUT },
{ USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_KNA5, HID_QUIRK_MULTI_INPUT },
{ USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_TWA60, HID_QUIRK_MULTI_INPUT },
- { USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_WP5540U, HID_QUIRK_MULTI_INPUT },
- { USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U, HID_QUIRK_MULTI_INPUT },
{ USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_MEDIA_TABLET_10_6_INCH, HID_QUIRK_MULTI_INPUT },
{ USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_MEDIA_TABLET_14_1_INCH, HID_QUIRK_MULTI_INPUT },
{ USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_SIRIUS_BATTERY_FREE_TABLET, HID_QUIRK_MULTI_INPUT },
@@ -127,6 +131,7 @@ static const struct hid_blacklist {
{ USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X, HID_QUIRK_MULTI_INPUT },
{ USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X_2, HID_QUIRK_MULTI_INPUT },
{ USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M610X, HID_QUIRK_MULTI_INPUT },
+ { USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_PENSKETCH_M912, HID_QUIRK_MULTI_INPUT },
{ USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_DUOSENSE, HID_QUIRK_NO_INIT_REPORTS },
{ USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD, HID_QUIRK_NO_INIT_REPORTS },
{ USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_LTS1, HID_QUIRK_NO_INIT_REPORTS },
diff --git a/drivers/hid/wacom.h b/drivers/hid/wacom.h
index 7db432809e9e..ad7318db1dfe 100644
--- a/drivers/hid/wacom.h
+++ b/drivers/hid/wacom.h
@@ -129,13 +129,6 @@ static inline void wacom_schedule_work(struct wacom_wac *wacom_wac)
schedule_work(&wacom->work);
}
-static inline void wacom_notify_battery(struct wacom_wac *wacom_wac)
-{
- struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
-
- power_supply_changed(&wacom->battery);
-}
-
extern const struct hid_device_id wacom_ids[];
void wacom_wac_irq(struct wacom_wac *wacom_wac, size_t len);
@@ -149,4 +142,5 @@ void wacom_wac_usage_mapping(struct hid_device *hdev,
int wacom_wac_event(struct hid_device *hdev, struct hid_field *field,
struct hid_usage *usage, __s32 value);
void wacom_wac_report(struct hid_device *hdev, struct hid_report *report);
+void wacom_battery_work(struct work_struct *work);
#endif
diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c
index f0568a7e6de9..1b00d8d4466c 100644
--- a/drivers/hid/wacom_sys.c
+++ b/drivers/hid/wacom_sys.c
@@ -406,6 +406,9 @@ static int wacom_query_tablet_data(struct hid_device *hdev,
else if (features->type == WACOM_27QHDT) {
return wacom_set_device_mode(hdev, 131, 3, 2);
}
+ else if (features->type == BAMBOO_PAD) {
+ return wacom_set_device_mode(hdev, 2, 2, 2);
+ }
} else if (features->device_type == BTN_TOOL_PEN) {
if (features->type <= BAMBOO_PT && features->type != WIRELESS) {
return wacom_set_device_mode(hdev, 2, 2, 2);
@@ -524,6 +527,11 @@ static int wacom_add_shared_data(struct hid_device *hdev)
wacom_wac->shared = &data->shared;
+ if (wacom_wac->features.device_type == BTN_TOOL_FINGER)
+ wacom_wac->shared->touch = hdev;
+ else if (wacom_wac->features.device_type == BTN_TOOL_PEN)
+ wacom_wac->shared->pen = hdev;
+
out:
mutex_unlock(&wacom_udev_list_lock);
return retval;
@@ -541,14 +549,22 @@ static void wacom_release_shared_data(struct kref *kref)
kfree(data);
}
-static void wacom_remove_shared_data(struct wacom_wac *wacom)
+static void wacom_remove_shared_data(struct wacom *wacom)
{
struct wacom_hdev_data *data;
+ struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+
+ if (wacom_wac->shared) {
+ data = container_of(wacom_wac->shared, struct wacom_hdev_data,
+ shared);
+
+ if (wacom_wac->shared->touch == wacom->hdev)
+ wacom_wac->shared->touch = NULL;
+ else if (wacom_wac->shared->pen == wacom->hdev)
+ wacom_wac->shared->pen = NULL;
- if (wacom->shared) {
- data = container_of(wacom->shared, struct wacom_hdev_data, shared);
kref_put(&data->kref, wacom_release_shared_data);
- wacom->shared = NULL;
+ wacom_wac->shared = NULL;
}
}
@@ -929,6 +945,7 @@ static void wacom_destroy_leds(struct wacom *wacom)
}
static enum power_supply_property wacom_battery_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_SCOPE,
POWER_SUPPLY_PROP_CAPACITY
@@ -948,6 +965,9 @@ static int wacom_battery_get_property(struct power_supply *psy,
int ret = 0;
switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = wacom->wacom_wac.bat_connected;
+ break;
case POWER_SUPPLY_PROP_SCOPE:
val->intval = POWER_SUPPLY_SCOPE_DEVICE;
break;
@@ -961,6 +981,8 @@ static int wacom_battery_get_property(struct power_supply *psy,
else if (wacom->wacom_wac.battery_capacity == 100 &&
wacom->wacom_wac.ps_connected)
val->intval = POWER_SUPPLY_STATUS_FULL;
+ else if (wacom->wacom_wac.ps_connected)
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
else
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
break;
@@ -1041,8 +1063,7 @@ static int wacom_initialize_battery(struct wacom *wacom)
static void wacom_destroy_battery(struct wacom *wacom)
{
- if ((wacom->wacom_wac.features.quirks & WACOM_QUIRK_BATTERY) &&
- wacom->battery.dev) {
+ if (wacom->battery.dev) {
power_supply_unregister(&wacom->battery);
wacom->battery.dev = NULL;
power_supply_unregister(&wacom->ac);
@@ -1313,6 +1334,20 @@ fail:
return;
}
+void wacom_battery_work(struct work_struct *work)
+{
+ struct wacom *wacom = container_of(work, struct wacom, work);
+
+ if ((wacom->wacom_wac.features.quirks & WACOM_QUIRK_BATTERY) &&
+ !wacom->battery.dev) {
+ wacom_initialize_battery(wacom);
+ }
+ else if (!(wacom->wacom_wac.features.quirks & WACOM_QUIRK_BATTERY) &&
+ wacom->battery.dev) {
+ wacom_destroy_battery(wacom);
+ }
+}
+
/*
* Not all devices report physical dimensions from HID.
* Compute the default from hardcoded logical dimension
@@ -1373,6 +1408,9 @@ static int wacom_probe(struct hid_device *hdev,
hdev->quirks |= HID_QUIRK_NO_INIT_REPORTS;
+ /* hid-core sets this quirk for the boot interface */
+ hdev->quirks &= ~HID_QUIRK_NOGET;
+
wacom = kzalloc(sizeof(struct wacom), GFP_KERNEL);
if (!wacom)
return -ENOMEM;
@@ -1412,6 +1450,21 @@ static int wacom_probe(struct hid_device *hdev,
goto fail_allocate_inputs;
}
+ /*
+ * Bamboo Pad has a generic hid handling for the Pen, and we switch it
+ * into debug mode for the touch part.
+ * We ignore the other interfaces.
+ */
+ if (features->type == BAMBOO_PAD) {
+ if (features->pktlen == WACOM_PKGLEN_PENABLED) {
+ features->type = HID_GENERIC;
+ } else if ((features->pktlen != WACOM_PKGLEN_BPAD_TOUCH) &&
+ (features->pktlen != WACOM_PKGLEN_BPAD_TOUCH_USB)) {
+ error = -ENODEV;
+ goto fail_shared_data;
+ }
+ }
+
/* set the default size in case we do not get them from hid */
wacom_set_default_phy(features);
@@ -1446,6 +1499,12 @@ static int wacom_probe(struct hid_device *hdev,
features->y_max = 4096;
}
+ /*
+ * Same thing for Bamboo PAD
+ */
+ if (features->type == BAMBOO_PAD)
+ features->device_type = BTN_TOOL_FINGER;
+
if (hdev->bus == BUS_BLUETOOTH)
features->quirks |= WACOM_QUIRK_BATTERY;
@@ -1462,19 +1521,17 @@ static int wacom_probe(struct hid_device *hdev,
snprintf(wacom_wac->pad_name, sizeof(wacom_wac->pad_name),
"%s Pad", features->name);
- if (features->quirks & WACOM_QUIRK_MULTI_INPUT) {
- /* Append the device type to the name */
- if (features->device_type != BTN_TOOL_FINGER)
- strlcat(wacom_wac->name, " Pen", WACOM_NAME_MAX);
- else if (features->touch_max)
- strlcat(wacom_wac->name, " Finger", WACOM_NAME_MAX);
- else
- strlcat(wacom_wac->name, " Pad", WACOM_NAME_MAX);
+ /* Append the device type to the name */
+ if (features->device_type != BTN_TOOL_FINGER)
+ strlcat(wacom_wac->name, " Pen", WACOM_NAME_MAX);
+ else if (features->touch_max)
+ strlcat(wacom_wac->name, " Finger", WACOM_NAME_MAX);
+ else
+ strlcat(wacom_wac->name, " Pad", WACOM_NAME_MAX);
- error = wacom_add_shared_data(hdev);
- if (error)
- goto fail_shared_data;
- }
+ error = wacom_add_shared_data(hdev);
+ if (error)
+ goto fail_shared_data;
if (!(features->quirks & WACOM_QUIRK_MONITOR) &&
(features->quirks & WACOM_QUIRK_BATTERY)) {
@@ -1527,7 +1584,7 @@ fail_register_inputs:
wacom_clean_inputs(wacom);
wacom_destroy_battery(wacom);
fail_battery:
- wacom_remove_shared_data(wacom_wac);
+ wacom_remove_shared_data(wacom);
fail_shared_data:
wacom_clean_inputs(wacom);
fail_allocate_inputs:
@@ -1550,7 +1607,7 @@ static void wacom_remove(struct hid_device *hdev)
if (hdev->bus == BUS_BLUETOOTH)
device_remove_file(&hdev->dev, &dev_attr_speed);
wacom_destroy_battery(wacom);
- wacom_remove_shared_data(&wacom->wacom_wac);
+ wacom_remove_shared_data(wacom);
hid_set_drvdata(hdev, NULL);
kfree(wacom);
diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c
index 046351cf17f3..69c7df78281d 100644
--- a/drivers/hid/wacom_wac.c
+++ b/drivers/hid/wacom_wac.c
@@ -45,6 +45,27 @@ static unsigned short batcap_gr[8] = { 1, 15, 25, 35, 50, 70, 100, 100 };
*/
static unsigned short batcap_i4[8] = { 1, 15, 30, 45, 60, 70, 85, 100 };
+static void wacom_notify_battery(struct wacom_wac *wacom_wac,
+ int bat_capacity, bool bat_charging, bool bat_connected,
+ bool ps_connected)
+{
+ struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
+ bool changed = wacom_wac->battery_capacity != bat_capacity ||
+ wacom_wac->bat_charging != bat_charging ||
+ wacom_wac->bat_connected != bat_connected ||
+ wacom_wac->ps_connected != ps_connected;
+
+ if (changed) {
+ wacom_wac->battery_capacity = bat_capacity;
+ wacom_wac->bat_charging = bat_charging;
+ wacom_wac->bat_connected = bat_connected;
+ wacom_wac->ps_connected = ps_connected;
+
+ if (wacom->battery.dev)
+ power_supply_changed(&wacom->battery);
+ }
+}
+
static int wacom_penpartner_irq(struct wacom_wac *wacom)
{
unsigned char *data = wacom->data;
@@ -419,17 +440,26 @@ static int wacom_graphire_irq(struct wacom_wac *wacom)
rw = (data[7] >> 2 & 0x07);
battery_capacity = batcap_gr[rw];
ps_connected = rw == 7;
- if ((wacom->battery_capacity != battery_capacity) ||
- (wacom->ps_connected != ps_connected)) {
- wacom->battery_capacity = battery_capacity;
- wacom->ps_connected = ps_connected;
- wacom_notify_battery(wacom);
- }
+ wacom_notify_battery(wacom, battery_capacity, ps_connected,
+ 1, ps_connected);
}
exit:
return retval;
}
+static void wacom_intuos_schedule_prox_event(struct wacom_wac *wacom_wac)
+{
+ struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
+ struct hid_report *r;
+ struct hid_report_enum *re;
+
+ re = &(wacom->hdev->report_enum[HID_FEATURE_REPORT]);
+ r = re->report_id_hash[WACOM_REPORT_INTUOSREAD];
+ if (r) {
+ hid_hw_request(wacom->hdev, r, HID_REQ_GET_REPORT);
+ }
+}
+
static int wacom_intuos_inout(struct wacom_wac *wacom)
{
struct wacom_features *features = &wacom->features;
@@ -551,8 +581,9 @@ static int wacom_intuos_inout(struct wacom_wac *wacom)
(features->type == CINTIQ && !(data[1] & 0x40)))
return 1;
- if (features->quirks & WACOM_QUIRK_MULTI_INPUT)
- wacom->shared->stylus_in_proximity = true;
+ wacom->shared->stylus_in_proximity = true;
+ if (wacom->shared->touch_down)
+ return 1;
/* in Range while exiting */
if (((data[1] & 0xfe) == 0x20) && wacom->reporting_data) {
@@ -564,8 +595,7 @@ static int wacom_intuos_inout(struct wacom_wac *wacom)
/* Exit report */
if ((data[1] & 0xfe) == 0x80) {
- if (features->quirks & WACOM_QUIRK_MULTI_INPUT)
- wacom->shared->stylus_in_proximity = false;
+ wacom->shared->stylus_in_proximity = false;
wacom->reporting_data = false;
/* don't report exit if we don't know the ID */
@@ -606,8 +636,11 @@ static int wacom_intuos_inout(struct wacom_wac *wacom)
}
/* don't report other events if we don't know the ID */
- if (!wacom->id[idx])
+ if (!wacom->id[idx]) {
+ /* but reschedule a read of the current tool */
+ wacom_intuos_schedule_prox_event(wacom);
return 1;
+ }
return 0;
}
@@ -1019,15 +1052,9 @@ static int wacom_intuos_bt_irq(struct wacom_wac *wacom, size_t len)
bat_charging = (power_raw & 0x08) ? 1 : 0;
ps_connected = (power_raw & 0x10) ? 1 : 0;
battery_capacity = batcap_i4[power_raw & 0x07];
- if ((wacom->battery_capacity != battery_capacity) ||
- (wacom->bat_charging != bat_charging) ||
- (wacom->ps_connected != ps_connected)) {
- wacom->battery_capacity = battery_capacity;
- wacom->bat_charging = bat_charging;
- wacom->ps_connected = ps_connected;
- wacom_notify_battery(wacom);
- }
-
+ wacom_notify_battery(wacom, battery_capacity, bat_charging,
+ battery_capacity || bat_charging,
+ ps_connected);
break;
default:
dev_dbg(wacom->input->dev.parent,
@@ -1038,12 +1065,34 @@ static int wacom_intuos_bt_irq(struct wacom_wac *wacom, size_t len)
return 0;
}
+static int wacom_wac_finger_count_touches(struct wacom_wac *wacom)
+{
+ struct input_dev *input = wacom->input;
+ unsigned touch_max = wacom->features.touch_max;
+ int count = 0;
+ int i;
+
+ /* non-HID_GENERIC single touch input doesn't call this routine */
+ if ((touch_max == 1) && (wacom->features.type == HID_GENERIC))
+ return wacom->hid_data.tipswitch &&
+ !wacom->shared->stylus_in_proximity;
+
+ for (i = 0; i < input->mt->num_slots; i++) {
+ struct input_mt_slot *ps = &input->mt->slots[i];
+ int id = input_mt_get_value(ps, ABS_MT_TRACKING_ID);
+ if (id >= 0)
+ count++;
+ }
+
+ return count;
+}
+
static int wacom_24hdt_irq(struct wacom_wac *wacom)
{
struct input_dev *input = wacom->input;
unsigned char *data = wacom->data;
int i;
- int current_num_contacts = 0;
+ int current_num_contacts = data[61];
int contacts_to_send = 0;
int num_contacts_left = 4; /* maximum contacts per packet */
int byte_per_packet = WACOM_BYTES_PER_24HDT_PACKET;
@@ -1054,8 +1103,6 @@ static int wacom_24hdt_irq(struct wacom_wac *wacom)
num_contacts_left = 10;
byte_per_packet = WACOM_BYTES_PER_QHDTHID_PACKET;
y_offset = 0;
- } else {
- current_num_contacts = data[61];
}
/*
@@ -1098,13 +1145,13 @@ static int wacom_24hdt_irq(struct wacom_wac *wacom)
}
}
}
- input_mt_report_pointer_emulation(input, true);
+ input_mt_sync_frame(input);
wacom->num_contacts_left -= contacts_to_send;
- if (wacom->num_contacts_left <= 0)
+ if (wacom->num_contacts_left <= 0) {
wacom->num_contacts_left = 0;
-
- wacom->shared->touch_down = (wacom->num_contacts_left > 0);
+ wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom);
+ }
return 1;
}
@@ -1149,13 +1196,13 @@ static int wacom_mt_touch(struct wacom_wac *wacom)
input_report_abs(input, ABS_MT_POSITION_Y, y);
}
}
- input_mt_report_pointer_emulation(input, true);
+ input_mt_sync_frame(input);
wacom->num_contacts_left -= contacts_to_send;
- if (wacom->num_contacts_left < 0)
+ if (wacom->num_contacts_left <= 0) {
wacom->num_contacts_left = 0;
-
- wacom->shared->touch_down = (wacom->num_contacts_left > 0);
+ wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom);
+ }
return 1;
}
@@ -1163,7 +1210,6 @@ static int wacom_tpc_mt_touch(struct wacom_wac *wacom)
{
struct input_dev *input = wacom->input;
unsigned char *data = wacom->data;
- int contact_with_no_pen_down_count = 0;
int i;
for (i = 0; i < 2; i++) {
@@ -1178,13 +1224,12 @@ static int wacom_tpc_mt_touch(struct wacom_wac *wacom)
input_report_abs(input, ABS_MT_POSITION_X, x);
input_report_abs(input, ABS_MT_POSITION_Y, y);
- contact_with_no_pen_down_count++;
}
}
- input_mt_report_pointer_emulation(input, true);
+ input_mt_sync_frame(input);
/* keep touch state for pen event */
- wacom->shared->touch_down = (contact_with_no_pen_down_count > 0);
+ wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom);
return 1;
}
@@ -1193,29 +1238,25 @@ static int wacom_tpc_single_touch(struct wacom_wac *wacom, size_t len)
{
unsigned char *data = wacom->data;
struct input_dev *input = wacom->input;
- bool prox;
+ bool prox = !wacom->shared->stylus_in_proximity;
int x = 0, y = 0;
if (wacom->features.touch_max > 1 || len > WACOM_PKGLEN_TPC2FG)
return 0;
- if (!wacom->shared->stylus_in_proximity) {
- if (len == WACOM_PKGLEN_TPC1FG) {
- prox = data[0] & 0x01;
- x = get_unaligned_le16(&data[1]);
- y = get_unaligned_le16(&data[3]);
- } else if (len == WACOM_PKGLEN_TPC1FG_B) {
- prox = data[2] & 0x01;
- x = get_unaligned_le16(&data[3]);
- y = get_unaligned_le16(&data[5]);
- } else {
- prox = data[1] & 0x01;
- x = le16_to_cpup((__le16 *)&data[2]);
- y = le16_to_cpup((__le16 *)&data[4]);
- }
- } else
- /* force touch out when pen is in prox */
- prox = 0;
+ if (len == WACOM_PKGLEN_TPC1FG) {
+ prox = prox && (data[0] & 0x01);
+ x = get_unaligned_le16(&data[1]);
+ y = get_unaligned_le16(&data[3]);
+ } else if (len == WACOM_PKGLEN_TPC1FG_B) {
+ prox = prox && (data[2] & 0x01);
+ x = get_unaligned_le16(&data[3]);
+ y = get_unaligned_le16(&data[5]);
+ } else {
+ prox = prox && (data[1] & 0x01);
+ x = le16_to_cpup((__le16 *)&data[2]);
+ y = le16_to_cpup((__le16 *)&data[4]);
+ }
if (prox) {
input_report_abs(input, ABS_X, x);
@@ -1516,29 +1557,6 @@ static int wacom_wac_finger_event(struct hid_device *hdev,
return 0;
}
-static int wacom_wac_finger_count_touches(struct hid_device *hdev)
-{
- struct wacom *wacom = hid_get_drvdata(hdev);
- struct wacom_wac *wacom_wac = &wacom->wacom_wac;
- struct input_dev *input = wacom_wac->input;
- unsigned touch_max = wacom_wac->features.touch_max;
- int count = 0;
- int i;
-
- if (touch_max == 1)
- return wacom_wac->hid_data.tipswitch &&
- !wacom_wac->shared->stylus_in_proximity;
-
- for (i = 0; i < input->mt->num_slots; i++) {
- struct input_mt_slot *ps = &input->mt->slots[i];
- int id = input_mt_get_value(ps, ABS_MT_TRACKING_ID);
- if (id >= 0)
- count++;
- }
-
- return count;
-}
-
static void wacom_wac_finger_report(struct hid_device *hdev,
struct hid_report *report)
{
@@ -1553,7 +1571,7 @@ static void wacom_wac_finger_report(struct hid_device *hdev,
input_sync(input);
/* keep touch state for pen event */
- wacom_wac->shared->touch_down = wacom_wac_finger_count_touches(hdev);
+ wacom_wac->shared->touch_down = wacom_wac_finger_count_touches(wacom_wac);
}
void wacom_wac_usage_mapping(struct hid_device *hdev,
@@ -1643,12 +1661,13 @@ static int wacom_bpt_touch(struct wacom_wac *wacom)
}
}
- input_mt_report_pointer_emulation(input, true);
+ input_mt_sync_frame(input);
input_report_key(pad_input, BTN_LEFT, (data[1] & 0x08) != 0);
input_report_key(pad_input, BTN_FORWARD, (data[1] & 0x04) != 0);
input_report_key(pad_input, BTN_BACK, (data[1] & 0x02) != 0);
input_report_key(pad_input, BTN_RIGHT, (data[1] & 0x01) != 0);
+ wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom);
return 1;
}
@@ -1733,7 +1752,8 @@ static int wacom_bpt3_touch(struct wacom_wac *wacom)
wacom_bpt3_button_msg(wacom, data + offset);
}
- input_mt_report_pointer_emulation(input, true);
+ input_mt_sync_frame(input);
+ wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom);
return 1;
}
@@ -1745,20 +1765,9 @@ static int wacom_bpt_pen(struct wacom_wac *wacom)
unsigned char *data = wacom->data;
int prox = 0, x = 0, y = 0, p = 0, d = 0, pen = 0, btn1 = 0, btn2 = 0;
- if (data[0] != WACOM_REPORT_PENABLED && data[0] != WACOM_REPORT_USB)
+ if (data[0] != WACOM_REPORT_PENABLED)
return 0;
- if (data[0] == WACOM_REPORT_USB) {
- if (features->type == INTUOSHT &&
- wacom->shared->touch_input &&
- features->touch_max) {
- input_report_switch(wacom->shared->touch_input,
- SW_MUTE_DEVICE, data[8] & 0x40);
- input_sync(wacom->shared->touch_input);
- }
- return 0;
- }
-
prox = (data[1] & 0x20) == 0x20;
/*
@@ -1771,17 +1780,21 @@ static int wacom_bpt_pen(struct wacom_wac *wacom)
*
* Hardware does report zero in most out-of-prox cases but not all.
*/
- if (prox) {
- if (!wacom->shared->stylus_in_proximity) {
- if (data[1] & 0x08) {
- wacom->tool[0] = BTN_TOOL_RUBBER;
- wacom->id[0] = ERASER_DEVICE_ID;
- } else {
- wacom->tool[0] = BTN_TOOL_PEN;
- wacom->id[0] = STYLUS_DEVICE_ID;
- }
- wacom->shared->stylus_in_proximity = true;
+ if (!wacom->shared->stylus_in_proximity) {
+ if (data[1] & 0x08) {
+ wacom->tool[0] = BTN_TOOL_RUBBER;
+ wacom->id[0] = ERASER_DEVICE_ID;
+ } else {
+ wacom->tool[0] = BTN_TOOL_PEN;
+ wacom->id[0] = STYLUS_DEVICE_ID;
}
+ }
+
+ wacom->shared->stylus_in_proximity = prox;
+ if (wacom->shared->touch_down)
+ return 0;
+
+ if (prox) {
x = le16_to_cpup((__le16 *)&data[2]);
y = le16_to_cpup((__le16 *)&data[4]);
p = le16_to_cpup((__le16 *)&data[6]);
@@ -1797,6 +1810,8 @@ static int wacom_bpt_pen(struct wacom_wac *wacom)
pen = data[1] & 0x01;
btn1 = data[1] & 0x02;
btn2 = data[1] & 0x04;
+ } else {
+ wacom->id[0] = 0;
}
input_report_key(input, BTN_TOUCH, pen);
@@ -1808,11 +1823,6 @@ static int wacom_bpt_pen(struct wacom_wac *wacom)
input_report_abs(input, ABS_PRESSURE, p);
input_report_abs(input, ABS_DISTANCE, d);
- if (!prox) {
- wacom->id[0] = 0;
- wacom->shared->stylus_in_proximity = false;
- }
-
input_report_key(input, wacom->tool[0], prox); /* PEN or RUBBER */
input_report_abs(input, ABS_MISC, wacom->id[0]); /* TOOL ID */
@@ -1831,6 +1841,91 @@ static int wacom_bpt_irq(struct wacom_wac *wacom, size_t len)
return 0;
}
+static void wacom_bamboo_pad_pen_event(struct wacom_wac *wacom,
+ unsigned char *data)
+{
+ unsigned char prefix;
+
+ /*
+ * We need to reroute the event from the debug interface to the
+ * pen interface.
+ * We need to add the report ID to the actual pen report, so we
+ * temporary overwrite the first byte to prevent having to kzalloc/kfree
+ * and memcpy the report.
+ */
+ prefix = data[0];
+ data[0] = WACOM_REPORT_BPAD_PEN;
+
+ /*
+ * actually reroute the event.
+ * No need to check if wacom->shared->pen is valid, hid_input_report()
+ * will check for us.
+ */
+ hid_input_report(wacom->shared->pen, HID_INPUT_REPORT, data,
+ WACOM_PKGLEN_PENABLED, 1);
+
+ data[0] = prefix;
+}
+
+static int wacom_bamboo_pad_touch_event(struct wacom_wac *wacom,
+ unsigned char *data)
+{
+ struct input_dev *input = wacom->input;
+ unsigned char *finger_data, prefix;
+ unsigned id;
+ int x, y;
+ bool valid;
+
+ prefix = data[0];
+
+ for (id = 0; id < wacom->features.touch_max; id++) {
+ valid = !!(prefix & BIT(id)) &&
+ !wacom->shared->stylus_in_proximity;
+
+ input_mt_slot(input, id);
+ input_mt_report_slot_state(input, MT_TOOL_FINGER, valid);
+
+ if (!valid)
+ continue;
+
+ finger_data = data + 1 + id * 3;
+ x = finger_data[0] | ((finger_data[1] & 0x0f) << 8);
+ y = (finger_data[2] << 4) | (finger_data[1] >> 4);
+
+ input_report_abs(input, ABS_MT_POSITION_X, x);
+ input_report_abs(input, ABS_MT_POSITION_Y, y);
+ }
+
+ input_mt_sync_frame(input);
+
+ input_report_key(input, BTN_LEFT, prefix & 0x40);
+ input_report_key(input, BTN_RIGHT, prefix & 0x80);
+
+ /* keep touch state for pen event */
+ wacom->shared->touch_down = !!prefix &&
+ !wacom->shared->stylus_in_proximity;
+
+ return 1;
+}
+
+static int wacom_bamboo_pad_irq(struct wacom_wac *wacom, size_t len)
+{
+ unsigned char *data = wacom->data;
+
+ if (!((len == WACOM_PKGLEN_BPAD_TOUCH) ||
+ (len == WACOM_PKGLEN_BPAD_TOUCH_USB)) ||
+ (data[0] != WACOM_REPORT_BPAD_TOUCH))
+ return 0;
+
+ if (data[1] & 0x01)
+ wacom_bamboo_pad_pen_event(wacom, &data[1]);
+
+ if (data[1] & 0x02)
+ return wacom_bamboo_pad_touch_event(wacom, &data[9]);
+
+ return 0;
+}
+
static int wacom_wireless_irq(struct wacom_wac *wacom, size_t len)
{
unsigned char *data = wacom->data;
@@ -1841,7 +1936,7 @@ static int wacom_wireless_irq(struct wacom_wac *wacom, size_t len)
connected = data[1] & 0x01;
if (connected) {
- int pid, battery, ps_connected;
+ int pid, battery, charging;
if ((wacom->shared->type == INTUOSHT) &&
wacom->shared->touch_input &&
@@ -1853,33 +1948,66 @@ static int wacom_wireless_irq(struct wacom_wac *wacom, size_t len)
pid = get_unaligned_be16(&data[6]);
battery = (data[5] & 0x3f) * 100 / 31;
- ps_connected = !!(data[5] & 0x80);
+ charging = !!(data[5] & 0x80);
if (wacom->pid != pid) {
wacom->pid = pid;
wacom_schedule_work(wacom);
}
- if (wacom->shared->type &&
- (battery != wacom->battery_capacity ||
- ps_connected != wacom->ps_connected)) {
- wacom->battery_capacity = battery;
- wacom->ps_connected = ps_connected;
- wacom->bat_charging = ps_connected &&
- wacom->battery_capacity < 100;
- wacom_notify_battery(wacom);
- }
+ if (wacom->shared->type)
+ wacom_notify_battery(wacom, battery, charging, 1, 0);
+
} else if (wacom->pid != 0) {
/* disconnected while previously connected */
wacom->pid = 0;
wacom_schedule_work(wacom);
- wacom->battery_capacity = 0;
- wacom->bat_charging = 0;
- wacom->ps_connected = 0;
+ wacom_notify_battery(wacom, 0, 0, 0, 0);
}
return 0;
}
+static int wacom_status_irq(struct wacom_wac *wacom_wac, size_t len)
+{
+ struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
+ struct wacom_features *features = &wacom_wac->features;
+ unsigned char *data = wacom_wac->data;
+
+ if (data[0] != WACOM_REPORT_USB)
+ return 0;
+
+ if (features->type == INTUOSHT &&
+ wacom_wac->shared->touch_input &&
+ features->touch_max) {
+ input_report_switch(wacom_wac->shared->touch_input,
+ SW_MUTE_DEVICE, data[8] & 0x40);
+ input_sync(wacom_wac->shared->touch_input);
+ }
+
+ if (data[9] & 0x02) { /* wireless module is attached */
+ int battery = (data[8] & 0x3f) * 100 / 31;
+ bool charging = !!(data[8] & 0x80);
+
+ wacom_notify_battery(wacom_wac, battery, charging,
+ battery || charging, 1);
+
+ if (!wacom->battery.dev &&
+ !(features->quirks & WACOM_QUIRK_BATTERY)) {
+ features->quirks |= WACOM_QUIRK_BATTERY;
+ INIT_WORK(&wacom->work, wacom_battery_work);
+ wacom_schedule_work(wacom_wac);
+ }
+ }
+ else if ((features->quirks & WACOM_QUIRK_BATTERY) &&
+ wacom->battery.dev) {
+ features->quirks &= ~WACOM_QUIRK_BATTERY;
+ INIT_WORK(&wacom->work, wacom_battery_work);
+ wacom_schedule_work(wacom_wac);
+ wacom_notify_battery(wacom_wac, 0, 0, 0, 0);
+ }
+ return 0;
+}
+
void wacom_wac_irq(struct wacom_wac *wacom_wac, size_t len)
{
bool sync;
@@ -1949,6 +2077,8 @@ void wacom_wac_irq(struct wacom_wac *wacom_wac, size_t len)
case INTUOSPL:
if (len == WACOM_PKGLEN_BBTOUCH3)
sync = wacom_bpt3_touch(wacom_wac);
+ else if (wacom_wac->data[0] == WACOM_REPORT_USB)
+ sync = wacom_status_irq(wacom_wac, len);
else
sync = wacom_intuos_irq(wacom_wac);
break;
@@ -1964,7 +2094,14 @@ void wacom_wac_irq(struct wacom_wac *wacom_wac, size_t len)
case BAMBOO_PT:
case INTUOSHT:
- sync = wacom_bpt_irq(wacom_wac, len);
+ if (wacom_wac->data[0] == WACOM_REPORT_USB)
+ sync = wacom_status_irq(wacom_wac, len);
+ else
+ sync = wacom_bpt_irq(wacom_wac, len);
+ break;
+
+ case BAMBOO_PAD:
+ sync = wacom_bamboo_pad_irq(wacom_wac, len);
break;
case WIRELESS:
@@ -2036,12 +2173,6 @@ void wacom_setup_device_quirks(struct wacom_features *features)
features->y_max = 1023;
}
- /* these device have multiple inputs */
- if (features->type >= WIRELESS ||
- (features->type >= INTUOS5S && features->type <= INTUOSHT) ||
- (features->oVid && features->oPid))
- features->quirks |= WACOM_QUIRK_MULTI_INPUT;
-
/* quirk for bamboo touch with 2 low res touches */
if (features->type == BAMBOO_PT &&
features->pktlen == WACOM_PKGLEN_BBTOUCH) {
@@ -2305,6 +2436,13 @@ int wacom_setup_pentouch_input_capabilities(struct input_dev *input_dev,
0, 0);
}
break;
+ case BAMBOO_PAD:
+ __clear_bit(ABS_MISC, input_dev->absbit);
+ input_mt_init_slots(input_dev, features->touch_max,
+ INPUT_MT_POINTER);
+ __set_bit(BTN_LEFT, input_dev->keybit);
+ __set_bit(BTN_RIGHT, input_dev->keybit);
+ break;
}
return 0;
}
@@ -2754,6 +2892,15 @@ static const struct wacom_features wacom_features_0x304 =
{ "Wacom Cintiq 13HD", 59152, 33448, 1023, 63,
WACOM_13HD, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES,
WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET };
+static const struct wacom_features wacom_features_0x333 =
+ { "Wacom Cintiq 13HD touch", 59152, 33448, 2047, 63,
+ WACOM_13HD, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES,
+ WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET,
+ .oVid = USB_VENDOR_ID_WACOM, .oPid = 0x335 };
+static const struct wacom_features wacom_features_0x335 =
+ { "Wacom Cintiq 13HD touch", .type = WACOM_24HDT, /* Touch */
+ .oVid = USB_VENDOR_ID_WACOM, .oPid = 0x333, .touch_max = 10,
+ .check_for_hid_type = true, .hid_type = HID_TYPE_USBNONE };
static const struct wacom_features wacom_features_0xC7 =
{ "Wacom DTU1931", 37832, 30305, 511, 0,
PL, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
@@ -2958,6 +3105,12 @@ static const struct wacom_features wacom_features_0x30C =
{ "Wacom ISDv5 30C", .type = WACOM_24HDT, /* Touch */
.oVid = USB_VENDOR_ID_WACOM, .oPid = 0x30A, .touch_max = 10,
.check_for_hid_type = true, .hid_type = HID_TYPE_USBNONE };
+static const struct wacom_features wacom_features_0x318 =
+ { "Wacom USB Bamboo PAD", 4095, 4095, /* Touch */
+ .type = BAMBOO_PAD, 35, 48, .touch_max = 4 };
+static const struct wacom_features wacom_features_0x319 =
+ { "Wacom Wireless Bamboo PAD", 4095, 4095, /* Touch */
+ .type = BAMBOO_PAD, 35, 48, .touch_max = 4 };
static const struct wacom_features wacom_features_0x323 =
{ "Wacom Intuos P M", 21600, 13500, 1023, 31,
INTUOSHT, WACOM_INTUOS_RES, WACOM_INTUOS_RES,
@@ -2974,6 +3127,10 @@ static const struct wacom_features wacom_features_HID_ANY_ID =
HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_WACOM, USB_VENDOR_ID_WACOM, prod),\
.driver_data = (kernel_ulong_t)&wacom_features_##prod
+#define I2C_DEVICE_WACOM(prod) \
+ HID_DEVICE(BUS_I2C, HID_GROUP_WACOM, USB_VENDOR_ID_WACOM, prod),\
+ .driver_data = (kernel_ulong_t)&wacom_features_##prod
+
#define USB_DEVICE_LENOVO(prod) \
HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, prod), \
.driver_data = (kernel_ulong_t)&wacom_features_##prod
@@ -3106,11 +3263,15 @@ const struct hid_device_id wacom_ids[] = {
{ USB_DEVICE_WACOM(0x314) },
{ USB_DEVICE_WACOM(0x315) },
{ USB_DEVICE_WACOM(0x317) },
+ { USB_DEVICE_WACOM(0x318) },
+ { USB_DEVICE_WACOM(0x319) },
{ USB_DEVICE_WACOM(0x323) },
{ USB_DEVICE_WACOM(0x32A) },
{ USB_DEVICE_WACOM(0x32B) },
{ USB_DEVICE_WACOM(0x32C) },
{ USB_DEVICE_WACOM(0x32F) },
+ { USB_DEVICE_WACOM(0x333) },
+ { USB_DEVICE_WACOM(0x335) },
{ USB_DEVICE_WACOM(0x4001) },
{ USB_DEVICE_WACOM(0x4004) },
{ USB_DEVICE_WACOM(0x5000) },
@@ -3118,6 +3279,7 @@ const struct hid_device_id wacom_ids[] = {
{ USB_DEVICE_LENOVO(0x6004) },
{ USB_DEVICE_WACOM(HID_ANY_ID) },
+ { I2C_DEVICE_WACOM(HID_ANY_ID) },
{ }
};
MODULE_DEVICE_TABLE(hid, wacom_ids);
diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h
index 021ee1c1980a..4700ac994a3b 100644
--- a/drivers/hid/wacom_wac.h
+++ b/drivers/hid/wacom_wac.h
@@ -33,6 +33,8 @@
#define WACOM_PKGLEN_MTTPC 40
#define WACOM_PKGLEN_DTUS 68
#define WACOM_PKGLEN_PENABLED 8
+#define WACOM_PKGLEN_BPAD_TOUCH 32
+#define WACOM_PKGLEN_BPAD_TOUCH_USB 64
/* wacom data size per MT contact */
#define WACOM_BYTES_PER_MT_PACKET 11
@@ -67,13 +69,14 @@
#define WACOM_REPORT_24HDT 1
#define WACOM_REPORT_WL 128
#define WACOM_REPORT_USB 192
+#define WACOM_REPORT_BPAD_PEN 3
+#define WACOM_REPORT_BPAD_TOUCH 16
/* device quirks */
-#define WACOM_QUIRK_MULTI_INPUT 0x0001
-#define WACOM_QUIRK_BBTOUCH_LOWRES 0x0002
-#define WACOM_QUIRK_NO_INPUT 0x0004
-#define WACOM_QUIRK_MONITOR 0x0008
-#define WACOM_QUIRK_BATTERY 0x0010
+#define WACOM_QUIRK_BBTOUCH_LOWRES 0x0001
+#define WACOM_QUIRK_NO_INPUT 0x0002
+#define WACOM_QUIRK_MONITOR 0x0004
+#define WACOM_QUIRK_BATTERY 0x0008
#define WACOM_PEN_FIELD(f) (((f)->logical == HID_DG_STYLUS) || \
((f)->physical == HID_DG_STYLUS) || \
@@ -122,6 +125,7 @@ enum {
BAMBOO_PT,
WACOM_24HDT,
WACOM_27QHDT,
+ BAMBOO_PAD,
TABLETPC, /* add new TPC below */
TABLETPCE,
TABLETPC2FG,
@@ -169,6 +173,8 @@ struct wacom_shared {
unsigned touch_max;
int type;
struct input_dev *touch_input;
+ struct hid_device *pen;
+ struct hid_device *touch;
};
struct hid_data {
@@ -205,6 +211,7 @@ struct wacom_wac {
int battery_capacity;
int num_contacts_left;
int bat_charging;
+ int bat_connected;
int ps_connected;
u8 bt_features;
u8 bt_high_speed;