diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2015-04-14 19:25:26 +0300 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2015-04-14 19:25:26 +0300 |
commit | 8de29a35dc840a05e451ad035bcb06e21ccf605f (patch) | |
tree | f887a98818ef7dd56c0c64c95039377931dcc903 | |
parent | 31f7dc796998d2967e999a0f9229d8a50c7b348d (diff) | |
parent | 2e455c27bddbf8cf6d1039daea40de8e6865c453 (diff) | |
download | linux-8de29a35dc840a05e451ad035bcb06e21ccf605f.tar.xz |
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid
Pull HID updates from Jiri Kosina:
- quite a few firmware fixes for RMI driver by Andrew Duggan
- huion and uclogic drivers have been substantially overlaping in
functionality laterly. This redundancy is fixed by hid-huion driver
being merged into hid-uclogic; work done by Benjamin Tissoires and
Nikolai Kondrashov
- i2c-hid now supports ACPI GPIO interrupts; patch from Mika Westerberg
- Some of the quirks, that got separated into individual drivers, have
historically had EXPERT dependency. As HID subsystem matured (as
well as the individual drivers), this made less and less sense. This
dependency is now being removed by patch from Jean Delvare
- Logitech lg4ff driver received a couple of improvements for mode
switching, by Michal Malý
- multitouch driver now supports clickpads, patches by Benjamin
Tissoires and Seth Forshee
- hid-sensor framework received a substantial update; namely support
for Custom and Generic pages is being added; work done by Srinivas
Pandruvada
- wacom driver received substantial update; it now supports
i2c-conntected devices (Mika Westerberg), Bamboo PADs are now
properly supported (Benjamin Tissoires), much improved battery
reporting (Jason Gerecke) and pen proximity cleanups (Ping Cheng)
- small assorted fixes and device ID additions
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid: (68 commits)
HID: sensor: Update document for custom sensor
HID: sensor: Custom and Generic sensor support
HID: debug: fix error handling in hid_debug_events_read()
Input - mt: Fix input_mt_get_slot_by_key
HID: logitech-hidpp: fix error return code
HID: wacom: Add support for Cintiq 13HD Touch
HID: logitech-hidpp: add a module parameter to keep firmware gestures
HID: usbhid: yet another mouse with ALWAYS_POLL
HID: usbhid: more mice with ALWAYS_POLL
HID: wacom: set stylus_in_proximity before checking touch_down
HID: wacom: use wacom_wac_finger_count_touches to set touch_down
HID: wacom: remove hardcoded WACOM_QUIRK_MULTI_INPUT
HID: pidff: effect can't be NULL
HID: add quirk for PIXART OEM mouse used by HP
HID: add HP OEM mouse to quirk ALWAYS_POLL
HID: wacom: ask for a in-prox report when it was missed
HID: hid-sensor-hub: Fix sparse warning
HID: hid-sensor-hub: fix attribute read for logical usage id
HID: plantronics: fix Kconfig default
HID: pidff: support more than one concurrent effect
...
47 files changed, 2980 insertions, 706 deletions
diff --git a/Documentation/ABI/testing/sysfs-driver-hid b/Documentation/ABI/testing/sysfs-driver-hid index b6490e14fe83..48942cacb0bf 100644 --- a/Documentation/ABI/testing/sysfs-driver-hid +++ b/Documentation/ABI/testing/sysfs-driver-hid @@ -8,3 +8,13 @@ Description: When read, this file returns the device's raw binary HID report descriptor. This file cannot be written. Users: HIDAPI library (http://www.signal11.us/oss/hidapi) + +What: For USB devices : /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/country + For BT devices : /sys/class/bluetooth/hci<addr>/<hid-bus>:<vendor-id>:<product-id>.<num>/country + Symlink : /sys/class/hidraw/hidraw<num>/device/country +Date: February 2015 +KernelVersion: 3.19 +Contact: Olivier Gay <ogay@logitech.com> +Description: When read, this file returns the hex integer value in ASCII + of the device's HID country code (e.g. 21 for US). + This file cannot be written. diff --git a/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff b/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff index 167d9032b970..b3f6a2ac5007 100644 --- a/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff +++ b/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff @@ -5,3 +5,48 @@ Contact: Michal Malý <madcatxster@gmail.com> Description: Display minimum, maximum and current range of the steering wheel. Writing a value within min and max boundaries sets the range of the wheel. + +What: /sys/bus/hid/drivers/logitech/<dev>/alternate_modes +Date: Feb 2015 +KernelVersion: 4.1 +Contact: Michal Malý <madcatxster@gmail.com> +Description: Displays a set of alternate modes supported by a wheel. Each + mode is listed as follows: + Tag: Mode Name + Currently active mode is marked with an asterisk. List also + contains an abstract item "native" which always denotes the + native mode of the wheel. Echoing the mode tag switches the + wheel into the corresponding mode. Depending on the exact model + of the wheel not all listed modes might always be selectable. + If a wheel cannot be switched into the desired mode, -EINVAL + is returned accompanied with an explanatory message in the + kernel log. + This entry is not created for devices that have only one mode. + + Currently supported mode switches: + Driving Force Pro: + DF-EX --> DFP + + G25: + DF-EX --> DFP --> G25 + + G27: + DF-EX <*> DFP <-> G25 <-> G27 + DF-EX <*--------> G25 <-> G27 + DF-EX <*----------------> G27 + + DFGT: + DF-EX <*> DFP <-> DFGT + DF-EX <*--------> DFGT + + * hid_logitech module must be loaded with lg4ff_no_autoswitch=1 + parameter set in order for the switch to DF-EX mode to work. + +What: /sys/bus/hid/drivers/logitech/<dev>/real_id +Date: Feb 2015 +KernelVersion: 4.1 +Contact: Michal Malý <madcatxster@gmail.com> +Description: Displays the real model of the wheel regardless of any + alternate mode the wheel might be switched to. + It is a read-only value. + This entry is not created for devices that have only one mode. diff --git a/Documentation/hid/hid-sensor.txt b/Documentation/hid/hid-sensor.txt index 948b0989c433..b287752a31cd 100644 --- a/Documentation/hid/hid-sensor.txt +++ b/Documentation/hid/hid-sensor.txt @@ -138,3 +138,87 @@ accelerometer wants to poll X axis value, then it can call this function with the usage id of X axis. HID sensors can provide events, so this is not necessary to poll for any field. If there is some new sample, the core driver will call registered callback function to process the sample. + + +---------- + +HID Custom and generic Sensors + +HID Sensor specification defines two special sensor usage types. Since they +don't represent a standard sensor, it is not possible to define using Linux IIO +type interfaces. +The purpose of these sensors is to extend the functionality or provide a +way to obfuscate the data being communicated by a sensor. Without knowing the +mapping between the data and its encapsulated form, it is difficult for +an application/driver to determine what data is being communicated by the sensor. +This allows some differentiating use cases, where vendor can provide applications. +Some common use cases are debug other sensors or to provide some events like +keyboard attached/detached or lid open/close. + +To allow application to utilize these sensors, here they are exported uses sysfs +attribute groups, attributes and misc device interface. + +An example of this representation on sysfs: +/sys/devices/pci0000:00/INT33C2:00/i2c-0/i2c-INT33D1:00/0018:8086:09FA.0001/HID-SENSOR-2000e1.6.auto$ tree -R +. +????????? enable_sensor +????????? feature-0-200316 +??????? ????????? feature-0-200316-maximum +??????? ????????? feature-0-200316-minimum +??????? ????????? feature-0-200316-name +??????? ????????? feature-0-200316-size +??????? ????????? feature-0-200316-unit-expo +??????? ????????? feature-0-200316-units +??????? ????????? feature-0-200316-value +????????? feature-1-200201 +??????? ????????? feature-1-200201-maximum +??????? ????????? feature-1-200201-minimum +??????? ????????? feature-1-200201-name +??????? ????????? feature-1-200201-size +??????? ????????? feature-1-200201-unit-expo +??????? ????????? feature-1-200201-units +??????? ????????? feature-1-200201-value +????????? input-0-200201 +??????? ????????? input-0-200201-maximum +??????? ????????? input-0-200201-minimum +??????? ????????? input-0-200201-name +??????? ????????? input-0-200201-size +??????? ????????? input-0-200201-unit-expo +??????? ????????? input-0-200201-units +??????? ????????? input-0-200201-value +????????? input-1-200202 +??????? ????????? input-1-200202-maximum +??????? ????????? input-1-200202-minimum +??????? ????????? input-1-200202-name +??????? ????????? input-1-200202-size +??????? ????????? input-1-200202-unit-expo +??????? ????????? input-1-200202-units +??????? ????????? input-1-200202-value + +Here there is a custom sensors with four fields, two feature and two inputs. +Each field is represented by a set of attributes. All fields except the "value" +are read only. The value field is a RW field. +Example +/sys/bus/platform/devices/HID-SENSOR-2000e1.6.auto/feature-0-200316$ grep -r . * +feature-0-200316-maximum:6 +feature-0-200316-minimum:0 +feature-0-200316-name:property-reporting-state +feature-0-200316-size:1 +feature-0-200316-unit-expo:0 +feature-0-200316-units:25 +feature-0-200316-value:1 + +How to enable such sensor? +By default sensor can be power gated. To enable sysfs attribute "enable" can be +used. +$ echo 1 > enable_sensor + +Once enabled and powered on, sensor can report value using HID reports. +These reports are pushed using misc device interface in a FIFO order. +/dev$ tree | grep HID-SENSOR-2000e1.6.auto +??????? ????????? 10:53 -> ../HID-SENSOR-2000e1.6.auto +????????? HID-SENSOR-2000e1.6.auto + +Each reports can be of variable length preceded by a header. This header +consist of a 32 bit usage id, 64 bit time stamp and 32 bit length field of raw +data. diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 152b006833cd..15338afdf7f9 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. @@ -885,6 +878,21 @@ config HID_SENSOR_HUB for events and handle data streams. Each sensor driver can format data and present to user mode using input or IIO interface. +config HID_SENSOR_CUSTOM_SENSOR + tristate "HID Sensors hub custom sensor support" + depends on HID_SENSOR_HUB + default n + ---help--- + HID Sensor hub specification allows definition of some custom and + generic sensors. Unlike other HID sensors, they can't be exported + via Linux IIO because of custom fields. This is up to the manufacturer + to decide how to interpret these special sensor ids and process in + the user space. Currently some manufacturers are using these ids for + sensor calibration and debugging other sensors. Manufacturers + should't use these special custom sensor ids to export any of the + standard sensors. + Select this config option for custom/generic sensor support. + endmenu endif # HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 6f19958dfc38..e4a21dfd7ef3 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 @@ -101,6 +100,7 @@ obj-$(CONFIG_HID_WACOM) += wacom.o obj-$(CONFIG_HID_WALTOP) += hid-waltop.o obj-$(CONFIG_HID_WIIMOTE) += hid-wiimote.o obj-$(CONFIG_HID_SENSOR_HUB) += hid-sensor-hub.o +obj-$(CONFIG_HID_SENSOR_CUSTOM_SENSOR) += hid-sensor-custom.o obj-$(CONFIG_USB_HID) += usbhid/ obj-$(CONFIG_USB_MOUSE) += usbhid/ diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 56ce8c2b5530..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) diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c index 8bf61d295ffd..c785095f9f2c 100644 --- a/drivers/hid/hid-debug.c +++ b/drivers/hid/hid-debug.c @@ -165,6 +165,7 @@ static const struct hid_usage_entry hid_usage_table[] = { {0, 0x53, "DeviceIndex"}, {0, 0x54, "ContactCount"}, {0, 0x55, "ContactMaximumNumber"}, + {0, 0x59, "ButtonType"}, {0, 0x5A, "SecondaryBarrelSwitch"}, {0, 0x5B, "TransducerSerialNumber"}, { 15, 0, "PhysicalInterfaceDevice" }, @@ -1127,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 9c4786759f16..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 @@ -591,6 +597,9 @@ #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 @@ -1022,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 32c2da49bd5b..008e89bf6f3c 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -720,6 +720,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 f65e78b46999..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> @@ -72,6 +71,8 @@ MODULE_LICENSE("GPL"); #define MT_INPUTMODE_TOUCHSCREEN 0x02 #define MT_INPUTMODE_TOUCHPAD 0x03 +#define MT_BUTTONTYPE_CLICKPAD 0 + struct mt_slot { __s32 x, y, cx, cy, p, w, h; __s32 contactid; /* the device ContactID assigned to this slot */ @@ -116,6 +117,8 @@ struct mt_device { __u8 touches_by_report; /* how many touches are present in one report: * 1 means we should use a serial protocol * > 1 means hybrid (multitouch) protocol */ + __u8 buttons_count; /* number of physical buttons per touchpad */ + bool is_buttonpad; /* is this device a button pad? */ bool serial_maybe; /* need to check for serial protocol */ bool curvalid; /* is the current contact valid? */ unsigned mt_flags; /* flags to pass to input-mt */ @@ -334,6 +337,16 @@ static void mt_feature_mapping(struct hid_device *hdev, td->maxcontacts = td->mtclass.maxcontacts; break; + case HID_DG_BUTTONTYPE: + if (usage->usage_index >= field->report_count) { + dev_err(&hdev->dev, "HID_DG_BUTTONTYPE out of range\n"); + break; + } + + if (field->value[usage->usage_index] == MT_BUTTONTYPE_CLICKPAD) + td->is_buttonpad = true; + + break; } } @@ -379,6 +392,10 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi, td->inputmode_value = MT_INPUTMODE_TOUCHPAD; } + /* count the buttons on touchpads */ + if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) + td->buttons_count++; + if (usage->usage_index) prev_usage = &field->usage[usage->usage_index - 1]; @@ -728,6 +745,13 @@ static void mt_touch_input_configured(struct hid_device *hdev, if (cls->quirks & MT_QUIRK_NOT_SEEN_MEANS_UP) td->mt_flags |= INPUT_MT_DROP_UNUSED; + /* check for clickpads */ + if ((td->mt_flags & INPUT_MT_POINTER) && (td->buttons_count == 1)) + td->is_buttonpad = true; + + if (td->is_buttonpad) + __set_bit(INPUT_PROP_BUTTONPAD, input->propbit); + input_mt_init_slots(input, td->maxcontacts, td->mt_flags); td->mt_flags = 0; 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-sensor-custom.c b/drivers/hid/hid-sensor-custom.c new file mode 100644 index 000000000000..5614fee82347 --- /dev/null +++ b/drivers/hid/hid-sensor-custom.c @@ -0,0 +1,849 @@ +/* + * hid-sensor-custom.c + * Copyright (c) 2015, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/miscdevice.h> +#include <linux/kfifo.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/poll.h> +#include <linux/bsearch.h> +#include <linux/platform_device.h> +#include <linux/hid-sensor-hub.h> + +#define HID_CUSTOM_NAME_LENGTH 64 +#define HID_CUSTOM_MAX_CORE_ATTRS 10 +#define HID_CUSTOM_TOTAL_ATTRS (HID_CUSTOM_MAX_CORE_ATTRS + 1) +#define HID_CUSTOM_FIFO_SIZE 4096 +#define HID_CUSTOM_MAX_FEATURE_BYTES 64 + +struct hid_sensor_custom_field { + int report_id; + char group_name[HID_CUSTOM_NAME_LENGTH]; + struct hid_sensor_hub_attribute_info attribute; + struct device_attribute sd_attrs[HID_CUSTOM_MAX_CORE_ATTRS]; + char attr_name[HID_CUSTOM_TOTAL_ATTRS][HID_CUSTOM_NAME_LENGTH]; + struct attribute *attrs[HID_CUSTOM_TOTAL_ATTRS]; + struct attribute_group hid_custom_attribute_group; +}; + +struct hid_sensor_custom { + struct mutex mutex; + struct platform_device *pdev; + struct hid_sensor_hub_device *hsdev; + struct hid_sensor_hub_callbacks callbacks; + int sensor_field_count; + struct hid_sensor_custom_field *fields; + int input_field_count; + int input_report_size; + int input_report_recd_size; + bool input_skip_sample; + bool enable; + struct hid_sensor_custom_field *power_state; + struct hid_sensor_custom_field *report_state; + struct miscdevice custom_dev; + struct kfifo data_fifo; + unsigned long misc_opened; + wait_queue_head_t wait; +}; + +/* Header for each sample to user space via dev interface */ +struct hid_sensor_sample { + u32 usage_id; + u64 timestamp; + u32 raw_len; +} __packed; + +static struct attribute hid_custom_attrs[] = { + {.name = "name", .mode = S_IRUGO}, + {.name = "units", .mode = S_IRUGO}, + {.name = "unit-expo", .mode = S_IRUGO}, + {.name = "minimum", .mode = S_IRUGO}, + {.name = "maximum", .mode = S_IRUGO}, + {.name = "size", .mode = S_IRUGO}, + {.name = "value", .mode = S_IWUSR | S_IRUGO}, + {.name = NULL} +}; + +static const struct hid_custom_usage_desc { + int usage_id; + char *desc; +} hid_custom_usage_desc_table[] = { + {0x200201, "event-sensor-state"}, + {0x200202, "event-sensor-event"}, + {0x200301, "property-friendly-name"}, + {0x200302, "property-persistent-unique-id"}, + {0x200303, "property-sensor-status"}, + {0x200304, "property-min-report-interval"}, + {0x200305, "property-sensor-manufacturer"}, + {0x200306, "property-sensor-model"}, + {0x200307, "property-sensor-serial-number"}, + {0x200308, "property-sensor-description"}, + {0x200309, "property-sensor-connection-type"}, + {0x20030A, "property-sensor-device-path"}, + {0x20030B, "property-hardware-revision"}, + {0x20030C, "property-firmware-version"}, + {0x20030D, "property-release-date"}, + {0x20030E, "property-report-interval"}, + {0x20030F, "property-change-sensitivity-absolute"}, + {0x200310, "property-change-sensitivity-percent-range"}, + {0x200311, "property-change-sensitivity-percent-relative"}, + {0x200312, "property-accuracy"}, + {0x200313, "property-resolution"}, + {0x200314, "property-maximum"}, + {0x200315, "property-minimum"}, + {0x200316, "property-reporting-state"}, + {0x200317, "property-sampling-rate"}, + {0x200318, "property-response-curve"}, + {0x200319, "property-power-state"}, + {0x200540, "data-field-custom"}, + {0x200541, "data-field-custom-usage"}, + {0x200542, "data-field-custom-boolean-array"}, + {0x200543, "data-field-custom-value"}, + {0x200544, "data-field-custom-value_1"}, + {0x200545, "data-field-custom-value_2"}, + {0x200546, "data-field-custom-value_3"}, + {0x200547, "data-field-custom-value_4"}, + {0x200548, "data-field-custom-value_5"}, + {0x200549, "data-field-custom-value_6"}, + {0x20054A, "data-field-custom-value_7"}, + {0x20054B, "data-field-custom-value_8"}, + {0x20054C, "data-field-custom-value_9"}, + {0x20054D, "data-field-custom-value_10"}, + {0x20054E, "data-field-custom-value_11"}, + {0x20054F, "data-field-custom-value_12"}, + {0x200550, "data-field-custom-value_13"}, + {0x200551, "data-field-custom-value_14"}, + {0x200552, "data-field-custom-value_15"}, + {0x200553, "data-field-custom-value_16"}, + {0x200554, "data-field-custom-value_17"}, + {0x200555, "data-field-custom-value_18"}, + {0x200556, "data-field-custom-value_19"}, + {0x200557, "data-field-custom-value_20"}, + {0x200558, "data-field-custom-value_21"}, + {0x200559, "data-field-custom-value_22"}, + {0x20055A, "data-field-custom-value_23"}, + {0x20055B, "data-field-custom-value_24"}, + {0x20055C, "data-field-custom-value_25"}, + {0x20055D, "data-field-custom-value_26"}, + {0x20055E, "data-field-custom-value_27"}, + {0x20055F, "data-field-custom-value_28"}, +}; + +static int usage_id_cmp(const void *p1, const void *p2) +{ + if (*(int *)p1 < *(int *)p2) + return -1; + + if (*(int *)p1 > *(int *)p2) + return 1; + + return 0; +} + +static ssize_t enable_sensor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct hid_sensor_custom *sensor_inst = platform_get_drvdata(pdev); + + return sprintf(buf, "%d\n", sensor_inst->enable); +} + +static int set_power_report_state(struct hid_sensor_custom *sensor_inst, + bool state) +{ + int power_val = -1; + int report_val = -1; + u32 power_state_usage_id; + u32 report_state_usage_id; + int ret; + + /* + * It is possible that the power/report state ids are not present. + * In this case this function will return success. But if the + * ids are present, then it will return error if set fails. + */ + if (state) { + power_state_usage_id = + HID_USAGE_SENSOR_PROP_POWER_STATE_D0_FULL_POWER_ENUM; + report_state_usage_id = + HID_USAGE_SENSOR_PROP_REPORTING_STATE_ALL_EVENTS_ENUM; + } else { + power_state_usage_id = + HID_USAGE_SENSOR_PROP_POWER_STATE_D4_POWER_OFF_ENUM; + report_state_usage_id = + HID_USAGE_SENSOR_PROP_REPORTING_STATE_NO_EVENTS_ENUM; + } + + if (sensor_inst->power_state) + power_val = hid_sensor_get_usage_index(sensor_inst->hsdev, + sensor_inst->power_state->attribute.report_id, + sensor_inst->power_state->attribute.index, + power_state_usage_id); + if (sensor_inst->report_state) + report_val = hid_sensor_get_usage_index(sensor_inst->hsdev, + sensor_inst->report_state->attribute.report_id, + sensor_inst->report_state->attribute.index, + report_state_usage_id); + + if (power_val >= 0) { + power_val += + sensor_inst->power_state->attribute.logical_minimum; + ret = sensor_hub_set_feature(sensor_inst->hsdev, + sensor_inst->power_state->attribute.report_id, + sensor_inst->power_state->attribute.index, + sizeof(power_val), + &power_val); + if (ret) { + hid_err(sensor_inst->hsdev->hdev, + "Set power state failed\n"); + return ret; + } + } + + if (report_val >= 0) { + report_val += + sensor_inst->report_state->attribute.logical_minimum; + ret = sensor_hub_set_feature(sensor_inst->hsdev, + sensor_inst->report_state->attribute.report_id, + sensor_inst->report_state->attribute.index, + sizeof(report_val), + &report_val); + if (ret) { + hid_err(sensor_inst->hsdev->hdev, + "Set report state failed\n"); + return ret; + } + } + + return 0; +} + +static ssize_t enable_sensor_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct hid_sensor_custom *sensor_inst = platform_get_drvdata(pdev); + int value; + int ret = -EINVAL; + + if (kstrtoint(buf, 0, &value) != 0) + return -EINVAL; + + mutex_lock(&sensor_inst->mutex); + if (value && !sensor_inst->enable) { + ret = sensor_hub_device_open(sensor_inst->hsdev); + if (ret) + goto unlock_state; + + ret = set_power_report_state(sensor_inst, true); + if (ret) { + sensor_hub_device_close(sensor_inst->hsdev); + goto unlock_state; + } + sensor_inst->enable = true; + } else if (!value && sensor_inst->enable) { + ret = set_power_report_state(sensor_inst, false); + sensor_hub_device_close(sensor_inst->hsdev); + sensor_inst->enable = false; + } +unlock_state: + mutex_unlock(&sensor_inst->mutex); + if (ret < 0) + return ret; + + return count; +} +static DEVICE_ATTR_RW(enable_sensor); + +static struct attribute *enable_sensor_attrs[] = { + &dev_attr_enable_sensor.attr, + NULL, +}; + +static struct attribute_group enable_sensor_attr_group = { + .attrs = enable_sensor_attrs, +}; + +static ssize_t show_value(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct hid_sensor_custom *sensor_inst = platform_get_drvdata(pdev); + struct hid_sensor_hub_attribute_info *attribute; + int index, usage, field_index; + char name[HID_CUSTOM_NAME_LENGTH]; + bool feature = false; + bool input = false; + int value = 0; + + if (sscanf(attr->attr.name, "feature-%d-%x-%s", &index, &usage, + name) == 3) { + feature = true; + field_index = index + sensor_inst->input_field_count; + } else if (sscanf(attr->attr.name, "input-%d-%x-%s", &index, &usage, + name) == 3) { + input = true; + field_index = index; + } else + return -EINVAL; + + if (!strncmp(name, "value", strlen("value"))) { + u32 report_id; + int ret; + + attribute = &sensor_inst->fields[field_index].attribute; + report_id = attribute->report_id; + if (feature) { + u8 values[HID_CUSTOM_MAX_FEATURE_BYTES]; + int len = 0; + u64 value = 0; + int i = 0; + + ret = sensor_hub_get_feature(sensor_inst->hsdev, + report_id, + index, + sizeof(values), values); + if (ret < 0) + return ret; + + while (i < ret) { + if (i + attribute->size > ret) { + len += snprintf(&buf[len], + PAGE_SIZE - len, + "%d ", values[i]); + break; + } + switch (attribute->size) { + case 2: + value = (u64) *(u16 *)&values[i]; + i += attribute->size; + break; + case 4: + value = (u64) *(u32 *)&values[i]; + i += attribute->size; + break; + case 8: + value = *(u64 *)&values[i]; + i += attribute->size; + break; + default: + value = (u64) values[i]; + ++i; + break; + } + len += snprintf(&buf[len], PAGE_SIZE - len, + "%lld ", value); + } + len += snprintf(&buf[len], PAGE_SIZE - len, "\n"); + + return len; + } else if (input) + value = sensor_hub_input_attr_get_raw_value( + sensor_inst->hsdev, + sensor_inst->hsdev->usage, + usage, report_id, + SENSOR_HUB_SYNC); + } else if (!strncmp(name, "units", strlen("units"))) + value = sensor_inst->fields[field_index].attribute.units; + else if (!strncmp(name, "unit-expo", strlen("unit-expo"))) + value = sensor_inst->fields[field_index].attribute.unit_expo; + else if (!strncmp(name, "size", strlen("size"))) + value = sensor_inst->fields[field_index].attribute.size; + else if (!strncmp(name, "minimum", strlen("minimum"))) + value = sensor_inst->fields[field_index].attribute. + logical_minimum; + else if (!strncmp(name, "maximum", strlen("maximum"))) + value = sensor_inst->fields[field_index].attribute. + logical_maximum; + else if (!strncmp(name, "name", strlen("name"))) { + struct hid_custom_usage_desc *usage_desc; + + usage_desc = bsearch(&usage, hid_custom_usage_desc_table, + ARRAY_SIZE(hid_custom_usage_desc_table), + sizeof(struct hid_custom_usage_desc), + usage_id_cmp); + if (usage_desc) + return snprintf(buf, PAGE_SIZE, "%s\n", + usage_desc->desc); + else + return sprintf(buf, "not-specified\n"); + } else + return -EINVAL; + + return sprintf(buf, "%d\n", value); +} + +static ssize_t store_value(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct hid_sensor_custom *sensor_inst = platform_get_drvdata(pdev); + int index, field_index, usage; + char name[HID_CUSTOM_NAME_LENGTH]; + int value; + + if (sscanf(attr->attr.name, "feature-%d-%x-%s", &index, &usage, + name) == 3) { + field_index = index + sensor_inst->input_field_count; + } else + return -EINVAL; + + if (!strncmp(name, "value", strlen("value"))) { + u32 report_id; + int ret; + + if (kstrtoint(buf, 0, &value) != 0) + return -EINVAL; + + report_id = sensor_inst->fields[field_index].attribute. + report_id; + ret = sensor_hub_set_feature(sensor_inst->hsdev, report_id, + index, sizeof(value), &value); + } else + return -EINVAL; + + return count; +} + +static int hid_sensor_capture_sample(struct hid_sensor_hub_device *hsdev, + unsigned usage_id, size_t raw_len, + char *raw_data, void *priv) +{ + struct hid_sensor_custom *sensor_inst = platform_get_drvdata(priv); + struct hid_sensor_sample header; + + /* If any error occurs in a sample, rest of the fields are ignored */ + if (sensor_inst->input_skip_sample) { + hid_err(sensor_inst->hsdev->hdev, "Skipped remaining data\n"); + return 0; + } + + hid_dbg(sensor_inst->hsdev->hdev, "%s received %d of %d\n", __func__, + (int) (sensor_inst->input_report_recd_size + raw_len), + sensor_inst->input_report_size); + + if (!test_bit(0, &sensor_inst->misc_opened)) + return 0; + + if (!sensor_inst->input_report_recd_size) { + int required_size = sizeof(struct hid_sensor_sample) + + sensor_inst->input_report_size; + header.usage_id = hsdev->usage; + header.raw_len = sensor_inst->input_report_size; + header.timestamp = ktime_get_real_ns(); + if (kfifo_avail(&sensor_inst->data_fifo) >= required_size) { + kfifo_in(&sensor_inst->data_fifo, + (unsigned char *)&header, + sizeof(header)); + } else + sensor_inst->input_skip_sample = true; + } + if (kfifo_avail(&sensor_inst->data_fifo) >= raw_len) + kfifo_in(&sensor_inst->data_fifo, (unsigned char *)raw_data, + raw_len); + + sensor_inst->input_report_recd_size += raw_len; + + return 0; +} + +static int hid_sensor_send_event(struct hid_sensor_hub_device *hsdev, + unsigned usage_id, void *priv) +{ + struct hid_sensor_custom *sensor_inst = platform_get_drvdata(priv); + + if (!test_bit(0, &sensor_inst->misc_opened)) + return 0; + + sensor_inst->input_report_recd_size = 0; + sensor_inst->input_skip_sample = false; + + wake_up(&sensor_inst->wait); + + return 0; +} + +static int hid_sensor_custom_add_field(struct hid_sensor_custom *sensor_inst, + int index, int report_type, + struct hid_report *report, + struct hid_field *field) +{ + struct hid_sensor_custom_field *sensor_field; + void *fields; + + fields = krealloc(sensor_inst->fields, + (sensor_inst->sensor_field_count + 1) * + sizeof(struct hid_sensor_custom_field), GFP_KERNEL); + if (!fields) { + kfree(sensor_inst->fields); + return -ENOMEM; + } + sensor_inst->fields = fields; + sensor_field = &sensor_inst->fields[sensor_inst->sensor_field_count]; + sensor_field->attribute.usage_id = sensor_inst->hsdev->usage; + if (field->logical) + sensor_field->attribute.attrib_id = field->logical; + else + sensor_field->attribute.attrib_id = field->usage[0].hid; + + sensor_field->attribute.index = index; + sensor_field->attribute.report_id = report->id; + sensor_field->attribute.units = field->unit; + sensor_field->attribute.unit_expo = field->unit_exponent; + sensor_field->attribute.size = (field->report_size / 8); + sensor_field->attribute.logical_minimum = field->logical_minimum; + sensor_field->attribute.logical_maximum = field->logical_maximum; + + if (report_type == HID_FEATURE_REPORT) + snprintf(sensor_field->group_name, + sizeof(sensor_field->group_name), "feature-%x-%x", + sensor_field->attribute.index, + sensor_field->attribute.attrib_id); + else if (report_type == HID_INPUT_REPORT) { + snprintf(sensor_field->group_name, + sizeof(sensor_field->group_name), + "input-%x-%x", sensor_field->attribute.index, + sensor_field->attribute.attrib_id); + sensor_inst->input_field_count++; + sensor_inst->input_report_size += (field->report_size * + field->report_count) / 8; + } + + memset(&sensor_field->hid_custom_attribute_group, 0, + sizeof(struct attribute_group)); + sensor_inst->sensor_field_count++; + + return 0; +} + +static int hid_sensor_custom_add_fields(struct hid_sensor_custom *sensor_inst, + struct hid_report_enum *report_enum, + int report_type) +{ + int i; + int ret; + struct hid_report *report; + struct hid_field *field; + struct hid_sensor_hub_device *hsdev = sensor_inst->hsdev; + + list_for_each_entry(report, &report_enum->report_list, list) { + for (i = 0; i < report->maxfield; ++i) { + field = report->field[i]; + if (field->maxusage && + ((field->usage[0].collection_index >= + hsdev->start_collection_index) && + (field->usage[0].collection_index < + hsdev->end_collection_index))) { + + ret = hid_sensor_custom_add_field(sensor_inst, + i, + report_type, + report, + field); + if (ret) + return ret; + + } + } + } + + return 0; +} + +static int hid_sensor_custom_add_attributes(struct hid_sensor_custom + *sensor_inst) +{ + struct hid_sensor_hub_device *hsdev = sensor_inst->hsdev; + struct hid_device *hdev = hsdev->hdev; + int ret = -1; + int i, j; + + for (j = 0; j < HID_REPORT_TYPES; ++j) { + if (j == HID_OUTPUT_REPORT) + continue; + + ret = hid_sensor_custom_add_fields(sensor_inst, + &hdev->report_enum[j], j); + if (ret) + return ret; + + } + + /* Create sysfs attributes */ + for (i = 0; i < sensor_inst->sensor_field_count; ++i) { + j = 0; + while (j < HID_CUSTOM_TOTAL_ATTRS && + hid_custom_attrs[j].name) { + struct device_attribute *device_attr; + + device_attr = &sensor_inst->fields[i].sd_attrs[j]; + + snprintf((char *)&sensor_inst->fields[i].attr_name[j], + HID_CUSTOM_NAME_LENGTH, "%s-%s", + sensor_inst->fields[i].group_name, + hid_custom_attrs[j].name); + sysfs_attr_init(&device_attr->attr); + device_attr->attr.name = + (char *)&sensor_inst->fields[i].attr_name[j]; + device_attr->attr.mode = hid_custom_attrs[j].mode; + device_attr->show = show_value; + if (hid_custom_attrs[j].mode & S_IWUSR) + device_attr->store = store_value; + sensor_inst->fields[i].attrs[j] = &device_attr->attr; + ++j; + } + sensor_inst->fields[i].attrs[j] = NULL; + sensor_inst->fields[i].hid_custom_attribute_group.attrs = + sensor_inst->fields[i].attrs; + sensor_inst->fields[i].hid_custom_attribute_group.name = + sensor_inst->fields[i].group_name; + ret = sysfs_create_group(&sensor_inst->pdev->dev.kobj, + &sensor_inst->fields[i]. + hid_custom_attribute_group); + if (ret) + break; + + /* For power or report field store indexes */ + if (sensor_inst->fields[i].attribute.attrib_id == + HID_USAGE_SENSOR_PROY_POWER_STATE) + sensor_inst->power_state = &sensor_inst->fields[i]; + else if (sensor_inst->fields[i].attribute.attrib_id == + HID_USAGE_SENSOR_PROP_REPORT_STATE) + sensor_inst->report_state = &sensor_inst->fields[i]; + } + + return ret; +} + +static void hid_sensor_custom_remove_attributes(struct hid_sensor_custom * + sensor_inst) +{ + int i; + + for (i = 0; i < sensor_inst->sensor_field_count; ++i) + sysfs_remove_group(&sensor_inst->pdev->dev.kobj, + &sensor_inst->fields[i]. + hid_custom_attribute_group); + + kfree(sensor_inst->fields); +} + +static ssize_t hid_sensor_custom_read(struct file *file, char __user *buf, + size_t count, loff_t *f_ps) +{ + struct hid_sensor_custom *sensor_inst; + unsigned int copied; + int ret; + + sensor_inst = container_of(file->private_data, + struct hid_sensor_custom, custom_dev); + + if (count < sizeof(struct hid_sensor_sample)) + return -EINVAL; + + do { + if (kfifo_is_empty(&sensor_inst->data_fifo)) { + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + ret = wait_event_interruptible(sensor_inst->wait, + !kfifo_is_empty(&sensor_inst->data_fifo)); + if (ret) + return ret; + } + ret = kfifo_to_user(&sensor_inst->data_fifo, buf, count, + &copied); + if (ret) + return ret; + + } while (copied == 0); + + return copied; +} + +static int hid_sensor_custom_release(struct inode *inode, struct file *file) +{ + struct hid_sensor_custom *sensor_inst; + + sensor_inst = container_of(file->private_data, + struct hid_sensor_custom, custom_dev); + + clear_bit(0, &sensor_inst->misc_opened); + + return 0; +} + +static int hid_sensor_custom_open(struct inode *inode, struct file *file) +{ + struct hid_sensor_custom *sensor_inst; + + sensor_inst = container_of(file->private_data, + struct hid_sensor_custom, custom_dev); + /* We essentially have single reader and writer */ + if (test_and_set_bit(0, &sensor_inst->misc_opened)) + return -EBUSY; + + return nonseekable_open(inode, file); +} + +static unsigned int hid_sensor_custom_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct hid_sensor_custom *sensor_inst; + unsigned int mask = 0; + + sensor_inst = container_of(file->private_data, + struct hid_sensor_custom, custom_dev); + + poll_wait(file, &sensor_inst->wait, wait); + + if (!kfifo_is_empty(&sensor_inst->data_fifo)) + mask = POLLIN | POLLRDNORM; + + return mask; +} + +static const struct file_operations hid_sensor_custom_fops = { + .open = hid_sensor_custom_open, + .read = hid_sensor_custom_read, + .release = hid_sensor_custom_release, + .poll = hid_sensor_custom_poll, + .llseek = noop_llseek, +}; + +static int hid_sensor_custom_dev_if_add(struct hid_sensor_custom *sensor_inst) +{ + int ret; + + ret = kfifo_alloc(&sensor_inst->data_fifo, HID_CUSTOM_FIFO_SIZE, + GFP_KERNEL); + if (ret) + return ret; + + init_waitqueue_head(&sensor_inst->wait); + + sensor_inst->custom_dev.minor = MISC_DYNAMIC_MINOR; + sensor_inst->custom_dev.name = dev_name(&sensor_inst->pdev->dev); + sensor_inst->custom_dev.fops = &hid_sensor_custom_fops, + ret = misc_register(&sensor_inst->custom_dev); + if (ret) { + kfifo_free(&sensor_inst->data_fifo); + return ret; + } + return 0; +} + +static void hid_sensor_custom_dev_if_remove(struct hid_sensor_custom + *sensor_inst) +{ + wake_up(&sensor_inst->wait); + misc_deregister(&sensor_inst->custom_dev); + kfifo_free(&sensor_inst->data_fifo); + +} + +static int hid_sensor_custom_probe(struct platform_device *pdev) +{ + struct hid_sensor_custom *sensor_inst; + struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; + int ret; + + sensor_inst = devm_kzalloc(&pdev->dev, sizeof(*sensor_inst), + GFP_KERNEL); + if (!sensor_inst) + return -ENOMEM; + + sensor_inst->callbacks.capture_sample = hid_sensor_capture_sample; + sensor_inst->callbacks.send_event = hid_sensor_send_event; + sensor_inst->callbacks.pdev = pdev; + sensor_inst->hsdev = hsdev; + sensor_inst->pdev = pdev; + mutex_init(&sensor_inst->mutex); + platform_set_drvdata(pdev, sensor_inst); + ret = sensor_hub_register_callback(hsdev, hsdev->usage, + &sensor_inst->callbacks); + if (ret < 0) { + dev_err(&pdev->dev, "callback reg failed\n"); + return ret; + } + + ret = sysfs_create_group(&sensor_inst->pdev->dev.kobj, + &enable_sensor_attr_group); + if (ret) + goto err_remove_callback; + + ret = hid_sensor_custom_add_attributes(sensor_inst); + if (ret) + goto err_remove_group; + + ret = hid_sensor_custom_dev_if_add(sensor_inst); + if (ret) + goto err_remove_attributes; + + return 0; + +err_remove_attributes: + hid_sensor_custom_remove_attributes(sensor_inst); +err_remove_group: + sysfs_remove_group(&sensor_inst->pdev->dev.kobj, + &enable_sensor_attr_group); +err_remove_callback: + sensor_hub_remove_callback(hsdev, hsdev->usage); + + return ret; +} + +static int hid_sensor_custom_remove(struct platform_device *pdev) +{ + struct hid_sensor_custom *sensor_inst = platform_get_drvdata(pdev); + struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; + + hid_sensor_custom_dev_if_remove(sensor_inst); + hid_sensor_custom_remove_attributes(sensor_inst); + sysfs_remove_group(&sensor_inst->pdev->dev.kobj, + &enable_sensor_attr_group); + sensor_hub_remove_callback(hsdev, hsdev->usage); + + return 0; +} + +static struct platform_device_id hid_sensor_custom_ids[] = { + { + .name = "HID-SENSOR-2000e1", + }, + { + .name = "HID-SENSOR-2000e2", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, hid_sensor_custom_ids); + +static struct platform_driver hid_sensor_custom_platform_driver = { + .id_table = hid_sensor_custom_ids, + .driver = { + .name = KBUILD_MODNAME, + }, + .probe = hid_sensor_custom_probe, + .remove = hid_sensor_custom_remove, +}; +module_platform_driver(hid_sensor_custom_platform_driver); + +MODULE_DESCRIPTION("HID Sensor Custom and Generic sensor Driver"); +MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c index e54ce1097e2c..c3f6f1e311ea 100644 --- a/drivers/hid/hid-sensor-hub.c +++ b/drivers/hid/hid-sensor-hub.c @@ -29,29 +29,10 @@ #define HID_SENSOR_HUB_ENUM_QUIRK 0x01 /** - * struct sensor_hub_pending - Synchronous read pending information - * @status: Pending status true/false. - * @ready: Completion synchronization data. - * @usage_id: Usage id for physical device, E.g. Gyro usage id. - * @attr_usage_id: Usage Id of a field, E.g. X-AXIS for a gyro. - * @raw_size: Response size for a read request. - * @raw_data: Place holder for received response. - */ -struct sensor_hub_pending { - bool status; - struct completion ready; - u32 usage_id; - u32 attr_usage_id; - int raw_size; - u8 *raw_data; -}; - -/** * struct sensor_hub_data - Hold a instance data for a HID hub device * @hsdev: Stored hid instance for current hub device. * @mutex: Mutex to serialize synchronous request. * @lock: Spin lock to protect pending request structure. - * @pending: Holds information of pending sync read request. * @dyn_callback_list: Holds callback function * @dyn_callback_lock: spin lock to protect callback list * @hid_sensor_hub_client_devs: Stores all MFD cells for a hub instance. @@ -61,7 +42,6 @@ struct sensor_hub_pending { struct sensor_hub_data { struct mutex mutex; spinlock_t lock; - struct sensor_hub_pending pending; struct list_head dyn_callback_list; spinlock_t dyn_callback_lock; struct mfd_cell *hid_sensor_hub_client_devs; @@ -106,7 +86,8 @@ static int sensor_hub_get_physical_device_count(struct hid_device *hdev) for (i = 0; i < hdev->maxcollection; ++i) { struct hid_collection *collection = &hdev->collection[i]; - if (collection->type == HID_COLLECTION_PHYSICAL) + if (collection->type == HID_COLLECTION_PHYSICAL || + collection->type == HID_COLLECTION_APPLICATION) ++count; } @@ -139,7 +120,8 @@ static struct hid_sensor_hub_callbacks *sensor_hub_get_callback( spin_lock_irqsave(&pdata->dyn_callback_lock, flags); list_for_each_entry(callback, &pdata->dyn_callback_list, list) - if (callback->usage_id == usage_id && + if ((callback->usage_id == usage_id || + callback->usage_id == HID_USAGE_SENSOR_COLLECTION) && (collection_index >= callback->hsdev->start_collection_index) && (collection_index < @@ -179,7 +161,18 @@ int sensor_hub_register_callback(struct hid_sensor_hub_device *hsdev, callback->usage_callback = usage_callback; callback->usage_id = usage_id; callback->priv = NULL; - list_add_tail(&callback->list, &pdata->dyn_callback_list); + /* + * If there is a handler registered for the collection type, then + * it will handle all reports for sensors in this collection. If + * there is also an individual sensor handler registration, then + * we want to make sure that the reports are directed to collection + * handler, as this may be a fusion sensor. So add collection handlers + * to the beginning of the list, so that they are matched first. + */ + if (usage_id == HID_USAGE_SENSOR_COLLECTION) + list_add(&callback->list, &pdata->dyn_callback_list); + else + list_add_tail(&callback->list, &pdata->dyn_callback_list); spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags); return 0; @@ -208,10 +201,14 @@ int sensor_hub_remove_callback(struct hid_sensor_hub_device *hsdev, EXPORT_SYMBOL_GPL(sensor_hub_remove_callback); int sensor_hub_set_feature(struct hid_sensor_hub_device *hsdev, u32 report_id, - u32 field_index, s32 value) + u32 field_index, int buffer_size, void *buffer) { struct hid_report *report; struct sensor_hub_data *data = hid_get_drvdata(hsdev->hdev); + __s32 *buf32 = buffer; + int i = 0; + int remaining_bytes; + __s32 value; int ret = 0; mutex_lock(&data->mutex); @@ -220,7 +217,21 @@ int sensor_hub_set_feature(struct hid_sensor_hub_device *hsdev, u32 report_id, ret = -EINVAL; goto done_proc; } - hid_set_field(report->field[field_index], 0, value); + + remaining_bytes = do_div(buffer_size, sizeof(__s32)); + if (buffer_size) { + for (i = 0; i < buffer_size; ++i) { + hid_set_field(report->field[field_index], i, + (__force __s32)cpu_to_le32(*buf32)); + ++buf32; + } + } + if (remaining_bytes) { + value = 0; + memcpy(&value, (u8 *)buf32, remaining_bytes); + hid_set_field(report->field[field_index], i, + (__force __s32)cpu_to_le32(value)); + } hid_hw_request(hsdev->hdev, report, HID_REQ_SET_REPORT); hid_hw_wait(hsdev->hdev); @@ -232,10 +243,11 @@ done_proc: EXPORT_SYMBOL_GPL(sensor_hub_set_feature); int sensor_hub_get_feature(struct hid_sensor_hub_device *hsdev, u32 report_id, - u32 field_index, s32 *value) + u32 field_index, int buffer_size, void *buffer) { struct hid_report *report; struct sensor_hub_data *data = hid_get_drvdata(hsdev->hdev); + int report_size; int ret = 0; mutex_lock(&data->mutex); @@ -247,7 +259,17 @@ int sensor_hub_get_feature(struct hid_sensor_hub_device *hsdev, u32 report_id, } hid_hw_request(hsdev->hdev, report, HID_REQ_GET_REPORT); hid_hw_wait(hsdev->hdev); - *value = report->field[field_index]->value[0]; + + /* calculate number of bytes required to read this field */ + report_size = DIV_ROUND_UP(report->field[field_index]->report_size, + 8) * + report->field[field_index]->report_count; + if (!report_size) { + ret = -EINVAL; + goto done_proc; + } + ret = min(report_size, buffer_size); + memcpy(buffer, report->field[field_index]->value, ret); done_proc: mutex_unlock(&data->mutex); @@ -259,47 +281,54 @@ EXPORT_SYMBOL_GPL(sensor_hub_get_feature); int sensor_hub_input_attr_get_raw_value(struct hid_sensor_hub_device *hsdev, u32 usage_id, - u32 attr_usage_id, u32 report_id) + u32 attr_usage_id, u32 report_id, + enum sensor_hub_read_flags flag) { struct sensor_hub_data *data = hid_get_drvdata(hsdev->hdev); unsigned long flags; struct hid_report *report; int ret_val = 0; - mutex_lock(&data->mutex); - memset(&data->pending, 0, sizeof(data->pending)); - init_completion(&data->pending.ready); - data->pending.usage_id = usage_id; - data->pending.attr_usage_id = attr_usage_id; - data->pending.raw_size = 0; - - spin_lock_irqsave(&data->lock, flags); - data->pending.status = true; - spin_unlock_irqrestore(&data->lock, flags); - report = sensor_hub_report(report_id, hsdev->hdev, HID_INPUT_REPORT); + report = sensor_hub_report(report_id, hsdev->hdev, + HID_INPUT_REPORT); if (!report) - goto err_free; - - hid_hw_request(hsdev->hdev, report, HID_REQ_GET_REPORT); - wait_for_completion_interruptible_timeout(&data->pending.ready, HZ*5); - switch (data->pending.raw_size) { - case 1: - ret_val = *(u8 *)data->pending.raw_data; - break; - case 2: - ret_val = *(u16 *)data->pending.raw_data; - break; - case 4: - ret_val = *(u32 *)data->pending.raw_data; - break; - default: - ret_val = 0; + return -EINVAL; + + mutex_lock(&hsdev->mutex); + if (flag == SENSOR_HUB_SYNC) { + memset(&hsdev->pending, 0, sizeof(hsdev->pending)); + init_completion(&hsdev->pending.ready); + hsdev->pending.usage_id = usage_id; + hsdev->pending.attr_usage_id = attr_usage_id; + hsdev->pending.raw_size = 0; + + spin_lock_irqsave(&data->lock, flags); + hsdev->pending.status = true; + spin_unlock_irqrestore(&data->lock, flags); } - kfree(data->pending.raw_data); - -err_free: - data->pending.status = false; + mutex_lock(&data->mutex); + hid_hw_request(hsdev->hdev, report, HID_REQ_GET_REPORT); mutex_unlock(&data->mutex); + if (flag == SENSOR_HUB_SYNC) { + wait_for_completion_interruptible_timeout( + &hsdev->pending.ready, HZ*5); + switch (hsdev->pending.raw_size) { + case 1: + ret_val = *(u8 *)hsdev->pending.raw_data; + break; + case 2: + ret_val = *(u16 *)hsdev->pending.raw_data; + break; + case 4: + ret_val = *(u32 *)hsdev->pending.raw_data; + break; + default: + ret_val = 0; + } + kfree(hsdev->pending.raw_data); + hsdev->pending.status = false; + } + mutex_unlock(&hsdev->mutex); return ret_val; } @@ -455,16 +484,6 @@ static int sensor_hub_raw_event(struct hid_device *hdev, report->field[i]->report_count)/8); sz = (report->field[i]->report_size * report->field[i]->report_count)/8; - if (pdata->pending.status && pdata->pending.attr_usage_id == - report->field[i]->usage->hid) { - hid_dbg(hdev, "data was pending ...\n"); - pdata->pending.raw_data = kmemdup(ptr, sz, GFP_ATOMIC); - if (pdata->pending.raw_data) - pdata->pending.raw_size = sz; - else - pdata->pending.raw_size = 0; - complete(&pdata->pending.ready); - } collection = &hdev->collection[ report->field[i]->usage->collection_index]; hid_dbg(hdev, "collection->usage %x\n", @@ -474,8 +493,23 @@ static int sensor_hub_raw_event(struct hid_device *hdev, report->field[i]->physical, report->field[i]->usage[0].collection_index, &hsdev, &priv); - - if (callback && callback->capture_sample) { + if (!callback) { + ptr += sz; + continue; + } + if (hsdev->pending.status && (hsdev->pending.attr_usage_id == + report->field[i]->usage->hid || + hsdev->pending.attr_usage_id == + report->field[i]->logical)) { + hid_dbg(hdev, "data was pending ...\n"); + hsdev->pending.raw_data = kmemdup(ptr, sz, GFP_ATOMIC); + if (hsdev->pending.raw_data) + hsdev->pending.raw_size = sz; + else + hsdev->pending.raw_size = 0; + complete(&hsdev->pending.ready); + } + if (callback->capture_sample) { if (report->field[i]->logical) callback->capture_sample(hsdev, report->field[i]->logical, sz, ptr, @@ -572,6 +606,7 @@ static int sensor_hub_probe(struct hid_device *hdev, int dev_cnt; struct hid_sensor_hub_device *hsdev; struct hid_sensor_hub_device *last_hsdev = NULL; + struct hid_sensor_hub_device *collection_hsdev = NULL; sd = devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL); if (!sd) { @@ -618,7 +653,8 @@ static int sensor_hub_probe(struct hid_device *hdev, for (i = 0; i < hdev->maxcollection; ++i) { struct hid_collection *collection = &hdev->collection[i]; - if (collection->type == HID_COLLECTION_PHYSICAL) { + if (collection->type == HID_COLLECTION_PHYSICAL || + collection->type == HID_COLLECTION_APPLICATION) { hsdev = devm_kzalloc(&hdev->dev, sizeof(*hsdev), GFP_KERNEL); @@ -630,6 +666,8 @@ static int sensor_hub_probe(struct hid_device *hdev, hsdev->hdev = hdev; hsdev->vendor_id = hdev->vendor; hsdev->product_id = hdev->product; + hsdev->usage = collection->usage; + mutex_init(&hsdev->mutex); hsdev->start_collection_index = i; if (last_hsdev) last_hsdev->end_collection_index = i; @@ -653,10 +691,17 @@ static int sensor_hub_probe(struct hid_device *hdev, hid_dbg(hdev, "Adding %s:%d\n", name, hsdev->start_collection_index); sd->hid_sensor_client_cnt++; + if (collection_hsdev) + collection_hsdev->end_collection_index = i; + if (collection->type == HID_COLLECTION_APPLICATION && + collection->usage == HID_USAGE_SENSOR_COLLECTION) + collection_hsdev = hsdev; } } if (last_hsdev) last_hsdev->end_collection_index = i; + if (collection_hsdev) + collection_hsdev->end_collection_index = i; ret = mfd_add_hotplug_devices(&hdev->dev, sd->hid_sensor_hub_client_devs, @@ -676,13 +721,18 @@ static void sensor_hub_remove(struct hid_device *hdev) { struct sensor_hub_data *data = hid_get_drvdata(hdev); unsigned long flags; + int i; hid_dbg(hdev, " hardware removed\n"); hid_hw_close(hdev); hid_hw_stop(hdev); spin_lock_irqsave(&data->lock, flags); - if (data->pending.status) - complete(&data->pending.ready); + for (i = 0; i < data->hid_sensor_client_cnt; ++i) { + struct hid_sensor_hub_device *hsdev = + data->hid_sensor_hub_client_devs[i].platform_data; + if (hsdev->pending.status) + complete(&hsdev->pending.ready); + } spin_unlock_irqrestore(&data->lock, flags); mfd_remove_devices(&hdev->dev); hid_set_drvdata(hdev, NULL); diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index c906300cf667..6ca96cebb44c 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); @@ -1131,18 +1132,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-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 a82127753461..a775143e6265 100644 --- a/drivers/hid/usbhid/hid-quirks.c +++ b/drivers/hid/usbhid/hid-quirks.c @@ -78,7 +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 }, @@ -92,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 }, @@ -107,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 }, @@ -128,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 0d0d0dd89d17..024f4d89d579 100644 --- a/drivers/hid/wacom.h +++ b/drivers/hid/wacom.h @@ -131,13 +131,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); @@ -151,4 +144,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 ba9af470bea0..e8607d096138 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; @@ -1045,8 +1067,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) { + if (wacom->battery) { power_supply_unregister(wacom->battery); wacom->battery = NULL; power_supply_unregister(wacom->ac); @@ -1317,6 +1338,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) { + wacom_initialize_battery(wacom); + } + else if (!(wacom->wacom_wac.features.quirks & WACOM_QUIRK_BATTERY) && + wacom->battery) { + wacom_destroy_battery(wacom); + } +} + /* * Not all devices report physical dimensions from HID. * Compute the default from hardcoded logical dimension @@ -1377,6 +1412,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; @@ -1416,6 +1454,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); @@ -1450,6 +1503,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; @@ -1466,19 +1525,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)) { @@ -1531,7 +1588,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: @@ -1554,7 +1611,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 bbe32d66e500..fa54d3290659 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) + 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,12 +581,9 @@ static int wacom_intuos_inout(struct wacom_wac *wacom) (features->type == CINTIQ && !(data[1] & 0x40))) return 1; - if (wacom->shared) { - wacom->shared->stylus_in_proximity = true; - - if (wacom->shared->touch_down) - return 1; - } + 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) { @@ -568,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 */ @@ -610,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; } @@ -1023,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, @@ -1042,6 +1065,28 @@ 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; @@ -1052,7 +1097,6 @@ static int wacom_24hdt_irq(struct wacom_wac *wacom) int num_contacts_left = 4; /* maximum contacts per packet */ int byte_per_packet = WACOM_BYTES_PER_24HDT_PACKET; int y_offset = 2; - static int contact_with_no_pen_down_count = 0; if (wacom->features.type == WACOM_27QHDT) { current_num_contacts = data[63]; @@ -1065,10 +1109,8 @@ static int wacom_24hdt_irq(struct wacom_wac *wacom) * First packet resets the counter since only the first * packet in series will have non-zero current_num_contacts. */ - if (current_num_contacts) { + if (current_num_contacts) wacom->num_contacts_left = current_num_contacts; - contact_with_no_pen_down_count = 0; - } contacts_to_send = min(num_contacts_left, wacom->num_contacts_left); @@ -1101,15 +1143,14 @@ static int wacom_24hdt_irq(struct wacom_wac *wacom) input_report_abs(input, ABS_MT_WIDTH_MINOR, min(w, h)); input_report_abs(input, ABS_MT_ORIENTATION, w > h); } - contact_with_no_pen_down_count++; } } - 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) { wacom->num_contacts_left = 0; - wacom->shared->touch_down = (contact_with_no_pen_down_count > 0); + wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom); } return 1; } @@ -1122,7 +1163,6 @@ static int wacom_mt_touch(struct wacom_wac *wacom) int current_num_contacts = data[2]; int contacts_to_send = 0; int x_offset = 0; - static int contact_with_no_pen_down_count = 0; /* MTTPC does not support Height and Width */ if (wacom->features.type == MTTPC || wacom->features.type == MTTPC_B) @@ -1132,10 +1172,8 @@ static int wacom_mt_touch(struct wacom_wac *wacom) * First packet resets the counter since only the first * packet in series will have non-zero current_num_contacts. */ - if (current_num_contacts) { + if (current_num_contacts) wacom->num_contacts_left = current_num_contacts; - contact_with_no_pen_down_count = 0; - } /* There are at most 5 contacts per packet */ contacts_to_send = min(5, wacom->num_contacts_left); @@ -1156,15 +1194,14 @@ static int wacom_mt_touch(struct wacom_wac *wacom) int y = get_unaligned_le16(&data[offset + x_offset + 9]); 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); wacom->num_contacts_left -= contacts_to_send; if (wacom->num_contacts_left <= 0) { wacom->num_contacts_left = 0; - wacom->shared->touch_down = (contact_with_no_pen_down_count > 0); + wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom); } return 1; } @@ -1173,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++) { @@ -1188,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; } @@ -1522,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) { @@ -1559,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, @@ -1619,7 +1631,6 @@ static int wacom_bpt_touch(struct wacom_wac *wacom) struct input_dev *pad_input = wacom->pad_input; unsigned char *data = wacom->data; int i; - int contact_with_no_pen_down_count = 0; if (data[0] != 0x02) return 0; @@ -1647,22 +1658,21 @@ static int wacom_bpt_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); 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 = (contact_with_no_pen_down_count > 0); + wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom); return 1; } -static int wacom_bpt3_touch_msg(struct wacom_wac *wacom, unsigned char *data, int last_touch_count) +static void wacom_bpt3_touch_msg(struct wacom_wac *wacom, unsigned char *data) { struct wacom_features *features = &wacom->features; struct input_dev *input = wacom->input; @@ -1670,7 +1680,7 @@ static int wacom_bpt3_touch_msg(struct wacom_wac *wacom, unsigned char *data, in int slot = input_mt_get_slot_by_key(input, data[0]); if (slot < 0) - return 0; + return; touch = touch && !wacom->shared->stylus_in_proximity; @@ -1702,9 +1712,7 @@ static int wacom_bpt3_touch_msg(struct wacom_wac *wacom, unsigned char *data, in input_report_abs(input, ABS_MT_POSITION_Y, y); input_report_abs(input, ABS_MT_TOUCH_MAJOR, width); input_report_abs(input, ABS_MT_TOUCH_MINOR, height); - last_touch_count++; } - return last_touch_count; } static void wacom_bpt3_button_msg(struct wacom_wac *wacom, unsigned char *data) @@ -1729,7 +1737,6 @@ static int wacom_bpt3_touch(struct wacom_wac *wacom) unsigned char *data = wacom->data; int count = data[1] & 0x07; int i; - int contact_with_no_pen_down_count = 0; if (data[0] != 0x02) return 0; @@ -1740,15 +1747,13 @@ static int wacom_bpt3_touch(struct wacom_wac *wacom) int msg_id = data[offset]; if (msg_id >= 2 && msg_id <= 17) - contact_with_no_pen_down_count = - wacom_bpt3_touch_msg(wacom, data + offset, - contact_with_no_pen_down_count); + wacom_bpt3_touch_msg(wacom, data + offset); else if (msg_id == 128) wacom_bpt3_button_msg(wacom, data + offset); } - input_mt_report_pointer_emulation(input, true); - wacom->shared->touch_down = (contact_with_no_pen_down_count > 0); + input_mt_sync_frame(input); + wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom); return 1; } @@ -1760,23 +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; - } - - if (wacom->shared->touch_down) - return 0; - prox = (data[1] & 0x20) == 0x20; /* @@ -1789,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]); @@ -1815,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); @@ -1826,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 */ @@ -1849,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; @@ -1859,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 && @@ -1871,30 +1948,63 @@ 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 && + !(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) { + 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; } @@ -1967,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; @@ -1982,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: @@ -2054,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) { @@ -2323,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; } @@ -2772,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 }; @@ -2976,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, @@ -2992,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 @@ -3124,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) }, @@ -3136,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; diff --git a/drivers/iio/accel/hid-sensor-accel-3d.c b/drivers/iio/accel/hid-sensor-accel-3d.c index df6a593bd4bd..2b4fad6998c1 100644 --- a/drivers/iio/accel/hid-sensor-accel-3d.c +++ b/drivers/iio/accel/hid-sensor-accel-3d.c @@ -123,7 +123,8 @@ static int accel_3d_read_raw(struct iio_dev *indio_dev, *val = sensor_hub_input_attr_get_raw_value( accel_state->common_attributes.hsdev, HID_USAGE_SENSOR_ACCEL_3D, address, - report_id); + report_id, + SENSOR_HUB_SYNC); else { *val = 0; hid_sensor_power_state(&accel_state->common_attributes, diff --git a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c index 25b01e156d82..e81f434760f4 100644 --- a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c +++ b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c @@ -153,8 +153,8 @@ s32 hid_sensor_read_poll_value(struct hid_sensor_common *st) int ret; ret = sensor_hub_get_feature(st->hsdev, - st->poll.report_id, - st->poll.index, &value); + st->poll.report_id, + st->poll.index, sizeof(value), &value); if (ret < 0 || value < 0) { return -EINVAL; @@ -174,8 +174,8 @@ int hid_sensor_read_samp_freq_value(struct hid_sensor_common *st, int ret; ret = sensor_hub_get_feature(st->hsdev, - st->poll.report_id, - st->poll.index, &value); + st->poll.report_id, + st->poll.index, sizeof(value), &value); if (ret < 0 || value < 0) { *val1 = *val2 = 0; return -EINVAL; @@ -212,9 +212,8 @@ int hid_sensor_write_samp_freq_value(struct hid_sensor_common *st, else value = 0; } - ret = sensor_hub_set_feature(st->hsdev, - st->poll.report_id, - st->poll.index, value); + ret = sensor_hub_set_feature(st->hsdev, st->poll.report_id, + st->poll.index, sizeof(value), &value); if (ret < 0 || value < 0) ret = -EINVAL; @@ -229,8 +228,9 @@ int hid_sensor_read_raw_hyst_value(struct hid_sensor_common *st, int ret; ret = sensor_hub_get_feature(st->hsdev, - st->sensitivity.report_id, - st->sensitivity.index, &value); + st->sensitivity.report_id, + st->sensitivity.index, sizeof(value), + &value); if (ret < 0 || value < 0) { *val1 = *val2 = 0; return -EINVAL; @@ -253,9 +253,9 @@ int hid_sensor_write_raw_hyst_value(struct hid_sensor_common *st, value = convert_to_vtf_format(st->sensitivity.size, st->sensitivity.unit_expo, val1, val2); - ret = sensor_hub_set_feature(st->hsdev, - st->sensitivity.report_id, - st->sensitivity.index, value); + ret = sensor_hub_set_feature(st->hsdev, st->sensitivity.report_id, + st->sensitivity.index, sizeof(value), + &value); if (ret < 0 || value < 0) ret = -EINVAL; diff --git a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c index 2f1d535b94c4..610fc98f88ef 100644 --- a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c +++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c @@ -68,20 +68,21 @@ static int _hid_sensor_power_state(struct hid_sensor_common *st, bool state) if (state_val >= 0) { state_val += st->power_state.logical_minimum; sensor_hub_set_feature(st->hsdev, st->power_state.report_id, - st->power_state.index, - (s32)state_val); + st->power_state.index, sizeof(state_val), + &state_val); } if (report_val >= 0) { report_val += st->report_state.logical_minimum; sensor_hub_set_feature(st->hsdev, st->report_state.report_id, - st->report_state.index, - (s32)report_val); + st->report_state.index, + sizeof(report_val), + &report_val); } sensor_hub_get_feature(st->hsdev, st->power_state.report_id, - st->power_state.index, - &state_val); + st->power_state.index, + sizeof(state_val), &state_val); if (state && poll_value) msleep_interruptible(poll_value * 2); diff --git a/drivers/iio/gyro/hid-sensor-gyro-3d.c b/drivers/iio/gyro/hid-sensor-gyro-3d.c index a3c3e19de527..b5883b6f4e50 100644 --- a/drivers/iio/gyro/hid-sensor-gyro-3d.c +++ b/drivers/iio/gyro/hid-sensor-gyro-3d.c @@ -123,7 +123,8 @@ static int gyro_3d_read_raw(struct iio_dev *indio_dev, *val = sensor_hub_input_attr_get_raw_value( gyro_state->common_attributes.hsdev, HID_USAGE_SENSOR_GYRO_3D, address, - report_id); + report_id, + SENSOR_HUB_SYNC); else { *val = 0; hid_sensor_power_state(&gyro_state->common_attributes, diff --git a/drivers/iio/light/hid-sensor-als.c b/drivers/iio/light/hid-sensor-als.c index 948acfc38b8c..1609ecdd01b0 100644 --- a/drivers/iio/light/hid-sensor-als.c +++ b/drivers/iio/light/hid-sensor-als.c @@ -101,7 +101,8 @@ static int als_read_raw(struct iio_dev *indio_dev, *val = sensor_hub_input_attr_get_raw_value( als_state->common_attributes.hsdev, HID_USAGE_SENSOR_ALS, address, - report_id); + report_id, + SENSOR_HUB_SYNC); hid_sensor_power_state(&als_state->common_attributes, false); } else { diff --git a/drivers/iio/light/hid-sensor-prox.c b/drivers/iio/light/hid-sensor-prox.c index 3ecf79ed08ac..91ecc46ffeaa 100644 --- a/drivers/iio/light/hid-sensor-prox.c +++ b/drivers/iio/light/hid-sensor-prox.c @@ -96,7 +96,8 @@ static int prox_read_raw(struct iio_dev *indio_dev, *val = sensor_hub_input_attr_get_raw_value( prox_state->common_attributes.hsdev, HID_USAGE_SENSOR_PROX, address, - report_id); + report_id, + SENSOR_HUB_SYNC); hid_sensor_power_state(&prox_state->common_attributes, false); } else { diff --git a/drivers/iio/magnetometer/hid-sensor-magn-3d.c b/drivers/iio/magnetometer/hid-sensor-magn-3d.c index d22993b4066a..4f9c0be24451 100644 --- a/drivers/iio/magnetometer/hid-sensor-magn-3d.c +++ b/drivers/iio/magnetometer/hid-sensor-magn-3d.c @@ -170,7 +170,8 @@ static int magn_3d_read_raw(struct iio_dev *indio_dev, *val = sensor_hub_input_attr_get_raw_value( magn_state->common_attributes.hsdev, HID_USAGE_SENSOR_COMPASS_3D, address, - report_id); + report_id, + SENSOR_HUB_SYNC); else { *val = 0; hid_sensor_power_state(&magn_state->common_attributes, diff --git a/drivers/iio/orientation/hid-sensor-incl-3d.c b/drivers/iio/orientation/hid-sensor-incl-3d.c index 73854460bb2c..5930fa32a2ab 100644 --- a/drivers/iio/orientation/hid-sensor-incl-3d.c +++ b/drivers/iio/orientation/hid-sensor-incl-3d.c @@ -124,7 +124,8 @@ static int incl_3d_read_raw(struct iio_dev *indio_dev, *val = sensor_hub_input_attr_get_raw_value( incl_state->common_attributes.hsdev, HID_USAGE_SENSOR_INCLINOMETER_3D, address, - report_id); + report_id, + SENSOR_HUB_SYNC); else { hid_sensor_power_state(&incl_state->common_attributes, false); diff --git a/drivers/iio/pressure/hid-sensor-press.c b/drivers/iio/pressure/hid-sensor-press.c index 1af314926ebd..7bb8d4c1f7df 100644 --- a/drivers/iio/pressure/hid-sensor-press.c +++ b/drivers/iio/pressure/hid-sensor-press.c @@ -100,7 +100,8 @@ static int press_read_raw(struct iio_dev *indio_dev, *val = sensor_hub_input_attr_get_raw_value( press_state->common_attributes.hsdev, HID_USAGE_SENSOR_PRESSURE, address, - report_id); + report_id, + SENSOR_HUB_SYNC); hid_sensor_power_state(&press_state->common_attributes, false); } else { diff --git a/drivers/input/input-mt.c b/drivers/input/input-mt.c index cb150a1dbaff..34feb3e1127b 100644 --- a/drivers/input/input-mt.c +++ b/drivers/input/input-mt.c @@ -88,10 +88,13 @@ int input_mt_init_slots(struct input_dev *dev, unsigned int num_slots, goto err_mem; } - /* Mark slots as 'unused' */ + /* Mark slots as 'inactive' */ for (i = 0; i < num_slots; i++) input_mt_set_value(&mt->slots[i], ABS_MT_TRACKING_ID, -1); + /* Mark slots as 'unused' */ + mt->frame = 1; + dev->mt = mt; return 0; err_mem: @@ -439,6 +442,8 @@ EXPORT_SYMBOL(input_mt_assign_slots); * set the key on the first unused slot and return. * * If no available slot can be found, -1 is returned. + * Note that for this function to work properly, input_mt_sync_frame() has + * to be called at each frame. */ int input_mt_get_slot_by_key(struct input_dev *dev, int key) { @@ -453,7 +458,7 @@ int input_mt_get_slot_by_key(struct input_dev *dev, int key) return s - mt->slots; for (s = mt->slots; s != mt->slots + mt->num_slots; s++) - if (!input_mt_is_active(s)) { + if (!input_mt_is_active(s) && !input_mt_is_used(mt, s)) { s->key = key; return s - mt->slots; } diff --git a/drivers/rtc/rtc-hid-sensor-time.c b/drivers/rtc/rtc-hid-sensor-time.c index ae7c2ba440cf..af4f85a66b39 100644 --- a/drivers/rtc/rtc-hid-sensor-time.c +++ b/drivers/rtc/rtc-hid-sensor-time.c @@ -213,7 +213,7 @@ static int hid_rtc_read_time(struct device *dev, struct rtc_time *tm) /* get a report with all values through requesting one value */ sensor_hub_input_attr_get_raw_value(time_state->common_attributes.hsdev, HID_USAGE_SENSOR_TIME, hid_time_addresses[0], - time_state->info[0].report_id); + time_state->info[0].report_id, SENSOR_HUB_SYNC); /* wait for all values (event) */ ret = wait_for_completion_killable_timeout( &time_state->comp_last_time, HZ*6); diff --git a/include/linux/hid-sensor-hub.h b/include/linux/hid-sensor-hub.h index 4173a8fdad9e..0408421d885f 100644 --- a/include/linux/hid-sensor-hub.h +++ b/include/linux/hid-sensor-hub.h @@ -49,19 +49,43 @@ struct hid_sensor_hub_attribute_info { }; /** + * struct sensor_hub_pending - Synchronous read pending information + * @status: Pending status true/false. + * @ready: Completion synchronization data. + * @usage_id: Usage id for physical device, E.g. Gyro usage id. + * @attr_usage_id: Usage Id of a field, E.g. X-AXIS for a gyro. + * @raw_size: Response size for a read request. + * @raw_data: Place holder for received response. + */ +struct sensor_hub_pending { + bool status; + struct completion ready; + u32 usage_id; + u32 attr_usage_id; + int raw_size; + u8 *raw_data; +}; + +/** * struct hid_sensor_hub_device - Stores the hub instance data * @hdev: Stores the hid instance. * @vendor_id: Vendor id of hub device. * @product_id: Product id of hub device. + * @usage: Usage id for this hub device instance. * @start_collection_index: Starting index for a phy type collection * @end_collection_index: Last index for a phy type collection + * @mutex: synchronizing mutex. + * @pending: Holds information of pending sync read request. */ struct hid_sensor_hub_device { struct hid_device *hdev; u32 vendor_id; u32 product_id; + u32 usage; int start_collection_index; int end_collection_index; + struct mutex mutex; + struct sensor_hub_pending pending; }; /** @@ -152,40 +176,51 @@ int sensor_hub_input_get_attribute_info(struct hid_sensor_hub_device *hsdev, * @usage_id: Attribute usage id of parent physical device as per spec * @attr_usage_id: Attribute usage id as per spec * @report_id: Report id to look for +* @flag: Synchronous or asynchronous read * -* Issues a synchronous read request for an input attribute. Returns -* data upto 32 bits. Since client can get events, so this call should -* not be used for data paths, this will impact performance. +* Issues a synchronous or asynchronous read request for an input attribute. +* Returns data upto 32 bits. */ +enum sensor_hub_read_flags { + SENSOR_HUB_SYNC, + SENSOR_HUB_ASYNC, +}; + int sensor_hub_input_attr_get_raw_value(struct hid_sensor_hub_device *hsdev, - u32 usage_id, - u32 attr_usage_id, u32 report_id); + u32 usage_id, + u32 attr_usage_id, u32 report_id, + enum sensor_hub_read_flags flag +); + /** * sensor_hub_set_feature() - Feature set request * @hsdev: Hub device instance. * @report_id: Report id to look for * @field_index: Field index inside a report -* @value: Value to set +* @buffer_size: size of the buffer +* @buffer: buffer to use in the feature set * * Used to set a field in feature report. For example this can set polling * interval, sensitivity, activate/deactivate state. */ int sensor_hub_set_feature(struct hid_sensor_hub_device *hsdev, u32 report_id, - u32 field_index, s32 value); + u32 field_index, int buffer_size, void *buffer); /** * sensor_hub_get_feature() - Feature get request * @hsdev: Hub device instance. * @report_id: Report id to look for * @field_index: Field index inside a report -* @value: Place holder for return value +* @buffer_size: size of the buffer +* @buffer: buffer to copy output * * Used to get a field in feature report. For example this can get polling -* interval, sensitivity, activate/deactivate state. +* interval, sensitivity, activate/deactivate state. On success it returns +* number of bytes copied to buffer. On failure, it returns value < 0. */ int sensor_hub_get_feature(struct hid_sensor_hub_device *hsdev, u32 report_id, - u32 field_index, s32 *value); + u32 field_index, int buffer_size, void *buffer); /* hid-sensor-attributes */ diff --git a/include/linux/hid-sensor-ids.h b/include/linux/hid-sensor-ids.h index 109f0e633e01..f2ee90aed0c2 100644 --- a/include/linux/hid-sensor-ids.h +++ b/include/linux/hid-sensor-ids.h @@ -21,6 +21,8 @@ #define HID_MAX_PHY_DEVICES 0xFF +#define HID_USAGE_SENSOR_COLLECTION 0x200001 + /* Accel 3D (200073) */ #define HID_USAGE_SENSOR_ACCEL_3D 0x200073 #define HID_USAGE_SENSOR_DATA_ACCELERATION 0x200452 diff --git a/include/linux/hid.h b/include/linux/hid.h index f94cf28e4b7c..176b43670e5d 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -159,6 +159,7 @@ struct hid_item { #define HID_UP_LED 0x00080000 #define HID_UP_BUTTON 0x00090000 #define HID_UP_ORDINAL 0x000a0000 +#define HID_UP_TELEPHONY 0x000b0000 #define HID_UP_CONSUMER 0x000c0000 #define HID_UP_DIGITIZER 0x000d0000 #define HID_UP_PID 0x000f0000 @@ -269,6 +270,7 @@ struct hid_item { #define HID_DG_DEVICEINDEX 0x000d0053 #define HID_DG_CONTACTCOUNT 0x000d0054 #define HID_DG_CONTACTMAX 0x000d0055 +#define HID_DG_BUTTONTYPE 0x000d0059 #define HID_DG_BARRELSWITCH2 0x000d005a #define HID_DG_TOOLSERIALNUMBER 0x000d005b diff --git a/include/uapi/linux/input.h b/include/uapi/linux/input.h index 2f62ab2d7bf9..8038e9aa3b95 100644 --- a/include/uapi/linux/input.h +++ b/include/uapi/linux/input.h @@ -702,6 +702,10 @@ struct input_keymap_entry { #define KEY_NUMERIC_9 0x209 #define KEY_NUMERIC_STAR 0x20a #define KEY_NUMERIC_POUND 0x20b +#define KEY_NUMERIC_A 0x20c /* Phone key A - HUT Telephony 0xb9 */ +#define KEY_NUMERIC_B 0x20d +#define KEY_NUMERIC_C 0x20e +#define KEY_NUMERIC_D 0x20f #define KEY_CAMERA_FOCUS 0x210 #define KEY_WPS_BUTTON 0x211 /* WiFi Protected Setup key */ diff --git a/samples/hidraw/Makefile b/samples/hidraw/Makefile index 382eeae77bd6..a9ab96188fbe 100644 --- a/samples/hidraw/Makefile +++ b/samples/hidraw/Makefile @@ -8,3 +8,5 @@ hostprogs-y := hid-example always := $(hostprogs-y) HOSTCFLAGS_hid-example.o += -I$(objtree)/usr/include + +all: hid-example diff --git a/samples/hidraw/hid-example.c b/samples/hidraw/hid-example.c index 512a7e50bcae..92e6c1511910 100644 --- a/samples/hidraw/hid-example.c +++ b/samples/hidraw/hid-example.c @@ -46,10 +46,14 @@ int main(int argc, char **argv) char buf[256]; struct hidraw_report_descriptor rpt_desc; struct hidraw_devinfo info; + char *device = "/dev/hidraw0"; + + if (argc > 1) + device = argv[1]; /* Open the Device with non-blocking reads. In real life, don't use a hard coded path; use libudev instead. */ - fd = open("/dev/hidraw0", O_RDWR|O_NONBLOCK); + fd = open(device, O_RDWR|O_NONBLOCK); if (fd < 0) { perror("Unable to open device"); |