Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid
Pull HID updates from Jiri Kosina: "Some highlights: - hid-sony improvements of Sixaxis device support by Antonio Ospite - hid-hyperv driven devices can now be used as wakeup source, by Dexuan Cui - hid-lenovo driver is now more generic and supports more devices, by Jamie Lentin - hid-huion now supports wider range of tablets, by Nikolai Kondrashov - other various unsorted fixes and device ID additions" * 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid: (30 commits) HID: hyperv: register as a wakeup source HID: sony: Default initialize all elements of the LED max_brightness array to 1 HID: huion: Fix sparse warnings HID: usbhid: Use flag HID_DISCONNECTED when a usb device is removed HID: ignore jabra gn9350e HID: cp2112: add I2C mode HID: use multi input quirk for 22b9:2968 HID: rmi: only bind the hid-rmi driver to the mouse interface of composite USB devices HID: rmi: check that report ids exist in the report_id_hash before accessing their size HID: lenovo: Add support for Compact (BT|USB) keyboard HID: lenovo: Don't call function in condition, show error codes HID: lenovo: Prepare support for adding other devices HID: lenovo: Rename hid-lenovo-tpkbd to hid-lenovo HID: huion: Handle tablets with UC-Logic vendor ID HID: huion: Switch to generating report descriptor HID: huion: Don't ignore other interfaces HID: huion: Use "tablet" instead of specific model HID: add quirk for 0x04d9:0xa096 device HID: i2c-hid: call the hid driver's suspend and resume callbacks HID: rmi: change logging level of log messages related to unexpected reports ...
This commit is contained in:
commit
172bfe09dc
@ -4,18 +4,21 @@ Contact: linux-input@vger.kernel.org
|
||||
Description: This controls if mouse clicks should be generated if the trackpoint is quickly pressed. How fast this press has to be
|
||||
is being controlled by press_speed.
|
||||
Values are 0 or 1.
|
||||
Applies to Thinkpad USB Keyboard with TrackPoint.
|
||||
|
||||
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/dragging
|
||||
Date: July 2011
|
||||
Contact: linux-input@vger.kernel.org
|
||||
Description: If this setting is enabled, it is possible to do dragging by pressing the trackpoint. This requires press_to_select to be enabled.
|
||||
Values are 0 or 1.
|
||||
Applies to Thinkpad USB Keyboard with TrackPoint.
|
||||
|
||||
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/release_to_select
|
||||
Date: July 2011
|
||||
Contact: linux-input@vger.kernel.org
|
||||
Description: For details regarding this setting please refer to http://www.pc.ibm.com/ww/healthycomputing/trkpntb.html
|
||||
Values are 0 or 1.
|
||||
Applies to Thinkpad USB Keyboard with TrackPoint.
|
||||
|
||||
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/select_right
|
||||
Date: July 2011
|
||||
@ -23,16 +26,25 @@ Contact: linux-input@vger.kernel.org
|
||||
Description: This setting controls if the mouse click events generated by pressing the trackpoint (if press_to_select is enabled) generate
|
||||
a left or right mouse button click.
|
||||
Values are 0 or 1.
|
||||
Applies to Thinkpad USB Keyboard with TrackPoint.
|
||||
|
||||
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/sensitivity
|
||||
Date: July 2011
|
||||
Contact: linux-input@vger.kernel.org
|
||||
Description: This file contains the trackpoint sensitivity.
|
||||
Values are decimal integers from 1 (lowest sensitivity) to 255 (highest sensitivity).
|
||||
Applies to Thinkpad USB Keyboard with TrackPoint.
|
||||
|
||||
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/press_speed
|
||||
Date: July 2011
|
||||
Contact: linux-input@vger.kernel.org
|
||||
Description: This setting controls how fast the trackpoint needs to be pressed to generate a mouse click if press_to_select is enabled.
|
||||
Values are decimal integers from 1 (slowest) to 255 (fastest).
|
||||
Applies to Thinkpad USB Keyboard with TrackPoint.
|
||||
|
||||
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/fn_lock
|
||||
Date: July 2014
|
||||
Contact: linux-input@vger.kernel.org
|
||||
Description: This setting controls whether Fn Lock is enabled on the keyboard (i.e. if F1 is Mute or F1)
|
||||
Values are 0 or 1
|
||||
Applies to ThinkPad Compact (USB|Bluetooth) Keyboard with TrackPoint.
|
@ -331,18 +331,20 @@ config HID_LCPOWER
|
||||
---help---
|
||||
Support for LC-Power RC1000MCE RF remote control.
|
||||
|
||||
config HID_LENOVO_TPKBD
|
||||
tristate "Lenovo ThinkPad USB Keyboard with TrackPoint"
|
||||
config HID_LENOVO
|
||||
tristate "Lenovo / Thinkpad devices"
|
||||
depends on HID
|
||||
select NEW_LEDS
|
||||
select LEDS_CLASS
|
||||
---help---
|
||||
Support for the Lenovo ThinkPad USB Keyboard with TrackPoint.
|
||||
Support for Lenovo devices that are not fully compliant with HID standard.
|
||||
|
||||
Say Y here if you have a Lenovo ThinkPad USB Keyboard with TrackPoint
|
||||
and would like to use device-specific features like changing the
|
||||
sensitivity of the trackpoint, using the microphone mute button or
|
||||
controlling the mute and microphone mute LEDs.
|
||||
Say Y if you want support for the non-compliant features of the Lenovo
|
||||
Thinkpad standalone keyboards, e.g:
|
||||
- ThinkPad USB Keyboard with TrackPoint (supports extra LEDs and trackpoint
|
||||
configuration)
|
||||
- ThinkPad Compact Bluetooth Keyboard with TrackPoint (supports Fn keys)
|
||||
- ThinkPad Compact USB Keyboard with TrackPoint (supports Fn keys)
|
||||
|
||||
config HID_LOGITECH
|
||||
tristate "Logitech devices" if EXPERT
|
||||
@ -785,7 +787,7 @@ config HID_XINMO
|
||||
depends on HID
|
||||
---help---
|
||||
Support for Xin-Mo devices that are not fully compliant with the HID
|
||||
standard. Currently only supports the Xin-Mo Dual Arcade. Say Y here
|
||||
standard. Currently only supports the Xin-Mo Dual Arcade. Say Y here
|
||||
if you have a Xin-Mo Dual Arcade controller.
|
||||
|
||||
config HID_ZEROPLUS
|
||||
|
@ -59,7 +59,7 @@ obj-$(CONFIG_HID_KENSINGTON) += hid-kensington.o
|
||||
obj-$(CONFIG_HID_KEYTOUCH) += hid-keytouch.o
|
||||
obj-$(CONFIG_HID_KYE) += hid-kye.o
|
||||
obj-$(CONFIG_HID_LCPOWER) += hid-lcpower.o
|
||||
obj-$(CONFIG_HID_LENOVO_TPKBD) += hid-lenovo-tpkbd.o
|
||||
obj-$(CONFIG_HID_LENOVO) += hid-lenovo.o
|
||||
obj-$(CONFIG_HID_LOGITECH) += hid-logitech.o
|
||||
obj-$(CONFIG_HID_LOGITECH_DJ) += hid-logitech-dj.o
|
||||
obj-$(CONFIG_HID_MAGICMOUSE) += hid-magicmouse.o
|
||||
|
@ -783,7 +783,9 @@ static int hid_scan_report(struct hid_device *hid)
|
||||
* Vendor specific handlings
|
||||
*/
|
||||
if ((hid->vendor == USB_VENDOR_ID_SYNAPTICS) &&
|
||||
(hid->group == HID_GROUP_GENERIC))
|
||||
(hid->group == HID_GROUP_GENERIC) &&
|
||||
/* only bind to the mouse interface of composite USB devices */
|
||||
(hid->bus != BUS_USB || hid->type == HID_TYPE_USBMOUSE))
|
||||
/* hid-rmi should take care of them, not hid-generic */
|
||||
hid->group = HID_GROUP_RMI;
|
||||
|
||||
@ -1782,7 +1784,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A070) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A072) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A081) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_580) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_TABLET) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_JESS2, USB_DEVICE_ID_JESS2_COLOR_RUMBLE_PAD) },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ION, USB_DEVICE_ID_ICADE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_KENSINGTON, USB_DEVICE_ID_KS_SLIMBLADE) },
|
||||
@ -1796,8 +1798,10 @@ static const struct hid_device_id hid_have_special_driver[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M610X) },
|
||||
{ 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_TPKBD)
|
||||
#if IS_ENABLED(CONFIG_HID_LENOVO)
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CUSBKBD) },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CBTKBD) },
|
||||
#endif
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER) },
|
||||
@ -2266,6 +2270,7 @@ static const struct hid_device_id hid_ignore_list[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_IMATION, USB_DEVICE_ID_DISC_STAKKA) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_JABRA, USB_DEVICE_ID_JABRA_SPEAK_410) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_JABRA, USB_DEVICE_ID_JABRA_SPEAK_510) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_JABRA, USB_DEVICE_ID_JABRA_GN9350E) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_KBGEAR, USB_DEVICE_ID_KBGEAR_JAMSTUDIO) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_KWORLD, USB_DEVICE_ID_KWORLD_RADIO_FM700) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_GPEN_560) },
|
||||
|
@ -240,8 +240,6 @@ static int cp2112_gpio_direction_output(struct gpio_chip *chip,
|
||||
u8 buf[5];
|
||||
int ret;
|
||||
|
||||
cp2112_gpio_set(chip, offset, value);
|
||||
|
||||
ret = hid_hw_raw_request(hdev, CP2112_GPIO_CONFIG, buf,
|
||||
sizeof(buf), HID_FEATURE_REPORT,
|
||||
HID_REQ_GET_REPORT);
|
||||
@ -260,6 +258,12 @@ static int cp2112_gpio_direction_output(struct gpio_chip *chip,
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set gpio value when output direction is already set,
|
||||
* as specified in AN495, Rev. 0.2, cpt. 4.4
|
||||
*/
|
||||
cp2112_gpio_set(chip, offset, value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -425,6 +429,105 @@ static int cp2112_write_req(void *buf, u8 slave_address, u8 command, u8 *data,
|
||||
return data_length + 4;
|
||||
}
|
||||
|
||||
static int cp2112_i2c_write_req(void *buf, u8 slave_address, u8 *data,
|
||||
u8 data_length)
|
||||
{
|
||||
struct cp2112_write_req_report *report = buf;
|
||||
|
||||
if (data_length > sizeof(report->data))
|
||||
return -EINVAL;
|
||||
|
||||
report->report = CP2112_DATA_WRITE_REQUEST;
|
||||
report->slave_address = slave_address << 1;
|
||||
report->length = data_length;
|
||||
memcpy(report->data, data, data_length);
|
||||
return data_length + 3;
|
||||
}
|
||||
|
||||
static int cp2112_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
|
||||
int num)
|
||||
{
|
||||
struct cp2112_device *dev = (struct cp2112_device *)adap->algo_data;
|
||||
struct hid_device *hdev = dev->hdev;
|
||||
u8 buf[64];
|
||||
ssize_t count;
|
||||
unsigned int retries;
|
||||
int ret;
|
||||
|
||||
hid_dbg(hdev, "I2C %d messages\n", num);
|
||||
|
||||
if (num != 1) {
|
||||
hid_err(hdev,
|
||||
"Multi-message I2C transactions not supported\n");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
if (msgs->flags & I2C_M_RD)
|
||||
count = cp2112_read_req(buf, msgs->addr, msgs->len);
|
||||
else
|
||||
count = cp2112_i2c_write_req(buf, msgs->addr, msgs->buf,
|
||||
msgs->len);
|
||||
|
||||
if (count < 0)
|
||||
return count;
|
||||
|
||||
ret = hid_hw_power(hdev, PM_HINT_FULLON);
|
||||
if (ret < 0) {
|
||||
hid_err(hdev, "power management error: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = cp2112_hid_output(hdev, buf, count, HID_OUTPUT_REPORT);
|
||||
if (ret < 0) {
|
||||
hid_warn(hdev, "Error starting transaction: %d\n", ret);
|
||||
goto power_normal;
|
||||
}
|
||||
|
||||
for (retries = 0; retries < XFER_STATUS_RETRIES; ++retries) {
|
||||
ret = cp2112_xfer_status(dev);
|
||||
if (-EBUSY == ret)
|
||||
continue;
|
||||
if (ret < 0)
|
||||
goto power_normal;
|
||||
break;
|
||||
}
|
||||
|
||||
if (XFER_STATUS_RETRIES <= retries) {
|
||||
hid_warn(hdev, "Transfer timed out, cancelling.\n");
|
||||
buf[0] = CP2112_CANCEL_TRANSFER;
|
||||
buf[1] = 0x01;
|
||||
|
||||
ret = cp2112_hid_output(hdev, buf, 2, HID_OUTPUT_REPORT);
|
||||
if (ret < 0)
|
||||
hid_warn(hdev, "Error cancelling transaction: %d\n",
|
||||
ret);
|
||||
|
||||
ret = -ETIMEDOUT;
|
||||
goto power_normal;
|
||||
}
|
||||
|
||||
if (!(msgs->flags & I2C_M_RD))
|
||||
goto finish;
|
||||
|
||||
ret = cp2112_read(dev, msgs->buf, msgs->len);
|
||||
if (ret < 0)
|
||||
goto power_normal;
|
||||
if (ret != msgs->len) {
|
||||
hid_warn(hdev, "short read: %d < %d\n", ret, msgs->len);
|
||||
ret = -EIO;
|
||||
goto power_normal;
|
||||
}
|
||||
|
||||
finish:
|
||||
/* return the number of transferred messages */
|
||||
ret = 1;
|
||||
|
||||
power_normal:
|
||||
hid_hw_power(hdev, PM_HINT_NORMAL);
|
||||
hid_dbg(hdev, "I2C transfer finished: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cp2112_xfer(struct i2c_adapter *adap, u16 addr,
|
||||
unsigned short flags, char read_write, u8 command,
|
||||
int size, union i2c_smbus_data *data)
|
||||
@ -591,7 +694,8 @@ static int cp2112_xfer(struct i2c_adapter *adap, u16 addr,
|
||||
|
||||
static u32 cp2112_functionality(struct i2c_adapter *adap)
|
||||
{
|
||||
return I2C_FUNC_SMBUS_BYTE |
|
||||
return I2C_FUNC_I2C |
|
||||
I2C_FUNC_SMBUS_BYTE |
|
||||
I2C_FUNC_SMBUS_BYTE_DATA |
|
||||
I2C_FUNC_SMBUS_WORD_DATA |
|
||||
I2C_FUNC_SMBUS_BLOCK_DATA |
|
||||
@ -601,6 +705,7 @@ static u32 cp2112_functionality(struct i2c_adapter *adap)
|
||||
}
|
||||
|
||||
static const struct i2c_algorithm smbus_algorithm = {
|
||||
.master_xfer = cp2112_i2c_xfer,
|
||||
.smbus_xfer = cp2112_xfer,
|
||||
.functionality = cp2112_functionality,
|
||||
};
|
||||
|
@ -2,6 +2,7 @@
|
||||
* HID driver for Huion devices not fully compliant with HID standard
|
||||
*
|
||||
* Copyright (c) 2013 Martin Rusko
|
||||
* Copyright (c) 2014 Nikolai Kondrashov
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -15,67 +16,89 @@
|
||||
#include <linux/hid.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/usb.h>
|
||||
#include <asm/unaligned.h>
|
||||
#include "usbhid/usbhid.h"
|
||||
|
||||
#include "hid-ids.h"
|
||||
|
||||
/* Original Huion 580 report descriptor size */
|
||||
#define HUION_580_RDESC_ORIG_SIZE 177
|
||||
/* Report descriptor template placeholder head */
|
||||
#define HUION_PH_HEAD 0xFE, 0xED, 0x1D
|
||||
|
||||
/* Fixed Huion 580 report descriptor */
|
||||
static __u8 huion_580_rdesc_fixed[] = {
|
||||
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), */
|
||||
0x46, 0x40, 0x1F, /* Physical Maximum (8000), */
|
||||
0x26, 0x00, 0x7D, /* Logical Maximum (32000), */
|
||||
0x81, 0x02, /* Input (Variable), */
|
||||
0x09, 0x31, /* Usage (Y), */
|
||||
0x46, 0x88, 0x13, /* Physical Maximum (5000), */
|
||||
0x26, 0x20, 0x4E, /* Logical Maximum (20000), */
|
||||
0x81, 0x02, /* Input (Variable), */
|
||||
0xB4, /* Pop, */
|
||||
0x09, 0x30, /* Usage (Tip Pressure), */
|
||||
0x26, 0xFF, 0x07, /* Logical Maximum (2047), */
|
||||
0x81, 0x02, /* Input (Variable), */
|
||||
0xC0, /* End Collection, */
|
||||
0xC0 /* End Collection */
|
||||
/* 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 */
|
||||
};
|
||||
|
||||
/* 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_580:
|
||||
if (*rsize == HUION_580_RDESC_ORIG_SIZE) {
|
||||
rdesc = huion_580_rdesc_fixed;
|
||||
*rsize = sizeof(huion_580_rdesc_fixed);
|
||||
case USB_DEVICE_ID_HUION_TABLET:
|
||||
if (drvdata->rdesc != NULL) {
|
||||
rdesc = drvdata->rdesc;
|
||||
*rsize = drvdata->rsize;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -83,82 +106,144 @@ static __u8 *huion_report_fixup(struct hid_device *hdev, __u8 *rdesc,
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable fully-functional tablet mode by reading special string
|
||||
* descriptor.
|
||||
* Enable fully-functional tablet mode and determine device parameters.
|
||||
*
|
||||
* @hdev: HID device
|
||||
*
|
||||
* The specific string descriptor and data were discovered by sniffing
|
||||
* the Windows driver traffic.
|
||||
*/
|
||||
static int huion_tablet_enable(struct hid_device *hdev)
|
||||
{
|
||||
int rc;
|
||||
char buf[22];
|
||||
struct usb_device *usb_dev = hid_to_usb_dev(hdev);
|
||||
struct huion_drvdata *drvdata = hid_get_drvdata(hdev);
|
||||
__le16 buf[6];
|
||||
|
||||
rc = usb_string(hid_to_usb_dev(hdev), 0x64, buf, sizeof(buf));
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
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, sizeof(buf),
|
||||
USB_CTRL_GET_TIMEOUT);
|
||||
if (rc == -EPIPE)
|
||||
hid_warn(hdev, "device parameters not found\n");
|
||||
else if (rc < 0)
|
||||
hid_warn(hdev, "failed to get device parameters: %d\n", rc);
|
||||
else if (rc != sizeof(buf))
|
||||
hid_warn(hdev, "invalid device parameters\n");
|
||||
else {
|
||||
s32 params[HUION_PH_ID_NUM];
|
||||
s32 resolution;
|
||||
__u8 *p;
|
||||
s32 v;
|
||||
|
||||
/* Extract device parameters */
|
||||
params[HUION_PH_ID_X_LM] = le16_to_cpu(buf[1]);
|
||||
params[HUION_PH_ID_Y_LM] = le16_to_cpu(buf[2]);
|
||||
params[HUION_PH_ID_PRESSURE_LM] = le16_to_cpu(buf[4]);
|
||||
resolution = le16_to_cpu(buf[5]);
|
||||
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");
|
||||
return -ENOMEM;
|
||||
}
|
||||
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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int huion_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
int rc;
|
||||
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
|
||||
struct huion_drvdata *drvdata;
|
||||
|
||||
/* Ignore interfaces 1 (mouse) and 2 (keyboard) for Huion 580 tablet,
|
||||
* as they are not used
|
||||
*/
|
||||
switch (id->product) {
|
||||
case USB_DEVICE_ID_HUION_580:
|
||||
if (intf->cur_altsetting->desc.bInterfaceNumber != 0x00)
|
||||
return -ENODEV;
|
||||
break;
|
||||
}
|
||||
|
||||
ret = hid_parse(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "parse failed\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hw start failed\n");
|
||||
goto err;
|
||||
/* 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_580:
|
||||
ret = huion_tablet_enable(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "tablet enabling failed\n");
|
||||
goto enabling_err;
|
||||
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;
|
||||
enabling_err:
|
||||
hid_hw_stop(hdev);
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int huion_raw_event(struct hid_device *hdev, struct hid_report *report,
|
||||
u8 *data, int size)
|
||||
{
|
||||
/* If this is a pen input report then invert the in-range bit */
|
||||
if (report->type == HID_INPUT_REPORT && report->id == 0x07 && size >= 2)
|
||||
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_580) },
|
||||
{ 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);
|
||||
|
@ -308,6 +308,9 @@ static void mousevsc_on_receive(struct hv_device *device,
|
||||
memcpy(input_dev->input_buf, input_report->buffer, len);
|
||||
hid_input_report(input_dev->hid_device, HID_INPUT_REPORT,
|
||||
input_dev->input_buf, len, 1);
|
||||
|
||||
pm_wakeup_event(&input_dev->device->device, 0);
|
||||
|
||||
break;
|
||||
default:
|
||||
pr_err("unsupported hid msg type - type %d len %d",
|
||||
@ -549,6 +552,8 @@ static int mousevsc_probe(struct hv_device *device,
|
||||
goto probe_err2;
|
||||
}
|
||||
|
||||
device_init_wakeup(&device->device, true);
|
||||
|
||||
input_dev->connected = true;
|
||||
input_dev->init_complete = true;
|
||||
|
||||
@ -571,6 +576,7 @@ static int mousevsc_remove(struct hv_device *dev)
|
||||
{
|
||||
struct mousevsc_dev *input_dev = hv_get_drvdata(dev);
|
||||
|
||||
device_init_wakeup(&dev->device, false);
|
||||
vmbus_close(dev->channel);
|
||||
hid_hw_stop(input_dev->hid_device);
|
||||
hid_destroy_device(input_dev->hid_device);
|
||||
|
@ -448,7 +448,7 @@
|
||||
#define USB_DEVICE_ID_UGCI_FIGHTING 0x0030
|
||||
|
||||
#define USB_VENDOR_ID_HUION 0x256c
|
||||
#define USB_DEVICE_ID_HUION_580 0x006e
|
||||
#define USB_DEVICE_ID_HUION_TABLET 0x006e
|
||||
|
||||
#define USB_VENDOR_ID_IDEACOM 0x1cb6
|
||||
#define USB_DEVICE_ID_IDEACOM_IDC6650 0x6650
|
||||
@ -479,6 +479,7 @@
|
||||
#define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A070 0xa070
|
||||
#define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A072 0xa072
|
||||
#define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A081 0xa081
|
||||
#define USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD_A096 0xa096
|
||||
|
||||
#define USB_VENDOR_ID_IMATION 0x0718
|
||||
#define USB_DEVICE_ID_DISC_STAKKA 0xd000
|
||||
@ -489,6 +490,7 @@
|
||||
#define USB_VENDOR_ID_JABRA 0x0b0e
|
||||
#define USB_DEVICE_ID_JABRA_SPEAK_410 0x0412
|
||||
#define USB_DEVICE_ID_JABRA_SPEAK_510 0x0420
|
||||
#define USB_DEVICE_ID_JABRA_GN9350E 0x9350
|
||||
|
||||
#define USB_VENDOR_ID_JESS 0x0c45
|
||||
#define USB_DEVICE_ID_JESS_YUREX 0x1010
|
||||
@ -561,6 +563,8 @@
|
||||
|
||||
#define USB_VENDOR_ID_LENOVO 0x17ef
|
||||
#define USB_DEVICE_ID_LENOVO_TPKBD 0x6009
|
||||
#define USB_DEVICE_ID_LENOVO_CUSBKBD 0x6047
|
||||
#define USB_DEVICE_ID_LENOVO_CBTKBD 0x6048
|
||||
|
||||
#define USB_VENDOR_ID_LG 0x1fd2
|
||||
#define USB_DEVICE_ID_LG_MULTITOUCH 0x0064
|
||||
|
@ -1,462 +0,0 @@
|
||||
/*
|
||||
* HID driver for Lenovo ThinkPad USB Keyboard with TrackPoint
|
||||
*
|
||||
* Copyright (c) 2012 Bernhard Seibold
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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/module.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/leds.h>
|
||||
|
||||
#include "hid-ids.h"
|
||||
|
||||
/* This is only used for the trackpoint part of the driver, hence _tp */
|
||||
struct tpkbd_data_pointer {
|
||||
int led_state;
|
||||
struct led_classdev led_mute;
|
||||
struct led_classdev led_micmute;
|
||||
int press_to_select;
|
||||
int dragging;
|
||||
int release_to_select;
|
||||
int select_right;
|
||||
int sensitivity;
|
||||
int press_speed;
|
||||
};
|
||||
|
||||
#define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
|
||||
|
||||
static int tpkbd_input_mapping(struct hid_device *hdev,
|
||||
struct hid_input *hi, struct hid_field *field,
|
||||
struct hid_usage *usage, unsigned long **bit, int *max)
|
||||
{
|
||||
if (usage->hid == (HID_UP_BUTTON | 0x0010)) {
|
||||
/* mark the device as pointer */
|
||||
hid_set_drvdata(hdev, (void *)1);
|
||||
map_key_clear(KEY_MICMUTE);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#undef map_key_clear
|
||||
|
||||
static int tpkbd_features_set(struct hid_device *hdev)
|
||||
{
|
||||
struct hid_report *report;
|
||||
struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
|
||||
|
||||
report = hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[4];
|
||||
|
||||
report->field[0]->value[0] = data_pointer->press_to_select ? 0x01 : 0x02;
|
||||
report->field[0]->value[0] |= data_pointer->dragging ? 0x04 : 0x08;
|
||||
report->field[0]->value[0] |= data_pointer->release_to_select ? 0x10 : 0x20;
|
||||
report->field[0]->value[0] |= data_pointer->select_right ? 0x80 : 0x40;
|
||||
report->field[1]->value[0] = 0x03; // unknown setting, imitate windows driver
|
||||
report->field[2]->value[0] = data_pointer->sensitivity;
|
||||
report->field[3]->value[0] = data_pointer->press_speed;
|
||||
|
||||
hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t pointer_press_to_select_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->press_to_select);
|
||||
}
|
||||
|
||||
static ssize_t pointer_press_to_select_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
|
||||
int value;
|
||||
|
||||
if (kstrtoint(buf, 10, &value))
|
||||
return -EINVAL;
|
||||
if (value < 0 || value > 1)
|
||||
return -EINVAL;
|
||||
|
||||
data_pointer->press_to_select = value;
|
||||
tpkbd_features_set(hdev);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t pointer_dragging_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->dragging);
|
||||
}
|
||||
|
||||
static ssize_t pointer_dragging_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
|
||||
int value;
|
||||
|
||||
if (kstrtoint(buf, 10, &value))
|
||||
return -EINVAL;
|
||||
if (value < 0 || value > 1)
|
||||
return -EINVAL;
|
||||
|
||||
data_pointer->dragging = value;
|
||||
tpkbd_features_set(hdev);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t pointer_release_to_select_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->release_to_select);
|
||||
}
|
||||
|
||||
static ssize_t pointer_release_to_select_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
|
||||
int value;
|
||||
|
||||
if (kstrtoint(buf, 10, &value))
|
||||
return -EINVAL;
|
||||
if (value < 0 || value > 1)
|
||||
return -EINVAL;
|
||||
|
||||
data_pointer->release_to_select = value;
|
||||
tpkbd_features_set(hdev);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t pointer_select_right_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->select_right);
|
||||
}
|
||||
|
||||
static ssize_t pointer_select_right_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
|
||||
int value;
|
||||
|
||||
if (kstrtoint(buf, 10, &value))
|
||||
return -EINVAL;
|
||||
if (value < 0 || value > 1)
|
||||
return -EINVAL;
|
||||
|
||||
data_pointer->select_right = value;
|
||||
tpkbd_features_set(hdev);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t pointer_sensitivity_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n",
|
||||
data_pointer->sensitivity);
|
||||
}
|
||||
|
||||
static ssize_t pointer_sensitivity_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
|
||||
int value;
|
||||
|
||||
if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
|
||||
return -EINVAL;
|
||||
|
||||
data_pointer->sensitivity = value;
|
||||
tpkbd_features_set(hdev);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t pointer_press_speed_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n",
|
||||
data_pointer->press_speed);
|
||||
}
|
||||
|
||||
static ssize_t pointer_press_speed_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
|
||||
int value;
|
||||
|
||||
if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
|
||||
return -EINVAL;
|
||||
|
||||
data_pointer->press_speed = value;
|
||||
tpkbd_features_set(hdev);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct device_attribute dev_attr_pointer_press_to_select =
|
||||
__ATTR(press_to_select, S_IWUSR | S_IRUGO,
|
||||
pointer_press_to_select_show,
|
||||
pointer_press_to_select_store);
|
||||
|
||||
static struct device_attribute dev_attr_pointer_dragging =
|
||||
__ATTR(dragging, S_IWUSR | S_IRUGO,
|
||||
pointer_dragging_show,
|
||||
pointer_dragging_store);
|
||||
|
||||
static struct device_attribute dev_attr_pointer_release_to_select =
|
||||
__ATTR(release_to_select, S_IWUSR | S_IRUGO,
|
||||
pointer_release_to_select_show,
|
||||
pointer_release_to_select_store);
|
||||
|
||||
static struct device_attribute dev_attr_pointer_select_right =
|
||||
__ATTR(select_right, S_IWUSR | S_IRUGO,
|
||||
pointer_select_right_show,
|
||||
pointer_select_right_store);
|
||||
|
||||
static struct device_attribute dev_attr_pointer_sensitivity =
|
||||
__ATTR(sensitivity, S_IWUSR | S_IRUGO,
|
||||
pointer_sensitivity_show,
|
||||
pointer_sensitivity_store);
|
||||
|
||||
static struct device_attribute dev_attr_pointer_press_speed =
|
||||
__ATTR(press_speed, S_IWUSR | S_IRUGO,
|
||||
pointer_press_speed_show,
|
||||
pointer_press_speed_store);
|
||||
|
||||
static struct attribute *tpkbd_attributes_pointer[] = {
|
||||
&dev_attr_pointer_press_to_select.attr,
|
||||
&dev_attr_pointer_dragging.attr,
|
||||
&dev_attr_pointer_release_to_select.attr,
|
||||
&dev_attr_pointer_select_right.attr,
|
||||
&dev_attr_pointer_sensitivity.attr,
|
||||
&dev_attr_pointer_press_speed.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group tpkbd_attr_group_pointer = {
|
||||
.attrs = tpkbd_attributes_pointer,
|
||||
};
|
||||
|
||||
static enum led_brightness tpkbd_led_brightness_get(
|
||||
struct led_classdev *led_cdev)
|
||||
{
|
||||
struct device *dev = led_cdev->dev->parent;
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
|
||||
int led_nr = 0;
|
||||
|
||||
if (led_cdev == &data_pointer->led_micmute)
|
||||
led_nr = 1;
|
||||
|
||||
return data_pointer->led_state & (1 << led_nr)
|
||||
? LED_FULL
|
||||
: LED_OFF;
|
||||
}
|
||||
|
||||
static void tpkbd_led_brightness_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct device *dev = led_cdev->dev->parent;
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
|
||||
struct hid_report *report;
|
||||
int led_nr = 0;
|
||||
|
||||
if (led_cdev == &data_pointer->led_micmute)
|
||||
led_nr = 1;
|
||||
|
||||
if (value == LED_OFF)
|
||||
data_pointer->led_state &= ~(1 << led_nr);
|
||||
else
|
||||
data_pointer->led_state |= 1 << led_nr;
|
||||
|
||||
report = hdev->report_enum[HID_OUTPUT_REPORT].report_id_hash[3];
|
||||
report->field[0]->value[0] = (data_pointer->led_state >> 0) & 1;
|
||||
report->field[0]->value[1] = (data_pointer->led_state >> 1) & 1;
|
||||
hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
|
||||
}
|
||||
|
||||
static int tpkbd_probe_tp(struct hid_device *hdev)
|
||||
{
|
||||
struct device *dev = &hdev->dev;
|
||||
struct tpkbd_data_pointer *data_pointer;
|
||||
size_t name_sz = strlen(dev_name(dev)) + 16;
|
||||
char *name_mute, *name_micmute;
|
||||
int i;
|
||||
|
||||
/* Validate required reports. */
|
||||
for (i = 0; i < 4; i++) {
|
||||
if (!hid_validate_values(hdev, HID_FEATURE_REPORT, 4, i, 1))
|
||||
return -ENODEV;
|
||||
}
|
||||
if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 3, 0, 2))
|
||||
return -ENODEV;
|
||||
|
||||
if (sysfs_create_group(&hdev->dev.kobj,
|
||||
&tpkbd_attr_group_pointer)) {
|
||||
hid_warn(hdev, "Could not create sysfs group\n");
|
||||
}
|
||||
|
||||
data_pointer = devm_kzalloc(&hdev->dev,
|
||||
sizeof(struct tpkbd_data_pointer),
|
||||
GFP_KERNEL);
|
||||
if (data_pointer == NULL) {
|
||||
hid_err(hdev, "Could not allocate memory for driver data\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
// set same default values as windows driver
|
||||
data_pointer->sensitivity = 0xa0;
|
||||
data_pointer->press_speed = 0x38;
|
||||
|
||||
name_mute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
|
||||
name_micmute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
|
||||
if (name_mute == NULL || name_micmute == NULL) {
|
||||
hid_err(hdev, "Could not allocate memory for led data\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
snprintf(name_mute, name_sz, "%s:amber:mute", dev_name(dev));
|
||||
snprintf(name_micmute, name_sz, "%s:amber:micmute", dev_name(dev));
|
||||
|
||||
hid_set_drvdata(hdev, data_pointer);
|
||||
|
||||
data_pointer->led_mute.name = name_mute;
|
||||
data_pointer->led_mute.brightness_get = tpkbd_led_brightness_get;
|
||||
data_pointer->led_mute.brightness_set = tpkbd_led_brightness_set;
|
||||
data_pointer->led_mute.dev = dev;
|
||||
led_classdev_register(dev, &data_pointer->led_mute);
|
||||
|
||||
data_pointer->led_micmute.name = name_micmute;
|
||||
data_pointer->led_micmute.brightness_get = tpkbd_led_brightness_get;
|
||||
data_pointer->led_micmute.brightness_set = tpkbd_led_brightness_set;
|
||||
data_pointer->led_micmute.dev = dev;
|
||||
led_classdev_register(dev, &data_pointer->led_micmute);
|
||||
|
||||
tpkbd_features_set(hdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tpkbd_probe(struct hid_device *hdev,
|
||||
const struct hid_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = hid_parse(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hid_parse failed\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hid_hw_start failed\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (hid_get_drvdata(hdev)) {
|
||||
hid_set_drvdata(hdev, NULL);
|
||||
ret = tpkbd_probe_tp(hdev);
|
||||
if (ret)
|
||||
goto err_hid;
|
||||
}
|
||||
|
||||
return 0;
|
||||
err_hid:
|
||||
hid_hw_stop(hdev);
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void tpkbd_remove_tp(struct hid_device *hdev)
|
||||
{
|
||||
struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
|
||||
|
||||
sysfs_remove_group(&hdev->dev.kobj,
|
||||
&tpkbd_attr_group_pointer);
|
||||
|
||||
led_classdev_unregister(&data_pointer->led_micmute);
|
||||
led_classdev_unregister(&data_pointer->led_mute);
|
||||
|
||||
hid_set_drvdata(hdev, NULL);
|
||||
}
|
||||
|
||||
static void tpkbd_remove(struct hid_device *hdev)
|
||||
{
|
||||
if (hid_get_drvdata(hdev))
|
||||
tpkbd_remove_tp(hdev);
|
||||
|
||||
hid_hw_stop(hdev);
|
||||
}
|
||||
|
||||
static const struct hid_device_id tpkbd_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(hid, tpkbd_devices);
|
||||
|
||||
static struct hid_driver tpkbd_driver = {
|
||||
.name = "lenovo_tpkbd",
|
||||
.id_table = tpkbd_devices,
|
||||
.input_mapping = tpkbd_input_mapping,
|
||||
.probe = tpkbd_probe,
|
||||
.remove = tpkbd_remove,
|
||||
};
|
||||
module_hid_driver(tpkbd_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
708
drivers/hid/hid-lenovo.c
Normal file
708
drivers/hid/hid-lenovo.c
Normal file
@ -0,0 +1,708 @@
|
||||
/*
|
||||
* HID driver for Lenovo:
|
||||
* - ThinkPad USB Keyboard with TrackPoint (tpkbd)
|
||||
* - ThinkPad Compact Bluetooth Keyboard with TrackPoint (cptkbd)
|
||||
* - ThinkPad Compact USB Keyboard with TrackPoint (cptkbd)
|
||||
*
|
||||
* Copyright (c) 2012 Bernhard Seibold
|
||||
* Copyright (c) 2014 Jamie Lentin <jm@lentin.co.uk>
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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/module.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/leds.h>
|
||||
|
||||
#include "hid-ids.h"
|
||||
|
||||
struct lenovo_drvdata_tpkbd {
|
||||
int led_state;
|
||||
struct led_classdev led_mute;
|
||||
struct led_classdev led_micmute;
|
||||
int press_to_select;
|
||||
int dragging;
|
||||
int release_to_select;
|
||||
int select_right;
|
||||
int sensitivity;
|
||||
int press_speed;
|
||||
};
|
||||
|
||||
struct lenovo_drvdata_cptkbd {
|
||||
bool fn_lock;
|
||||
};
|
||||
|
||||
#define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
|
||||
|
||||
static int lenovo_input_mapping_tpkbd(struct hid_device *hdev,
|
||||
struct hid_input *hi, struct hid_field *field,
|
||||
struct hid_usage *usage, unsigned long **bit, int *max)
|
||||
{
|
||||
if (usage->hid == (HID_UP_BUTTON | 0x0010)) {
|
||||
/* This sub-device contains trackpoint, mark it */
|
||||
hid_set_drvdata(hdev, (void *)1);
|
||||
map_key_clear(KEY_MICMUTE);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lenovo_input_mapping_cptkbd(struct hid_device *hdev,
|
||||
struct hid_input *hi, struct hid_field *field,
|
||||
struct hid_usage *usage, unsigned long **bit, int *max)
|
||||
{
|
||||
/* HID_UP_LNVENDOR = USB, HID_UP_MSVENDOR = BT */
|
||||
if ((usage->hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR ||
|
||||
(usage->hid & HID_USAGE_PAGE) == HID_UP_LNVENDOR) {
|
||||
set_bit(EV_REP, hi->input->evbit);
|
||||
switch (usage->hid & HID_USAGE) {
|
||||
case 0x00f1: /* Fn-F4: Mic mute */
|
||||
map_key_clear(KEY_MICMUTE);
|
||||
return 1;
|
||||
case 0x00f2: /* Fn-F5: Brightness down */
|
||||
map_key_clear(KEY_BRIGHTNESSDOWN);
|
||||
return 1;
|
||||
case 0x00f3: /* Fn-F6: Brightness up */
|
||||
map_key_clear(KEY_BRIGHTNESSUP);
|
||||
return 1;
|
||||
case 0x00f4: /* Fn-F7: External display (projector) */
|
||||
map_key_clear(KEY_SWITCHVIDEOMODE);
|
||||
return 1;
|
||||
case 0x00f5: /* Fn-F8: Wireless */
|
||||
map_key_clear(KEY_WLAN);
|
||||
return 1;
|
||||
case 0x00f6: /* Fn-F9: Control panel */
|
||||
map_key_clear(KEY_CONFIG);
|
||||
return 1;
|
||||
case 0x00f8: /* Fn-F11: View open applications (3 boxes) */
|
||||
map_key_clear(KEY_SCALE);
|
||||
return 1;
|
||||
case 0x00fa: /* Fn-Esc: Fn-lock toggle */
|
||||
map_key_clear(KEY_FN_ESC);
|
||||
return 1;
|
||||
case 0x00fb: /* Fn-F12: Open My computer (6 boxes) USB-only */
|
||||
/* NB: This mapping is invented in raw_event below */
|
||||
map_key_clear(KEY_FILE);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lenovo_input_mapping(struct hid_device *hdev,
|
||||
struct hid_input *hi, struct hid_field *field,
|
||||
struct hid_usage *usage, unsigned long **bit, int *max)
|
||||
{
|
||||
switch (hdev->product) {
|
||||
case USB_DEVICE_ID_LENOVO_TPKBD:
|
||||
return lenovo_input_mapping_tpkbd(hdev, hi, field,
|
||||
usage, bit, max);
|
||||
case USB_DEVICE_ID_LENOVO_CUSBKBD:
|
||||
case USB_DEVICE_ID_LENOVO_CBTKBD:
|
||||
return lenovo_input_mapping_cptkbd(hdev, hi, field,
|
||||
usage, bit, max);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#undef map_key_clear
|
||||
|
||||
/* Send a config command to the keyboard */
|
||||
static int lenovo_send_cmd_cptkbd(struct hid_device *hdev,
|
||||
unsigned char byte2, unsigned char byte3)
|
||||
{
|
||||
int ret;
|
||||
unsigned char buf[] = {0x18, byte2, byte3};
|
||||
|
||||
switch (hdev->product) {
|
||||
case USB_DEVICE_ID_LENOVO_CUSBKBD:
|
||||
ret = hid_hw_raw_request(hdev, 0x13, buf, sizeof(buf),
|
||||
HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
|
||||
break;
|
||||
case USB_DEVICE_ID_LENOVO_CBTKBD:
|
||||
ret = hid_hw_output_report(hdev, buf, sizeof(buf));
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret < 0 ? ret : 0; /* BT returns 0, USB returns sizeof(buf) */
|
||||
}
|
||||
|
||||
static void lenovo_features_set_cptkbd(struct hid_device *hdev)
|
||||
{
|
||||
int ret;
|
||||
struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
|
||||
|
||||
ret = lenovo_send_cmd_cptkbd(hdev, 0x05, cptkbd_data->fn_lock);
|
||||
if (ret)
|
||||
hid_err(hdev, "Fn-lock setting failed: %d\n", ret);
|
||||
}
|
||||
|
||||
static ssize_t attr_fn_lock_show_cptkbd(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", cptkbd_data->fn_lock);
|
||||
}
|
||||
|
||||
static ssize_t attr_fn_lock_store_cptkbd(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
|
||||
int value;
|
||||
|
||||
if (kstrtoint(buf, 10, &value))
|
||||
return -EINVAL;
|
||||
if (value < 0 || value > 1)
|
||||
return -EINVAL;
|
||||
|
||||
cptkbd_data->fn_lock = !!value;
|
||||
lenovo_features_set_cptkbd(hdev);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct device_attribute dev_attr_fn_lock_cptkbd =
|
||||
__ATTR(fn_lock, S_IWUSR | S_IRUGO,
|
||||
attr_fn_lock_show_cptkbd,
|
||||
attr_fn_lock_store_cptkbd);
|
||||
|
||||
static struct attribute *lenovo_attributes_cptkbd[] = {
|
||||
&dev_attr_fn_lock_cptkbd.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group lenovo_attr_group_cptkbd = {
|
||||
.attrs = lenovo_attributes_cptkbd,
|
||||
};
|
||||
|
||||
static int lenovo_raw_event(struct hid_device *hdev,
|
||||
struct hid_report *report, u8 *data, int size)
|
||||
{
|
||||
/*
|
||||
* Compact USB keyboard's Fn-F12 report holds down many other keys, and
|
||||
* its own key is outside the usage page range. Remove extra
|
||||
* keypresses and remap to inside usage page.
|
||||
*/
|
||||
if (unlikely(hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD
|
||||
&& size == 3
|
||||
&& data[0] == 0x15
|
||||
&& data[1] == 0x94
|
||||
&& data[2] == 0x01)) {
|
||||
data[1] = 0x0;
|
||||
data[2] = 0x4;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lenovo_features_set_tpkbd(struct hid_device *hdev)
|
||||
{
|
||||
struct hid_report *report;
|
||||
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
|
||||
|
||||
report = hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[4];
|
||||
|
||||
report->field[0]->value[0] = data_pointer->press_to_select ? 0x01 : 0x02;
|
||||
report->field[0]->value[0] |= data_pointer->dragging ? 0x04 : 0x08;
|
||||
report->field[0]->value[0] |= data_pointer->release_to_select ? 0x10 : 0x20;
|
||||
report->field[0]->value[0] |= data_pointer->select_right ? 0x80 : 0x40;
|
||||
report->field[1]->value[0] = 0x03; // unknown setting, imitate windows driver
|
||||
report->field[2]->value[0] = data_pointer->sensitivity;
|
||||
report->field[3]->value[0] = data_pointer->press_speed;
|
||||
|
||||
hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t attr_press_to_select_show_tpkbd(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->press_to_select);
|
||||
}
|
||||
|
||||
static ssize_t attr_press_to_select_store_tpkbd(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
|
||||
int value;
|
||||
|
||||
if (kstrtoint(buf, 10, &value))
|
||||
return -EINVAL;
|
||||
if (value < 0 || value > 1)
|
||||
return -EINVAL;
|
||||
|
||||
data_pointer->press_to_select = value;
|
||||
lenovo_features_set_tpkbd(hdev);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t attr_dragging_show_tpkbd(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->dragging);
|
||||
}
|
||||
|
||||
static ssize_t attr_dragging_store_tpkbd(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
|
||||
int value;
|
||||
|
||||
if (kstrtoint(buf, 10, &value))
|
||||
return -EINVAL;
|
||||
if (value < 0 || value > 1)
|
||||
return -EINVAL;
|
||||
|
||||
data_pointer->dragging = value;
|
||||
lenovo_features_set_tpkbd(hdev);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t attr_release_to_select_show_tpkbd(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->release_to_select);
|
||||
}
|
||||
|
||||
static ssize_t attr_release_to_select_store_tpkbd(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
|
||||
int value;
|
||||
|
||||
if (kstrtoint(buf, 10, &value))
|
||||
return -EINVAL;
|
||||
if (value < 0 || value > 1)
|
||||
return -EINVAL;
|
||||
|
||||
data_pointer->release_to_select = value;
|
||||
lenovo_features_set_tpkbd(hdev);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t attr_select_right_show_tpkbd(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->select_right);
|
||||
}
|
||||
|
||||
static ssize_t attr_select_right_store_tpkbd(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
|
||||
int value;
|
||||
|
||||
if (kstrtoint(buf, 10, &value))
|
||||
return -EINVAL;
|
||||
if (value < 0 || value > 1)
|
||||
return -EINVAL;
|
||||
|
||||
data_pointer->select_right = value;
|
||||
lenovo_features_set_tpkbd(hdev);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t attr_sensitivity_show_tpkbd(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n",
|
||||
data_pointer->sensitivity);
|
||||
}
|
||||
|
||||
static ssize_t attr_sensitivity_store_tpkbd(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
|
||||
int value;
|
||||
|
||||
if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
|
||||
return -EINVAL;
|
||||
|
||||
data_pointer->sensitivity = value;
|
||||
lenovo_features_set_tpkbd(hdev);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t attr_press_speed_show_tpkbd(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n",
|
||||
data_pointer->press_speed);
|
||||
}
|
||||
|
||||
static ssize_t attr_press_speed_store_tpkbd(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
|
||||
int value;
|
||||
|
||||
if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
|
||||
return -EINVAL;
|
||||
|
||||
data_pointer->press_speed = value;
|
||||
lenovo_features_set_tpkbd(hdev);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct device_attribute dev_attr_press_to_select_tpkbd =
|
||||
__ATTR(press_to_select, S_IWUSR | S_IRUGO,
|
||||
attr_press_to_select_show_tpkbd,
|
||||
attr_press_to_select_store_tpkbd);
|
||||
|
||||
static struct device_attribute dev_attr_dragging_tpkbd =
|
||||
__ATTR(dragging, S_IWUSR | S_IRUGO,
|
||||
attr_dragging_show_tpkbd,
|
||||
attr_dragging_store_tpkbd);
|
||||
|
||||
static struct device_attribute dev_attr_release_to_select_tpkbd =
|
||||
__ATTR(release_to_select, S_IWUSR | S_IRUGO,
|
||||
attr_release_to_select_show_tpkbd,
|
||||
attr_release_to_select_store_tpkbd);
|
||||
|
||||
static struct device_attribute dev_attr_select_right_tpkbd =
|
||||
__ATTR(select_right, S_IWUSR | S_IRUGO,
|
||||
attr_select_right_show_tpkbd,
|
||||
attr_select_right_store_tpkbd);
|
||||
|
||||
static struct device_attribute dev_attr_sensitivity_tpkbd =
|
||||
__ATTR(sensitivity, S_IWUSR | S_IRUGO,
|
||||
attr_sensitivity_show_tpkbd,
|
||||
attr_sensitivity_store_tpkbd);
|
||||
|
||||
static struct device_attribute dev_attr_press_speed_tpkbd =
|
||||
__ATTR(press_speed, S_IWUSR | S_IRUGO,
|
||||
attr_press_speed_show_tpkbd,
|
||||
attr_press_speed_store_tpkbd);
|
||||
|
||||
static struct attribute *lenovo_attributes_tpkbd[] = {
|
||||
&dev_attr_press_to_select_tpkbd.attr,
|
||||
&dev_attr_dragging_tpkbd.attr,
|
||||
&dev_attr_release_to_select_tpkbd.attr,
|
||||
&dev_attr_select_right_tpkbd.attr,
|
||||
&dev_attr_sensitivity_tpkbd.attr,
|
||||
&dev_attr_press_speed_tpkbd.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group lenovo_attr_group_tpkbd = {
|
||||
.attrs = lenovo_attributes_tpkbd,
|
||||
};
|
||||
|
||||
static enum led_brightness lenovo_led_brightness_get_tpkbd(
|
||||
struct led_classdev *led_cdev)
|
||||
{
|
||||
struct device *dev = led_cdev->dev->parent;
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
|
||||
int led_nr = 0;
|
||||
|
||||
if (led_cdev == &data_pointer->led_micmute)
|
||||
led_nr = 1;
|
||||
|
||||
return data_pointer->led_state & (1 << led_nr)
|
||||
? LED_FULL
|
||||
: LED_OFF;
|
||||
}
|
||||
|
||||
static void lenovo_led_brightness_set_tpkbd(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct device *dev = led_cdev->dev->parent;
|
||||
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
|
||||
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
|
||||
struct hid_report *report;
|
||||
int led_nr = 0;
|
||||
|
||||
if (led_cdev == &data_pointer->led_micmute)
|
||||
led_nr = 1;
|
||||
|
||||
if (value == LED_OFF)
|
||||
data_pointer->led_state &= ~(1 << led_nr);
|
||||
else
|
||||
data_pointer->led_state |= 1 << led_nr;
|
||||
|
||||
report = hdev->report_enum[HID_OUTPUT_REPORT].report_id_hash[3];
|
||||
report->field[0]->value[0] = (data_pointer->led_state >> 0) & 1;
|
||||
report->field[0]->value[1] = (data_pointer->led_state >> 1) & 1;
|
||||
hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
|
||||
}
|
||||
|
||||
static int lenovo_probe_tpkbd(struct hid_device *hdev)
|
||||
{
|
||||
struct device *dev = &hdev->dev;
|
||||
struct lenovo_drvdata_tpkbd *data_pointer;
|
||||
size_t name_sz = strlen(dev_name(dev)) + 16;
|
||||
char *name_mute, *name_micmute;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Only register extra settings against subdevice where input_mapping
|
||||
* set drvdata to 1, i.e. the trackpoint.
|
||||
*/
|
||||
if (!hid_get_drvdata(hdev))
|
||||
return 0;
|
||||
|
||||
hid_set_drvdata(hdev, NULL);
|
||||
|
||||
/* Validate required reports. */
|
||||
for (i = 0; i < 4; i++) {
|
||||
if (!hid_validate_values(hdev, HID_FEATURE_REPORT, 4, i, 1))
|
||||
return -ENODEV;
|
||||
}
|
||||
if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 3, 0, 2))
|
||||
return -ENODEV;
|
||||
|
||||
ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_tpkbd);
|
||||
if (ret)
|
||||
hid_warn(hdev, "Could not create sysfs group: %d\n", ret);
|
||||
|
||||
data_pointer = devm_kzalloc(&hdev->dev,
|
||||
sizeof(struct lenovo_drvdata_tpkbd),
|
||||
GFP_KERNEL);
|
||||
if (data_pointer == NULL) {
|
||||
hid_err(hdev, "Could not allocate memory for driver data\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
// set same default values as windows driver
|
||||
data_pointer->sensitivity = 0xa0;
|
||||
data_pointer->press_speed = 0x38;
|
||||
|
||||
name_mute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
|
||||
name_micmute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
|
||||
if (name_mute == NULL || name_micmute == NULL) {
|
||||
hid_err(hdev, "Could not allocate memory for led data\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
snprintf(name_mute, name_sz, "%s:amber:mute", dev_name(dev));
|
||||
snprintf(name_micmute, name_sz, "%s:amber:micmute", dev_name(dev));
|
||||
|
||||
hid_set_drvdata(hdev, data_pointer);
|
||||
|
||||
data_pointer->led_mute.name = name_mute;
|
||||
data_pointer->led_mute.brightness_get = lenovo_led_brightness_get_tpkbd;
|
||||
data_pointer->led_mute.brightness_set = lenovo_led_brightness_set_tpkbd;
|
||||
data_pointer->led_mute.dev = dev;
|
||||
led_classdev_register(dev, &data_pointer->led_mute);
|
||||
|
||||
data_pointer->led_micmute.name = name_micmute;
|
||||
data_pointer->led_micmute.brightness_get =
|
||||
lenovo_led_brightness_get_tpkbd;
|
||||
data_pointer->led_micmute.brightness_set =
|
||||
lenovo_led_brightness_set_tpkbd;
|
||||
data_pointer->led_micmute.dev = dev;
|
||||
led_classdev_register(dev, &data_pointer->led_micmute);
|
||||
|
||||
lenovo_features_set_tpkbd(hdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lenovo_probe_cptkbd(struct hid_device *hdev)
|
||||
{
|
||||
int ret;
|
||||
struct lenovo_drvdata_cptkbd *cptkbd_data;
|
||||
|
||||
/* All the custom action happens on the USBMOUSE device for USB */
|
||||
if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD
|
||||
&& hdev->type != HID_TYPE_USBMOUSE) {
|
||||
hid_dbg(hdev, "Ignoring keyboard half of device\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
cptkbd_data = devm_kzalloc(&hdev->dev,
|
||||
sizeof(*cptkbd_data),
|
||||
GFP_KERNEL);
|
||||
if (cptkbd_data == NULL) {
|
||||
hid_err(hdev, "can't alloc keyboard descriptor\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
hid_set_drvdata(hdev, cptkbd_data);
|
||||
|
||||
/*
|
||||
* Tell the keyboard a driver understands it, and turn F7, F9, F11 into
|
||||
* regular keys
|
||||
*/
|
||||
ret = lenovo_send_cmd_cptkbd(hdev, 0x01, 0x03);
|
||||
if (ret)
|
||||
hid_warn(hdev, "Failed to switch F7/9/11 mode: %d\n", ret);
|
||||
|
||||
/* Turn Fn-Lock on by default */
|
||||
cptkbd_data->fn_lock = true;
|
||||
lenovo_features_set_cptkbd(hdev);
|
||||
|
||||
ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_cptkbd);
|
||||
if (ret)
|
||||
hid_warn(hdev, "Could not create sysfs group: %d\n", ret);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lenovo_probe(struct hid_device *hdev,
|
||||
const struct hid_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = hid_parse(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hid_parse failed\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hid_hw_start failed\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
switch (hdev->product) {
|
||||
case USB_DEVICE_ID_LENOVO_TPKBD:
|
||||
ret = lenovo_probe_tpkbd(hdev);
|
||||
break;
|
||||
case USB_DEVICE_ID_LENOVO_CUSBKBD:
|
||||
case USB_DEVICE_ID_LENOVO_CBTKBD:
|
||||
ret = lenovo_probe_cptkbd(hdev);
|
||||
break;
|
||||
default:
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
if (ret)
|
||||
goto err_hid;
|
||||
|
||||
return 0;
|
||||
err_hid:
|
||||
hid_hw_stop(hdev);
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void lenovo_remove_tpkbd(struct hid_device *hdev)
|
||||
{
|
||||
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
|
||||
|
||||
/*
|
||||
* Only the trackpoint half of the keyboard has drvdata and stuff that
|
||||
* needs unregistering.
|
||||
*/
|
||||
if (data_pointer == NULL)
|
||||
return;
|
||||
|
||||
sysfs_remove_group(&hdev->dev.kobj,
|
||||
&lenovo_attr_group_tpkbd);
|
||||
|
||||
led_classdev_unregister(&data_pointer->led_micmute);
|
||||
led_classdev_unregister(&data_pointer->led_mute);
|
||||
|
||||
hid_set_drvdata(hdev, NULL);
|
||||
}
|
||||
|
||||
static void lenovo_remove_cptkbd(struct hid_device *hdev)
|
||||
{
|
||||
sysfs_remove_group(&hdev->dev.kobj,
|
||||
&lenovo_attr_group_cptkbd);
|
||||
}
|
||||
|
||||
static void lenovo_remove(struct hid_device *hdev)
|
||||
{
|
||||
switch (hdev->product) {
|
||||
case USB_DEVICE_ID_LENOVO_TPKBD:
|
||||
lenovo_remove_tpkbd(hdev);
|
||||
break;
|
||||
case USB_DEVICE_ID_LENOVO_CUSBKBD:
|
||||
case USB_DEVICE_ID_LENOVO_CBTKBD:
|
||||
lenovo_remove_cptkbd(hdev);
|
||||
break;
|
||||
}
|
||||
|
||||
hid_hw_stop(hdev);
|
||||
}
|
||||
|
||||
static const struct hid_device_id lenovo_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CUSBKBD) },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CBTKBD) },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(hid, lenovo_devices);
|
||||
|
||||
static struct hid_driver lenovo_driver = {
|
||||
.name = "lenovo",
|
||||
.id_table = lenovo_devices,
|
||||
.input_mapping = lenovo_input_mapping,
|
||||
.probe = lenovo_probe,
|
||||
.remove = lenovo_remove,
|
||||
.raw_event = lenovo_raw_event,
|
||||
};
|
||||
module_hid_driver(lenovo_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
@ -883,16 +883,13 @@ void picolcd_exit_devfs(struct picolcd_data *data)
|
||||
|
||||
dent = data->debug_reset;
|
||||
data->debug_reset = NULL;
|
||||
if (dent)
|
||||
debugfs_remove(dent);
|
||||
debugfs_remove(dent);
|
||||
dent = data->debug_eeprom;
|
||||
data->debug_eeprom = NULL;
|
||||
if (dent)
|
||||
debugfs_remove(dent);
|
||||
debugfs_remove(dent);
|
||||
dent = data->debug_flash;
|
||||
data->debug_flash = NULL;
|
||||
if (dent)
|
||||
debugfs_remove(dent);
|
||||
debugfs_remove(dent);
|
||||
mutex_destroy(&data->mutex_flash);
|
||||
}
|
||||
|
||||
|
@ -377,7 +377,7 @@ static int rmi_input_event(struct hid_device *hdev, u8 *data, int size)
|
||||
irq_mask |= hdata->f30.irq_mask;
|
||||
|
||||
if (data[1] & ~irq_mask)
|
||||
hid_warn(hdev, "unknown intr source:%02lx %s:%d\n",
|
||||
hid_dbg(hdev, "unknown intr source:%02lx %s:%d\n",
|
||||
data[1] & ~irq_mask, __FILE__, __LINE__);
|
||||
|
||||
if (hdata->f11.interrupt_base < hdata->f30.interrupt_base) {
|
||||
@ -400,7 +400,7 @@ static int rmi_read_data_event(struct hid_device *hdev, u8 *data, int size)
|
||||
struct rmi_data *hdata = hid_get_drvdata(hdev);
|
||||
|
||||
if (!test_bit(RMI_READ_REQUEST_PENDING, &hdata->flags)) {
|
||||
hid_err(hdev, "no read request pending\n");
|
||||
hid_dbg(hdev, "no read request pending\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -549,10 +549,12 @@ static int rmi_populate_f11(struct hid_device *hdev)
|
||||
u8 buf[20];
|
||||
int ret;
|
||||
bool has_query9;
|
||||
bool has_query10;
|
||||
bool has_query10 = false;
|
||||
bool has_query11;
|
||||
bool has_query12;
|
||||
bool has_physical_props;
|
||||
bool has_gestures;
|
||||
bool has_rel;
|
||||
unsigned x_size, y_size;
|
||||
u16 query12_offset;
|
||||
|
||||
@ -589,19 +591,32 @@ static int rmi_populate_f11(struct hid_device *hdev)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* query 8 to find out if query 10 exists */
|
||||
ret = rmi_read(hdev, data->f11.query_base_addr + 8, buf);
|
||||
if (ret) {
|
||||
hid_err(hdev, "can not read gesture information: %d.\n", ret);
|
||||
return ret;
|
||||
has_rel = !!(buf[0] & BIT(3));
|
||||
has_gestures = !!(buf[0] & BIT(5));
|
||||
|
||||
if (has_gestures) {
|
||||
/* query 8 to find out if query 10 exists */
|
||||
ret = rmi_read(hdev, data->f11.query_base_addr + 8, buf);
|
||||
if (ret) {
|
||||
hid_err(hdev, "can not read gesture information: %d.\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
has_query10 = !!(buf[0] & BIT(2));
|
||||
}
|
||||
has_query10 = !!(buf[0] & BIT(2));
|
||||
|
||||
/*
|
||||
* At least 8 queries are guaranteed to be present in F11
|
||||
* +1 for query12.
|
||||
* At least 4 queries are guaranteed to be present in F11
|
||||
* +1 for query 5 which is present since absolute events are
|
||||
* reported and +1 for query 12.
|
||||
*/
|
||||
query12_offset = 9;
|
||||
query12_offset = 6;
|
||||
|
||||
if (has_rel)
|
||||
++query12_offset; /* query 6 is present */
|
||||
|
||||
if (has_gestures)
|
||||
query12_offset += 2; /* query 7 and 8 are present */
|
||||
|
||||
if (has_query9)
|
||||
++query12_offset;
|
||||
@ -833,6 +848,8 @@ static int rmi_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
struct rmi_data *data = NULL;
|
||||
int ret;
|
||||
size_t alloc_size;
|
||||
struct hid_report *input_report;
|
||||
struct hid_report *output_report;
|
||||
|
||||
data = devm_kzalloc(&hdev->dev, sizeof(struct rmi_data), GFP_KERNEL);
|
||||
if (!data)
|
||||
@ -851,12 +868,26 @@ static int rmi_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
return ret;
|
||||
}
|
||||
|
||||
data->input_report_size = (hdev->report_enum[HID_INPUT_REPORT]
|
||||
.report_id_hash[RMI_ATTN_REPORT_ID]->size >> 3)
|
||||
+ 1 /* report id */;
|
||||
data->output_report_size = (hdev->report_enum[HID_OUTPUT_REPORT]
|
||||
.report_id_hash[RMI_WRITE_REPORT_ID]->size >> 3)
|
||||
+ 1 /* report id */;
|
||||
input_report = hdev->report_enum[HID_INPUT_REPORT]
|
||||
.report_id_hash[RMI_ATTN_REPORT_ID];
|
||||
if (!input_report) {
|
||||
hid_err(hdev, "device does not have expected input report\n");
|
||||
ret = -ENODEV;
|
||||
return ret;
|
||||
}
|
||||
|
||||
data->input_report_size = (input_report->size >> 3) + 1 /* report id */;
|
||||
|
||||
output_report = hdev->report_enum[HID_OUTPUT_REPORT]
|
||||
.report_id_hash[RMI_WRITE_REPORT_ID];
|
||||
if (!output_report) {
|
||||
hid_err(hdev, "device does not have expected output report\n");
|
||||
ret = -ENODEV;
|
||||
return ret;
|
||||
}
|
||||
|
||||
data->output_report_size = (output_report->size >> 3)
|
||||
+ 1 /* report id */;
|
||||
|
||||
alloc_size = data->output_report_size + data->input_report_size;
|
||||
|
||||
|
@ -61,7 +61,7 @@ static ssize_t lua_sysfs_write(struct file *fp, struct kobject *kobj,
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&lua->lua_lock);
|
||||
retval = roccat_common2_send(usb_dev, command, (void *)buf, real_size);
|
||||
retval = roccat_common2_send(usb_dev, command, buf, real_size);
|
||||
mutex_unlock(&lua->lua_lock);
|
||||
|
||||
return retval ? retval : real_size;
|
||||
|
@ -56,32 +56,81 @@
|
||||
|
||||
#define MAX_LEDS 4
|
||||
|
||||
static const u8 sixaxis_rdesc_fixup[] = {
|
||||
0x95, 0x13, 0x09, 0x01, 0x81, 0x02, 0x95, 0x0C,
|
||||
0x81, 0x01, 0x75, 0x10, 0x95, 0x04, 0x26, 0xFF,
|
||||
0x03, 0x46, 0xFF, 0x03, 0x09, 0x01, 0x81, 0x02
|
||||
};
|
||||
|
||||
static const u8 sixaxis_rdesc_fixup2[] = {
|
||||
0x05, 0x01, 0x09, 0x04, 0xa1, 0x01, 0xa1, 0x02,
|
||||
0x85, 0x01, 0x75, 0x08, 0x95, 0x01, 0x15, 0x00,
|
||||
0x26, 0xff, 0x00, 0x81, 0x03, 0x75, 0x01, 0x95,
|
||||
0x13, 0x15, 0x00, 0x25, 0x01, 0x35, 0x00, 0x45,
|
||||
0x01, 0x05, 0x09, 0x19, 0x01, 0x29, 0x13, 0x81,
|
||||
0x02, 0x75, 0x01, 0x95, 0x0d, 0x06, 0x00, 0xff,
|
||||
0x81, 0x03, 0x15, 0x00, 0x26, 0xff, 0x00, 0x05,
|
||||
0x01, 0x09, 0x01, 0xa1, 0x00, 0x75, 0x08, 0x95,
|
||||
0x04, 0x35, 0x00, 0x46, 0xff, 0x00, 0x09, 0x30,
|
||||
0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x81, 0x02,
|
||||
0xc0, 0x05, 0x01, 0x95, 0x13, 0x09, 0x01, 0x81,
|
||||
0x02, 0x95, 0x0c, 0x81, 0x01, 0x75, 0x10, 0x95,
|
||||
0x04, 0x26, 0xff, 0x03, 0x46, 0xff, 0x03, 0x09,
|
||||
0x01, 0x81, 0x02, 0xc0, 0xa1, 0x02, 0x85, 0x02,
|
||||
0x75, 0x08, 0x95, 0x30, 0x09, 0x01, 0xb1, 0x02,
|
||||
0xc0, 0xa1, 0x02, 0x85, 0xee, 0x75, 0x08, 0x95,
|
||||
0x30, 0x09, 0x01, 0xb1, 0x02, 0xc0, 0xa1, 0x02,
|
||||
0x85, 0xef, 0x75, 0x08, 0x95, 0x30, 0x09, 0x01,
|
||||
0xb1, 0x02, 0xc0, 0xc0,
|
||||
static __u8 sixaxis_rdesc[] = {
|
||||
0x05, 0x01, /* Usage Page (Desktop), */
|
||||
0x09, 0x04, /* Usage (Joystik), */
|
||||
0xA1, 0x01, /* Collection (Application), */
|
||||
0xA1, 0x02, /* Collection (Logical), */
|
||||
0x85, 0x01, /* Report ID (1), */
|
||||
0x75, 0x08, /* Report Size (8), */
|
||||
0x95, 0x01, /* Report Count (1), */
|
||||
0x15, 0x00, /* Logical Minimum (0), */
|
||||
0x26, 0xFF, 0x00, /* Logical Maximum (255), */
|
||||
0x81, 0x03, /* Input (Constant, Variable), */
|
||||
0x75, 0x01, /* Report Size (1), */
|
||||
0x95, 0x13, /* Report Count (19), */
|
||||
0x15, 0x00, /* Logical Minimum (0), */
|
||||
0x25, 0x01, /* Logical Maximum (1), */
|
||||
0x35, 0x00, /* Physical Minimum (0), */
|
||||
0x45, 0x01, /* Physical Maximum (1), */
|
||||
0x05, 0x09, /* Usage Page (Button), */
|
||||
0x19, 0x01, /* Usage Minimum (01h), */
|
||||
0x29, 0x13, /* Usage Maximum (13h), */
|
||||
0x81, 0x02, /* Input (Variable), */
|
||||
0x75, 0x01, /* Report Size (1), */
|
||||
0x95, 0x0D, /* Report Count (13), */
|
||||
0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
|
||||
0x81, 0x03, /* Input (Constant, Variable), */
|
||||
0x15, 0x00, /* Logical Minimum (0), */
|
||||
0x26, 0xFF, 0x00, /* Logical Maximum (255), */
|
||||
0x05, 0x01, /* Usage Page (Desktop), */
|
||||
0x09, 0x01, /* Usage (Pointer), */
|
||||
0xA1, 0x00, /* Collection (Physical), */
|
||||
0x75, 0x08, /* Report Size (8), */
|
||||
0x95, 0x04, /* Report Count (4), */
|
||||
0x35, 0x00, /* Physical Minimum (0), */
|
||||
0x46, 0xFF, 0x00, /* Physical Maximum (255), */
|
||||
0x09, 0x30, /* Usage (X), */
|
||||
0x09, 0x31, /* Usage (Y), */
|
||||
0x09, 0x32, /* Usage (Z), */
|
||||
0x09, 0x35, /* Usage (Rz), */
|
||||
0x81, 0x02, /* Input (Variable), */
|
||||
0xC0, /* End Collection, */
|
||||
0x05, 0x01, /* Usage Page (Desktop), */
|
||||
0x95, 0x13, /* Report Count (19), */
|
||||
0x09, 0x01, /* Usage (Pointer), */
|
||||
0x81, 0x02, /* Input (Variable), */
|
||||
0x95, 0x0C, /* Report Count (12), */
|
||||
0x81, 0x01, /* Input (Constant), */
|
||||
0x75, 0x10, /* Report Size (16), */
|
||||
0x95, 0x04, /* Report Count (4), */
|
||||
0x26, 0xFF, 0x03, /* Logical Maximum (1023), */
|
||||
0x46, 0xFF, 0x03, /* Physical Maximum (1023), */
|
||||
0x09, 0x01, /* Usage (Pointer), */
|
||||
0x81, 0x02, /* Input (Variable), */
|
||||
0xC0, /* End Collection, */
|
||||
0xA1, 0x02, /* Collection (Logical), */
|
||||
0x85, 0x02, /* Report ID (2), */
|
||||
0x75, 0x08, /* Report Size (8), */
|
||||
0x95, 0x30, /* Report Count (48), */
|
||||
0x09, 0x01, /* Usage (Pointer), */
|
||||
0xB1, 0x02, /* Feature (Variable), */
|
||||
0xC0, /* End Collection, */
|
||||
0xA1, 0x02, /* Collection (Logical), */
|
||||
0x85, 0xEE, /* Report ID (238), */
|
||||
0x75, 0x08, /* Report Size (8), */
|
||||
0x95, 0x30, /* Report Count (48), */
|
||||
0x09, 0x01, /* Usage (Pointer), */
|
||||
0xB1, 0x02, /* Feature (Variable), */
|
||||
0xC0, /* End Collection, */
|
||||
0xA1, 0x02, /* Collection (Logical), */
|
||||
0x85, 0xEF, /* Report ID (239), */
|
||||
0x75, 0x08, /* Report Size (8), */
|
||||
0x95, 0x30, /* Report Count (48), */
|
||||
0x09, 0x01, /* Usage (Pointer), */
|
||||
0xB1, 0x02, /* Feature (Variable), */
|
||||
0xC0, /* End Collection, */
|
||||
0xC0 /* End Collection */
|
||||
};
|
||||
|
||||
/*
|
||||
@ -778,6 +827,13 @@ struct sony_sc {
|
||||
__u8 led_count;
|
||||
};
|
||||
|
||||
static __u8 *sixaxis_fixup(struct hid_device *hdev, __u8 *rdesc,
|
||||
unsigned int *rsize)
|
||||
{
|
||||
*rsize = sizeof(sixaxis_rdesc);
|
||||
return sixaxis_rdesc;
|
||||
}
|
||||
|
||||
static __u8 *ps3remote_fixup(struct hid_device *hdev, __u8 *rdesc,
|
||||
unsigned int *rsize)
|
||||
{
|
||||
@ -819,8 +875,6 @@ static int ps3remote_mapping(struct hid_device *hdev, struct hid_input *hi,
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/* Sony Vaio VGX has wrongly mouse pointer declared as constant */
|
||||
static __u8 *sony_report_fixup(struct hid_device *hdev, __u8 *rdesc,
|
||||
unsigned int *rsize)
|
||||
{
|
||||
@ -857,20 +911,8 @@ static __u8 *sony_report_fixup(struct hid_device *hdev, __u8 *rdesc,
|
||||
*rsize = sizeof(dualshock4_bt_rdesc);
|
||||
}
|
||||
|
||||
/* The HID descriptor exposed over BT has a trailing zero byte */
|
||||
if ((((sc->quirks & SIXAXIS_CONTROLLER_USB) && *rsize == 148) ||
|
||||
((sc->quirks & SIXAXIS_CONTROLLER_BT) && *rsize == 149)) &&
|
||||
rdesc[83] == 0x75) {
|
||||
hid_info(hdev, "Fixing up Sony Sixaxis report descriptor\n");
|
||||
memcpy((void *)&rdesc[83], (void *)&sixaxis_rdesc_fixup,
|
||||
sizeof(sixaxis_rdesc_fixup));
|
||||
} else if (sc->quirks & SIXAXIS_CONTROLLER_USB &&
|
||||
*rsize > sizeof(sixaxis_rdesc_fixup2)) {
|
||||
hid_info(hdev, "Sony Sixaxis clone detected. Using original report descriptor (size: %d clone; %d new)\n",
|
||||
*rsize, (int)sizeof(sixaxis_rdesc_fixup2));
|
||||
*rsize = sizeof(sixaxis_rdesc_fixup2);
|
||||
memcpy(rdesc, &sixaxis_rdesc_fixup2, *rsize);
|
||||
}
|
||||
if (sc->quirks & SIXAXIS_CONTROLLER)
|
||||
return sixaxis_fixup(hdev, rdesc, rsize);
|
||||
|
||||
if (sc->quirks & PS3REMOTE)
|
||||
return ps3remote_fixup(hdev, rdesc, rsize);
|
||||
@ -1307,7 +1349,7 @@ static int sony_leds_init(struct sony_sc *sc)
|
||||
static const char * const ds4_name_str[] = { "red", "green", "blue",
|
||||
"global" };
|
||||
__u8 initial_values[MAX_LEDS] = { 0 };
|
||||
__u8 max_brightness[MAX_LEDS] = { 1 };
|
||||
__u8 max_brightness[MAX_LEDS] = { [0 ... (MAX_LEDS - 1)] = 1 };
|
||||
__u8 use_hw_blink[MAX_LEDS] = { 0 };
|
||||
|
||||
BUG_ON(!(sc->quirks & SONY_LED_SUPPORT));
|
||||
@ -1830,9 +1872,7 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
|
||||
if (sc->quirks & VAIO_RDESC_CONSTANT)
|
||||
connect_mask |= HID_CONNECT_HIDDEV_FORCE;
|
||||
else if (sc->quirks & SIXAXIS_CONTROLLER_USB)
|
||||
connect_mask |= HID_CONNECT_HIDDEV_FORCE;
|
||||
else if (sc->quirks & SIXAXIS_CONTROLLER_BT)
|
||||
else if (sc->quirks & SIXAXIS_CONTROLLER)
|
||||
connect_mask |= HID_CONNECT_HIDDEV_FORCE;
|
||||
|
||||
ret = hid_hw_start(hdev, connect_mask);
|
||||
|
@ -1054,21 +1054,29 @@ static int i2c_hid_remove(struct i2c_client *client)
|
||||
static int i2c_hid_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct i2c_hid *ihid = i2c_get_clientdata(client);
|
||||
struct hid_device *hid = ihid->hid;
|
||||
int ret = 0;
|
||||
|
||||
disable_irq(client->irq);
|
||||
if (device_may_wakeup(&client->dev))
|
||||
enable_irq_wake(client->irq);
|
||||
|
||||
if (hid->driver && hid->driver->suspend)
|
||||
ret = hid->driver->suspend(hid, PMSG_SUSPEND);
|
||||
|
||||
/* Save some power */
|
||||
i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int i2c_hid_resume(struct device *dev)
|
||||
{
|
||||
int ret;
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct i2c_hid *ihid = i2c_get_clientdata(client);
|
||||
struct hid_device *hid = ihid->hid;
|
||||
|
||||
enable_irq(client->irq);
|
||||
ret = i2c_hid_hwreset(client);
|
||||
@ -1078,6 +1086,11 @@ static int i2c_hid_resume(struct device *dev)
|
||||
if (device_may_wakeup(&client->dev))
|
||||
disable_irq_wake(client->irq);
|
||||
|
||||
if (hid->driver && hid->driver->reset_resume) {
|
||||
ret = hid->driver->reset_resume(hid);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
@ -58,7 +58,7 @@ module_param_named(ignoreled, ignoreled, uint, 0644);
|
||||
MODULE_PARM_DESC(ignoreled, "Autosuspend with active leds");
|
||||
|
||||
/* Quirks specified at module load time */
|
||||
static char *quirks_param[MAX_USBHID_BOOT_QUIRKS] = { [ 0 ... (MAX_USBHID_BOOT_QUIRKS - 1) ] = NULL };
|
||||
static char *quirks_param[MAX_USBHID_BOOT_QUIRKS];
|
||||
module_param_array_named(quirks, quirks_param, charp, NULL, 0444);
|
||||
MODULE_PARM_DESC(quirks, "Add/modify USB HID quirks by specifying "
|
||||
" quirks=vendorID:productID:quirks"
|
||||
@ -536,7 +536,8 @@ static void __usbhid_submit_report(struct hid_device *hid, struct hid_report *re
|
||||
int head;
|
||||
struct usbhid_device *usbhid = hid->driver_data;
|
||||
|
||||
if ((hid->quirks & HID_QUIRK_NOGET) && dir == USB_DIR_IN)
|
||||
if (((hid->quirks & HID_QUIRK_NOGET) && dir == USB_DIR_IN) ||
|
||||
test_bit(HID_DISCONNECTED, &usbhid->iofl))
|
||||
return;
|
||||
|
||||
if (usbhid->urbout && dir == USB_DIR_OUT && report->type == HID_OUTPUT_REPORT) {
|
||||
@ -1366,6 +1367,9 @@ static void usbhid_disconnect(struct usb_interface *intf)
|
||||
return;
|
||||
|
||||
usbhid = hid->driver_data;
|
||||
spin_lock_irq(&usbhid->lock); /* Sync with error and led handlers */
|
||||
set_bit(HID_DISCONNECTED, &usbhid->iofl);
|
||||
spin_unlock_irq(&usbhid->lock);
|
||||
hid_destroy_device(hid);
|
||||
kfree(usbhid);
|
||||
}
|
||||
|
@ -124,6 +124,7 @@ static const struct hid_blacklist {
|
||||
{ USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_HD, HID_QUIRK_NO_INIT_REPORTS },
|
||||
{ USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_QUAD_HD, HID_QUIRK_NO_INIT_REPORTS },
|
||||
{ USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_TP_V103, HID_QUIRK_NO_INIT_REPORTS },
|
||||
{ USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD_A096, HID_QUIRK_NO_INIT_INPUT_REPORTS },
|
||||
|
||||
{ 0, 0 }
|
||||
};
|
||||
|
@ -167,6 +167,7 @@ struct hid_item {
|
||||
#define HID_UP_MSVENDOR 0xff000000
|
||||
#define HID_UP_CUSTOM 0x00ff0000
|
||||
#define HID_UP_LOGIVENDOR 0xffbc0000
|
||||
#define HID_UP_LNVENDOR 0xffa00000
|
||||
#define HID_UP_SENSOR 0x00200000
|
||||
|
||||
#define HID_USAGE 0x0000ffff
|
||||
|
Loading…
Reference in New Issue
Block a user