diff options
Diffstat (limited to 'drivers/hid')
32 files changed, 2909 insertions, 769 deletions
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 db4fb6e1cc5b..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) @@ -1872,6 +1893,7 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_SIDEWINDER_GV) }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_NE4K) }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_NE4K_JP) }, + { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_NE7K) }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_LK6K) }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_PRESENTER_8K_USB) }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_DIGITAL_MEDIA_3K) }, @@ -1926,6 +1948,7 @@ static const struct hid_device_id hid_have_special_driver[] = { #endif #if IS_ENABLED(CONFIG_HID_SAITEK) { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_PS1000) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT7_OLD) }, { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT7) }, { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_MMO7) }, { HID_USB_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_RAT9) }, @@ -1957,6 +1980,7 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb65a) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE_BT) }, { HID_USB_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE_PRO) }, { HID_USB_DEVICE(USB_VENDOR_ID_TOPSEED, USB_DEVICE_ID_TOPSEED_CYBERLINK) }, { HID_USB_DEVICE(USB_VENDOR_ID_TOPSEED2, USB_DEVICE_ID_TOPSEED2_RF_COMBO) }, { HID_USB_DEVICE(USB_VENDOR_ID_TWINHAN, USB_DEVICE_ID_TWINHAN_IR_REMOTE) }, diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c index 8bf61d295ffd..2886b645ced7 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" }, @@ -814,7 +815,7 @@ static const char *keys[KEY_MAX + 1] = { [KEY_DELETEFILE] = "DeleteFile", [KEY_XFER] = "X-fer", [KEY_PROG1] = "Prog1", [KEY_PROG2] = "Prog2", [KEY_WWW] = "WWW", [KEY_MSDOS] = "MSDOS", - [KEY_COFFEE] = "Coffee", [KEY_DIRECTION] = "Direction", + [KEY_COFFEE] = "Coffee", [KEY_ROTATE_DISPLAY] = "RotateDisplay", [KEY_CYCLEWINDOWS] = "CycleWindows", [KEY_MAIL] = "Mail", [KEY_BOOKMARKS] = "Bookmarks", [KEY_COMPUTER] = "Computer", [KEY_BACK] = "Back", [KEY_FORWARD] = "Forward", @@ -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 46edb4d3ed28..41f167e4d75f 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -459,6 +459,11 @@ #define USB_DEVICE_ID_UGCI_FLYING 0x0020 #define USB_DEVICE_ID_UGCI_FIGHTING 0x0030 +#define USB_VENDOR_ID_HP 0x03f0 +#define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0A4A 0x0a4a +#define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0B4A 0x0b4a +#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE 0x134a + #define USB_VENDOR_ID_HUION 0x256c #define USB_DEVICE_ID_HUION_TABLET 0x006e @@ -533,6 +538,7 @@ #define USB_DEVICE_ID_KYE_MOUSEPEN_I608X 0x5011 #define USB_DEVICE_ID_KYE_MOUSEPEN_I608X_2 0x501a #define USB_DEVICE_ID_KYE_EASYPEN_M610X 0x5013 +#define USB_DEVICE_ID_KYE_PENSKETCH_M912 0x5015 #define USB_VENDOR_ID_LABTEC 0x1020 #define USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD 0x0006 @@ -586,10 +592,14 @@ #define USB_VENDOR_ID_LOGITECH 0x046d #define USB_DEVICE_ID_LOGITECH_AUDIOHUB 0x0a0e #define USB_DEVICE_ID_LOGITECH_T651 0xb00c +#define USB_DEVICE_ID_LOGITECH_C077 0xc007 #define USB_DEVICE_ID_LOGITECH_RECEIVER 0xc101 #define USB_DEVICE_ID_LOGITECH_HARMONY_FIRST 0xc110 #define USB_DEVICE_ID_LOGITECH_HARMONY_LAST 0xc14f #define USB_DEVICE_ID_LOGITECH_HARMONY_PS3 0x0306 +#define USB_DEVICE_ID_LOGITECH_MOUSE_C01A 0xc01a +#define USB_DEVICE_ID_LOGITECH_MOUSE_C05A 0xc05a +#define USB_DEVICE_ID_LOGITECH_MOUSE_C06A 0xc06a #define USB_DEVICE_ID_LOGITECH_RUMBLEPAD_CORD 0xc20a #define USB_DEVICE_ID_LOGITECH_RUMBLEPAD 0xc211 #define USB_DEVICE_ID_LOGITECH_EXTREME_3D 0xc215 @@ -654,6 +664,7 @@ #define USB_DEVICE_ID_MS_LK6K 0x00f9 #define USB_DEVICE_ID_MS_PRESENTER_8K_BT 0x0701 #define USB_DEVICE_ID_MS_PRESENTER_8K_USB 0x0713 +#define USB_DEVICE_ID_MS_NE7K 0x071d #define USB_DEVICE_ID_MS_DIGITAL_MEDIA_3K 0x0730 #define USB_DEVICE_ID_MS_COMFORT_MOUSE_4500 0x076c #define USB_DEVICE_ID_MS_SURFACE_PRO_2 0x0799 @@ -802,6 +813,7 @@ #define USB_VENDOR_ID_SAITEK 0x06a3 #define USB_DEVICE_ID_SAITEK_RUMBLEPAD 0xff17 #define USB_DEVICE_ID_SAITEK_PS1000 0x0621 +#define USB_DEVICE_ID_SAITEK_RAT7_OLD 0x0ccb #define USB_DEVICE_ID_SAITEK_RAT7 0x0cd7 #define USB_DEVICE_ID_SAITEK_MMO7 0x0cd0 @@ -896,6 +908,7 @@ #define USB_VENDOR_ID_TIVO 0x150a #define USB_DEVICE_ID_TIVO_SLIDE_BT 0x1200 #define USB_DEVICE_ID_TIVO_SLIDE 0x1201 +#define USB_DEVICE_ID_TIVO_SLIDE_PRO 0x1203 #define USB_VENDOR_ID_TOPSEED 0x0766 #define USB_DEVICE_ID_TOPSEED_CYBERLINK 0x0204 @@ -1018,6 +1031,7 @@ #define USB_DEVICE_ID_ZYTRONIC_ZXY100 0x0005 #define USB_VENDOR_ID_PRIMAX 0x0461 +#define USB_DEVICE_ID_PRIMAX_MOUSE_4D22 0x4d22 #define USB_DEVICE_ID_PRIMAX_KEYBOARD 0x4e05 diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 052869d0ab78..008e89bf6f3c 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -339,7 +339,7 @@ static int hidinput_get_battery_property(struct power_supply *psy, enum power_supply_property prop, union power_supply_propval *val) { - struct hid_device *dev = container_of(psy, struct hid_device, battery); + struct hid_device *dev = power_supply_get_drvdata(psy); int ret = 0; __u8 *buf; @@ -397,26 +397,32 @@ static int hidinput_get_battery_property(struct power_supply *psy, static bool hidinput_setup_battery(struct hid_device *dev, unsigned report_type, struct hid_field *field) { - struct power_supply *battery = &dev->battery; - int ret; + struct power_supply_desc *psy_desc = NULL; + struct power_supply_config psy_cfg = { .drv_data = dev, }; unsigned quirks; s32 min, max; if (field->usage->hid != HID_DC_BATTERYSTRENGTH) return false; /* no match */ - if (battery->name != NULL) + if (dev->battery != NULL) goto out; /* already initialized? */ - battery->name = kasprintf(GFP_KERNEL, "hid-%s-battery", dev->uniq); - if (battery->name == NULL) + psy_desc = kzalloc(sizeof(*psy_desc), GFP_KERNEL); + if (psy_desc == NULL) goto out; - battery->type = POWER_SUPPLY_TYPE_BATTERY; - battery->properties = hidinput_battery_props; - battery->num_properties = ARRAY_SIZE(hidinput_battery_props); - battery->use_for_apm = 0; - battery->get_property = hidinput_get_battery_property; + psy_desc->name = kasprintf(GFP_KERNEL, "hid-%s-battery", dev->uniq); + if (psy_desc->name == NULL) { + kfree(psy_desc); + goto out; + } + + psy_desc->type = POWER_SUPPLY_TYPE_BATTERY; + psy_desc->properties = hidinput_battery_props; + psy_desc->num_properties = ARRAY_SIZE(hidinput_battery_props); + psy_desc->use_for_apm = 0; + psy_desc->get_property = hidinput_get_battery_property; quirks = find_battery_quirk(dev); @@ -439,27 +445,30 @@ static bool hidinput_setup_battery(struct hid_device *dev, unsigned report_type, dev->battery_report_type = report_type; dev->battery_report_id = field->report->id; - ret = power_supply_register(&dev->dev, battery); - if (ret != 0) { - hid_warn(dev, "can't register power supply: %d\n", ret); - kfree(battery->name); - battery->name = NULL; + dev->battery = power_supply_register(&dev->dev, psy_desc, &psy_cfg); + if (IS_ERR(dev->battery)) { + hid_warn(dev, "can't register power supply: %ld\n", + PTR_ERR(dev->battery)); + kfree(psy_desc->name); + kfree(psy_desc); + dev->battery = NULL; + } else { + power_supply_powers(dev->battery, &dev->dev); } - power_supply_powers(battery, &dev->dev); - out: return true; } static void hidinput_cleanup_battery(struct hid_device *dev) { - if (!dev->battery.name) + if (!dev->battery) return; - power_supply_unregister(&dev->battery); - kfree(dev->battery.name); - dev->battery.name = NULL; + power_supply_unregister(dev->battery); + kfree(dev->battery->desc->name); + kfree(dev->battery->desc); + dev->battery = NULL; } #else /* !CONFIG_HID_BATTERY_STRENGTH */ static bool hidinput_setup_battery(struct hid_device *dev, unsigned report_type, @@ -711,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-microsoft.c b/drivers/hid/hid-microsoft.c index fbaea6eb882e..af935eb198c9 100644 --- a/drivers/hid/hid-microsoft.c +++ b/drivers/hid/hid-microsoft.c @@ -264,6 +264,8 @@ static const struct hid_device_id ms_devices[] = { .driver_data = MS_ERGONOMY }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_NE4K_JP), .driver_data = MS_ERGONOMY }, + { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_NE7K), + .driver_data = MS_ERGONOMY }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_LK6K), .driver_data = MS_ERGONOMY | MS_RDESC }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_PRESENTER_8K_USB), 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-saitek.c b/drivers/hid/hid-saitek.c index 5632c54eadf0..a014f21275d8 100644 --- a/drivers/hid/hid-saitek.c +++ b/drivers/hid/hid-saitek.c @@ -177,6 +177,8 @@ static int saitek_event(struct hid_device *hdev, struct hid_field *field, static const struct hid_device_id saitek_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_PS1000), .driver_data = SAITEK_FIX_PS1000 }, + { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT7_OLD), + .driver_data = SAITEK_RELEASE_MODE_RAT7 }, { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT7), .driver_data = SAITEK_RELEASE_MODE_RAT7 }, { HID_USB_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_RAT9), 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 6a58b6c723aa..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; } @@ -135,20 +116,23 @@ static struct hid_sensor_hub_callbacks *sensor_hub_get_callback( { struct hid_sensor_hub_callbacks_list *callback; struct sensor_hub_data *pdata = hid_get_drvdata(hdev); + unsigned long flags; - spin_lock(&pdata->dyn_callback_lock); + 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 < callback->hsdev->end_collection_index)) { *priv = callback->priv; *hsdev = callback->hsdev; - spin_unlock(&pdata->dyn_callback_lock); + spin_unlock_irqrestore(&pdata->dyn_callback_lock, + flags); return callback->usage_callback; } - spin_unlock(&pdata->dyn_callback_lock); + spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags); return NULL; } @@ -177,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; @@ -206,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); @@ -218,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); @@ -230,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); @@ -245,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); @@ -257,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; } @@ -453,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", @@ -472,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, @@ -570,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) { @@ -616,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); @@ -628,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; @@ -651,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, @@ -674,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 31e9d2561106..6ca96cebb44c 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -802,9 +802,10 @@ 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 spinlock_t sony_dev_list_lock; +static DEFINE_SPINLOCK(sony_dev_list_lock); static LIST_HEAD(sony_device_list); static DEFINE_IDA(sony_device_id_allocator); @@ -815,7 +816,8 @@ struct sony_sc { struct led_classdev *leds[MAX_LEDS]; unsigned long quirks; struct work_struct state_worker; - struct power_supply battery; + struct power_supply *battery; + struct power_supply_desc battery_desc; int device_id; __u8 *output_report_dmabuf; @@ -1130,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; @@ -1660,7 +1682,7 @@ static int sony_battery_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { - struct sony_sc *sc = container_of(psy, struct sony_sc, battery); + struct sony_sc *sc = power_supply_get_drvdata(psy); unsigned long flags; int ret = 0; u8 battery_charging, battery_capacity, cable_state; @@ -1699,6 +1721,7 @@ static int sony_battery_get_property(struct power_supply *psy, static int sony_battery_probe(struct sony_sc *sc) { + struct power_supply_config psy_cfg = { .drv_data = sc, }; struct hid_device *hdev = sc->hdev; int ret; @@ -1708,39 +1731,42 @@ static int sony_battery_probe(struct sony_sc *sc) */ sc->battery_capacity = 100; - sc->battery.properties = sony_battery_props; - sc->battery.num_properties = ARRAY_SIZE(sony_battery_props); - sc->battery.get_property = sony_battery_get_property; - sc->battery.type = POWER_SUPPLY_TYPE_BATTERY; - sc->battery.use_for_apm = 0; - sc->battery.name = kasprintf(GFP_KERNEL, "sony_controller_battery_%pMR", - sc->mac_address); - if (!sc->battery.name) + sc->battery_desc.properties = sony_battery_props; + sc->battery_desc.num_properties = ARRAY_SIZE(sony_battery_props); + sc->battery_desc.get_property = sony_battery_get_property; + sc->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY; + sc->battery_desc.use_for_apm = 0; + sc->battery_desc.name = kasprintf(GFP_KERNEL, + "sony_controller_battery_%pMR", + sc->mac_address); + if (!sc->battery_desc.name) return -ENOMEM; - ret = power_supply_register(&hdev->dev, &sc->battery); - if (ret) { + sc->battery = power_supply_register(&hdev->dev, &sc->battery_desc, + &psy_cfg); + if (IS_ERR(sc->battery)) { + ret = PTR_ERR(sc->battery); hid_err(hdev, "Unable to register battery device\n"); goto err_free; } - power_supply_powers(&sc->battery, &hdev->dev); + power_supply_powers(sc->battery, &hdev->dev); return 0; err_free: - kfree(sc->battery.name); - sc->battery.name = NULL; + kfree(sc->battery_desc.name); + sc->battery_desc.name = NULL; return ret; } static void sony_battery_remove(struct sony_sc *sc) { - if (!sc->battery.name) + if (!sc->battery_desc.name) return; - power_supply_unregister(&sc->battery); - kfree(sc->battery.name); - sc->battery.name = NULL; + power_supply_unregister(sc->battery); + kfree(sc->battery_desc.name); + sc->battery_desc.name = NULL; } /* @@ -1944,6 +1970,8 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) return -ENOMEM; } + spin_lock_init(&sc->lock); + sc->quirks = quirks; hid_set_drvdata(hdev, sc); sc->hdev = hdev; @@ -2147,8 +2175,8 @@ static void __exit sony_exit(void) { dbg_hid("Sony:%s\n", __func__); - ida_destroy(&sony_device_id_allocator); hid_unregister_driver(&sony_driver); + ida_destroy(&sony_device_id_allocator); } module_init(sony_init); module_exit(sony_exit); diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c index 29f328f411fb..3edd4ac36494 100644 --- a/drivers/hid/hid-steelseries.c +++ b/drivers/hid/hid-steelseries.c @@ -12,7 +12,6 @@ */ #include <linux/device.h> -#include <linux/usb.h> #include <linux/hid.h> #include <linux/module.h> diff --git a/drivers/hid/hid-tivo.c b/drivers/hid/hid-tivo.c index d790d8d71f7f..d98696927453 100644 --- a/drivers/hid/hid-tivo.c +++ b/drivers/hid/hid-tivo.c @@ -64,6 +64,7 @@ static const struct hid_device_id tivo_devices[] = { /* TiVo Slide Bluetooth remote, pairs with a Broadcom dongle */ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE_BT) }, { HID_USB_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE_PRO) }, { } }; MODULE_DEVICE_TABLE(hid, tivo_devices); diff --git a/drivers/hid/hid-uclogic.c b/drivers/hid/hid-uclogic.c index fb8b516ff0ed..94167310e15a 100644 --- a/drivers/hid/hid-uclogic.c +++ b/drivers/hid/hid-uclogic.c @@ -1,7 +1,8 @@ /* * HID driver for UC-Logic devices not fully compliant with HID standard * - * Copyright (c) 2010 Nikolai Kondrashov + * Copyright (c) 2010-2014 Nikolai Kondrashov + * Copyright (c) 2013 Martin Rusko */ /* @@ -15,6 +16,8 @@ #include <linux/hid.h> #include <linux/module.h> #include <linux/usb.h> +#include <asm/unaligned.h> +#include "usbhid/usbhid.h" #include "hid-ids.h" @@ -546,11 +549,93 @@ static __u8 twha60_rdesc_fixed1[] = { 0xC0 /* End Collection */ }; +/* Report descriptor template placeholder head */ +#define UCLOGIC_PH_HEAD 0xFE, 0xED, 0x1D + +/* Report descriptor template placeholder IDs */ +enum uclogic_ph_id { + UCLOGIC_PH_ID_X_LM, + UCLOGIC_PH_ID_X_PM, + UCLOGIC_PH_ID_Y_LM, + UCLOGIC_PH_ID_Y_PM, + UCLOGIC_PH_ID_PRESSURE_LM, + UCLOGIC_PH_ID_NUM +}; + +/* Report descriptor template placeholder */ +#define UCLOGIC_PH(_ID) UCLOGIC_PH_HEAD, UCLOGIC_PH_ID_##_ID +#define UCLOGIC_PEN_REPORT_ID 0x07 + +/* Fixed report descriptor template */ +static const __u8 uclogic_tablet_rdesc_template[] = { + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x09, 0x02, /* Usage (Pen), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, 0x07, /* Report ID (7), */ + 0x09, 0x20, /* Usage (Stylus), */ + 0xA0, /* Collection (Physical), */ + 0x14, /* Logical Minimum (0), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x75, 0x01, /* Report Size (1), */ + 0x09, 0x42, /* Usage (Tip Switch), */ + 0x09, 0x44, /* Usage (Barrel Switch), */ + 0x09, 0x46, /* Usage (Tablet Pick), */ + 0x95, 0x03, /* Report Count (3), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x03, /* Report Count (3), */ + 0x81, 0x03, /* Input (Constant, Variable), */ + 0x09, 0x32, /* Usage (In Range), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x03, /* Input (Constant, Variable), */ + 0x75, 0x10, /* Report Size (16), */ + 0x95, 0x01, /* Report Count (1), */ + 0xA4, /* Push, */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x65, 0x13, /* Unit (Inch), */ + 0x55, 0xFD, /* Unit Exponent (-3), */ + 0x34, /* Physical Minimum (0), */ + 0x09, 0x30, /* Usage (X), */ + 0x27, UCLOGIC_PH(X_LM), /* Logical Maximum (PLACEHOLDER), */ + 0x47, UCLOGIC_PH(X_PM), /* Physical Maximum (PLACEHOLDER), */ + 0x81, 0x02, /* Input (Variable), */ + 0x09, 0x31, /* Usage (Y), */ + 0x27, UCLOGIC_PH(Y_LM), /* Logical Maximum (PLACEHOLDER), */ + 0x47, UCLOGIC_PH(Y_PM), /* Physical Maximum (PLACEHOLDER), */ + 0x81, 0x02, /* Input (Variable), */ + 0xB4, /* Pop, */ + 0x09, 0x30, /* Usage (Tip Pressure), */ + 0x27, + UCLOGIC_PH(PRESSURE_LM),/* Logical Maximum (PLACEHOLDER), */ + 0x81, 0x02, /* Input (Variable), */ + 0xC0, /* End Collection, */ + 0xC0 /* End Collection */ +}; + +/* Parameter indices */ +enum uclogic_prm { + UCLOGIC_PRM_X_LM = 1, + UCLOGIC_PRM_Y_LM = 2, + UCLOGIC_PRM_PRESSURE_LM = 4, + UCLOGIC_PRM_RESOLUTION = 5, + UCLOGIC_PRM_NUM +}; + +/* Driver data */ +struct uclogic_drvdata { + __u8 *rdesc; + unsigned int rsize; + bool invert_pen_inrange; + bool ignore_pen_usage; +}; + static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize) { struct usb_interface *iface = to_usb_interface(hdev->dev.parent); __u8 iface_num = iface->cur_altsetting->desc.bInterfaceNumber; + struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); switch (hdev->product) { case USB_DEVICE_ID_UCLOGIC_TABLET_PF1209: @@ -621,11 +706,241 @@ static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc, break; } break; + default: + if (drvdata->rdesc != NULL) { + rdesc = drvdata->rdesc; + *rsize = drvdata->rsize; + } } return rdesc; } +static int uclogic_input_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); + + /* discard the unused pen interface */ + if ((drvdata->ignore_pen_usage) && + (field->application == HID_DG_PEN)) + return -1; + + /* let hid-core decide what to do */ + return 0; +} + +static void uclogic_input_configured(struct hid_device *hdev, + struct hid_input *hi) +{ + char *name; + const char *suffix = NULL; + struct hid_field *field; + size_t len; + + /* no report associated (HID_QUIRK_MULTI_INPUT not set) */ + if (!hi->report) + return; + + field = hi->report->field[0]; + + switch (field->application) { + case HID_GD_KEYBOARD: + suffix = "Keyboard"; + break; + case HID_GD_MOUSE: + suffix = "Mouse"; + break; + case HID_GD_KEYPAD: + suffix = "Pad"; + break; + case HID_DG_PEN: + suffix = "Pen"; + break; + case HID_CP_CONSUMER_CONTROL: + suffix = "Consumer Control"; + break; + case HID_GD_SYSTEM_CONTROL: + suffix = "System Control"; + break; + } + + if (suffix) { + len = strlen(hdev->name) + 2 + strlen(suffix); + name = devm_kzalloc(&hi->input->dev, len, GFP_KERNEL); + if (name) { + snprintf(name, len, "%s %s", hdev->name, suffix); + hi->input->name = name; + } + } +} + +/** + * Enable fully-functional tablet mode and determine device parameters. + * + * @hdev: HID device + */ +static int uclogic_tablet_enable(struct hid_device *hdev) +{ + int rc; + struct usb_device *usb_dev = hid_to_usb_dev(hdev); + struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); + __le16 *buf = NULL; + size_t len; + s32 params[UCLOGIC_PH_ID_NUM]; + s32 resolution; + __u8 *p; + s32 v; + + /* + * Read string descriptor containing tablet parameters. The specific + * string descriptor and data were discovered by sniffing the Windows + * driver traffic. + * NOTE: This enables fully-functional tablet mode. + */ + len = UCLOGIC_PRM_NUM * sizeof(*buf); + buf = kmalloc(len, GFP_KERNEL); + if (buf == NULL) { + hid_err(hdev, "failed to allocate parameter buffer\n"); + rc = -ENOMEM; + goto cleanup; + } + rc = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, + (USB_DT_STRING << 8) + 0x64, + 0x0409, buf, len, + USB_CTRL_GET_TIMEOUT); + if (rc == -EPIPE) { + hid_err(hdev, "device parameters not found\n"); + rc = -ENODEV; + goto cleanup; + } else if (rc < 0) { + hid_err(hdev, "failed to get device parameters: %d\n", rc); + rc = -ENODEV; + goto cleanup; + } else if (rc != len) { + hid_err(hdev, "invalid device parameters\n"); + rc = -ENODEV; + goto cleanup; + } + + /* Extract device parameters */ + params[UCLOGIC_PH_ID_X_LM] = le16_to_cpu(buf[UCLOGIC_PRM_X_LM]); + params[UCLOGIC_PH_ID_Y_LM] = le16_to_cpu(buf[UCLOGIC_PRM_Y_LM]); + params[UCLOGIC_PH_ID_PRESSURE_LM] = + le16_to_cpu(buf[UCLOGIC_PRM_PRESSURE_LM]); + resolution = le16_to_cpu(buf[UCLOGIC_PRM_RESOLUTION]); + if (resolution == 0) { + params[UCLOGIC_PH_ID_X_PM] = 0; + params[UCLOGIC_PH_ID_Y_PM] = 0; + } else { + params[UCLOGIC_PH_ID_X_PM] = params[UCLOGIC_PH_ID_X_LM] * + 1000 / resolution; + params[UCLOGIC_PH_ID_Y_PM] = params[UCLOGIC_PH_ID_Y_LM] * + 1000 / resolution; + } + + /* Allocate fixed report descriptor */ + drvdata->rdesc = devm_kzalloc(&hdev->dev, + sizeof(uclogic_tablet_rdesc_template), + GFP_KERNEL); + if (drvdata->rdesc == NULL) { + hid_err(hdev, "failed to allocate fixed rdesc\n"); + rc = -ENOMEM; + goto cleanup; + } + drvdata->rsize = sizeof(uclogic_tablet_rdesc_template); + + /* Format fixed report descriptor */ + memcpy(drvdata->rdesc, uclogic_tablet_rdesc_template, + drvdata->rsize); + for (p = drvdata->rdesc; + p <= drvdata->rdesc + drvdata->rsize - 4;) { + if (p[0] == 0xFE && p[1] == 0xED && p[2] == 0x1D && + p[3] < sizeof(params)) { + v = params[p[3]]; + put_unaligned(cpu_to_le32(v), (s32 *)p); + p += 4; + } else { + p++; + } + } + + rc = 0; + +cleanup: + kfree(buf); + return rc; +} + +static int uclogic_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int rc; + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct uclogic_drvdata *drvdata; + + /* + * libinput requires the pad interface to be on a different node + * than the pen, so use QUIRK_MULTI_INPUT for all tablets. + */ + hdev->quirks |= HID_QUIRK_MULTI_INPUT; + hdev->quirks |= HID_QUIRK_NO_EMPTY_INPUT; + + /* Allocate and assign driver data */ + drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL); + if (drvdata == NULL) + return -ENOMEM; + + hid_set_drvdata(hdev, drvdata); + + switch (id->product) { + case USB_DEVICE_ID_HUION_TABLET: + /* If this is the pen interface */ + if (intf->cur_altsetting->desc.bInterfaceNumber == 0) { + rc = uclogic_tablet_enable(hdev); + if (rc) { + hid_err(hdev, "tablet enabling failed\n"); + return rc; + } + drvdata->invert_pen_inrange = true; + } else { + drvdata->ignore_pen_usage = true; + } + break; + } + + rc = hid_parse(hdev); + if (rc) { + hid_err(hdev, "parse failed\n"); + return rc; + } + + rc = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (rc) { + hid_err(hdev, "hw start failed\n"); + return rc; + } + + return 0; +} + +static int uclogic_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); + + if ((drvdata->invert_pen_inrange) && + (report->type == HID_INPUT_REPORT) && + (report->id == UCLOGIC_PEN_REPORT_ID) && + (size >= 2)) + /* Invert the in-range bit */ + data[1] ^= 0x40; + + return 0; +} + static const struct hid_device_id uclogic_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_PF1209) }, @@ -641,6 +956,8 @@ static const struct hid_device_id uclogic_devices[] = { USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850) }, { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60) }, + { HID_USB_DEVICE(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_TABLET) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_HUION_TABLET) }, { } }; MODULE_DEVICE_TABLE(hid, uclogic_devices); @@ -648,8 +965,14 @@ MODULE_DEVICE_TABLE(hid, uclogic_devices); static struct hid_driver uclogic_driver = { .name = "uclogic", .id_table = uclogic_devices, + .probe = uclogic_probe, .report_fixup = uclogic_report_fixup, + .raw_event = uclogic_raw_event, + .input_mapping = uclogic_input_mapping, + .input_configured = uclogic_input_configured, }; module_hid_driver(uclogic_driver); +MODULE_AUTHOR("Martin Rusko"); +MODULE_AUTHOR("Nikolai Kondrashov"); MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-wiimote-modules.c b/drivers/hid/hid-wiimote-modules.c index 6b61f01e01e7..05e23c417d50 100644 --- a/drivers/hid/hid-wiimote-modules.c +++ b/drivers/hid/hid-wiimote-modules.c @@ -203,8 +203,7 @@ static int wiimod_battery_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { - struct wiimote_data *wdata = container_of(psy, struct wiimote_data, - battery); + struct wiimote_data *wdata = power_supply_get_drvdata(psy); int ret = 0, state; unsigned long flags; @@ -238,42 +237,46 @@ static int wiimod_battery_get_property(struct power_supply *psy, static int wiimod_battery_probe(const struct wiimod_ops *ops, struct wiimote_data *wdata) { + struct power_supply_config psy_cfg = { .drv_data = wdata, }; int ret; - wdata->battery.properties = wiimod_battery_props; - wdata->battery.num_properties = ARRAY_SIZE(wiimod_battery_props); - wdata->battery.get_property = wiimod_battery_get_property; - wdata->battery.type = POWER_SUPPLY_TYPE_BATTERY; - wdata->battery.use_for_apm = 0; - wdata->battery.name = kasprintf(GFP_KERNEL, "wiimote_battery_%s", - wdata->hdev->uniq); - if (!wdata->battery.name) + wdata->battery_desc.properties = wiimod_battery_props; + wdata->battery_desc.num_properties = ARRAY_SIZE(wiimod_battery_props); + wdata->battery_desc.get_property = wiimod_battery_get_property; + wdata->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY; + wdata->battery_desc.use_for_apm = 0; + wdata->battery_desc.name = kasprintf(GFP_KERNEL, "wiimote_battery_%s", + wdata->hdev->uniq); + if (!wdata->battery_desc.name) return -ENOMEM; - ret = power_supply_register(&wdata->hdev->dev, &wdata->battery); - if (ret) { + wdata->battery = power_supply_register(&wdata->hdev->dev, + &wdata->battery_desc, + &psy_cfg); + if (IS_ERR(wdata->battery)) { hid_err(wdata->hdev, "cannot register battery device\n"); + ret = PTR_ERR(wdata->battery); goto err_free; } - power_supply_powers(&wdata->battery, &wdata->hdev->dev); + power_supply_powers(wdata->battery, &wdata->hdev->dev); return 0; err_free: - kfree(wdata->battery.name); - wdata->battery.name = NULL; + kfree(wdata->battery_desc.name); + wdata->battery_desc.name = NULL; return ret; } static void wiimod_battery_remove(const struct wiimod_ops *ops, struct wiimote_data *wdata) { - if (!wdata->battery.name) + if (!wdata->battery_desc.name) return; - power_supply_unregister(&wdata->battery); - kfree(wdata->battery.name); - wdata->battery.name = NULL; + power_supply_unregister(wdata->battery); + kfree(wdata->battery_desc.name); + wdata->battery_desc.name = NULL; } static const struct wiimod_ops wiimod_battery = { diff --git a/drivers/hid/hid-wiimote.h b/drivers/hid/hid-wiimote.h index 10934aa129fb..875694d43e4d 100644 --- a/drivers/hid/hid-wiimote.h +++ b/drivers/hid/hid-wiimote.h @@ -147,7 +147,8 @@ struct wiimote_data { struct led_classdev *leds[4]; struct input_dev *accel; struct input_dev *ir; - struct power_supply battery; + struct power_supply *battery; + struct power_supply_desc battery_desc; struct input_dev *mp; struct timer_list timer; struct wiimote_debug *debug; diff --git a/drivers/hid/i2c-hid/i2c-hid.c b/drivers/hid/i2c-hid/i2c-hid.c index d43e967e7533..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; }; @@ -370,7 +373,10 @@ static int i2c_hid_hwreset(struct i2c_client *client) static void i2c_hid_get_input(struct i2c_hid *ihid) { int ret, ret_size; - int size = ihid->bufsize; + int size = le16_to_cpu(ihid->hdesc.wMaxInputLength); + + if (size > ihid->bufsize) + size = ihid->bufsize; ret = i2c_master_recv(ihid->client, ihid->inbuf, size); if (ret != size) { @@ -782,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, - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + 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; } @@ -838,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) { @@ -863,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[] = { @@ -927,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; @@ -952,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; @@ -1014,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; @@ -1039,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; } @@ -1057,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); @@ -1077,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); @@ -1098,17 +1131,19 @@ static int i2c_hid_resume(struct device *dev) static int i2c_hid_runtime_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); + struct i2c_hid *ihid = i2c_get_clientdata(client); i2c_hid_set_power(client, I2C_HID_PWR_SLEEP); - disable_irq(client->irq); + disable_irq(ihid->irq); return 0; } static int i2c_hid_runtime_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); + struct i2c_hid *ihid = i2c_get_clientdata(client); - enable_irq(client->irq); + enable_irq(ihid->irq); i2c_hid_set_power(client, I2C_HID_PWR_ON); return 0; } diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index 0b531c6a76a5..08174d341f4a 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -568,6 +568,12 @@ static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *effect, int type_id; int error; + pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] = 0; + if (old) { + pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] = + pidff->pid_id[effect->id]; + } + switch (effect->type) { case FF_CONSTANT: if (!old) { diff --git a/drivers/hid/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c index 9be99a67bfe2..a775143e6265 100644 --- a/drivers/hid/usbhid/hid-quirks.c +++ b/drivers/hid/usbhid/hid-quirks.c @@ -78,6 +78,13 @@ static const struct hid_blacklist { { USB_VENDOR_ID_ELO, USB_DEVICE_ID_ELO_TS2700, HID_QUIRK_NOGET }, { USB_VENDOR_ID_FORMOSA, USB_DEVICE_ID_FORMOSA_IR_RECEIVER, HID_QUIRK_NO_INIT_REPORTS }, { USB_VENDOR_ID_FREESCALE, USB_DEVICE_ID_FREESCALE_MX28, HID_QUIRK_NOGET }, + { USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0A4A, HID_QUIRK_ALWAYS_POLL }, + { USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0B4A, HID_QUIRK_ALWAYS_POLL }, + { USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE, HID_QUIRK_ALWAYS_POLL }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_C077, HID_QUIRK_ALWAYS_POLL }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOUSE_C01A, HID_QUIRK_ALWAYS_POLL }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOUSE_C05A, HID_QUIRK_ALWAYS_POLL }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOUSE_C06A, HID_QUIRK_ALWAYS_POLL }, { USB_VENDOR_ID_MGE, USB_DEVICE_ID_MGE_UPS, HID_QUIRK_NOGET }, { USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_3, HID_QUIRK_NO_INIT_REPORTS }, { USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_3_JP, HID_QUIRK_NO_INIT_REPORTS }, @@ -91,6 +98,7 @@ static const struct hid_blacklist { { USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN, HID_QUIRK_NO_INIT_REPORTS }, { USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN1, HID_QUIRK_NO_INIT_REPORTS }, { USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN2, HID_QUIRK_NO_INIT_REPORTS }, + { USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_MOUSE_4D22, HID_QUIRK_ALWAYS_POLL }, { USB_VENDOR_ID_PRODIGE, USB_DEVICE_ID_PRODIGE_CORDLESS, HID_QUIRK_NOGET }, { USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3001, HID_QUIRK_NOGET }, { USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3008, HID_QUIRK_NOGET }, @@ -106,12 +114,8 @@ static const struct hid_blacklist { { USB_VENDOR_ID_SYMBOL, USB_DEVICE_ID_SYMBOL_SCANNER_2, HID_QUIRK_NOGET }, { USB_VENDOR_ID_TPV, USB_DEVICE_ID_TPV_OPTICAL_TOUCHSCREEN, HID_QUIRK_NOGET }, { USB_VENDOR_ID_TURBOX, USB_DEVICE_ID_TURBOX_KEYBOARD, HID_QUIRK_NOGET }, - { USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_PF1209, HID_QUIRK_MULTI_INPUT }, - { USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_WP4030U, HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_KNA5, HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_TWA60, HID_QUIRK_MULTI_INPUT }, - { USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_WP5540U, HID_QUIRK_MULTI_INPUT }, - { USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U, HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_MEDIA_TABLET_10_6_INCH, HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_MEDIA_TABLET_14_1_INCH, HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_SIRIUS_BATTERY_FREE_TABLET, HID_QUIRK_MULTI_INPUT }, @@ -127,6 +131,7 @@ static const struct hid_blacklist { { USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X, HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X_2, HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M610X, HID_QUIRK_MULTI_INPUT }, + { USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_PENSKETCH_M912, HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_DUOSENSE, HID_QUIRK_NO_INIT_REPORTS }, { USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD, HID_QUIRK_NO_INIT_REPORTS }, { USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_LTS1, HID_QUIRK_NO_INIT_REPORTS }, diff --git a/drivers/hid/wacom.h b/drivers/hid/wacom.h index 7db432809e9e..024f4d89d579 100644 --- a/drivers/hid/wacom.h +++ b/drivers/hid/wacom.h @@ -119,8 +119,10 @@ struct wacom { u8 img_lum; /* OLED matrix display brightness */ } led; bool led_initialized; - struct power_supply battery; - struct power_supply ac; + struct power_supply *battery; + struct power_supply *ac; + struct power_supply_desc battery_desc; + struct power_supply_desc ac_desc; }; static inline void wacom_schedule_work(struct wacom_wac *wacom_wac) @@ -129,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); @@ -149,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 f0568a7e6de9..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 @@ -944,10 +961,13 @@ static int wacom_battery_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { - struct wacom *wacom = container_of(psy, struct wacom, battery); + struct wacom *wacom = power_supply_get_drvdata(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; @@ -976,7 +998,7 @@ static int wacom_ac_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { - struct wacom *wacom = container_of(psy, struct wacom, ac); + struct wacom *wacom = power_supply_get_drvdata(psy); int ret = 0; switch (psp) { @@ -998,42 +1020,46 @@ static int wacom_ac_get_property(struct power_supply *psy, static int wacom_initialize_battery(struct wacom *wacom) { static atomic_t battery_no = ATOMIC_INIT(0); - int error; + struct power_supply_config psy_cfg = { .drv_data = wacom, }; unsigned long n; if (wacom->wacom_wac.features.quirks & WACOM_QUIRK_BATTERY) { + struct power_supply_desc *bat_desc = &wacom->battery_desc; + struct power_supply_desc *ac_desc = &wacom->ac_desc; n = atomic_inc_return(&battery_no) - 1; - wacom->battery.properties = wacom_battery_props; - wacom->battery.num_properties = ARRAY_SIZE(wacom_battery_props); - wacom->battery.get_property = wacom_battery_get_property; + bat_desc->properties = wacom_battery_props; + bat_desc->num_properties = ARRAY_SIZE(wacom_battery_props); + bat_desc->get_property = wacom_battery_get_property; sprintf(wacom->wacom_wac.bat_name, "wacom_battery_%ld", n); - wacom->battery.name = wacom->wacom_wac.bat_name; - wacom->battery.type = POWER_SUPPLY_TYPE_BATTERY; - wacom->battery.use_for_apm = 0; + bat_desc->name = wacom->wacom_wac.bat_name; + bat_desc->type = POWER_SUPPLY_TYPE_BATTERY; + bat_desc->use_for_apm = 0; - wacom->ac.properties = wacom_ac_props; - wacom->ac.num_properties = ARRAY_SIZE(wacom_ac_props); - wacom->ac.get_property = wacom_ac_get_property; + ac_desc->properties = wacom_ac_props; + ac_desc->num_properties = ARRAY_SIZE(wacom_ac_props); + ac_desc->get_property = wacom_ac_get_property; sprintf(wacom->wacom_wac.ac_name, "wacom_ac_%ld", n); - wacom->ac.name = wacom->wacom_wac.ac_name; - wacom->ac.type = POWER_SUPPLY_TYPE_MAINS; - wacom->ac.use_for_apm = 0; - - error = power_supply_register(&wacom->hdev->dev, - &wacom->battery); - if (error) - return error; - - power_supply_powers(&wacom->battery, &wacom->hdev->dev); - - error = power_supply_register(&wacom->hdev->dev, &wacom->ac); - if (error) { - power_supply_unregister(&wacom->battery); - return error; + ac_desc->name = wacom->wacom_wac.ac_name; + ac_desc->type = POWER_SUPPLY_TYPE_MAINS; + ac_desc->use_for_apm = 0; + + wacom->battery = power_supply_register(&wacom->hdev->dev, + &wacom->battery_desc, &psy_cfg); + if (IS_ERR(wacom->battery)) + return PTR_ERR(wacom->battery); + + power_supply_powers(wacom->battery, &wacom->hdev->dev); + + wacom->ac = power_supply_register(&wacom->hdev->dev, + &wacom->ac_desc, + &psy_cfg); + if (IS_ERR(wacom->ac)) { + power_supply_unregister(wacom->battery); + return PTR_ERR(wacom->ac); } - power_supply_powers(&wacom->ac, &wacom->hdev->dev); + power_supply_powers(wacom->ac, &wacom->hdev->dev); } return 0; @@ -1041,12 +1067,11 @@ static int wacom_initialize_battery(struct wacom *wacom) static void wacom_destroy_battery(struct wacom *wacom) { - if ((wacom->wacom_wac.features.quirks & WACOM_QUIRK_BATTERY) && - wacom->battery.dev) { - power_supply_unregister(&wacom->battery); - wacom->battery.dev = NULL; - power_supply_unregister(&wacom->ac); - wacom->ac.dev = NULL; + if (wacom->battery) { + power_supply_unregister(wacom->battery); + wacom->battery = NULL; + power_supply_unregister(wacom->ac); + wacom->ac = NULL; } } @@ -1313,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 @@ -1373,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; @@ -1412,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); @@ -1446,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; @@ -1462,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)) { @@ -1527,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: @@ -1550,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 1a6507999a65..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,8 +581,9 @@ static int wacom_intuos_inout(struct wacom_wac *wacom) (features->type == CINTIQ && !(data[1] & 0x40))) return 1; - if (features->quirks & WACOM_QUIRK_MULTI_INPUT) - wacom->shared->stylus_in_proximity = true; + wacom->shared->stylus_in_proximity = true; + if (wacom->shared->touch_down) + return 1; /* in Range while exiting */ if (((data[1] & 0xfe) == 0x20) && wacom->reporting_data) { @@ -564,8 +595,7 @@ static int wacom_intuos_inout(struct wacom_wac *wacom) /* Exit report */ if ((data[1] & 0xfe) == 0x80) { - if (features->quirks & WACOM_QUIRK_MULTI_INPUT) - wacom->shared->stylus_in_proximity = false; + wacom->shared->stylus_in_proximity = false; wacom->reporting_data = false; /* don't report exit if we don't know the ID */ @@ -606,8 +636,11 @@ static int wacom_intuos_inout(struct wacom_wac *wacom) } /* don't report other events if we don't know the ID */ - if (!wacom->id[idx]) + if (!wacom->id[idx]) { + /* but reschedule a read of the current tool */ + wacom_intuos_schedule_prox_event(wacom); return 1; + } return 0; } @@ -778,6 +811,11 @@ static int wacom_intuos_irq(struct wacom_wac *wacom) input_report_abs(input, ABS_X, be16_to_cpup((__be16 *)&data[4])); input_report_abs(input, ABS_Y, be16_to_cpup((__be16 *)&data[6])); input_report_abs(input, ABS_Z, be16_to_cpup((__be16 *)&data[8])); + if ((data[2] & 0x07) | data[4] | data[5] | data[6] | data[7] | data[8] | data[9]) { + input_report_abs(input, ABS_MISC, PAD_DEVICE_ID); + } else { + input_report_abs(input, ABS_MISC, 0); + } } else if (features->type == CINTIQ_HYBRID) { /* * Do not send hardware buttons under Android. They @@ -1014,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, @@ -1033,12 +1065,34 @@ static int wacom_intuos_bt_irq(struct wacom_wac *wacom, size_t len) return 0; } +static int wacom_wac_finger_count_touches(struct wacom_wac *wacom) +{ + struct input_dev *input = wacom->input; + unsigned touch_max = wacom->features.touch_max; + int count = 0; + int i; + + /* non-HID_GENERIC single touch input doesn't call this routine */ + if ((touch_max == 1) && (wacom->features.type == HID_GENERIC)) + return wacom->hid_data.tipswitch && + !wacom->shared->stylus_in_proximity; + + for (i = 0; i < input->mt->num_slots; i++) { + struct input_mt_slot *ps = &input->mt->slots[i]; + int id = input_mt_get_value(ps, ABS_MT_TRACKING_ID); + if (id >= 0) + count++; + } + + return count; +} + static int wacom_24hdt_irq(struct wacom_wac *wacom) { struct input_dev *input = wacom->input; unsigned char *data = wacom->data; int i; - int current_num_contacts = 0; + int current_num_contacts = data[61]; int contacts_to_send = 0; int num_contacts_left = 4; /* maximum contacts per packet */ int byte_per_packet = WACOM_BYTES_PER_24HDT_PACKET; @@ -1049,8 +1103,6 @@ static int wacom_24hdt_irq(struct wacom_wac *wacom) num_contacts_left = 10; byte_per_packet = WACOM_BYTES_PER_QHDTHID_PACKET; y_offset = 0; - } else { - current_num_contacts = data[61]; } /* @@ -1093,13 +1145,13 @@ static int wacom_24hdt_irq(struct wacom_wac *wacom) } } } - input_mt_report_pointer_emulation(input, true); + input_mt_sync_frame(input); wacom->num_contacts_left -= contacts_to_send; - if (wacom->num_contacts_left <= 0) + if (wacom->num_contacts_left <= 0) { wacom->num_contacts_left = 0; - - wacom->shared->touch_down = (wacom->num_contacts_left > 0); + wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom); + } return 1; } @@ -1144,13 +1196,13 @@ static int wacom_mt_touch(struct wacom_wac *wacom) input_report_abs(input, ABS_MT_POSITION_Y, y); } } - input_mt_report_pointer_emulation(input, true); + input_mt_sync_frame(input); wacom->num_contacts_left -= contacts_to_send; - if (wacom->num_contacts_left < 0) + if (wacom->num_contacts_left <= 0) { wacom->num_contacts_left = 0; - - wacom->shared->touch_down = (wacom->num_contacts_left > 0); + wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom); + } return 1; } @@ -1158,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++) { @@ -1173,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; } @@ -1188,29 +1238,25 @@ static int wacom_tpc_single_touch(struct wacom_wac *wacom, size_t len) { unsigned char *data = wacom->data; struct input_dev *input = wacom->input; - bool prox; + bool prox = !wacom->shared->stylus_in_proximity; int x = 0, y = 0; if (wacom->features.touch_max > 1 || len > WACOM_PKGLEN_TPC2FG) return 0; - if (!wacom->shared->stylus_in_proximity) { - if (len == WACOM_PKGLEN_TPC1FG) { - prox = data[0] & 0x01; - x = get_unaligned_le16(&data[1]); - y = get_unaligned_le16(&data[3]); - } else if (len == WACOM_PKGLEN_TPC1FG_B) { - prox = data[2] & 0x01; - x = get_unaligned_le16(&data[3]); - y = get_unaligned_le16(&data[5]); - } else { - prox = data[1] & 0x01; - x = le16_to_cpup((__le16 *)&data[2]); - y = le16_to_cpup((__le16 *)&data[4]); - } - } else - /* force touch out when pen is in prox */ - prox = 0; + if (len == WACOM_PKGLEN_TPC1FG) { + prox = prox && (data[0] & 0x01); + x = get_unaligned_le16(&data[1]); + y = get_unaligned_le16(&data[3]); + } else if (len == WACOM_PKGLEN_TPC1FG_B) { + prox = prox && (data[2] & 0x01); + x = get_unaligned_le16(&data[3]); + y = get_unaligned_le16(&data[5]); + } else { + prox = prox && (data[1] & 0x01); + x = le16_to_cpup((__le16 *)&data[2]); + y = le16_to_cpup((__le16 *)&data[4]); + } if (prox) { input_report_abs(input, ABS_X, x); @@ -1511,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) { @@ -1548,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, @@ -1638,12 +1661,13 @@ static int wacom_bpt_touch(struct wacom_wac *wacom) } } - input_mt_report_pointer_emulation(input, true); + input_mt_sync_frame(input); input_report_key(pad_input, BTN_LEFT, (data[1] & 0x08) != 0); input_report_key(pad_input, BTN_FORWARD, (data[1] & 0x04) != 0); input_report_key(pad_input, BTN_BACK, (data[1] & 0x02) != 0); input_report_key(pad_input, BTN_RIGHT, (data[1] & 0x01) != 0); + wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom); return 1; } @@ -1728,7 +1752,8 @@ static int wacom_bpt3_touch(struct wacom_wac *wacom) wacom_bpt3_button_msg(wacom, data + offset); } - input_mt_report_pointer_emulation(input, true); + input_mt_sync_frame(input); + wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom); return 1; } @@ -1740,20 +1765,9 @@ static int wacom_bpt_pen(struct wacom_wac *wacom) unsigned char *data = wacom->data; int prox = 0, x = 0, y = 0, p = 0, d = 0, pen = 0, btn1 = 0, btn2 = 0; - if (data[0] != WACOM_REPORT_PENABLED && data[0] != WACOM_REPORT_USB) + if (data[0] != WACOM_REPORT_PENABLED) return 0; - if (data[0] == WACOM_REPORT_USB) { - if (features->type == INTUOSHT && - wacom->shared->touch_input && - features->touch_max) { - input_report_switch(wacom->shared->touch_input, - SW_MUTE_DEVICE, data[8] & 0x40); - input_sync(wacom->shared->touch_input); - } - return 0; - } - prox = (data[1] & 0x20) == 0x20; /* @@ -1766,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]); @@ -1792,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); @@ -1803,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 */ @@ -1826,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; @@ -1836,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 && @@ -1848,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; } @@ -1944,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; @@ -1959,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: @@ -2031,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) { @@ -2300,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; } @@ -2725,9 +2868,9 @@ static const struct wacom_features wacom_features_0xF6 = .oVid = USB_VENDOR_ID_WACOM, .oPid = 0xf8, .touch_max = 10, .check_for_hid_type = true, .hid_type = HID_TYPE_USBNONE }; static const struct wacom_features wacom_features_0x32A = - { "Wacom Cintiq 27QHD", 119740, 67520, 2047, - 63, WACOM_27QHD, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, - WACOM_27QHD, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES }; + { "Wacom Cintiq 27QHD", 119740, 67520, 2047, 63, + WACOM_27QHD, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, + WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET }; static const struct wacom_features wacom_features_0x32B = { "Wacom Cintiq 27QHD touch", 119740, 67520, 2047, 63, WACOM_27QHD, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, @@ -2749,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 }; @@ -2953,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, @@ -2969,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 @@ -3101,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) }, @@ -3113,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; |