From 364b936fc38dec7653c690d710e10657af235a36 Mon Sep 17 00:00:00 2001 From: Sergei Kolzun Date: Thu, 4 Aug 2011 00:25:56 -0700 Subject: HID: ACRUX - fix enabling force feedback support The config option needs to be a 'bool' and not a tristate, otheriwse force feedback support never makes it into the module. Signed-off-by: Sergei Kolzun Cc: stable@kernel.org Signed-off-by: Dmitry Torokhov Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 306b15f39c9c..342be43211d8 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -69,7 +69,7 @@ config HID_ACRUX Say Y here if you want to enable support for ACRUX game controllers. config HID_ACRUX_FF - tristate "ACRUX force feedback support" + bool "ACRUX force feedback support" depends on HID_ACRUX select INPUT_FF_MEMLESS ---help--- -- cgit v1.2.3-55-g7522 From b55ebc27b0a54ff4cdbfdcb218a85f96e502db3c Mon Sep 17 00:00:00 2001 From: Sergei Kolzun Date: Thu, 4 Aug 2011 00:25:57 -0700 Subject: HID: ACRUX - handle gamepads with different report layout There are gamepads that share the same VID and PID but have different report structure - instead of having 4 fields with one value they have one field that can hold all 4 values. Make the driver cope with devices using both styles. Signed-off-by: Sergei Kolzun Signed-off-by: Dmitry Torokhov Signed-off-by: Jiri Kosina --- drivers/hid/hid-axff.c | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/drivers/hid/hid-axff.c b/drivers/hid/hid-axff.c index 121514149e0b..3bdb4500f95e 100644 --- a/drivers/hid/hid-axff.c +++ b/drivers/hid/hid-axff.c @@ -6,7 +6,7 @@ * Xbox 360 controller. * * 1a34:0802 "ACRUX USB GAMEPAD 8116" - * - tested with a EXEQ EQ-PCU-02090 game controller. + * - tested with an EXEQ EQ-PCU-02090 game controller. * * Copyright (c) 2010 Sergei Kolzun */ @@ -45,7 +45,10 @@ static int axff_play(struct input_dev *dev, void *data, struct ff_effect *effect { struct hid_device *hid = input_get_drvdata(dev); struct axff_device *axff = data; + struct hid_report *report = axff->report; + int field_count = 0; int left, right; + int i, j; left = effect->u.rumble.strong_magnitude; right = effect->u.rumble.weak_magnitude; @@ -55,10 +58,14 @@ static int axff_play(struct input_dev *dev, void *data, struct ff_effect *effect left = left * 0xff / 0xffff; right = right * 0xff / 0xffff; - axff->report->field[0]->value[0] = left; - axff->report->field[1]->value[0] = right; - axff->report->field[2]->value[0] = left; - axff->report->field[3]->value[0] = right; + for (i = 0; i < report->maxfield; i++) { + for (j = 0; j < report->field[i]->report_count; j++) { + report->field[i]->value[j] = + field_count % 2 ? right : left; + field_count++; + } + } + dbg_hid("running with 0x%02x 0x%02x", left, right); usbhid_submit_report(hid, axff->report, USB_DIR_OUT); @@ -72,6 +79,8 @@ static int axff_init(struct hid_device *hid) struct hid_input *hidinput = list_first_entry(&hid->inputs, struct hid_input, list); struct list_head *report_list =&hid->report_enum[HID_OUTPUT_REPORT].report_list; struct input_dev *dev = hidinput->input; + int field_count = 0; + int i, j; int error; if (list_empty(report_list)) { @@ -80,9 +89,16 @@ static int axff_init(struct hid_device *hid) } report = list_first_entry(report_list, struct hid_report, list); + for (i = 0; i < report->maxfield; i++) { + for (j = 0; j < report->field[i]->report_count; j++) { + report->field[i]->value[j] = 0x00; + field_count++; + } + } - if (report->maxfield < 4) { - hid_err(hid, "no fields in the report: %d\n", report->maxfield); + if (field_count < 4) { + hid_err(hid, "not enough fields in the report: %d\n", + field_count); return -ENODEV; } @@ -97,13 +113,9 @@ static int axff_init(struct hid_device *hid) goto err_free_mem; axff->report = report; - axff->report->field[0]->value[0] = 0x00; - axff->report->field[1]->value[0] = 0x00; - axff->report->field[2]->value[0] = 0x00; - axff->report->field[3]->value[0] = 0x00; usbhid_submit_report(hid, axff->report, USB_DIR_OUT); - hid_info(hid, "Force Feedback for ACRUX game controllers by Sergei Kolzun\n"); + hid_info(hid, "Force Feedback for ACRUX game controllers by Sergei Kolzun \n"); return 0; -- cgit v1.2.3-55-g7522 From 7362cd2286d2364cca6738b583668f64254fe04b Mon Sep 17 00:00:00 2001 From: Michal Malý Date: Thu, 4 Aug 2011 16:16:09 +0200 Subject: HID: lg4ff - Move handling of Logitech wheels to lg4ff driver This is the first out of five patches me and Simon Wood (CC'd) have been working on. It separates the handling of Logite from the generic lgff driver and adds additional features specific for the Logitech wheels, namely - Native mode support for Driving Force GT, Driving Force Pro, G25 and G27 wheels Every Logitech wheel reports itself as generic Logitech Driving Force wheel (VID 046d, PID c294). This is done to ensu wheel will work on every USB HID-aware system even when no Logitech driver is available. It however limits the capabilit wheel - range is limited to 200 degrees, G25/G27 don't report the clutch pedal and there is only one combined axis for t brake. The switch to native mode is done via hardware-specific command which is different for each wheel. When the wheel receives such command, it simulates reconnect and reports to the OS with its actual PID. - Adjustable wheel range DFGT, DFP, G25 and G27 have variable range of the steering wheel. The range is limited by applying a maximum constant when the wheel is turned beyond the allowed range. The limit as also set by a hardware-specific command. There is a comm command for DFGT, G25 and G27 and another one for DFP. It is probably possible to use the DFP command to limit the range other Logitech wheels too, but this is not supported by the official Logitech driver for Windows. The patch adds a sysfs interface which allows for the range to be set from userspace. - Fixed autocentering command All Logitech wheels support FF_AUTOCENTER effect. The original implementation in the lgff driver didn't work well with patch fixes it. According to USB communication sniffs the Formula Force EX (pretty much rebranded original Driving Force accept the generic autocentering command, this issue is also addressed by the patch There are still some features this patch doesn't cover, but since some of them will most likely require modifications of memless driver we have decided not to include them yet. As first we decided to move the handling of Logitech wheels from hid-lgff driver to hid-lg4ff driver (originally used fo At also adds PID of Logitech Driving Force GT. Signed-off-by: Michal Malý Signed-off-by: Simon Wood Signed-off-by: Jiri Kosina --- drivers/hid/hid-ids.h | 1 + drivers/hid/hid-lg.c | 19 ++++++++------ drivers/hid/hid-lg4ff.c | 70 +++++++++++++++++++++++++++++++++---------------- drivers/hid/hid-lgff.c | 13 --------- 4 files changed, 60 insertions(+), 43 deletions(-) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index db63ccf21cc8..ea2c338814fe 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -436,6 +436,7 @@ #define USB_DEVICE_ID_LOGITECH_MOMO_WHEEL 0xc295 #define USB_DEVICE_ID_LOGITECH_DFP_WHEEL 0xc298 #define USB_DEVICE_ID_LOGITECH_G25_WHEEL 0xc299 +#define USB_DEVICE_ID_LOGITECH_DFGT_WHEEL 0xc29a #define USB_DEVICE_ID_LOGITECH_G27_WHEEL 0xc29b #define USB_DEVICE_ID_LOGITECH_WII_WHEEL 0xc29c #define USB_DEVICE_ID_LOGITECH_ELITE_KBD 0xc30a diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c index a7f916e8fc32..e0ae4a09425d 100644 --- a/drivers/hid/hid-lg.c +++ b/drivers/hid/hid-lg.c @@ -363,7 +363,7 @@ static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id) goto err_free; } - if (quirks & (LG_FF | LG_FF2 | LG_FF3)) + if (quirks & (LG_FF | LG_FF2 | LG_FF3 | LG_FF4)) connect_mask &= ~HID_CONNECT_FF; ret = hid_hw_start(hdev, connect_mask); @@ -372,7 +372,8 @@ static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id) goto err_free; } - if (quirks & LG_FF4) { + /* Setup wireless link with Logitech Wii wheel */ + if(hdev->product == USB_DEVICE_ID_LOGITECH_WII_WHEEL) { unsigned char buf[] = { 0x00, 0xAF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; ret = hdev->hid_output_raw_report(hdev, buf, sizeof(buf), HID_FEATURE_REPORT); @@ -431,7 +432,7 @@ static const struct hid_device_id lg_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_EXTREME_3D), .driver_data = LG_NOGET }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WHEEL), - .driver_data = LG_NOGET | LG_FF }, + .driver_data = LG_NOGET | LG_FF4 }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD_CORD), .driver_data = LG_FF2 }, @@ -444,15 +445,17 @@ static const struct hid_device_id lg_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_FORCE3D_PRO), .driver_data = LG_FF }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOMO_WHEEL), - .driver_data = LG_FF }, + .driver_data = LG_FF4 }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2), - .driver_data = LG_FF }, + .driver_data = LG_FF4 }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G25_WHEEL), - .driver_data = LG_FF }, + .driver_data = LG_FF4 }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DFGT_WHEEL), + .driver_data = LG_FF4 }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G27_WHEEL), - .driver_data = LG_FF }, + .driver_data = LG_FF4 }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DFP_WHEEL), - .driver_data = LG_NOGET | LG_FF }, + .driver_data = LG_NOGET | LG_FF4 }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WII_WHEEL), .driver_data = LG_FF4 }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_FFG ), diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c index fa550c8e1d1b..1fcb2da32785 100644 --- a/drivers/hid/hid-lg4ff.c +++ b/drivers/hid/hid-lg4ff.c @@ -29,17 +29,32 @@ #include "usbhid/usbhid.h" #include "hid-lg.h" +#include "hid-ids.h" -struct lg4ff_device { - struct hid_report *report; -}; - -static const signed short ff4_wheel_ac[] = { +static const signed short lg4ff_wheel_effects[] = { FF_CONSTANT, FF_AUTOCENTER, -1 }; +struct lg4ff_wheel { + const __u32 product_id; + const signed short *ff_effects; + const __u16 min_range; + const __u16 max_range; +}; + +static const struct lg4ff_wheel lg4ff_devices[] = { + {USB_DEVICE_ID_LOGITECH_WHEEL, lg4ff_wheel_effects, 40, 270}, + {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL, lg4ff_wheel_effects, 40, 270}, + {USB_DEVICE_ID_LOGITECH_DFP_WHEEL, lg4ff_wheel_effects, 40, 900}, + {USB_DEVICE_ID_LOGITECH_G25_WHEEL, lg4ff_wheel_effects, 40, 900}, + {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, lg4ff_wheel_effects, 40, 900}, + {USB_DEVICE_ID_LOGITECH_G27_WHEEL, lg4ff_wheel_effects, 40, 900}, + {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2, lg4ff_wheel_effects, 40, 270}, + {USB_DEVICE_ID_LOGITECH_WII_WHEEL, lg4ff_wheel_effects, 40, 270} +}; + static int hid_lg4ff_play(struct input_dev *dev, void *data, struct ff_effect *effect) { @@ -55,13 +70,12 @@ static int hid_lg4ff_play(struct input_dev *dev, void *data, x = effect->u.ramp.start_level + 0x80; /* 0x80 is no force */ CLAMP(x); report->field[0]->value[0] = 0x11; /* Slot 1 */ - report->field[0]->value[1] = 0x10; + report->field[0]->value[1] = 0x08; report->field[0]->value[2] = x; - report->field[0]->value[3] = 0x00; + report->field[0]->value[3] = 0x80; report->field[0]->value[4] = 0x00; - report->field[0]->value[5] = 0x08; + report->field[0]->value[5] = 0x00; report->field[0]->value[6] = 0x00; - dbg_hid("Autocenter, x=0x%02X\n", x); usbhid_submit_report(hid, report, USB_DIR_OUT); break; @@ -74,15 +88,14 @@ static void hid_lg4ff_set_autocenter(struct input_dev *dev, u16 magnitude) struct hid_device *hid = input_get_drvdata(dev); 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); - __s32 *value = report->field[0]->value; - *value++ = 0xfe; - *value++ = 0x0d; - *value++ = 0x07; - *value++ = 0x07; - *value++ = (magnitude >> 8) & 0xff; - *value++ = 0x00; - *value = 0x00; + report->field[0]->value[0] = 0xfe; + report->field[0]->value[1] = 0x0d; + report->field[0]->value[2] = magnitude >> 13; + report->field[0]->value[3] = magnitude >> 13; + report->field[0]->value[4] = magnitude >> 8; + report->field[0]->value[5] = 0x00; + report->field[0]->value[6] = 0x00; usbhid_submit_report(hid, report, USB_DIR_OUT); } @@ -95,9 +108,7 @@ int lg4ff_init(struct hid_device *hid) struct input_dev *dev = hidinput->input; struct hid_report *report; struct hid_field *field; - const signed short *ff_bits = ff4_wheel_ac; - int error; - int i; + int error, i, j; /* Find the report to use */ if (list_empty(report_list)) { @@ -117,9 +128,24 @@ int lg4ff_init(struct hid_device *hid) hid_err(hid, "NULL field\n"); return -1; } + + /* Check what wheel has been connected */ + for (i = 0; i < ARRAY_SIZE(lg4ff_devices); i++) { + if (hid->product == lg4ff_devices[i].product_id) { + dbg_hid("Found compatible device, product ID %04X\n", lg4ff_devices[i].product_id); + break; + } + } + + if (i == ARRAY_SIZE(lg4ff_devices)) { + hid_err(hid, "Device is not supported by lg4ff driver. If you think it should be, consider reporting a bug to" + "LKML, Simon Wood or Michal Maly \n"); + return -1; + } - for (i = 0; ff_bits[i] >= 0; i++) - set_bit(ff_bits[i], dev->ffbit); + /* Set supported force feedback capabilities */ + for (j = 0; lg4ff_devices[i].ff_effects[j] >= 0; j++) + set_bit(lg4ff_devices[i].ff_effects[j], dev->ffbit); error = input_ff_create_memless(dev, NULL, hid_lg4ff_play); diff --git a/drivers/hid/hid-lgff.c b/drivers/hid/hid-lgff.c index 088f85049290..27bc54f92f44 100644 --- a/drivers/hid/hid-lgff.c +++ b/drivers/hid/hid-lgff.c @@ -58,12 +58,6 @@ static const signed short ff_joystick_ac[] = { -1 }; -static const signed short ff_wheel[] = { - FF_CONSTANT, - FF_AUTOCENTER, - -1 -}; - static const struct dev_type devices[] = { { 0x046d, 0xc211, ff_rumble }, { 0x046d, 0xc219, ff_rumble }, @@ -71,14 +65,7 @@ static const struct dev_type devices[] = { { 0x046d, 0xc286, ff_joystick_ac }, { 0x046d, 0xc287, ff_joystick_ac }, { 0x046d, 0xc293, ff_joystick }, - { 0x046d, 0xc294, ff_wheel }, - { 0x046d, 0xc298, ff_wheel }, - { 0x046d, 0xc299, ff_wheel }, - { 0x046d, 0xc29b, ff_wheel }, { 0x046d, 0xc295, ff_joystick }, - { 0x046d, 0xc298, ff_wheel }, - { 0x046d, 0xc299, ff_wheel }, - { 0x046d, 0xca03, ff_wheel }, }; static int hid_lgff_play(struct input_dev *dev, void *data, struct ff_effect *effect) -- cgit v1.2.3-55-g7522 From 96440c8a00e22e541135dee2eba9f3e7d8195f65 Mon Sep 17 00:00:00 2001 From: Michal Malý Date: Thu, 4 Aug 2011 16:18:11 +0200 Subject: HID: lg4ff - Add support for native mode switching This patch allows the lg4ff driver to switch wheels to the native mode. Since this is specific to Logitech wheels only, it's handled in hid-lg4ff rather than hid-lg. Signed-off-by: Michal Malý Signed-off-by: Simon Wood Signed-off-by: Jiri Kosina --- drivers/hid/hid-lg4ff.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c index 1fcb2da32785..8eb938d7aa02 100644 --- a/drivers/hid/hid-lg4ff.c +++ b/drivers/hid/hid-lg4ff.c @@ -31,6 +31,17 @@ #include "hid-lg.h" #include "hid-ids.h" +#define DFGT_REV_MAJ 0x13 +#define DFGT_REV_MIN 0x22 +#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 + static const signed short lg4ff_wheel_effects[] = { FF_CONSTANT, FF_AUTOCENTER, @@ -55,6 +66,46 @@ static const struct lg4ff_wheel lg4ff_devices[] = { {USB_DEVICE_ID_LOGITECH_WII_WHEEL, lg4ff_wheel_effects, 40, 270} }; +struct lg4ff_native_cmd { + const __u8 cmd_num; /* Number of commands to send */ + const __u8 cmd[]; +}; + +struct lg4ff_usb_revision { + const __u16 rev_maj; + const __u16 rev_min; + const struct lg4ff_native_cmd *command; +}; + +static const struct lg4ff_native_cmd native_dfp = { + 1, + {0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00} +}; + +static const struct lg4ff_native_cmd native_dfgt = { + 2, + {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1st command */ + 0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00} /* 2nd command */ +}; + +static const struct lg4ff_native_cmd native_g25 = { + 1, + {0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00} +}; + +static const struct lg4ff_native_cmd native_g27 = { + 2, + {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1st command */ + 0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00} /* 2nd command */ +}; + +static const struct lg4ff_usb_revision lg4ff_revs[] = { + {DFGT_REV_MAJ, DFGT_REV_MIN, &native_dfgt}, /* Driving Force GT */ + {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 */ +}; + static int hid_lg4ff_play(struct input_dev *dev, void *data, struct ff_effect *effect) { @@ -100,6 +151,20 @@ static void hid_lg4ff_set_autocenter(struct input_dev *dev, u16 magnitude) usbhid_submit_report(hid, report, USB_DIR_OUT); } +static void hid_lg4ff_switch_native(struct hid_device *hid, const struct lg4ff_native_cmd *cmd) +{ + 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; + + j = 0; + while (j < 7*cmd->cmd_num) { + for (i = 0; i < 7; i++) + report->field[0]->value[i] = cmd->cmd[j++]; + + usbhid_submit_report(hid, report, USB_DIR_OUT); + } +} int lg4ff_init(struct hid_device *hid) { @@ -108,7 +173,9 @@ int lg4ff_init(struct hid_device *hid) struct input_dev *dev = hidinput->input; struct hid_report *report; struct hid_field *field; + struct usb_device_descriptor *udesc = 0; int error, i, j; + __u16 bcdDevice, rev_maj, rev_min; /* Find the report to use */ if (list_empty(report_list)) { @@ -143,6 +210,28 @@ 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); + + 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"); + } + } + } + /* Set supported force feedback capabilities */ for (j = 0; lg4ff_devices[i].ff_effects[j] >= 0; j++) set_bit(lg4ff_devices[i].ff_effects[j], dev->ffbit); -- cgit v1.2.3-55-g7522 From 30bb75d71b3732c0adb6297815288ce0fb9cc04c Mon Sep 17 00:00:00 2001 From: Michal Malý Date: Thu, 4 Aug 2011 16:20:40 +0200 Subject: HID: lg4ff - Add range setting support and sysfs interface Wheel range of certain Logitech wheels - namely Driving Force GT, Driving Force Pro, G25 and G27 can be adjusted. Minimu is 40 degrees, maximum 900. DFGT, G25 and G27 all use a common command, DFP uses another one. Range can be set from userspace by writing to "/sys/module/hid_logitech/drivers/hid:logitech/range". The driver use list to store range of each connected wheel; it's not possible to use driver_data in hid_device struct as it's already b hig-lg driver. Signed-off-by: Michal Malý Signed-off-by: Simon Wood Signed-off-by: Jiri Kosina --- drivers/hid/hid-lg.c | 10 +++ drivers/hid/hid-lg.h | 2 + drivers/hid/hid-lg4ff.c | 232 +++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 232 insertions(+), 12 deletions(-) diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c index e0ae4a09425d..e7a7bd1eb34a 100644 --- a/drivers/hid/hid-lg.c +++ b/drivers/hid/hid-lg.c @@ -406,6 +406,15 @@ err_free: return ret; } +static void lg_remove(struct hid_device *hdev) +{ + unsigned long quirks = (unsigned long)hid_get_drvdata(hdev); + if(quirks & LG_FF4) + lg4ff_deinit(hdev); + + hid_hw_stop(hdev); +} + static const struct hid_device_id lg_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER), .driver_data = LG_RDESC | LG_WIRELESS }, @@ -481,6 +490,7 @@ static struct hid_driver lg_driver = { .input_mapped = lg_input_mapped, .event = lg_event, .probe = lg_probe, + .remove = lg_remove, }; static int __init lg_init(void) diff --git a/drivers/hid/hid-lg.h b/drivers/hid/hid-lg.h index b0100ba2ae0b..3a3295991ab3 100644 --- a/drivers/hid/hid-lg.h +++ b/drivers/hid/hid-lg.h @@ -21,8 +21,10 @@ static inline int lg3ff_init(struct hid_device *hdev) { return -1; } #ifdef CONFIG_LOGIWII_FF int lg4ff_init(struct hid_device *hdev); +int lg4ff_deinit(struct hid_device *hdev); #else 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 8eb938d7aa02..5c9eef2267ed 100644 --- a/drivers/hid/hid-lg4ff.c +++ b/drivers/hid/hid-lg4ff.c @@ -42,6 +42,29 @@ #define G27_REV_MAJ 0x12 #define G27_REV_MIN 0x38 +#define to_hid_device(pdev) container_of(pdev, struct hid_device, dev) + +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); +static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr, char *buf); +static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); + +static DEVICE_ATTR(range, S_IRWXU | S_IRWXG | S_IRWXO, lg4ff_range_show, lg4ff_range_store); + +static bool list_inited; + +struct lg4ff_device_entry { + char *device_id; /* Use name in respective kobject structure's address as the ID */ + __u16 range; + __u16 min_range; + __u16 max_range; + __u8 leds; + struct list_head list; + void (*set_range)(struct hid_device *hid, u16 range); +}; + +static struct lg4ff_device_entry device_list; + static const signed short lg4ff_wheel_effects[] = { FF_CONSTANT, FF_AUTOCENTER, @@ -53,17 +76,18 @@ struct lg4ff_wheel { const signed short *ff_effects; const __u16 min_range; const __u16 max_range; + void (*set_range)(struct hid_device *hid, u16 range); }; static const struct lg4ff_wheel lg4ff_devices[] = { - {USB_DEVICE_ID_LOGITECH_WHEEL, lg4ff_wheel_effects, 40, 270}, - {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL, lg4ff_wheel_effects, 40, 270}, - {USB_DEVICE_ID_LOGITECH_DFP_WHEEL, lg4ff_wheel_effects, 40, 900}, - {USB_DEVICE_ID_LOGITECH_G25_WHEEL, lg4ff_wheel_effects, 40, 900}, - {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, lg4ff_wheel_effects, 40, 900}, - {USB_DEVICE_ID_LOGITECH_G27_WHEEL, lg4ff_wheel_effects, 40, 900}, - {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2, lg4ff_wheel_effects, 40, 270}, - {USB_DEVICE_ID_LOGITECH_WII_WHEEL, lg4ff_wheel_effects, 40, 270} + {USB_DEVICE_ID_LOGITECH_WHEEL, lg4ff_wheel_effects, 40, 270, NULL}, + {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL, lg4ff_wheel_effects, 40, 270, NULL}, + {USB_DEVICE_ID_LOGITECH_DFP_WHEEL, lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_dfp}, + {USB_DEVICE_ID_LOGITECH_G25_WHEEL, lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_g25}, + {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_g25}, + {USB_DEVICE_ID_LOGITECH_G27_WHEEL, lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_g25}, + {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2, lg4ff_wheel_effects, 40, 270, NULL}, + {USB_DEVICE_ID_LOGITECH_WII_WHEEL, lg4ff_wheel_effects, 40, 270, NULL} }; struct lg4ff_native_cmd { @@ -106,8 +130,7 @@ static const struct lg4ff_usb_revision lg4ff_revs[] = { {G27_REV_MAJ, G27_REV_MIN, &native_g27}, /* G27 */ }; -static int hid_lg4ff_play(struct input_dev *dev, void *data, - struct ff_effect *effect) +static int hid_lg4ff_play(struct input_dev *dev, void *data, struct ff_effect *effect) { struct hid_device *hid = input_get_drvdata(dev); struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; @@ -151,6 +174,77 @@ static void hid_lg4ff_set_autocenter(struct input_dev *dev, u16 magnitude) usbhid_submit_report(hid, report, USB_DIR_OUT); } +/* Sends command to set range compatible with G25/G27/Driving Force GT */ +static void hid_lg4ff_set_range_g25(struct hid_device *hid, u16 range) +{ + 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); + dbg_hid("G25/G27/DFGT: setting range to %u\n", range); + + report->field[0]->value[0] = 0xf8; + report->field[0]->value[1] = 0x81; + report->field[0]->value[2] = range & 0x00ff; + report->field[0]->value[3] = (range & 0xff00) >> 8; + report->field[0]->value[4] = 0x00; + report->field[0]->value[5] = 0x00; + report->field[0]->value[6] = 0x00; + + usbhid_submit_report(hid, report, USB_DIR_OUT); +} + +/* Sends commands to set range compatible with Driving Force Pro wheel */ +static void hid_lg4ff_set_range_dfp(struct hid_device *hid, __u16 range) +{ + 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); + int start_left, start_right, full_range; + dbg_hid("Driving Force Pro: setting range to %u\n", range); + + /* Prepare "coarse" limit command */ + report->field[0]->value[0] = 0xf8; + report->field[0]->value[1] = 0x00; /* Set later */ + report->field[0]->value[2] = 0x00; + report->field[0]->value[3] = 0x00; + report->field[0]->value[4] = 0x00; + report->field[0]->value[5] = 0x00; + report->field[0]->value[6] = 0x00; + + if (range > 200) { + report->field[0]->value[1] = 0x03; + full_range = 900; + } else { + report->field[0]->value[1] = 0x02; + full_range = 200; + } + usbhid_submit_report(hid, report, USB_DIR_OUT); + + /* Prepare "fine" limit command */ + report->field[0]->value[0] = 0x81; + report->field[0]->value[1] = 0x0b; + report->field[0]->value[2] = 0x00; + report->field[0]->value[3] = 0x00; + report->field[0]->value[4] = 0x00; + report->field[0]->value[5] = 0x00; + report->field[0]->value[6] = 0x00; + + if (range == 200 || range == 900) { /* Do not apply any fine limit */ + usbhid_submit_report(hid, report, USB_DIR_OUT); + return; + } + + /* Construct fine limit command */ + start_left = (((full_range - range + 1) * 2047) / full_range); + start_right = 0xfff - start_left; + + report->field[0]->value[2] = start_left >> 4; + report->field[0]->value[3] = start_right >> 4; + report->field[0]->value[4] = 0xff; + report->field[0]->value[5] = (start_right & 0xe) << 4 | (start_left & 0xe); + report->field[0]->value[6] = 0xff; + + usbhid_submit_report(hid, report, USB_DIR_OUT); +} + static void hid_lg4ff_switch_native(struct hid_device *hid, const struct lg4ff_native_cmd *cmd) { struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; @@ -166,6 +260,60 @@ static void hid_lg4ff_switch_native(struct hid_device *hid, const struct lg4ff_n } } +/* Read current range and display it in terminal */ +static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct lg4ff_device_entry *entry = 0; + struct list_head *h; + struct hid_device *hid = to_hid_device(dev); + size_t count; + + list_for_each(h, &device_list.list) { + entry = list_entry(h, struct lg4ff_device_entry, list); + if (strcmp(entry->device_id, (&hid->dev)->kobj.name) == 0) + break; + } + if (h == &device_list.list) { + dbg_hid("Device not found!"); + return 0; + } + + count = scnprintf(buf, PAGE_SIZE, "%u\n", entry->range); + return count; +} + +/* Set range to user specified value, call appropriate function + * according to the type of the wheel */ +static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct lg4ff_device_entry *entry = 0; + struct list_head *h; + struct hid_device *hid = to_hid_device(dev); + __u16 range = simple_strtoul(buf, NULL, 10); + + list_for_each(h, &device_list.list) { + entry = list_entry(h, struct lg4ff_device_entry, list); + if (strcmp(entry->device_id, (&hid->dev)->kobj.name) == 0) + break; + } + if (h == &device_list.list) { + dbg_hid("Device not found!"); + return count; + } + + if (range == 0) + range = entry->max_range; + + /* Check if the wheel supports range setting + * and that the range is within limits for the wheel */ + if (entry->set_range != NULL && range >= entry->min_range && range <= entry->max_range) { + entry->set_range(hid, range); + entry->range = range; + } + + return count; +} + int lg4ff_init(struct hid_device *hid) { struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list); @@ -173,7 +321,8 @@ int lg4ff_init(struct hid_device *hid) struct input_dev *dev = hidinput->input; struct hid_report *report; struct hid_field *field; - struct usb_device_descriptor *udesc = 0; + struct lg4ff_device_entry *entry; + struct usb_device_descriptor *udesc; int error, i, j; __u16 bcdDevice, rev_maj, rev_min; @@ -195,7 +344,7 @@ int lg4ff_init(struct hid_device *hid) hid_err(hid, "NULL field\n"); return -1; } - + /* Check what wheel has been connected */ for (i = 0; i < ARRAY_SIZE(lg4ff_devices); i++) { if (hid->product == lg4ff_devices[i].product_id) { @@ -244,7 +393,66 @@ int lg4ff_init(struct hid_device *hid) if (test_bit(FF_AUTOCENTER, dev->ffbit)) dev->ff->set_autocenter = hid_lg4ff_set_autocenter; + /* Initialize device_list if this is the first device to handle by lg4ff */ + if (!list_inited) { + INIT_LIST_HEAD(&device_list.list); + list_inited = 1; + } + + /* Add the device to device_list */ + entry = (struct lg4ff_device_entry *)kzalloc(sizeof(struct lg4ff_device_entry), GFP_KERNEL); + if (!entry) { + hid_err(hid, "Cannot add device, insufficient memory.\n"); + return -ENOMEM; + } + entry->device_id = (char *)kzalloc(strlen((&hid->dev)->kobj.name) + 1, GFP_KERNEL); + if (!entry->device_id) { + hid_err(hid, "Cannot set device_id, insufficient memory.\n"); + return -ENOMEM; + } + strcpy(entry->device_id, (&hid->dev)->kobj.name); + entry->min_range = lg4ff_devices[i].min_range; + entry->max_range = lg4ff_devices[i].max_range; + entry->set_range = lg4ff_devices[i].set_range; + list_add(&entry->list, &device_list.list); + + /* Create sysfs interface */ + error = device_create_file(&hid->dev, &dev_attr_range); + if (error) + return error; + dbg_hid("sysfs interface created\n"); + + /* Set the maximum range to start with */ + entry->range = entry->max_range; + if (entry->set_range != NULL) + entry->set_range(hid, entry->range); + hid_info(hid, "Force feedback for Logitech Speed Force Wireless by Simon Wood \n"); return 0; } +int lg4ff_deinit(struct hid_device *hid) +{ + bool found = 0; + struct lg4ff_device_entry *entry; + struct list_head *h, *g; + list_for_each_safe(h, g, &device_list.list) { + entry = list_entry(h, struct lg4ff_device_entry, list); + if (strcmp(entry->device_id, (&hid->dev)->kobj.name) == 0) { + list_del(h); + kfree(entry->device_id); + kfree(entry); + found = 1; + break; + } + } + + if (!found) { + dbg_hid("Device entry not found!\n"); + return -1; + } + + device_remove_file(&hid->dev, &dev_attr_range); + dbg_hid("Device successfully unregistered\n"); + return 0; +} -- cgit v1.2.3-55-g7522 From 6e2de8e0ab238f7ee818c545a7ea97a4fc333e33 Mon Sep 17 00:00:00 2001 From: Michal Malý Date: Thu, 4 Aug 2011 16:22:07 +0200 Subject: HID: lg4ff - Add autocentering command accepted by Formula Force EX The Logitech driver sends Formula Force EX wheel a different command to autocenering force. FFEX will accept the standard command used by the rest of the wheels, but it won't set the centering properly. Signed-off-by: Michal Malý Signed-off-by: Simon Wood Signed-off-by: Jiri Kosina --- drivers/hid/hid-lg4ff.c | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c index 5c9eef2267ed..dc38c2d89df1 100644 --- a/drivers/hid/hid-lg4ff.c +++ b/drivers/hid/hid-lg4ff.c @@ -157,7 +157,9 @@ static int hid_lg4ff_play(struct input_dev *dev, void *data, struct ff_effect *e return 0; } -static void hid_lg4ff_set_autocenter(struct input_dev *dev, u16 magnitude) +/* Sends default autocentering command compatible with + * all wheels except Formula Force EX */ +static void hid_lg4ff_set_autocenter_default(struct input_dev *dev, u16 magnitude) { struct hid_device *hid = input_get_drvdata(dev); struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; @@ -174,6 +176,26 @@ static void hid_lg4ff_set_autocenter(struct input_dev *dev, u16 magnitude) usbhid_submit_report(hid, report, USB_DIR_OUT); } +/* Sends autocentering command compatible with Formula Force EX */ +static void hid_lg4ff_set_autocenter_ffex(struct input_dev *dev, u16 magnitude) +{ + struct hid_device *hid = input_get_drvdata(dev); + 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); + magnitude = magnitude * 90 / 65535; + + + report->field[0]->value[0] = 0xfe; + report->field[0]->value[1] = 0x03; + report->field[0]->value[2] = magnitude >> 14; + report->field[0]->value[3] = magnitude >> 14; + report->field[0]->value[4] = magnitude; + report->field[0]->value[5] = 0x00; + report->field[0]->value[6] = 0x00; + + usbhid_submit_report(hid, report, USB_DIR_OUT); +} + /* Sends command to set range compatible with G25/G27/Driving Force GT */ static void hid_lg4ff_set_range_g25(struct hid_device *hid, u16 range) { @@ -390,8 +412,16 @@ int lg4ff_init(struct hid_device *hid) if (error) return error; - if (test_bit(FF_AUTOCENTER, dev->ffbit)) - dev->ff->set_autocenter = hid_lg4ff_set_autocenter; + /* 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 */ + dev->ff->set_autocenter = hid_lg4ff_set_autocenter_ffex; + else + dev->ff->set_autocenter = hid_lg4ff_set_autocenter_default; + + dev->ff->set_autocenter(dev, 0); + } /* Initialize device_list if this is the first device to handle by lg4ff */ if (!list_inited) { -- cgit v1.2.3-55-g7522 From a7ac90f18cf1182f01846f36f7d2706e2cc7747c Mon Sep 17 00:00:00 2001 From: Michal Malý Date: Thu, 4 Aug 2011 16:24:22 +0200 Subject: HID: lg4ff - Fix misleading info in Kconfig The description of lg4ff driver has to be changed to reflect the fact that the driver now handles a lot more Logitech wh the Wii. Entry in Kconfig has been renamed to LOGIWHEELS_FF Signed-off-by: Michal Malý Signed-off-by: Simon Wood Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 15 +++++++++++---- drivers/hid/Makefile | 2 +- drivers/hid/hid-lg.h | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 306b15f39c9c..6ae234f21453 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -278,13 +278,20 @@ config LOGIG940_FF Say Y here if you want to enable force feedback support for Logitech Flight System G940 devices. -config LOGIWII_FF - bool "Logitech Speed Force Wireless force feedback support" +config LOGIWHEELS_FF + bool "Logitech wheels configuration and force feedback support" depends on HID_LOGITECH select INPUT_FF_MEMLESS help - Say Y here if you want to enable force feedback support for Logitech - Speed Force Wireless (Wii) devices. + Say Y here if you want to enable force feedback and range setting + support for following Logitech wheels: + - Logitech Driving Force + - Logitech Driving Force Pro + - Logitech Driving Force GT + - Logitech G25 + - Logitech G27 + - Logitech MOMO/MOMO 2 + - Logitech Formula Force EX config HID_MAGICMOUSE tristate "Apple MagicMouse multi-touch support" diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 0a0a38e9fd28..77c9e5d7ff97 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -21,7 +21,7 @@ endif ifdef CONFIG_LOGIG940_FF hid-logitech-y += hid-lg3ff.o endif -ifdef CONFIG_LOGIWII_FF +ifdef CONFIG_LOGIWHEELS_FF hid-logitech-y += hid-lg4ff.o endif diff --git a/drivers/hid/hid-lg.h b/drivers/hid/hid-lg.h index 3a3295991ab3..4b097286dc78 100644 --- a/drivers/hid/hid-lg.h +++ b/drivers/hid/hid-lg.h @@ -19,7 +19,7 @@ int lg3ff_init(struct hid_device *hdev); static inline int lg3ff_init(struct hid_device *hdev) { return -1; } #endif -#ifdef CONFIG_LOGIWII_FF +#ifdef CONFIG_LOGIWHEELS_FF int lg4ff_init(struct hid_device *hdev); int lg4ff_deinit(struct hid_device *hdev); #else -- cgit v1.2.3-55-g7522 From 18e10aba7f2a41767b9aa97911696c28d0f29083 Mon Sep 17 00:00:00 2001 From: Michal Malý Date: Thu, 4 Aug 2011 16:27:18 +0200 Subject: HID: lg4ff - Document sysfs interface for range setting Adds documentation of the sysfs interface used to set the wheel range. Signed-off-by: Michal Malý Signed-off-by: Simon Wood Signed-off-by: Jiri Kosina --- Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff diff --git a/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff b/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff new file mode 100644 index 000000000000..9aec8ef228b0 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff @@ -0,0 +1,7 @@ +What: /sys/module/hid_logitech/drivers/hid:logitech//range. +Date: July 2011 +KernelVersion: 3.2 +Contact: Michal Malý +Description: Display minimum, maximum and current range of the steering + wheel. Writing a value within min and max boundaries sets the + range of the wheel. -- cgit v1.2.3-55-g7522 From 70c2cabd6ab6616490c7920ade9fd4143e539884 Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Wed, 10 Aug 2011 18:11:10 +0200 Subject: HID: make CONFIG_LOGIWHEELS_FF default to CONFIG_LOGITECH_FF Do this for backwards compatibility reasons (LOGIWHEELS_FF has been split-off from LOGITECH_FF), so that users don't have regressions with 'default' kernel configs. Reported-and-tested-by: simon@mungewell.org Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 6ae234f21453..410eaa902495 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -282,6 +282,7 @@ config LOGIWHEELS_FF bool "Logitech wheels configuration and force feedback support" depends on HID_LOGITECH select INPUT_FF_MEMLESS + default LOGITECH_FF help Say Y here if you want to enable force feedback and range setting support for following Logitech wheels: -- cgit v1.2.3-55-g7522 From 658d4aed59b36f877edc668cc27b188a33e643e5 Mon Sep 17 00:00:00 2001 From: Jeff Brown Date: Mon, 15 Aug 2011 16:44:28 -0700 Subject: HID: hid-multitouch: Filter collections by application usage. This change fixes two problems. First, it ensures that the hid-multitouch driver does not incorrectly map GenericDesktop usages that are intended for other applications, such as a Mouse. Second, it sets the appropriate input properties so that user-space can distinguish TouchScreen devices (INPUT_PROP_DIRECT) from TouchPad devices (INPUT_PROP_POINTER) and configure them accordingly. Signed-off-by: jeffbrown@android.com Acked-By: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-multitouch.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index 58d0e7aaf088..4ee21ac8e856 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -213,6 +213,16 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, struct mt_class *cls = td->mtclass; __s32 quirks = cls->quirks; + /* Only map fields from TouchScreen or TouchPad collections. + * We need to ignore fields that belong to other collections + * such as Mouse that might have the same GenericDesktop usages. */ + if (field->application == HID_DG_TOUCHSCREEN) + set_bit(INPUT_PROP_DIRECT, hi->input->propbit); + else if (field->application == HID_DG_TOUCHPAD) + set_bit(INPUT_PROP_POINTER, hi->input->propbit); + else + return 0; + switch (usage->hid & HID_USAGE_PAGE) { case HID_UP_GENDESK: -- cgit v1.2.3-55-g7522 From c50bb1a4005630f47b5da26336f74a485033a515 Mon Sep 17 00:00:00 2001 From: Jeff Brown Date: Mon, 15 Aug 2011 21:12:09 -0700 Subject: HID: hid-multitouch: Add LG Display Multitouch device. This panel is also known as the Dell ST2220Tc. Signed-off-by: jeffbrown@android.com Reviewed-By: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 1 + drivers/hid/hid-core.c | 1 + drivers/hid/hid-ids.h | 3 +++ drivers/hid/hid-multitouch.c | 5 +++++ 4 files changed, 10 insertions(+) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 306b15f39c9c..3cbe8cc51ef3 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -328,6 +328,7 @@ config HID_MULTITOUCH - Hanvon dual touch panels - Ilitek dual touch panels - IrTouch Infrared USB panels + - LG Display panels (Dell ST2220Tc) - Lumio CrystalTouch panels - MosArt dual-touch panels - PenMount dual touch panels diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 1a5cf0c9cfca..11b3b39401ee 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1396,6 +1396,7 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_ERGO_525V) }, { 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 ) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LG, USB_DEVICE_ID_LG_MULTITOUCH) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER_2) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index db63ccf21cc8..637e6e96a0d7 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -419,6 +419,9 @@ #define USB_DEVICE_ID_LD_HYBRID 0x2090 #define USB_DEVICE_ID_LD_HEATCONTROL 0x20A0 +#define USB_VENDOR_ID_LG 0x1fd2 +#define USB_DEVICE_ID_LG_MULTITOUCH 0x0064 + #define USB_VENDOR_ID_LOGITECH 0x046d #define USB_DEVICE_ID_LOGITECH_RECEIVER 0xc101 #define USB_DEVICE_ID_LOGITECH_HARMONY_FIRST 0xc110 diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index 4ee21ac8e856..b03a0b0e9b63 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -682,6 +682,11 @@ static const struct hid_device_id mt_devices[] = { HID_USB_DEVICE(USB_VENDOR_ID_IRTOUCHSYSTEMS, USB_DEVICE_ID_IRTOUCH_INFRARED_USB) }, + /* LG Display panels */ + { .driver_data = MT_CLS_DEFAULT, + HID_USB_DEVICE(USB_VENDOR_ID_LG, + USB_DEVICE_ID_LG_MULTITOUCH) }, + /* Lumio panels */ { .driver_data = MT_CLS_CONFIDENCE_MINUS_ONE, HID_USB_DEVICE(USB_VENDOR_ID_LUMIO, -- cgit v1.2.3-55-g7522 From 2bbaf771eb69f7fd333c4708fd76f94a3bdaf207 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 18 Aug 2011 16:42:26 +0300 Subject: HID: hid-lg4ff: silence sparse complaint Sparse complains that: drivers/hid/hid-lg4ff.c:288:44: warning: Using plain integer as NULL pointer drivers/hid/hid-lg4ff.c:311:44: warning: Using plain integer as NULL pointer Signed-off-by: Dan Carpenter Signed-off-by: Jiri Kosina --- drivers/hid/hid-lg4ff.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c index dc38c2d89df1..2d8762d4f32a 100644 --- a/drivers/hid/hid-lg4ff.c +++ b/drivers/hid/hid-lg4ff.c @@ -285,7 +285,7 @@ static void hid_lg4ff_switch_native(struct hid_device *hid, const struct lg4ff_n /* Read current range and display it in terminal */ static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct lg4ff_device_entry *entry = 0; + struct lg4ff_device_entry *uninitialized_var(entry); struct list_head *h; struct hid_device *hid = to_hid_device(dev); size_t count; @@ -308,7 +308,7 @@ static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *att * according to the type of the wheel */ static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct lg4ff_device_entry *entry = 0; + struct lg4ff_device_entry *uninitialized_var(entry); struct list_head *h; struct hid_device *hid = to_hid_device(dev); __u16 range = simple_strtoul(buf, NULL, 10); -- cgit v1.2.3-55-g7522 From 8f25229026c89912574558d0a4e36c8fe51b9bb4 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 19 Aug 2011 11:00:55 +0300 Subject: HID: hid-lg4ff: add a kfree() to an error path There is a small rare potential memory leak here. Also Walter Harms points out that we can do a small cleanup as well by using kstrdup(). Signed-off-by: Dan Carpenter Signed-off-by: Jiri Kosina --- drivers/hid/hid-lg4ff.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c index 2d8762d4f32a..103f30d93f76 100644 --- a/drivers/hid/hid-lg4ff.c +++ b/drivers/hid/hid-lg4ff.c @@ -435,12 +435,12 @@ int lg4ff_init(struct hid_device *hid) hid_err(hid, "Cannot add device, insufficient memory.\n"); return -ENOMEM; } - entry->device_id = (char *)kzalloc(strlen((&hid->dev)->kobj.name) + 1, GFP_KERNEL); + entry->device_id = kstrdup((&hid->dev)->kobj.name, GFP_KERNEL); if (!entry->device_id) { hid_err(hid, "Cannot set device_id, insufficient memory.\n"); + kfree(entry); return -ENOMEM; } - strcpy(entry->device_id, (&hid->dev)->kobj.name); entry->min_range = lg4ff_devices[i].min_range; entry->max_range = lg4ff_devices[i].max_range; entry->set_range = lg4ff_devices[i].set_range; -- cgit v1.2.3-55-g7522 From c003ec216561077b09a8ab38876a7d6ce375f739 Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Tue, 6 Sep 2011 13:50:26 +0200 Subject: HID: wiimote: Support rumble device This adds support for the wiimote's rumble device. Every output report can enable and disable the rumble motor. Hence, every output report must look up our new RUMBLE flag and make sure that it does not unintentionally toggle the rumble motor. Signed-off-by: David Herrmann Signed-off-by: Jiri Kosina --- drivers/hid/hid-wiimote.c | 45 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-wiimote.c b/drivers/hid/hid-wiimote.c index 85a02e5f9fe8..680975436289 100644 --- a/drivers/hid/hid-wiimote.c +++ b/drivers/hid/hid-wiimote.c @@ -46,10 +46,11 @@ struct wiimote_data { struct wiimote_state state; }; -#define WIIPROTO_FLAG_LED1 0x01 -#define WIIPROTO_FLAG_LED2 0x02 -#define WIIPROTO_FLAG_LED3 0x04 -#define WIIPROTO_FLAG_LED4 0x08 +#define WIIPROTO_FLAG_LED1 0x01 +#define WIIPROTO_FLAG_LED2 0x02 +#define WIIPROTO_FLAG_LED3 0x04 +#define WIIPROTO_FLAG_LED4 0x08 +#define WIIPROTO_FLAG_RUMBLE 0x10 #define WIIPROTO_FLAGS_LEDS (WIIPROTO_FLAG_LED1 | WIIPROTO_FLAG_LED2 | \ WIIPROTO_FLAG_LED3 | WIIPROTO_FLAG_LED4) @@ -58,6 +59,7 @@ struct wiimote_data { enum wiiproto_reqs { WIIPROTO_REQ_NULL = 0x0, + WIIPROTO_REQ_RUMBLE = 0x10, WIIPROTO_REQ_LED = 0x11, WIIPROTO_REQ_DRM = 0x12, WIIPROTO_REQ_STATUS = 0x20, @@ -172,6 +174,39 @@ static void wiimote_queue(struct wiimote_data *wdata, const __u8 *buffer, spin_unlock_irqrestore(&wdata->qlock, flags); } +/* + * This sets the rumble bit on the given output report if rumble is + * currently enabled. + * \cmd1 must point to the second byte in the output report => &cmd[1] + * This must be called on nearly every output report before passing it + * into the output queue! + */ +static inline void wiiproto_keep_rumble(struct wiimote_data *wdata, __u8 *cmd1) +{ + if (wdata->state.flags & WIIPROTO_FLAG_RUMBLE) + *cmd1 |= 0x01; +} + +static void wiiproto_req_rumble(struct wiimote_data *wdata, __u8 rumble) +{ + __u8 cmd[2]; + + rumble = !!rumble; + if (rumble == !!(wdata->state.flags & WIIPROTO_FLAG_RUMBLE)) + return; + + if (rumble) + wdata->state.flags |= WIIPROTO_FLAG_RUMBLE; + else + wdata->state.flags &= ~WIIPROTO_FLAG_RUMBLE; + + cmd[0] = WIIPROTO_REQ_RUMBLE; + cmd[1] = 0; + + wiiproto_keep_rumble(wdata, &cmd[1]); + wiimote_queue(wdata, cmd, sizeof(cmd)); +} + static void wiiproto_req_leds(struct wiimote_data *wdata, int leds) { __u8 cmd[2]; @@ -193,6 +228,7 @@ static void wiiproto_req_leds(struct wiimote_data *wdata, int leds) if (leds & WIIPROTO_FLAG_LED4) cmd[1] |= 0x80; + wiiproto_keep_rumble(wdata, &cmd[1]); wiimote_queue(wdata, cmd, sizeof(cmd)); } @@ -217,6 +253,7 @@ static void wiiproto_req_drm(struct wiimote_data *wdata, __u8 drm) cmd[1] = 0; cmd[2] = drm; + wiiproto_keep_rumble(wdata, &cmd[1]); wiimote_queue(wdata, cmd, sizeof(cmd)); } -- cgit v1.2.3-55-g7522 From d020be9246735ff1fc49b99bea0574a597592709 Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Tue, 6 Sep 2011 13:50:27 +0200 Subject: HID: wiimote: Add force-feedback support The wiimote has a single rumble motor. This adds force feedback support for wiimote devices with FF_RUMBLE. The rumble motor is very simple and only supports an on/off switch so no complex ff-effects are supported. This also removes the event callback that was registered before but unused. The ff-device overwrites this callback, anyway. Signed-off-by: David Herrmann Signed-off-by: Jiri Kosina --- drivers/hid/hid-wiimote.c | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-wiimote.c b/drivers/hid/hid-wiimote.c index 680975436289..57faac527230 100644 --- a/drivers/hid/hid-wiimote.c +++ b/drivers/hid/hid-wiimote.c @@ -305,9 +305,28 @@ static void wiimote_leds_set(struct led_classdev *led_dev, } } -static int wiimote_input_event(struct input_dev *dev, unsigned int type, - unsigned int code, int value) +static int wiimote_ff_play(struct input_dev *dev, void *data, + struct ff_effect *eff) { + struct wiimote_data *wdata = input_get_drvdata(dev); + __u8 value; + unsigned long flags; + + /* + * The wiimote supports only a single rumble motor so if any magnitude + * is set to non-zero then we start the rumble motor. If both are set to + * zero, we stop the rumble motor. + */ + + if (eff->u.rumble.strong_magnitude || eff->u.rumble.weak_magnitude) + value = 1; + else + value = 0; + + spin_lock_irqsave(&wdata->state.lock, flags); + wiiproto_req_rumble(wdata, value); + spin_unlock_irqrestore(&wdata->state.lock, flags); + return 0; } @@ -480,7 +499,6 @@ static struct wiimote_data *wiimote_create(struct hid_device *hdev) hid_set_drvdata(hdev, wdata); input_set_drvdata(wdata->input, wdata); - wdata->input->event = wiimote_input_event; wdata->input->open = wiimote_input_open; wdata->input->close = wiimote_input_close; wdata->input->dev.parent = &wdata->hdev->dev; @@ -494,6 +512,13 @@ static struct wiimote_data *wiimote_create(struct hid_device *hdev) for (i = 0; i < WIIPROTO_KEY_COUNT; ++i) set_bit(wiiproto_keymap[i], wdata->input->keybit); + set_bit(FF_RUMBLE, wdata->input->ffbit); + if (input_ff_create_memless(wdata->input, NULL, wiimote_ff_play)) { + input_free_device(wdata->input); + kfree(wdata); + return NULL; + } + spin_lock_init(&wdata->qlock); INIT_WORK(&wdata->worker, wiimote_worker); -- cgit v1.2.3-55-g7522 From 98a558ae35930e02dfc2dd0a124c93ad39b0828d Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Tue, 6 Sep 2011 13:50:28 +0200 Subject: HID: wiimote: Add accelerometer input device Add new input device for every wiimote which is used to report accelerometer data to userspace. Only if the input device is currently open, we make the wiimote send accelerometer data. This saves a whole lot of energy on the wiimote if an application is only interested in button input reports. Signed-off-by: David Herrmann Signed-off-by: Jiri Kosina --- drivers/hid/hid-wiimote.c | 99 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 89 insertions(+), 10 deletions(-) diff --git a/drivers/hid/hid-wiimote.c b/drivers/hid/hid-wiimote.c index 57faac527230..9fb7bd63e7f7 100644 --- a/drivers/hid/hid-wiimote.c +++ b/drivers/hid/hid-wiimote.c @@ -36,6 +36,7 @@ struct wiimote_data { struct hid_device *hdev; struct input_dev *input; struct led_classdev *leds[4]; + struct input_dev *accel; spinlock_t qlock; __u8 head; @@ -51,6 +52,7 @@ struct wiimote_data { #define WIIPROTO_FLAG_LED3 0x04 #define WIIPROTO_FLAG_LED4 0x08 #define WIIPROTO_FLAG_RUMBLE 0x10 +#define WIIPROTO_FLAG_ACCEL 0x20 #define WIIPROTO_FLAGS_LEDS (WIIPROTO_FLAG_LED1 | WIIPROTO_FLAG_LED2 | \ WIIPROTO_FLAG_LED3 | WIIPROTO_FLAG_LED4) @@ -257,6 +259,20 @@ static void wiiproto_req_drm(struct wiimote_data *wdata, __u8 drm) wiimote_queue(wdata, cmd, sizeof(cmd)); } +static void wiiproto_req_accel(struct wiimote_data *wdata, __u8 accel) +{ + accel = !!accel; + if (accel == !!(wdata->state.flags & WIIPROTO_FLAG_ACCEL)) + return; + + if (accel) + wdata->state.flags |= WIIPROTO_FLAG_ACCEL; + else + wdata->state.flags &= ~WIIPROTO_FLAG_ACCEL; + + wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL); +} + static enum led_brightness wiimote_leds_get(struct led_classdev *led_dev) { struct wiimote_data *wdata; @@ -344,6 +360,35 @@ static void wiimote_input_close(struct input_dev *dev) hid_hw_close(wdata->hdev); } +static int wiimote_accel_open(struct input_dev *dev) +{ + struct wiimote_data *wdata = input_get_drvdata(dev); + int ret; + unsigned long flags; + + ret = hid_hw_open(wdata->hdev); + if (ret) + return ret; + + spin_lock_irqsave(&wdata->state.lock, flags); + wiiproto_req_accel(wdata, true); + spin_unlock_irqrestore(&wdata->state.lock, flags); + + return 0; +} + +static void wiimote_accel_close(struct input_dev *dev) +{ + struct wiimote_data *wdata = input_get_drvdata(dev); + unsigned long flags; + + spin_lock_irqsave(&wdata->state.lock, flags); + wiiproto_req_accel(wdata, false); + spin_unlock_irqrestore(&wdata->state.lock, flags); + + hid_hw_close(wdata->hdev); +} + static void handler_keys(struct wiimote_data *wdata, const __u8 *payload) { input_report_key(wdata->input, wiiproto_keymap[WIIPROTO_KEY_LEFT], @@ -490,10 +535,8 @@ static struct wiimote_data *wiimote_create(struct hid_device *hdev) return NULL; wdata->input = input_allocate_device(); - if (!wdata->input) { - kfree(wdata); - return NULL; - } + if (!wdata->input) + goto err; wdata->hdev = hdev; hid_set_drvdata(hdev, wdata); @@ -513,11 +556,30 @@ static struct wiimote_data *wiimote_create(struct hid_device *hdev) set_bit(wiiproto_keymap[i], wdata->input->keybit); set_bit(FF_RUMBLE, wdata->input->ffbit); - if (input_ff_create_memless(wdata->input, NULL, wiimote_ff_play)) { - input_free_device(wdata->input); - kfree(wdata); - return NULL; - } + if (input_ff_create_memless(wdata->input, NULL, wiimote_ff_play)) + goto err_input; + + wdata->accel = input_allocate_device(); + if (!wdata->accel) + goto err_input; + + input_set_drvdata(wdata->accel, wdata); + wdata->accel->open = wiimote_accel_open; + wdata->accel->close = wiimote_accel_close; + wdata->accel->dev.parent = &wdata->hdev->dev; + wdata->accel->id.bustype = wdata->hdev->bus; + wdata->accel->id.vendor = wdata->hdev->vendor; + wdata->accel->id.product = wdata->hdev->product; + wdata->accel->id.version = wdata->hdev->version; + wdata->accel->name = WIIMOTE_NAME " Accelerometer"; + + set_bit(EV_ABS, wdata->accel->evbit); + set_bit(ABS_RX, wdata->accel->absbit); + set_bit(ABS_RY, wdata->accel->absbit); + set_bit(ABS_RZ, wdata->accel->absbit); + input_set_abs_params(wdata->accel, ABS_RX, -500, 500, 2, 4); + input_set_abs_params(wdata->accel, ABS_RY, -500, 500, 2, 4); + input_set_abs_params(wdata->accel, ABS_RZ, -500, 500, 2, 4); spin_lock_init(&wdata->qlock); INIT_WORK(&wdata->worker, wiimote_worker); @@ -525,12 +587,19 @@ static struct wiimote_data *wiimote_create(struct hid_device *hdev) spin_lock_init(&wdata->state.lock); return wdata; + +err_input: + input_free_device(wdata->input); +err: + kfree(wdata); + return NULL; } static void wiimote_destroy(struct wiimote_data *wdata) { wiimote_leds_destroy(wdata); + input_unregister_device(wdata->accel); input_unregister_device(wdata->input); cancel_work_sync(&wdata->worker); hid_hw_stop(wdata->hdev); @@ -562,12 +631,18 @@ static int wiimote_hid_probe(struct hid_device *hdev, goto err; } - ret = input_register_device(wdata->input); + ret = input_register_device(wdata->accel); if (ret) { hid_err(hdev, "Cannot register input device\n"); goto err_stop; } + ret = input_register_device(wdata->input); + if (ret) { + hid_err(hdev, "Cannot register input device\n"); + goto err_input; + } + ret = wiimote_leds_create(wdata); if (ret) goto err_free; @@ -585,9 +660,13 @@ err_free: wiimote_destroy(wdata); return ret; +err_input: + input_unregister_device(wdata->accel); + wdata->accel = NULL; err_stop: hid_hw_stop(hdev); err: + input_free_device(wdata->accel); input_free_device(wdata->input); kfree(wdata); return ret; -- cgit v1.2.3-55-g7522 From efcf91887419ec37ca564073a9fe30db49fe6c7c Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Tue, 6 Sep 2011 13:50:29 +0200 Subject: HID: wiimote: Parse accelerometer data Add parser functions for accelerometer data reported by the wiimote. The data is almost always reported in the same format, so we can use a single handler. However, an own handler function is created for each DRM-mode because when IR and extension support is added, each of them is parsed differently. Also set the appropriate DRM including accelerometer data on DRM requests to actually retrieve the accelerometer data. Data is reported to userspace as ABS_RX/Y/Z values. The values are between -500 and 500 and 0 means no acceleration. See also userspace xwiimote library for data parsing. Signed-off-by: David Herrmann Signed-off-by: Jiri Kosina --- drivers/hid/hid-wiimote.c | 102 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-wiimote.c b/drivers/hid/hid-wiimote.c index 9fb7bd63e7f7..de9aadf9218d 100644 --- a/drivers/hid/hid-wiimote.c +++ b/drivers/hid/hid-wiimote.c @@ -30,6 +30,7 @@ struct wiimote_buf { struct wiimote_state { spinlock_t lock; __u8 flags; + __u8 accel_split[2]; }; struct wiimote_data { @@ -67,6 +68,12 @@ enum wiiproto_reqs { WIIPROTO_REQ_STATUS = 0x20, WIIPROTO_REQ_RETURN = 0x22, WIIPROTO_REQ_DRM_K = 0x30, + WIIPROTO_REQ_DRM_KA = 0x31, + WIIPROTO_REQ_DRM_KAI = 0x33, + WIIPROTO_REQ_DRM_KAE = 0x35, + WIIPROTO_REQ_DRM_KAIE = 0x37, + WIIPROTO_REQ_DRM_SKAI1 = 0x3e, + WIIPROTO_REQ_DRM_SKAI2 = 0x3f, }; enum wiiproto_keys { @@ -241,7 +248,10 @@ static void wiiproto_req_leds(struct wiimote_data *wdata, int leds) */ static __u8 select_drm(struct wiimote_data *wdata) { - return WIIPROTO_REQ_DRM_K; + if (wdata->state.flags & WIIPROTO_FLAG_ACCEL) + return WIIPROTO_REQ_DRM_KA; + else + return WIIPROTO_REQ_DRM_K; } static void wiiproto_req_drm(struct wiimote_data *wdata, __u8 drm) @@ -416,6 +426,40 @@ static void handler_keys(struct wiimote_data *wdata, const __u8 *payload) input_sync(wdata->input); } +static void handler_accel(struct wiimote_data *wdata, const __u8 *payload) +{ + __u16 x, y, z; + + if (!(wdata->state.flags & WIIPROTO_FLAG_ACCEL)) + return; + + /* + * payload is: BB BB XX YY ZZ + * Accelerometer data is encoded into 3 10bit values. XX, YY and ZZ + * contain the upper 8 bits of each value. The lower 2 bits are + * contained in the buttons data BB BB. + * Bits 6 and 7 of the first buttons byte BB is the lower 2 bits of the + * X accel value. Bit 5 of the second buttons byte is the 2nd bit of Y + * accel value and bit 6 is the second bit of the Z value. + * The first bit of Y and Z values is not available and always set to 0. + * 0x200 is returned on no movement. + */ + + x = payload[2] << 2; + y = payload[3] << 2; + z = payload[4] << 2; + + x |= (payload[0] >> 5) & 0x3; + y |= (payload[1] >> 4) & 0x2; + z |= (payload[1] >> 5) & 0x2; + + input_report_abs(wdata->accel, ABS_RX, x - 0x200); + input_report_abs(wdata->accel, ABS_RY, y - 0x200); + input_report_abs(wdata->accel, ABS_RZ, z - 0x200); + input_sync(wdata->accel); +} + + static void handler_status(struct wiimote_data *wdata, const __u8 *payload) { handler_keys(wdata, payload); @@ -436,6 +480,56 @@ static void handler_return(struct wiimote_data *wdata, const __u8 *payload) cmd); } +static void handler_drm_KA(struct wiimote_data *wdata, const __u8 *payload) +{ + handler_keys(wdata, payload); + handler_accel(wdata, payload); +} + +static void handler_drm_KAI(struct wiimote_data *wdata, const __u8 *payload) +{ + handler_keys(wdata, payload); + handler_accel(wdata, payload); +} + +static void handler_drm_KAE(struct wiimote_data *wdata, const __u8 *payload) +{ + handler_keys(wdata, payload); + handler_accel(wdata, payload); +} + +static void handler_drm_KAIE(struct wiimote_data *wdata, const __u8 *payload) +{ + handler_keys(wdata, payload); + handler_accel(wdata, payload); +} + +static void handler_drm_SKAI1(struct wiimote_data *wdata, const __u8 *payload) +{ + handler_keys(wdata, payload); + + wdata->state.accel_split[0] = payload[2]; + wdata->state.accel_split[1] = (payload[0] >> 1) & (0x10 | 0x20); + wdata->state.accel_split[1] |= (payload[1] << 1) & (0x40 | 0x80); +} + +static void handler_drm_SKAI2(struct wiimote_data *wdata, const __u8 *payload) +{ + __u8 buf[5]; + + handler_keys(wdata, payload); + + wdata->state.accel_split[1] |= (payload[0] >> 5) & (0x01 | 0x02); + wdata->state.accel_split[1] |= (payload[1] >> 3) & (0x04 | 0x08); + + buf[0] = 0; + buf[1] = 0; + buf[2] = wdata->state.accel_split[0]; + buf[3] = payload[2]; + buf[4] = wdata->state.accel_split[1]; + handler_accel(wdata, buf); +} + struct wiiproto_handler { __u8 id; size_t size; @@ -446,6 +540,12 @@ static struct wiiproto_handler handlers[] = { { .id = WIIPROTO_REQ_STATUS, .size = 6, .func = handler_status }, { .id = WIIPROTO_REQ_RETURN, .size = 4, .func = handler_return }, { .id = WIIPROTO_REQ_DRM_K, .size = 2, .func = handler_keys }, + { .id = WIIPROTO_REQ_DRM_KA, .size = 5, .func = handler_drm_KA }, + { .id = WIIPROTO_REQ_DRM_KAI, .size = 17, .func = handler_drm_KAI }, + { .id = WIIPROTO_REQ_DRM_KAE, .size = 21, .func = handler_drm_KAE }, + { .id = WIIPROTO_REQ_DRM_KAIE, .size = 21, .func = handler_drm_KAIE }, + { .id = WIIPROTO_REQ_DRM_SKAI1, .size = 21, .func = handler_drm_SKAI1 }, + { .id = WIIPROTO_REQ_DRM_SKAI2, .size = 21, .func = handler_drm_SKAI2 }, { .id = 0 } }; -- cgit v1.2.3-55-g7522 From f363e4f6ab71168cbdaadeef974b515512b41636 Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Tue, 6 Sep 2011 13:50:30 +0200 Subject: HID: wiimote: Add IR input device The IR cam of the wiimote reports 4 trackable lights as absolute values. Since we can turn the IR cam on and off, we register a separate input device so we can react on open/close callbacks to save wiimote battery power when IR cam is not needed. The cam can be in four states: off, basic, extended and full The DRM chooser automatically selects a proper DRM that includes all required IR data so no information is lost. Signed-off-by: David Herrmann Signed-off-by: Jiri Kosina --- drivers/hid/hid-wiimote.c | 71 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-wiimote.c b/drivers/hid/hid-wiimote.c index de9aadf9218d..4cdaaf6e0307 100644 --- a/drivers/hid/hid-wiimote.c +++ b/drivers/hid/hid-wiimote.c @@ -38,6 +38,7 @@ struct wiimote_data { struct input_dev *input; struct led_classdev *leds[4]; struct input_dev *accel; + struct input_dev *ir; spinlock_t qlock; __u8 head; @@ -54,8 +55,13 @@ struct wiimote_data { #define WIIPROTO_FLAG_LED4 0x08 #define WIIPROTO_FLAG_RUMBLE 0x10 #define WIIPROTO_FLAG_ACCEL 0x20 +#define WIIPROTO_FLAG_IR_BASIC 0x40 +#define WIIPROTO_FLAG_IR_EXT 0x80 +#define WIIPROTO_FLAG_IR_FULL 0xc0 /* IR_BASIC | IR_EXT */ #define WIIPROTO_FLAGS_LEDS (WIIPROTO_FLAG_LED1 | WIIPROTO_FLAG_LED2 | \ WIIPROTO_FLAG_LED3 | WIIPROTO_FLAG_LED4) +#define WIIPROTO_FLAGS_IR (WIIPROTO_FLAG_IR_BASIC | WIIPROTO_FLAG_IR_EXT | \ + WIIPROTO_FLAG_IR_FULL) /* return flag for led \num */ #define WIIPROTO_FLAG_LED(num) (WIIPROTO_FLAG_LED1 << (num - 1)) @@ -71,6 +77,7 @@ enum wiiproto_reqs { WIIPROTO_REQ_DRM_KA = 0x31, WIIPROTO_REQ_DRM_KAI = 0x33, WIIPROTO_REQ_DRM_KAE = 0x35, + WIIPROTO_REQ_DRM_KIE = 0x36, WIIPROTO_REQ_DRM_KAIE = 0x37, WIIPROTO_REQ_DRM_SKAI1 = 0x3e, WIIPROTO_REQ_DRM_SKAI2 = 0x3f, @@ -248,10 +255,23 @@ static void wiiproto_req_leds(struct wiimote_data *wdata, int leds) */ static __u8 select_drm(struct wiimote_data *wdata) { - if (wdata->state.flags & WIIPROTO_FLAG_ACCEL) - return WIIPROTO_REQ_DRM_KA; - else - return WIIPROTO_REQ_DRM_K; + __u8 ir = wdata->state.flags & WIIPROTO_FLAGS_IR; + + if (ir == WIIPROTO_FLAG_IR_BASIC) { + if (wdata->state.flags & WIIPROTO_FLAG_ACCEL) + return WIIPROTO_REQ_DRM_KAIE; + else + return WIIPROTO_REQ_DRM_KIE; + } else if (ir == WIIPROTO_FLAG_IR_EXT) { + return WIIPROTO_REQ_DRM_KAI; + } else if (ir == WIIPROTO_FLAG_IR_FULL) { + return WIIPROTO_REQ_DRM_SKAI1; + } else { + if (wdata->state.flags & WIIPROTO_FLAG_ACCEL) + return WIIPROTO_REQ_DRM_KA; + else + return WIIPROTO_REQ_DRM_K; + } } static void wiiproto_req_drm(struct wiimote_data *wdata, __u8 drm) @@ -681,6 +701,36 @@ static struct wiimote_data *wiimote_create(struct hid_device *hdev) input_set_abs_params(wdata->accel, ABS_RY, -500, 500, 2, 4); input_set_abs_params(wdata->accel, ABS_RZ, -500, 500, 2, 4); + wdata->ir = input_allocate_device(); + if (!wdata->ir) + goto err_ir; + + input_set_drvdata(wdata->ir, wdata); + wdata->ir->dev.parent = &wdata->hdev->dev; + wdata->ir->id.bustype = wdata->hdev->bus; + wdata->ir->id.vendor = wdata->hdev->vendor; + wdata->ir->id.product = wdata->hdev->product; + wdata->ir->id.version = wdata->hdev->version; + wdata->ir->name = WIIMOTE_NAME " IR"; + + set_bit(EV_ABS, wdata->ir->evbit); + set_bit(ABS_HAT0X, wdata->ir->absbit); + set_bit(ABS_HAT0Y, wdata->ir->absbit); + set_bit(ABS_HAT1X, wdata->ir->absbit); + set_bit(ABS_HAT1Y, wdata->ir->absbit); + set_bit(ABS_HAT2X, wdata->ir->absbit); + set_bit(ABS_HAT2Y, wdata->ir->absbit); + set_bit(ABS_HAT3X, wdata->ir->absbit); + set_bit(ABS_HAT3Y, wdata->ir->absbit); + input_set_abs_params(wdata->ir, ABS_HAT0X, 0, 1023, 2, 4); + input_set_abs_params(wdata->ir, ABS_HAT0Y, 0, 767, 2, 4); + input_set_abs_params(wdata->ir, ABS_HAT1X, 0, 1023, 2, 4); + input_set_abs_params(wdata->ir, ABS_HAT1Y, 0, 767, 2, 4); + input_set_abs_params(wdata->ir, ABS_HAT2X, 0, 1023, 2, 4); + input_set_abs_params(wdata->ir, ABS_HAT2Y, 0, 767, 2, 4); + input_set_abs_params(wdata->ir, ABS_HAT3X, 0, 1023, 2, 4); + input_set_abs_params(wdata->ir, ABS_HAT3Y, 0, 767, 2, 4); + spin_lock_init(&wdata->qlock); INIT_WORK(&wdata->worker, wiimote_worker); @@ -688,6 +738,8 @@ static struct wiimote_data *wiimote_create(struct hid_device *hdev) return wdata; +err_ir: + input_free_device(wdata->accel); err_input: input_free_device(wdata->input); err: @@ -700,6 +752,7 @@ static void wiimote_destroy(struct wiimote_data *wdata) wiimote_leds_destroy(wdata); input_unregister_device(wdata->accel); + input_unregister_device(wdata->ir); input_unregister_device(wdata->input); cancel_work_sync(&wdata->worker); hid_hw_stop(wdata->hdev); @@ -737,6 +790,12 @@ static int wiimote_hid_probe(struct hid_device *hdev, goto err_stop; } + ret = input_register_device(wdata->ir); + if (ret) { + hid_err(hdev, "Cannot register input device\n"); + goto err_ir; + } + ret = input_register_device(wdata->input); if (ret) { hid_err(hdev, "Cannot register input device\n"); @@ -761,11 +820,15 @@ err_free: return ret; err_input: + input_unregister_device(wdata->ir); + wdata->ir = NULL; +err_ir: input_unregister_device(wdata->accel); wdata->accel = NULL; err_stop: hid_hw_stop(hdev); err: + input_free_device(wdata->ir); input_free_device(wdata->accel); input_free_device(wdata->input); kfree(wdata); -- cgit v1.2.3-55-g7522 From eac39e7eda47cec9de44d64661ef44c6b37dd45f Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Tue, 6 Sep 2011 13:50:31 +0200 Subject: HID: wiimote: Parse IR data Parse IR data and report it to IR input-device. IR data is sent in 3 different formats, but we only support the basic format as there is no way to send the additional information to userspace. All three formats are compatible with the basic IR data format so we need only one parser. Signed-off-by: David Herrmann Signed-off-by: Jiri Kosina --- drivers/hid/hid-wiimote.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/drivers/hid/hid-wiimote.c b/drivers/hid/hid-wiimote.c index 4cdaaf6e0307..3a7dec0c9fe6 100644 --- a/drivers/hid/hid-wiimote.c +++ b/drivers/hid/hid-wiimote.c @@ -479,6 +479,50 @@ static void handler_accel(struct wiimote_data *wdata, const __u8 *payload) input_sync(wdata->accel); } +#define ir_to_input0(wdata, ir, packed) __ir_to_input((wdata), (ir), (packed), \ + ABS_HAT0X, ABS_HAT0Y) +#define ir_to_input1(wdata, ir, packed) __ir_to_input((wdata), (ir), (packed), \ + ABS_HAT1X, ABS_HAT1Y) +#define ir_to_input2(wdata, ir, packed) __ir_to_input((wdata), (ir), (packed), \ + ABS_HAT2X, ABS_HAT2Y) +#define ir_to_input3(wdata, ir, packed) __ir_to_input((wdata), (ir), (packed), \ + ABS_HAT3X, ABS_HAT3Y) + +static void __ir_to_input(struct wiimote_data *wdata, const __u8 *ir, + bool packed, __u8 xid, __u8 yid) +{ + __u16 x, y; + + if (!(wdata->state.flags & WIIPROTO_FLAGS_IR)) + return; + + /* + * Basic IR data is encoded into 3 bytes. The first two bytes are the + * upper 8 bit of the X/Y data, the 3rd byte contains the lower 2 bits + * of both. + * If data is packed, then the 3rd byte is put first and slightly + * reordered. This allows to interleave packed and non-packed data to + * have two IR sets in 5 bytes instead of 6. + * The resulting 10bit X/Y values are passed to the ABS_HATXY input dev. + */ + + if (packed) { + x = ir[1] << 2; + y = ir[2] << 2; + + x |= ir[0] & 0x3; + y |= (ir[0] >> 2) & 0x3; + } else { + x = ir[0] << 2; + y = ir[1] << 2; + + x |= (ir[2] >> 4) & 0x3; + y |= (ir[2] >> 6) & 0x3; + } + + input_report_abs(wdata->ir, xid, x); + input_report_abs(wdata->ir, yid, y); +} static void handler_status(struct wiimote_data *wdata, const __u8 *payload) { @@ -510,6 +554,21 @@ static void handler_drm_KAI(struct wiimote_data *wdata, const __u8 *payload) { handler_keys(wdata, payload); handler_accel(wdata, payload); + ir_to_input0(wdata, &payload[5], false); + ir_to_input1(wdata, &payload[8], false); + ir_to_input2(wdata, &payload[11], false); + ir_to_input3(wdata, &payload[14], false); + input_sync(wdata->ir); +} + +static void handler_drm_KIE(struct wiimote_data *wdata, const __u8 *payload) +{ + handler_keys(wdata, payload); + ir_to_input0(wdata, &payload[2], false); + ir_to_input1(wdata, &payload[4], true); + ir_to_input2(wdata, &payload[7], false); + ir_to_input3(wdata, &payload[9], true); + input_sync(wdata->ir); } static void handler_drm_KAE(struct wiimote_data *wdata, const __u8 *payload) @@ -522,6 +581,11 @@ static void handler_drm_KAIE(struct wiimote_data *wdata, const __u8 *payload) { handler_keys(wdata, payload); handler_accel(wdata, payload); + ir_to_input0(wdata, &payload[5], false); + ir_to_input1(wdata, &payload[7], true); + ir_to_input2(wdata, &payload[10], false); + ir_to_input3(wdata, &payload[12], true); + input_sync(wdata->ir); } static void handler_drm_SKAI1(struct wiimote_data *wdata, const __u8 *payload) @@ -531,6 +595,10 @@ static void handler_drm_SKAI1(struct wiimote_data *wdata, const __u8 *payload) wdata->state.accel_split[0] = payload[2]; wdata->state.accel_split[1] = (payload[0] >> 1) & (0x10 | 0x20); wdata->state.accel_split[1] |= (payload[1] << 1) & (0x40 | 0x80); + + ir_to_input0(wdata, &payload[3], false); + ir_to_input1(wdata, &payload[12], false); + input_sync(wdata->ir); } static void handler_drm_SKAI2(struct wiimote_data *wdata, const __u8 *payload) @@ -548,6 +616,10 @@ static void handler_drm_SKAI2(struct wiimote_data *wdata, const __u8 *payload) buf[3] = payload[2]; buf[4] = wdata->state.accel_split[1]; handler_accel(wdata, buf); + + ir_to_input2(wdata, &payload[3], false); + ir_to_input3(wdata, &payload[12], false); + input_sync(wdata->ir); } struct wiiproto_handler { @@ -563,6 +635,7 @@ static struct wiiproto_handler handlers[] = { { .id = WIIPROTO_REQ_DRM_KA, .size = 5, .func = handler_drm_KA }, { .id = WIIPROTO_REQ_DRM_KAI, .size = 17, .func = handler_drm_KAI }, { .id = WIIPROTO_REQ_DRM_KAE, .size = 21, .func = handler_drm_KAE }, + { .id = WIIPROTO_REQ_DRM_KIE, .size = 21, .func = handler_drm_KIE }, { .id = WIIPROTO_REQ_DRM_KAIE, .size = 21, .func = handler_drm_KAIE }, { .id = WIIPROTO_REQ_DRM_SKAI1, .size = 21, .func = handler_drm_SKAI1 }, { .id = WIIPROTO_REQ_DRM_SKAI2, .size = 21, .func = handler_drm_SKAI2 }, -- cgit v1.2.3-55-g7522 From 7336b9f93a26082ecb068b5db42b2ed0dbf802bd Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Tue, 6 Sep 2011 13:50:32 +0200 Subject: HID: wiimote: Add missing extension DRM handlers If an extension is connected the wiimote may report data though DRMs that contain extension data. This adds handlers for these DRMs but discards extension data since we do not support it, yet. It prints a warning to kernel log if an unhandled report is catched. Since we handle all requests now, this should never happen, though. Signed-off-by: David Herrmann Signed-off-by: Jiri Kosina --- drivers/hid/hid-wiimote.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-wiimote.c b/drivers/hid/hid-wiimote.c index 3a7dec0c9fe6..a99e058676fa 100644 --- a/drivers/hid/hid-wiimote.c +++ b/drivers/hid/hid-wiimote.c @@ -75,10 +75,13 @@ enum wiiproto_reqs { WIIPROTO_REQ_RETURN = 0x22, WIIPROTO_REQ_DRM_K = 0x30, WIIPROTO_REQ_DRM_KA = 0x31, + WIIPROTO_REQ_DRM_KE = 0x32, WIIPROTO_REQ_DRM_KAI = 0x33, + WIIPROTO_REQ_DRM_KEE = 0x34, WIIPROTO_REQ_DRM_KAE = 0x35, WIIPROTO_REQ_DRM_KIE = 0x36, WIIPROTO_REQ_DRM_KAIE = 0x37, + WIIPROTO_REQ_DRM_E = 0x3d, WIIPROTO_REQ_DRM_SKAI1 = 0x3e, WIIPROTO_REQ_DRM_SKAI2 = 0x3f, }; @@ -550,6 +553,11 @@ static void handler_drm_KA(struct wiimote_data *wdata, const __u8 *payload) handler_accel(wdata, payload); } +static void handler_drm_KE(struct wiimote_data *wdata, const __u8 *payload) +{ + handler_keys(wdata, payload); +} + static void handler_drm_KAI(struct wiimote_data *wdata, const __u8 *payload) { handler_keys(wdata, payload); @@ -561,6 +569,11 @@ static void handler_drm_KAI(struct wiimote_data *wdata, const __u8 *payload) input_sync(wdata->ir); } +static void handler_drm_KEE(struct wiimote_data *wdata, const __u8 *payload) +{ + handler_keys(wdata, payload); +} + static void handler_drm_KIE(struct wiimote_data *wdata, const __u8 *payload) { handler_keys(wdata, payload); @@ -588,6 +601,10 @@ static void handler_drm_KAIE(struct wiimote_data *wdata, const __u8 *payload) input_sync(wdata->ir); } +static void handler_drm_E(struct wiimote_data *wdata, const __u8 *payload) +{ +} + static void handler_drm_SKAI1(struct wiimote_data *wdata, const __u8 *payload) { handler_keys(wdata, payload); @@ -633,10 +650,13 @@ static struct wiiproto_handler handlers[] = { { .id = WIIPROTO_REQ_RETURN, .size = 4, .func = handler_return }, { .id = WIIPROTO_REQ_DRM_K, .size = 2, .func = handler_keys }, { .id = WIIPROTO_REQ_DRM_KA, .size = 5, .func = handler_drm_KA }, + { .id = WIIPROTO_REQ_DRM_KE, .size = 10, .func = handler_drm_KE }, { .id = WIIPROTO_REQ_DRM_KAI, .size = 17, .func = handler_drm_KAI }, + { .id = WIIPROTO_REQ_DRM_KEE, .size = 21, .func = handler_drm_KEE }, { .id = WIIPROTO_REQ_DRM_KAE, .size = 21, .func = handler_drm_KAE }, { .id = WIIPROTO_REQ_DRM_KIE, .size = 21, .func = handler_drm_KIE }, { .id = WIIPROTO_REQ_DRM_KAIE, .size = 21, .func = handler_drm_KAIE }, + { .id = WIIPROTO_REQ_DRM_E, .size = 21, .func = handler_drm_E }, { .id = WIIPROTO_REQ_DRM_SKAI1, .size = 21, .func = handler_drm_SKAI1 }, { .id = WIIPROTO_REQ_DRM_SKAI2, .size = 21, .func = handler_drm_SKAI2 }, { .id = 0 } @@ -649,6 +669,7 @@ static int wiimote_hid_event(struct hid_device *hdev, struct hid_report *report, struct wiiproto_handler *h; int i; unsigned long flags; + bool handled = false; if (size < 1) return -EINVAL; @@ -657,10 +678,16 @@ static int wiimote_hid_event(struct hid_device *hdev, struct hid_report *report, for (i = 0; handlers[i].id; ++i) { h = &handlers[i]; - if (h->id == raw_data[0] && h->size < size) + if (h->id == raw_data[0] && h->size < size) { h->func(wdata, &raw_data[1]); + handled = true; + } } + if (!handled) + hid_warn(hdev, "Unhandled report %hhu size %d\n", raw_data[0], + size); + spin_unlock_irqrestore(&wdata->state.lock, flags); return 0; -- cgit v1.2.3-55-g7522 From be1ecd62e619dae8d7c5b7f212333558fcc85d4d Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Tue, 6 Sep 2011 13:50:33 +0200 Subject: HID: wiimote: Add register/eeprom memory support The wiimote allows direct access to its memory mapped registers and internal eeprom. This adds support to access this memory and handle memory events. There are two macros which wrap up the memory access functions to avoid accidentally overwriting sensitive eeprom data because a boolean value was wrongly set. Signed-off-by: David Herrmann Signed-off-by: Jiri Kosina --- drivers/hid/hid-wiimote.c | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/drivers/hid/hid-wiimote.c b/drivers/hid/hid-wiimote.c index a99e058676fa..c811a7d14174 100644 --- a/drivers/hid/hid-wiimote.c +++ b/drivers/hid/hid-wiimote.c @@ -71,7 +71,10 @@ enum wiiproto_reqs { WIIPROTO_REQ_RUMBLE = 0x10, WIIPROTO_REQ_LED = 0x11, WIIPROTO_REQ_DRM = 0x12, + WIIPROTO_REQ_WMEM = 0x16, + WIIPROTO_REQ_RMEM = 0x17, WIIPROTO_REQ_STATUS = 0x20, + WIIPROTO_REQ_DATA = 0x21, WIIPROTO_REQ_RETURN = 0x22, WIIPROTO_REQ_DRM_K = 0x30, WIIPROTO_REQ_DRM_KA = 0x31, @@ -306,6 +309,37 @@ static void wiiproto_req_accel(struct wiimote_data *wdata, __u8 accel) wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL); } +#define wiiproto_req_wreg(wdata, os, buf, sz) \ + wiiproto_req_wmem((wdata), false, (os), (buf), (sz)) + +#define wiiproto_req_weeprom(wdata, os, buf, sz) \ + wiiproto_req_wmem((wdata), true, (os), (buf), (sz)) + +static void wiiproto_req_wmem(struct wiimote_data *wdata, bool eeprom, + __u32 offset, const __u8 *buf, __u8 size) +{ + __u8 cmd[22]; + + if (size > 16 || size == 0) { + hid_warn(wdata->hdev, "Invalid length %d wmem request\n", size); + return; + } + + memset(cmd, 0, sizeof(cmd)); + cmd[0] = WIIPROTO_REQ_WMEM; + cmd[2] = (offset >> 16) & 0xff; + cmd[3] = (offset >> 8) & 0xff; + cmd[4] = offset & 0xff; + cmd[5] = size; + memcpy(&cmd[6], buf, size); + + if (!eeprom) + cmd[1] |= 0x04; + + wiiproto_keep_rumble(wdata, &cmd[1]); + wiimote_queue(wdata, cmd, sizeof(cmd)); +} + static enum led_brightness wiimote_leds_get(struct led_classdev *led_dev) { struct wiimote_data *wdata; @@ -535,6 +569,11 @@ static void handler_status(struct wiimote_data *wdata, const __u8 *payload) wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL); } +static void handler_data(struct wiimote_data *wdata, const __u8 *payload) +{ + handler_keys(wdata, payload); +} + static void handler_return(struct wiimote_data *wdata, const __u8 *payload) { __u8 err = payload[3]; @@ -647,6 +686,7 @@ struct wiiproto_handler { static struct wiiproto_handler handlers[] = { { .id = WIIPROTO_REQ_STATUS, .size = 6, .func = handler_status }, + { .id = WIIPROTO_REQ_DATA, .size = 21, .func = handler_data }, { .id = WIIPROTO_REQ_RETURN, .size = 4, .func = handler_return }, { .id = WIIPROTO_REQ_DRM_K, .size = 2, .func = handler_keys }, { .id = WIIPROTO_REQ_DRM_KA, .size = 5, .func = handler_drm_KA }, -- cgit v1.2.3-55-g7522 From 29d28064e27d11433c4249369f469fab86826d0c Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Tue, 6 Sep 2011 13:50:34 +0200 Subject: HID: wiimote: Helper functions for synchronous requests To initialize wiimote peripherals, the stream to the wiimote must be held exclusively by the initializer, otherwise the initialization will fail. Many initializations require multiple memory requests to be sent synchronously so we need a way to lock the stream and release it when we are done. This adds several helper functions which allow to lock the stream, then send requests, wait for the answers and release the stream again. When holding the lock, the function may sleep and interrupted by signals. Also it returns after a short timeout so userspace shouldn't notice long delays. Signed-off-by: David Herrmann Signed-off-by: Jiri Kosina --- drivers/hid/hid-wiimote.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/drivers/hid/hid-wiimote.c b/drivers/hid/hid-wiimote.c index c811a7d14174..0a5e458820b2 100644 --- a/drivers/hid/hid-wiimote.c +++ b/drivers/hid/hid-wiimote.c @@ -10,11 +10,13 @@ * any later version. */ +#include #include #include #include #include #include +#include #include #include "hid-ids.h" @@ -31,6 +33,12 @@ struct wiimote_state { spinlock_t lock; __u8 flags; __u8 accel_split[2]; + + /* synchronous cmd requests */ + struct mutex sync; + struct completion ready; + int cmd; + __u32 opt; }; struct wiimote_data { @@ -118,6 +126,52 @@ static __u16 wiiproto_keymap[] = { BTN_MODE, /* WIIPROTO_KEY_HOME */ }; +/* requires the state.lock spinlock to be held */ +static inline bool wiimote_cmd_pending(struct wiimote_data *wdata, int cmd, + __u32 opt) +{ + return wdata->state.cmd == cmd && wdata->state.opt == opt; +} + +/* requires the state.lock spinlock to be held */ +static inline void wiimote_cmd_complete(struct wiimote_data *wdata) +{ + wdata->state.cmd = WIIPROTO_REQ_NULL; + complete(&wdata->state.ready); +} + +static inline int wiimote_cmd_acquire(struct wiimote_data *wdata) +{ + return mutex_lock_interruptible(&wdata->state.sync) ? -ERESTARTSYS : 0; +} + +/* requires the state.lock spinlock to be held */ +static inline void wiimote_cmd_set(struct wiimote_data *wdata, int cmd, + __u32 opt) +{ + INIT_COMPLETION(wdata->state.ready); + wdata->state.cmd = cmd; + wdata->state.opt = opt; +} + +static inline void wiimote_cmd_release(struct wiimote_data *wdata) +{ + mutex_unlock(&wdata->state.sync); +} + +static inline int wiimote_cmd_wait(struct wiimote_data *wdata) +{ + int ret; + + ret = wait_for_completion_interruptible_timeout(&wdata->state.ready, HZ); + if (ret < 0) + return -ERESTARTSYS; + else if (ret == 0) + return -EIO; + else + return 0; +} + static ssize_t wiimote_hid_send(struct hid_device *hdev, __u8 *buffer, size_t count) { @@ -875,6 +929,8 @@ static struct wiimote_data *wiimote_create(struct hid_device *hdev) INIT_WORK(&wdata->worker, wiimote_worker); spin_lock_init(&wdata->state.lock); + init_completion(&wdata->state.ready); + mutex_init(&wdata->state.sync); return wdata; -- cgit v1.2.3-55-g7522 From 33e84013d7ce80e071c05171543371d6f71438e5 Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Tue, 6 Sep 2011 13:50:35 +0200 Subject: HID: wiimote: Add write-register helpers Add helpers to synchronously write registers of the wiimote. This is heavily used by initialization functions for wiimote peripherals. Signed-off-by: David Herrmann Signed-off-by: Jiri Kosina --- drivers/hid/hid-wiimote.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-wiimote.c b/drivers/hid/hid-wiimote.c index 0a5e458820b2..59a08933c73c 100644 --- a/drivers/hid/hid-wiimote.c +++ b/drivers/hid/hid-wiimote.c @@ -39,6 +39,9 @@ struct wiimote_state { struct completion ready; int cmd; __u32 opt; + + /* results of synchronous requests */ + __u8 cmd_err; }; struct wiimote_data { @@ -394,6 +397,25 @@ static void wiiproto_req_wmem(struct wiimote_data *wdata, bool eeprom, wiimote_queue(wdata, cmd, sizeof(cmd)); } +/* requries the cmd-mutex to be held */ +static int wiimote_cmd_write(struct wiimote_data *wdata, __u32 offset, + const __u8 *wmem, __u8 size) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&wdata->state.lock, flags); + wiimote_cmd_set(wdata, WIIPROTO_REQ_WMEM, 0); + wiiproto_req_wreg(wdata, offset, wmem, size); + spin_unlock_irqrestore(&wdata->state.lock, flags); + + ret = wiimote_cmd_wait(wdata); + if (!ret && wdata->state.cmd_err) + ret = -EIO; + + return ret; +} + static enum led_brightness wiimote_leds_get(struct led_classdev *led_dev) { struct wiimote_data *wdata; @@ -635,9 +657,13 @@ static void handler_return(struct wiimote_data *wdata, const __u8 *payload) handler_keys(wdata, payload); - if (err) + if (wiimote_cmd_pending(wdata, cmd, 0)) { + wdata->state.cmd_err = err; + wiimote_cmd_complete(wdata); + } else if (err) { hid_warn(wdata->hdev, "Remote error %hhu on req %hhu\n", err, cmd); + } } static void handler_drm_KA(struct wiimote_data *wdata, const __u8 *payload) -- cgit v1.2.3-55-g7522 From fc221cda3b1220bb3d356349bd843fabef3cf96f Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Tue, 6 Sep 2011 13:50:36 +0200 Subject: HID: wiimote: Add IR initializer The wiimote IR cam needs a fairly complex initialization sequence. This adds a helper function that performs IR initialization synchronously. Signed-off-by: David Herrmann Signed-off-by: Jiri Kosina --- drivers/hid/hid-wiimote.c | 136 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/drivers/hid/hid-wiimote.c b/drivers/hid/hid-wiimote.c index 59a08933c73c..35dc293db40e 100644 --- a/drivers/hid/hid-wiimote.c +++ b/drivers/hid/hid-wiimote.c @@ -82,8 +82,10 @@ enum wiiproto_reqs { WIIPROTO_REQ_RUMBLE = 0x10, WIIPROTO_REQ_LED = 0x11, WIIPROTO_REQ_DRM = 0x12, + WIIPROTO_REQ_IR1 = 0x13, WIIPROTO_REQ_WMEM = 0x16, WIIPROTO_REQ_RMEM = 0x17, + WIIPROTO_REQ_IR2 = 0x1a, WIIPROTO_REQ_STATUS = 0x20, WIIPROTO_REQ_DATA = 0x21, WIIPROTO_REQ_RETURN = 0x22, @@ -366,6 +368,28 @@ static void wiiproto_req_accel(struct wiimote_data *wdata, __u8 accel) wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL); } +static void wiiproto_req_ir1(struct wiimote_data *wdata, __u8 flags) +{ + __u8 cmd[2]; + + cmd[0] = WIIPROTO_REQ_IR1; + cmd[1] = flags; + + wiiproto_keep_rumble(wdata, &cmd[1]); + wiimote_queue(wdata, cmd, sizeof(cmd)); +} + +static void wiiproto_req_ir2(struct wiimote_data *wdata, __u8 flags) +{ + __u8 cmd[2]; + + cmd[0] = WIIPROTO_REQ_IR2; + cmd[1] = flags; + + wiiproto_keep_rumble(wdata, &cmd[1]); + wiimote_queue(wdata, cmd, sizeof(cmd)); +} + #define wiiproto_req_wreg(wdata, os, buf, sz) \ wiiproto_req_wmem((wdata), false, (os), (buf), (sz)) @@ -416,6 +440,118 @@ static int wiimote_cmd_write(struct wiimote_data *wdata, __u32 offset, return ret; } +static int wiimote_init_ir(struct wiimote_data *wdata, __u16 mode) +{ + int ret; + unsigned long flags; + __u8 format = 0; + static const __u8 data_enable[] = { 0x01 }; + static const __u8 data_sens1[] = { 0x02, 0x00, 0x00, 0x71, 0x01, + 0x00, 0xaa, 0x00, 0x64 }; + static const __u8 data_sens2[] = { 0x63, 0x03 }; + static const __u8 data_fin[] = { 0x08 }; + + spin_lock_irqsave(&wdata->state.lock, flags); + + if (mode == (wdata->state.flags & WIIPROTO_FLAGS_IR)) { + spin_unlock_irqrestore(&wdata->state.lock, flags); + return 0; + } + + if (mode == 0) { + wdata->state.flags &= ~WIIPROTO_FLAGS_IR; + wiiproto_req_ir1(wdata, 0); + wiiproto_req_ir2(wdata, 0); + wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL); + spin_unlock_irqrestore(&wdata->state.lock, flags); + return 0; + } + + spin_unlock_irqrestore(&wdata->state.lock, flags); + + ret = wiimote_cmd_acquire(wdata); + if (ret) + return ret; + + /* send PIXEL CLOCK ENABLE cmd first */ + spin_lock_irqsave(&wdata->state.lock, flags); + wiimote_cmd_set(wdata, WIIPROTO_REQ_IR1, 0); + wiiproto_req_ir1(wdata, 0x06); + spin_unlock_irqrestore(&wdata->state.lock, flags); + + ret = wiimote_cmd_wait(wdata); + if (ret) + goto unlock; + if (wdata->state.cmd_err) { + ret = -EIO; + goto unlock; + } + + /* enable IR LOGIC */ + spin_lock_irqsave(&wdata->state.lock, flags); + wiimote_cmd_set(wdata, WIIPROTO_REQ_IR2, 0); + wiiproto_req_ir2(wdata, 0x06); + spin_unlock_irqrestore(&wdata->state.lock, flags); + + ret = wiimote_cmd_wait(wdata); + if (ret) + goto unlock; + if (wdata->state.cmd_err) { + ret = -EIO; + goto unlock; + } + + /* enable IR cam but do not make it send data, yet */ + ret = wiimote_cmd_write(wdata, 0xb00030, data_enable, + sizeof(data_enable)); + if (ret) + goto unlock; + + /* write first sensitivity block */ + ret = wiimote_cmd_write(wdata, 0xb00000, data_sens1, + sizeof(data_sens1)); + if (ret) + goto unlock; + + /* write second sensitivity block */ + ret = wiimote_cmd_write(wdata, 0xb0001a, data_sens2, + sizeof(data_sens2)); + if (ret) + goto unlock; + + /* put IR cam into desired state */ + switch (mode) { + case WIIPROTO_FLAG_IR_FULL: + format = 5; + break; + case WIIPROTO_FLAG_IR_EXT: + format = 3; + break; + case WIIPROTO_FLAG_IR_BASIC: + format = 1; + break; + } + ret = wiimote_cmd_write(wdata, 0xb00033, &format, sizeof(format)); + if (ret) + goto unlock; + + /* make IR cam send data */ + ret = wiimote_cmd_write(wdata, 0xb00030, data_fin, sizeof(data_fin)); + if (ret) + goto unlock; + + /* request new DRM mode compatible to IR mode */ + spin_lock_irqsave(&wdata->state.lock, flags); + wdata->state.flags &= ~WIIPROTO_FLAGS_IR; + wdata->state.flags |= mode & WIIPROTO_FLAGS_IR; + wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL); + spin_unlock_irqrestore(&wdata->state.lock, flags); + +unlock: + wiimote_cmd_release(wdata); + return ret; +} + static enum led_brightness wiimote_leds_get(struct led_classdev *led_dev) { struct wiimote_data *wdata; -- cgit v1.2.3-55-g7522 From 0370d7cb30716b202b2412b9b2d4740fd3152d30 Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Tue, 6 Sep 2011 13:50:37 +0200 Subject: HID: wiimote: Initialize IR cam on request Initialize the IR cam if the related input device is opened by userspace. Stop IR cam again if userspace is no longer interested in its data events. Signed-off-by: David Herrmann Signed-off-by: Jiri Kosina --- drivers/hid/hid-wiimote.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/drivers/hid/hid-wiimote.c b/drivers/hid/hid-wiimote.c index 35dc293db40e..66583986bcdb 100644 --- a/drivers/hid/hid-wiimote.c +++ b/drivers/hid/hid-wiimote.c @@ -668,6 +668,32 @@ static void wiimote_accel_close(struct input_dev *dev) hid_hw_close(wdata->hdev); } +static int wiimote_ir_open(struct input_dev *dev) +{ + struct wiimote_data *wdata = input_get_drvdata(dev); + int ret; + + ret = hid_hw_open(wdata->hdev); + if (ret) + return ret; + + ret = wiimote_init_ir(wdata, WIIPROTO_FLAG_IR_BASIC); + if (ret) { + hid_hw_close(wdata->hdev); + return ret; + } + + return 0; +} + +static void wiimote_ir_close(struct input_dev *dev) +{ + struct wiimote_data *wdata = input_get_drvdata(dev); + + wiimote_init_ir(wdata, 0); + hid_hw_close(wdata->hdev); +} + static void handler_keys(struct wiimote_data *wdata, const __u8 *payload) { input_report_key(wdata->input, wiiproto_keymap[WIIPROTO_KEY_LEFT], @@ -1062,6 +1088,8 @@ static struct wiimote_data *wiimote_create(struct hid_device *hdev) goto err_ir; input_set_drvdata(wdata->ir, wdata); + wdata->ir->open = wiimote_ir_open; + wdata->ir->close = wiimote_ir_close; wdata->ir->dev.parent = &wdata->hdev->dev; wdata->ir->id.bustype = wdata->hdev->bus; wdata->ir->id.vendor = wdata->hdev->vendor; -- cgit v1.2.3-55-g7522 From e3979a9189d744ffecae1bcd36ae0a8b6d22f65f Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Tue, 6 Sep 2011 13:50:38 +0200 Subject: HID: wiimote: Add status request The wiimote does not send status reports continuously so this adds a helper function to request a status report and parses the battery charge level. Signed-off-by: David Herrmann Signed-off-by: Jiri Kosina --- drivers/hid/hid-wiimote.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/hid/hid-wiimote.c b/drivers/hid/hid-wiimote.c index 66583986bcdb..48198cb0fed2 100644 --- a/drivers/hid/hid-wiimote.c +++ b/drivers/hid/hid-wiimote.c @@ -41,6 +41,7 @@ struct wiimote_state { __u32 opt; /* results of synchronous requests */ + __u8 cmd_battery; __u8 cmd_err; }; @@ -83,6 +84,7 @@ enum wiiproto_reqs { WIIPROTO_REQ_LED = 0x11, WIIPROTO_REQ_DRM = 0x12, WIIPROTO_REQ_IR1 = 0x13, + WIIPROTO_REQ_SREQ = 0x15, WIIPROTO_REQ_WMEM = 0x16, WIIPROTO_REQ_RMEM = 0x17, WIIPROTO_REQ_IR2 = 0x1a, @@ -354,6 +356,17 @@ static void wiiproto_req_drm(struct wiimote_data *wdata, __u8 drm) wiimote_queue(wdata, cmd, sizeof(cmd)); } +static void wiiproto_req_status(struct wiimote_data *wdata) +{ + __u8 cmd[2]; + + cmd[0] = WIIPROTO_REQ_SREQ; + cmd[1] = 0; + + wiiproto_keep_rumble(wdata, &cmd[1]); + wiimote_queue(wdata, cmd, sizeof(cmd)); +} + static void wiiproto_req_accel(struct wiimote_data *wdata, __u8 accel) { accel = !!accel; @@ -805,6 +818,11 @@ static void handler_status(struct wiimote_data *wdata, const __u8 *payload) /* on status reports the drm is reset so we need to resend the drm */ wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL); + + if (wiimote_cmd_pending(wdata, WIIPROTO_REQ_SREQ, 0)) { + wdata->state.cmd_battery = payload[5]; + wiimote_cmd_complete(wdata); + } } static void handler_data(struct wiimote_data *wdata, const __u8 *payload) -- cgit v1.2.3-55-g7522 From 6591d758d54291fcc5fd560653edef73d50bd978 Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Tue, 6 Sep 2011 13:50:39 +0200 Subject: HID: wiimote: Read wiimote battery charge level This registers a power_supply device for every remote to retrieve the current battery charge level. Since this information is not sent by the wiimote continously, we need to explicitely request it. Signed-off-by: David Herrmann Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 1 + drivers/hid/hid-wiimote.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 1130a8987125..9907dc9cfb12 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -590,6 +590,7 @@ config HID_WIIMOTE tristate "Nintendo Wii Remote support" depends on BT_HIDP depends on LEDS_CLASS + select POWER_SUPPLY ---help--- Support for the Nintendo Wii Remote bluetooth device. diff --git a/drivers/hid/hid-wiimote.c b/drivers/hid/hid-wiimote.c index 48198cb0fed2..c83cafaa46a3 100644 --- a/drivers/hid/hid-wiimote.c +++ b/drivers/hid/hid-wiimote.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include "hid-ids.h" @@ -51,6 +52,7 @@ struct wiimote_data { struct led_classdev *leds[4]; struct input_dev *accel; struct input_dev *ir; + struct power_supply battery; spinlock_t qlock; __u8 head; @@ -133,6 +135,10 @@ static __u16 wiiproto_keymap[] = { BTN_MODE, /* WIIPROTO_KEY_HOME */ }; +static enum power_supply_property wiimote_battery_props[] = { + POWER_SUPPLY_PROP_CAPACITY +}; + /* requires the state.lock spinlock to be held */ static inline bool wiimote_cmd_pending(struct wiimote_data *wdata, int cmd, __u32 opt) @@ -453,6 +459,43 @@ static int wiimote_cmd_write(struct wiimote_data *wdata, __u32 offset, return ret; } +static int wiimote_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); + int ret = 0, state; + unsigned long flags; + + ret = wiimote_cmd_acquire(wdata); + if (ret) + return ret; + + spin_lock_irqsave(&wdata->state.lock, flags); + wiimote_cmd_set(wdata, WIIPROTO_REQ_SREQ, 0); + wiiproto_req_status(wdata); + spin_unlock_irqrestore(&wdata->state.lock, flags); + + ret = wiimote_cmd_wait(wdata); + state = wdata->state.cmd_battery; + wiimote_cmd_release(wdata); + + if (ret) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = state * 100 / 255; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + static int wiimote_init_ir(struct wiimote_data *wdata, __u16 mode) { int ret; @@ -1155,6 +1198,7 @@ static void wiimote_destroy(struct wiimote_data *wdata) { wiimote_leds_destroy(wdata); + power_supply_unregister(&wdata->battery); input_unregister_device(wdata->accel); input_unregister_device(wdata->ir); input_unregister_device(wdata->input); @@ -1206,6 +1250,19 @@ static int wiimote_hid_probe(struct hid_device *hdev, goto err_input; } + wdata->battery.properties = wiimote_battery_props; + wdata->battery.num_properties = ARRAY_SIZE(wiimote_battery_props); + wdata->battery.get_property = wiimote_battery_get_property; + wdata->battery.name = "wiimote_battery"; + wdata->battery.type = POWER_SUPPLY_TYPE_BATTERY; + wdata->battery.use_for_apm = 0; + + ret = power_supply_register(&wdata->hdev->dev, &wdata->battery); + if (ret) { + hid_err(hdev, "Cannot register battery device\n"); + goto err_battery; + } + ret = wiimote_leds_create(wdata); if (ret) goto err_free; @@ -1223,6 +1280,9 @@ err_free: wiimote_destroy(wdata); return ret; +err_battery: + input_unregister_device(wdata->input); + wdata->input = NULL; err_input: input_unregister_device(wdata->ir); wdata->ir = NULL; -- cgit v1.2.3-55-g7522 From b22e00f3ece8f8ca9d7b9772d750db380e742bbb Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Tue, 6 Sep 2011 13:50:40 +0200 Subject: HID: wiimote: Add MAINTAINERS entry Add entry to MAINTAINERS and also bump version level as the core driver is feature complete now. Signed-off-by: David Herrmann Signed-off-by: Jiri Kosina --- MAINTAINERS | 6 ++++++ drivers/hid/hid-wiimote.c | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 28f65c249b97..afe223d00525 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7134,6 +7134,12 @@ L: linux-scsi@vger.kernel.org S: Maintained F: drivers/scsi/wd7000.c +WIIMOTE HID DRIVER +M: David Herrmann +L: linux-input@vger.kernel.org +S: Maintained +F: drivers/hid/hid-wiimote* + WINBOND CIR DRIVER M: David Härdeman S: Maintained diff --git a/drivers/hid/hid-wiimote.c b/drivers/hid/hid-wiimote.c index c83cafaa46a3..76739c07fa3c 100644 --- a/drivers/hid/hid-wiimote.c +++ b/drivers/hid/hid-wiimote.c @@ -21,7 +21,7 @@ #include #include "hid-ids.h" -#define WIIMOTE_VERSION "0.1" +#define WIIMOTE_VERSION "0.2" #define WIIMOTE_NAME "Nintendo Wii Remote" #define WIIMOTE_BUFSIZE 32 -- cgit v1.2.3-55-g7522 From 1c5784da12e34e98eb0a1b8f4323419dd84ea0b0 Mon Sep 17 00:00:00 2001 From: Stefan Achatz Date: Sat, 27 Aug 2011 15:24:36 +0200 Subject: HID: roccat: Fixed false dpi reporting when using osd event Actual dpi resolution was set on wrong occassion. Signed-off-by: Stefan Achatz Signed-off-by: Jiri Kosina --- drivers/hid/hid-roccat-kone.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-roccat-kone.c b/drivers/hid/hid-roccat-kone.c index 2b8f3a31ffb3..7847a5bb88fb 100644 --- a/drivers/hid/hid-roccat-kone.c +++ b/drivers/hid/hid-roccat-kone.c @@ -776,10 +776,10 @@ static void kone_keep_values_up_to_date(struct kone_device *kone, { switch (event->event) { case kone_mouse_event_switch_profile: + kone->actual_dpi = kone->profiles[event->value - 1]. + startup_dpi; case kone_mouse_event_osd_profile: kone->actual_profile = event->value; - kone->actual_dpi = kone->profiles[kone->actual_profile - 1]. - startup_dpi; break; case kone_mouse_event_switch_dpi: case kone_mouse_event_osd_dpi: -- cgit v1.2.3-55-g7522 From dc186b661cbb30ef593131a43d0d7ce721def512 Mon Sep 17 00:00:00 2001 From: Stefan Achatz Date: Sat, 27 Aug 2011 15:24:41 +0200 Subject: HID: roccat: Pyra now reports external profile changes via roccat device Profile changes were only reported when issued mouse internal. Now all changes are reported. Signed-off-by: Stefan Achatz Signed-off-by: Jiri Kosina --- drivers/hid/hid-roccat-pyra.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/drivers/hid/hid-roccat-pyra.c b/drivers/hid/hid-roccat-pyra.c index 8140776bd8c5..df05c1b1064f 100644 --- a/drivers/hid/hid-roccat-pyra.c +++ b/drivers/hid/hid-roccat-pyra.c @@ -298,6 +298,7 @@ static ssize_t pyra_sysfs_write_settings(struct file *fp, struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); int retval = 0; int difference; + struct pyra_roccat_report roccat_report; if (off != 0 || count != sizeof(struct pyra_settings)) return -EINVAL; @@ -307,17 +308,23 @@ static ssize_t pyra_sysfs_write_settings(struct file *fp, if (difference) { retval = pyra_set_settings(usb_dev, (struct pyra_settings const *)buf); - if (!retval) - memcpy(&pyra->settings, buf, - sizeof(struct pyra_settings)); - } - mutex_unlock(&pyra->pyra_lock); + if (retval) { + mutex_unlock(&pyra->pyra_lock); + return retval; + } - if (retval) - return retval; + memcpy(&pyra->settings, buf, + sizeof(struct pyra_settings)); - profile_activated(pyra, pyra->settings.startup_profile); + profile_activated(pyra, pyra->settings.startup_profile); + roccat_report.type = PYRA_MOUSE_EVENT_BUTTON_TYPE_PROFILE_2; + roccat_report.value = pyra->settings.startup_profile + 1; + roccat_report.key = 0; + roccat_report_event(pyra->chrdev_minor, + (uint8_t const *)&roccat_report); + } + mutex_unlock(&pyra->pyra_lock); return sizeof(struct pyra_settings); } -- cgit v1.2.3-55-g7522 From 6b9a57b9fb8194e00d49779bd0d1130844db6a84 Mon Sep 17 00:00:00 2001 From: Stefan Achatz Date: Sat, 27 Aug 2011 15:24:45 +0200 Subject: HID: roccat: Kovaplus now reports external profile changes via roccat device Profile changes were only reported when issued mouse internal. Now all changes are reported. Signed-off-by: Stefan Achatz Signed-off-by: Jiri Kosina --- drivers/hid/hid-roccat-kovaplus.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-roccat-kovaplus.c b/drivers/hid/hid-roccat-kovaplus.c index 1f8336e3f584..112d934132c8 100644 --- a/drivers/hid/hid-roccat-kovaplus.c +++ b/drivers/hid/hid-roccat-kovaplus.c @@ -323,6 +323,7 @@ static ssize_t kovaplus_sysfs_set_actual_profile(struct device *dev, struct usb_device *usb_dev; unsigned long profile; int retval; + struct kovaplus_roccat_report roccat_report; dev = dev->parent->parent; kovaplus = hid_get_drvdata(dev_get_drvdata(dev)); @@ -337,10 +338,22 @@ static ssize_t kovaplus_sysfs_set_actual_profile(struct device *dev, mutex_lock(&kovaplus->kovaplus_lock); retval = kovaplus_set_actual_profile(usb_dev, profile); + if (retval) { + mutex_unlock(&kovaplus->kovaplus_lock); + return retval; + } + kovaplus_profile_activated(kovaplus, profile); + + roccat_report.type = KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE_1; + roccat_report.profile = profile + 1; + roccat_report.button = 0; + roccat_report.data1 = profile + 1; + roccat_report.data2 = 0; + roccat_report_event(kovaplus->chrdev_minor, + (uint8_t const *)&roccat_report); + mutex_unlock(&kovaplus->kovaplus_lock); - if (retval) - return retval; return size; } -- cgit v1.2.3-55-g7522 From bd9c35d0e5d442568f22e64066a5e687e54881a4 Mon Sep 17 00:00:00 2001 From: Stefan Achatz Date: Sat, 27 Aug 2011 15:24:48 +0200 Subject: HID: roccat: cleaned up code for Kone and fixed wrong initialization value Introduced function kone_profile_activated() to reduce code duplication. This by the way fixes a wrong initialization value. Also fixes early mutex unlocks. Signed-off-by: Stefan Achatz Signed-off-by: Jiri Kosina --- drivers/hid/hid-roccat-kone.c | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/drivers/hid/hid-roccat-kone.c b/drivers/hid/hid-roccat-kone.c index 7847a5bb88fb..c931d00c70b9 100644 --- a/drivers/hid/hid-roccat-kone.c +++ b/drivers/hid/hid-roccat-kone.c @@ -37,6 +37,12 @@ static uint profile_numbers[5] = {0, 1, 2, 3, 4}; +static void kone_profile_activated(struct kone_device *kone, uint new_profile) +{ + kone->actual_profile = new_profile; + kone->actual_dpi = kone->profiles[new_profile - 1].startup_dpi; +} + static int kone_receive(struct usb_device *usb_dev, uint usb_command, void *data, uint size) { @@ -294,21 +300,16 @@ static ssize_t kone_sysfs_write_settings(struct file *fp, struct kobject *kobj, if (difference) { retval = kone_set_settings(usb_dev, (struct kone_settings const *)buf); - if (!retval) - memcpy(&kone->settings, buf, - sizeof(struct kone_settings)); - } - mutex_unlock(&kone->kone_lock); + if (retval) { + mutex_unlock(&kone->kone_lock); + return retval; + } - if (retval) - return retval; + memcpy(&kone->settings, buf, sizeof(struct kone_settings)); - /* - * If we get here, treat settings as okay and update actual values - * according to startup_profile - */ - kone->actual_profile = kone->settings.startup_profile; - kone->actual_dpi = kone->profiles[kone->actual_profile - 1].startup_dpi; + kone_profile_activated(kone, kone->settings.startup_profile); + } + mutex_unlock(&kone->kone_lock); return sizeof(struct kone_settings); } @@ -501,6 +502,8 @@ static ssize_t kone_sysfs_set_tcu(struct device *dev, goto exit_no_settings; goto exit_unlock; } + /* calibration resets profile */ + kone_profile_activated(kone, kone->settings.startup_profile); } retval = size; @@ -544,16 +547,15 @@ static ssize_t kone_sysfs_set_startup_profile(struct device *dev, kone_set_settings_checksum(&kone->settings); retval = kone_set_settings(usb_dev, &kone->settings); - - mutex_unlock(&kone->kone_lock); - - if (retval) + if (retval) { + mutex_unlock(&kone->kone_lock); return retval; + } /* changing the startup profile immediately activates this profile */ - kone->actual_profile = new_startup_profile; - kone->actual_dpi = kone->profiles[kone->actual_profile - 1].startup_dpi; + kone_profile_activated(kone, new_startup_profile); + mutex_unlock(&kone->kone_lock); return size; } @@ -665,8 +667,7 @@ static int kone_init_kone_device_struct(struct usb_device *usb_dev, if (retval) return retval; - kone->actual_profile = kone->settings.startup_profile; - kone->actual_dpi = kone->profiles[kone->actual_profile].startup_dpi; + kone_profile_activated(kone, kone->settings.startup_profile); return 0; } -- cgit v1.2.3-55-g7522 From 3200a6a5fa36585ec1c547d4fefeb622ae02c5ec Mon Sep 17 00:00:00 2001 From: Stefan Achatz Date: Sat, 27 Aug 2011 15:24:51 +0200 Subject: HID: roccat: Kone now reports external profile changes via roccat device Profile changes were only reported when issued mouse internal. Now all changes are reported. Signed-off-by: Stefan Achatz Signed-off-by: Jiri Kosina --- drivers/hid/hid-roccat-kone.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-roccat-kone.c b/drivers/hid/hid-roccat-kone.c index c931d00c70b9..e2072afb34bb 100644 --- a/drivers/hid/hid-roccat-kone.c +++ b/drivers/hid/hid-roccat-kone.c @@ -43,6 +43,15 @@ static void kone_profile_activated(struct kone_device *kone, uint new_profile) kone->actual_dpi = kone->profiles[new_profile - 1].startup_dpi; } +static void kone_profile_report(struct kone_device *kone, uint new_profile) +{ + struct kone_roccat_report roccat_report; + roccat_report.event = kone_mouse_event_switch_profile; + roccat_report.value = new_profile; + roccat_report.key = 0; + roccat_report_event(kone->chrdev_minor, (uint8_t *)&roccat_report); +} + static int kone_receive(struct usb_device *usb_dev, uint usb_command, void *data, uint size) { @@ -289,7 +298,7 @@ static ssize_t kone_sysfs_write_settings(struct file *fp, struct kobject *kobj, container_of(kobj, struct device, kobj)->parent->parent; struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev)); struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); - int retval = 0, difference; + int retval = 0, difference, old_profile; /* I need to get my data in one piece */ if (off != 0 || count != sizeof(struct kone_settings)) @@ -305,9 +314,13 @@ static ssize_t kone_sysfs_write_settings(struct file *fp, struct kobject *kobj, return retval; } + old_profile = kone->settings.startup_profile; memcpy(&kone->settings, buf, sizeof(struct kone_settings)); kone_profile_activated(kone, kone->settings.startup_profile); + + if (kone->settings.startup_profile != old_profile) + kone_profile_report(kone, kone->settings.startup_profile); } mutex_unlock(&kone->kone_lock); @@ -554,6 +567,7 @@ static ssize_t kone_sysfs_set_startup_profile(struct device *dev, /* changing the startup profile immediately activates this profile */ kone_profile_activated(kone, new_startup_profile); + kone_profile_report(kone, new_startup_profile); mutex_unlock(&kone->kone_lock); return size; -- cgit v1.2.3-55-g7522 From 534a7b8e10ec55d9f521e68c20dbb3634c25b98a Mon Sep 17 00:00:00 2001 From: Nestor Lopez Casado Date: Thu, 15 Sep 2011 11:34:49 +0200 Subject: HID: Add full support for Logitech Unifying receivers With this driver, all the devices paired to a single Unifying receiver are exposed to user processes in separated /input/dev nodes. Keyboards with different layouts can be treated differently, Multiplayer games on single PC (like home theater PC) can differentiate input coming from different kbds paired to the same receiver. Up to now, when Logitech Unifying receivers are connected to a Linux based system, a single keyboard and a single mouse are presented to the HID Layer, even if the Unifying receiver can pair up to six compatible devices. The Unifying receiver by default multiplexes all incoming events (from multiple keyboards/mice) into these two. Signed-off-by: Nestor Lopez Casado Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 9 + drivers/hid/Makefile | 1 + drivers/hid/hid-core.c | 2 + drivers/hid/hid-ids.h | 2 + drivers/hid/hid-logitech-dj.c | 925 ++++++++++++++++++++++++++++++++++++++++++ drivers/hid/hid-logitech-dj.h | 120 ++++++ 6 files changed, 1059 insertions(+) create mode 100644 drivers/hid/hid-logitech-dj.c create mode 100644 drivers/hid/hid-logitech-dj.h diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 410eaa902495..d2c97343fc0d 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -245,6 +245,15 @@ config HID_LOGITECH ---help--- Support for Logitech devices that are not fully compliant with HID standard. +config HID_LOGITECH_DJ + tristate "Logitech Unifying receivers full support" + depends on HID_LOGITECH + default m + ---help--- + Say Y if you want support for Logitech Unifying receivers and devices. + Unifying receivers are capable of pairing up to 6 Logitech compliant + devices to the same receiver. + config LOGITECH_FF bool "Logitech force feedback support" depends on HID_LOGITECH diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 77c9e5d7ff97..b7ddabb0b34c 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_HID_KEYTOUCH) += hid-keytouch.o obj-$(CONFIG_HID_KYE) += hid-kye.o obj-$(CONFIG_HID_LCPOWER) += hid-lcpower.o obj-$(CONFIG_HID_LOGITECH) += hid-logitech.o +obj-$(CONFIG_HID_LOGITECH_DJ) += hid-logitech-dj.o obj-$(CONFIG_HID_MAGICMOUSE) += hid-magicmouse.o obj-$(CONFIG_HID_MICROSOFT) += hid-microsoft.o obj-$(CONFIG_HID_MONTEREY) += hid-monterey.o diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 1a5cf0c9cfca..ab4ae12e0a01 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1419,6 +1419,8 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DFP_WHEEL) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G25_WHEEL) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G27_WHEEL) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER_2) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WII_WHEEL) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACETRAVELLER) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index ea2c338814fe..3d065d096a07 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -444,6 +444,8 @@ #define USB_DEVICE_ID_S510_RECEIVER_2 0xc517 #define USB_DEVICE_ID_LOGITECH_CORDLESS_DESKTOP_LX500 0xc512 #define USB_DEVICE_ID_MX3000_RECEIVER 0xc513 +#define USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER 0xc52b +#define USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER_2 0xc532 #define USB_DEVICE_ID_SPACETRAVELLER 0xc623 #define USB_DEVICE_ID_SPACENAVIGATOR 0xc626 #define USB_DEVICE_ID_DINOVO_DESKTOP 0xc704 diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c new file mode 100644 index 000000000000..5bb6f42a4278 --- /dev/null +++ b/drivers/hid/hid-logitech-dj.c @@ -0,0 +1,925 @@ +/* + * HID driver for Logitech Unifying receivers + * + * Copyright (c) 2011 Logitech + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + + +#include +#include +#include +#include +#include "usbhid/usbhid.h" +#include "hid-ids.h" +#include "hid-logitech-dj.h" + +/* Keyboard descriptor (1) */ +static const char kbd_descriptor[] = { + 0x05, 0x01, /* USAGE_PAGE (generic Desktop) */ + 0x09, 0x06, /* USAGE (Keyboard) */ + 0xA1, 0x01, /* COLLECTION (Application) */ + 0x85, 0x01, /* REPORT_ID (1) */ + 0x95, 0x08, /* REPORT_COUNT (8) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ + 0x05, 0x07, /* USAGE_PAGE (Keyboard) */ + 0x19, 0xE0, /* USAGE_MINIMUM (Left Control) */ + 0x29, 0xE7, /* USAGE_MAXIMUM (Right GUI) */ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */ + 0x95, 0x05, /* REPORT COUNT (5) */ + 0x05, 0x08, /* USAGE PAGE (LED page) */ + 0x19, 0x01, /* USAGE MINIMUM (1) */ + 0x29, 0x05, /* USAGE MAXIMUM (5) */ + 0x91, 0x02, /* OUTPUT (Data, Variable, Absolute) */ + 0x95, 0x01, /* REPORT COUNT (1) */ + 0x75, 0x03, /* REPORT SIZE (3) */ + 0x91, 0x01, /* OUTPUT (Constant) */ + 0x95, 0x06, /* REPORT_COUNT (6) */ + 0x75, 0x08, /* REPORT_SIZE (8) */ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ + 0x26, 0xFF, 0x00, /* LOGICAL_MAXIMUM (255) */ + 0x05, 0x07, /* USAGE_PAGE (Keyboard) */ + 0x19, 0x00, /* USAGE_MINIMUM (no event) */ + 0x2A, 0xFF, 0x00, /* USAGE_MAXIMUM (reserved) */ + 0x81, 0x00, /* INPUT (Data,Ary,Abs) */ + 0xC0 +}; + +/* Mouse descriptor (2) */ +static const char mse_descriptor[] = { + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ + 0x09, 0x02, /* USAGE (Mouse) */ + 0xA1, 0x01, /* COLLECTION (Application) */ + 0x85, 0x02, /* REPORT_ID = 2 */ + 0x09, 0x01, /* USAGE (pointer) */ + 0xA1, 0x00, /* COLLECTION (physical) */ + 0x05, 0x09, /* USAGE_PAGE (buttons) */ + 0x19, 0x01, /* USAGE_MIN (1) */ + 0x29, 0x10, /* USAGE_MAX (16) */ + 0x15, 0x00, /* LOGICAL_MIN (0) */ + 0x25, 0x01, /* LOGICAL_MAX (1) */ + 0x95, 0x10, /* REPORT_COUNT (16) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0x81, 0x02, /* INPUT (data var abs) */ + 0x05, 0x01, /* USAGE_PAGE (generic desktop) */ + 0x16, 0x01, 0xF8, /* LOGICAL_MIN (-2047) */ + 0x26, 0xFF, 0x07, /* LOGICAL_MAX (2047) */ + 0x75, 0x0C, /* REPORT_SIZE (12) */ + 0x95, 0x02, /* REPORT_COUNT (2) */ + 0x09, 0x30, /* USAGE (X) */ + 0x09, 0x31, /* USAGE (Y) */ + 0x81, 0x06, /* INPUT */ + 0x15, 0x81, /* LOGICAL_MIN (-127) */ + 0x25, 0x7F, /* LOGICAL_MAX (127) */ + 0x75, 0x08, /* REPORT_SIZE (8) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x09, 0x38, /* USAGE (wheel) */ + 0x81, 0x06, /* INPUT */ + 0x05, 0x0C, /* USAGE_PAGE(consumer) */ + 0x0A, 0x38, 0x02, /* USAGE(AC Pan) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x81, 0x06, /* INPUT */ + 0xC0, /* END_COLLECTION */ + 0xC0, /* END_COLLECTION */ +}; + +/* Consumer Control descriptor (3) */ +static const char consumer_descriptor[] = { + 0x05, 0x0C, /* USAGE_PAGE (Consumer Devices) */ + 0x09, 0x01, /* USAGE (Consumer Control) */ + 0xA1, 0x01, /* COLLECTION (Application) */ + 0x85, 0x03, /* REPORT_ID = 3 */ + 0x75, 0x10, /* REPORT_SIZE (16) */ + 0x95, 0x02, /* REPORT_COUNT (2) */ + 0x15, 0x01, /* LOGICAL_MIN (1) */ + 0x26, 0x8C, 0x02, /* LOGICAL_MAX (652) */ + 0x19, 0x01, /* USAGE_MIN (1) */ + 0x2A, 0x8C, 0x02, /* USAGE_MAX (652) */ + 0x81, 0x00, /* INPUT (Data Ary Abs) */ + 0xC0, /* END_COLLECTION */ +}; /* */ + +/* System control descriptor (4) */ +static const char syscontrol_descriptor[] = { + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ + 0x09, 0x80, /* USAGE (System Control) */ + 0xA1, 0x01, /* COLLECTION (Application) */ + 0x85, 0x04, /* REPORT_ID = 4 */ + 0x75, 0x02, /* REPORT_SIZE (2) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x15, 0x01, /* LOGICAL_MIN (1) */ + 0x25, 0x03, /* LOGICAL_MAX (3) */ + 0x09, 0x82, /* USAGE (System Sleep) */ + 0x09, 0x81, /* USAGE (System Power Down) */ + 0x09, 0x83, /* USAGE (System Wake Up) */ + 0x81, 0x60, /* INPUT (Data Ary Abs NPrf Null) */ + 0x75, 0x06, /* REPORT_SIZE (6) */ + 0x81, 0x03, /* INPUT (Cnst Var Abs) */ + 0xC0, /* END_COLLECTION */ +}; + +/* Media descriptor (8) */ +static const char media_descriptor[] = { + 0x06, 0xbc, 0xff, /* Usage Page 0xffbc */ + 0x09, 0x88, /* Usage 0x0088 */ + 0xa1, 0x01, /* BeginCollection */ + 0x85, 0x08, /* Report ID 8 */ + 0x19, 0x01, /* Usage Min 0x0001 */ + 0x29, 0xff, /* Usage Max 0x00ff */ + 0x15, 0x01, /* Logical Min 1 */ + 0x26, 0xff, 0x00, /* Logical Max 255 */ + 0x75, 0x08, /* Report Size 8 */ + 0x95, 0x01, /* Report Count 1 */ + 0x81, 0x00, /* Input */ + 0xc0, /* EndCollection */ +}; /* */ + +/* Maximum size of all defined hid reports in bytes (including report id) */ +#define MAX_REPORT_SIZE 8 + +/* Number of possible hid report types that can be created by this driver. + * + * Right now, RF report types have the same report types (or report id's) + * than the hid report created from those RF reports. In the future + * this doesnt have to be true. + * + * For instance, RF report type 0x01 which has a size of 8 bytes, corresponds + * to hid report id 0x01, this is standard keyboard. Same thing applies to mice + * reports and consumer control, etc. If a new RF report is created, it doesn't + * has to have the same report id as its corresponding hid report, so an + * translation may have to take place for future report types. + */ +#define NUMBER_OF_HID_REPORTS 32 +static const u8 hid_reportid_size_map[NUMBER_OF_HID_REPORTS] = { + [1] = 8, /* Standard keyboard */ + [2] = 8, /* Standard mouse */ + [3] = 5, /* Consumer control */ + [4] = 2, /* System control */ + [8] = 2, /* Media Center */ +}; + + +#define LOGITECH_DJ_INTERFACE_NUMBER 0x02 + +#define DJ_DEVICE_INDEX_MIN 1 +#define DJ_DEVICE_INDEX_MAX 6 + +static struct hid_ll_driver logi_dj_ll_driver; + +static int logi_dj_output_hidraw_report(struct hid_device *hid, u8 * buf, + size_t count, + unsigned char report_type); + +static void logi_dj_recv_destroy_djhid_device(struct dj_receiver_dev *djrcv_dev, + struct dj_report *dj_report) +{ + /* Called in delayed work context */ + struct dj_device *dj_dev; + unsigned long flags; + + spin_lock_irqsave(&djrcv_dev->lock, flags); + dj_dev = djrcv_dev->paired_dj_devices[dj_report->device_index]; + djrcv_dev->paired_dj_devices[dj_report->device_index] = NULL; + spin_unlock_irqrestore(&djrcv_dev->lock, flags); + + if (dj_dev != NULL) { + hid_destroy_device(dj_dev->hdev); + kfree(dj_dev); + } else { + dev_err(&djrcv_dev->hdev->dev, "%s: can't destroy a NULL device\n", + __func__); + } +} + +static void logi_dj_recv_add_djhid_device(struct dj_receiver_dev *djrcv_dev, + struct dj_report *dj_report) +{ + /* Called in delayed work context */ + struct hid_device *djrcv_hdev = djrcv_dev->hdev; + struct usb_interface *intf = to_usb_interface(djrcv_hdev->dev.parent); + struct usb_device *usbdev = interface_to_usbdev(intf); + struct hid_device *dj_hiddev; + struct dj_device *dj_dev; + + /* Device index goes from 1 to 6, we need 3 bytes to store the + * semicolon, the index, and a null terminator + */ + unsigned char tmpstr[3]; + + if (dj_report->report_params[DEVICE_PAIRED_PARAM_SPFUNCTION] & + SPFUNCTION_DEVICE_LIST_EMPTY) { + dbg_hid("%s: device list is empty\n", __func__); + return; + } + + if ((dj_report->device_index < DJ_DEVICE_INDEX_MIN) || + (dj_report->device_index > DJ_DEVICE_INDEX_MAX)) { + dev_err(&djrcv_hdev->dev, "%s: invalid device index:%d\n", + __func__, dj_report->device_index); + return; + } + + dj_hiddev = hid_allocate_device(); + if (IS_ERR(dj_hiddev)) { + dev_err(&djrcv_hdev->dev, "%s: hid_allocate_device failed\n", + __func__); + return; + } + + dj_hiddev->ll_driver = &logi_dj_ll_driver; + dj_hiddev->hid_output_raw_report = logi_dj_output_hidraw_report; + + dj_hiddev->dev.parent = &djrcv_hdev->dev; + dj_hiddev->bus = BUS_USB; + dj_hiddev->vendor = le16_to_cpu(usbdev->descriptor.idVendor); + dj_hiddev->product = le16_to_cpu(usbdev->descriptor.idProduct); + snprintf(dj_hiddev->name, sizeof(dj_hiddev->name), + "Logitech Unifying Device. Wireless PID:%02x%02x", + dj_report->report_params[DEVICE_PAIRED_PARAM_EQUAD_ID_MSB], + dj_report->report_params[DEVICE_PAIRED_PARAM_EQUAD_ID_LSB]); + + usb_make_path(usbdev, dj_hiddev->phys, sizeof(dj_hiddev->phys)); + snprintf(tmpstr, sizeof(tmpstr), ":%d", dj_report->device_index); + strlcat(dj_hiddev->phys, tmpstr, sizeof(dj_hiddev->phys)); + + dj_dev = kzalloc(sizeof(struct dj_device), GFP_KERNEL); + + if (!dj_dev) { + dev_err(&djrcv_hdev->dev, "%s: failed allocating dj_device\n", + __func__); + goto dj_device_allocate_fail; + } + + dj_dev->reports_supported = le32_to_cpu( + dj_report->report_params[DEVICE_PAIRED_RF_REPORT_TYPE]); + dj_dev->hdev = dj_hiddev; + dj_dev->dj_receiver_dev = djrcv_dev; + dj_dev->device_index = dj_report->device_index; + dj_hiddev->driver_data = dj_dev; + + djrcv_dev->paired_dj_devices[dj_report->device_index] = dj_dev; + + if (hid_add_device(dj_hiddev)) { + dev_err(&djrcv_hdev->dev, "%s: failed adding dj_device\n", + __func__); + goto hid_add_device_fail; + } + + return; + +hid_add_device_fail: + djrcv_dev->paired_dj_devices[dj_report->device_index] = NULL; + kfree(dj_dev); +dj_device_allocate_fail: + hid_destroy_device(dj_hiddev); +} + +static void delayedwork_callback(struct work_struct *work) +{ + struct dj_receiver_dev *djrcv_dev = + container_of(work, struct dj_receiver_dev, work); + + struct dj_report dj_report; + unsigned long flags; + int count; + + dbg_hid("%s\n", __func__); + + spin_lock_irqsave(&djrcv_dev->lock, flags); + + count = kfifo_out(&djrcv_dev->notif_fifo, &dj_report, + sizeof(struct dj_report)); + + if (count != sizeof(struct dj_report)) { + dev_err(&djrcv_dev->hdev->dev, "%s: workitem triggered without " + "notifications available\n", __func__); + spin_unlock_irqrestore(&djrcv_dev->lock, flags); + return; + } + + if (!kfifo_is_empty(&djrcv_dev->notif_fifo)) { + if (schedule_work(&djrcv_dev->work) == 0) { + dbg_hid("%s: did not schedule the work item, was " + "already queued\n", __func__); + } + } + + spin_unlock_irqrestore(&djrcv_dev->lock, flags); + + switch (dj_report.report_type) { + case REPORT_TYPE_NOTIF_DEVICE_PAIRED: + logi_dj_recv_add_djhid_device(djrcv_dev, &dj_report); + break; + case REPORT_TYPE_NOTIF_DEVICE_UNPAIRED: + logi_dj_recv_destroy_djhid_device(djrcv_dev, &dj_report); + break; + default: + dbg_hid("%s: unexpected report type\n", __func__); + } +} + +static void logi_dj_recv_queue_notification(struct dj_receiver_dev *djrcv_dev, + struct dj_report *dj_report) +{ + /* We are called from atomic context (tasklet && djrcv->lock held) */ + + kfifo_in(&djrcv_dev->notif_fifo, dj_report, sizeof(struct dj_report)); + + if (schedule_work(&djrcv_dev->work) == 0) { + dbg_hid("%s: did not schedule the work item, was already " + "queued\n", __func__); + } +} + +static void logi_dj_recv_forward_null_report(struct dj_receiver_dev *djrcv_dev, + struct dj_report *dj_report) +{ + /* We are called from atomic context (tasklet && djrcv->lock held) */ + unsigned int i; + u8 reportbuffer[MAX_REPORT_SIZE]; + struct dj_device *djdev; + + djdev = djrcv_dev->paired_dj_devices[dj_report->device_index]; + + if (!djdev) { + dbg_hid("djrcv_dev->paired_dj_devices[dj_report->device_index]" + " is NULL, index %d\n", dj_report->device_index); + return; + } + + memset(reportbuffer, 0, sizeof(reportbuffer)); + + for (i = 0; i < NUMBER_OF_HID_REPORTS; i++) { + if (djdev->reports_supported & (1 << i)) { + reportbuffer[0] = i; + if (hid_input_report(djdev->hdev, + HID_INPUT_REPORT, + reportbuffer, + hid_reportid_size_map[i], 1)) { + dbg_hid("hid_input_report error sending null " + "report\n"); + } + } + } +} + +static void logi_dj_recv_forward_report(struct dj_receiver_dev *djrcv_dev, + struct dj_report *dj_report) +{ + /* We are called from atomic context (tasklet && djrcv->lock held) */ + struct dj_device *dj_device; + + dj_device = djrcv_dev->paired_dj_devices[dj_report->device_index]; + + if (dj_device == NULL) { + dbg_hid("djrcv_dev->paired_dj_devices[dj_report->device_index]" + " is NULL, index %d\n", dj_report->device_index); + return; + } + + if ((dj_report->report_type > ARRAY_SIZE(hid_reportid_size_map) - 1) || + (hid_reportid_size_map[dj_report->report_type] == 0)) { + dbg_hid("invalid report type:%x\n", dj_report->report_type); + return; + } + + if (hid_input_report(dj_device->hdev, + HID_INPUT_REPORT, &dj_report->report_type, + hid_reportid_size_map[dj_report->report_type], 1)) { + dbg_hid("hid_input_report error\n"); + } +} + + +static int logi_dj_recv_send_report(struct dj_receiver_dev *djrcv_dev, + struct dj_report *dj_report) +{ + struct hid_device *hdev = djrcv_dev->hdev; + int sent_bytes; + + if (!hdev->hid_output_raw_report) { + dev_err(&hdev->dev, "%s:" + "hid_output_raw_report is null\n", __func__); + return -ENODEV; + } + + sent_bytes = hdev->hid_output_raw_report(hdev, (u8 *) dj_report, + sizeof(struct dj_report), + HID_OUTPUT_REPORT); + + return (sent_bytes < 0) ? sent_bytes : 0; +} + +static int logi_dj_recv_query_paired_devices(struct dj_receiver_dev *djrcv_dev) +{ + struct dj_report dj_report; + + memset(&dj_report, 0, sizeof(dj_report)); + dj_report.report_id = REPORT_ID_DJ_SHORT; + dj_report.device_index = 0xFF; + dj_report.report_type = REPORT_TYPE_CMD_GET_PAIRED_DEVICES; + return logi_dj_recv_send_report(djrcv_dev, &dj_report); +} + +static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev, + unsigned timeout) +{ + struct dj_report dj_report; + + memset(&dj_report, 0, sizeof(dj_report)); + dj_report.report_id = REPORT_ID_DJ_SHORT; + dj_report.device_index = 0xFF; + dj_report.report_type = REPORT_TYPE_CMD_SWITCH; + dj_report.report_params[CMD_SWITCH_PARAM_DEVBITFIELD] = 0x1F; + dj_report.report_params[CMD_SWITCH_PARAM_TIMEOUT_SECONDS] = (u8)timeout; + return logi_dj_recv_send_report(djrcv_dev, &dj_report); +} + + +static int logi_dj_ll_open(struct hid_device *hid) +{ + dbg_hid("%s:%s\n", __func__, hid->phys); + return 0; + +} + +static void logi_dj_ll_close(struct hid_device *hid) +{ + dbg_hid("%s:%s\n", __func__, hid->phys); +} + +static int logi_dj_output_hidraw_report(struct hid_device *hid, u8 * buf, + size_t count, + unsigned char report_type) +{ + /* Called by hid raw to send data */ + dbg_hid("%s\n", __func__); + + return 0; +} + +static int logi_dj_ll_parse(struct hid_device *hid) +{ + struct dj_device *djdev = hid->driver_data; + int retval; + + dbg_hid("%s\n", __func__); + + djdev->hdev->version = 0x0111; + djdev->hdev->country = 0x00; + + if (djdev->reports_supported & STD_KEYBOARD) { + dbg_hid("%s: sending a kbd descriptor, reports_supported: %x\n", + __func__, djdev->reports_supported); + retval = hid_parse_report(hid, + (u8 *) kbd_descriptor, + sizeof(kbd_descriptor)); + if (retval) { + dbg_hid("%s: sending a kbd descriptor, hid_parse failed" + " error: %d\n", __func__, retval); + return retval; + } + } + + if (djdev->reports_supported & STD_MOUSE) { + dbg_hid("%s: sending a mouse descriptor, reports_supported: " + "%x\n", __func__, djdev->reports_supported); + retval = hid_parse_report(hid, + (u8 *) mse_descriptor, + sizeof(mse_descriptor)); + if (retval) { + dbg_hid("%s: sending a mouse descriptor, hid_parse " + "failed error: %d\n", __func__, retval); + return retval; + } + } + + if (djdev->reports_supported & MULTIMEDIA) { + dbg_hid("%s: sending a multimedia report descriptor: %x\n", + __func__, djdev->reports_supported); + retval = hid_parse_report(hid, + (u8 *) consumer_descriptor, + sizeof(consumer_descriptor)); + if (retval) { + dbg_hid("%s: sending a consumer_descriptor, hid_parse " + "failed error: %d\n", __func__, retval); + return retval; + } + } + + if (djdev->reports_supported & POWER_KEYS) { + dbg_hid("%s: sending a power keys report descriptor: %x\n", + __func__, djdev->reports_supported); + retval = hid_parse_report(hid, + (u8 *) syscontrol_descriptor, + sizeof(syscontrol_descriptor)); + if (retval) { + dbg_hid("%s: sending a syscontrol_descriptor, " + "hid_parse failed error: %d\n", + __func__, retval); + return retval; + } + } + + if (djdev->reports_supported & MEDIA_CENTER) { + dbg_hid("%s: sending a media center report descriptor: %x\n", + __func__, djdev->reports_supported); + retval = hid_parse_report(hid, + (u8 *) media_descriptor, + sizeof(media_descriptor)); + if (retval) { + dbg_hid("%s: sending a media_descriptor, hid_parse " + "failed error: %d\n", __func__, retval); + return retval; + } + } + + if (djdev->reports_supported & KBD_LEDS) { + dbg_hid("%s: need to send kbd leds report descriptor: %x\n", + __func__, djdev->reports_supported); + } + + return 0; +} + +static int logi_dj_ll_input_event(struct input_dev *dev, unsigned int type, + unsigned int code, int value) +{ + /* Sent by the input layer to handle leds and Force Feedback */ + struct hid_device *dj_hiddev = input_get_drvdata(dev); + struct dj_device *dj_dev = dj_hiddev->driver_data; + + struct dj_receiver_dev *djrcv_dev = + dev_get_drvdata(dj_hiddev->dev.parent); + struct hid_device *dj_rcv_hiddev = djrcv_dev->hdev; + struct hid_report_enum *output_report_enum; + + struct hid_field *field; + struct hid_report *report; + unsigned char data[8]; + int offset; + + dbg_hid("%s: %s, type:%d | code:%d | value:%d\n", + __func__, dev->phys, type, code, value); + + if (type != EV_LED) + return -1; + + offset = hidinput_find_field(dj_hiddev, type, code, &field); + + if (offset == -1) { + dev_warn(&dev->dev, "event field not found\n"); + return -1; + } + hid_set_field(field, offset, value); + hid_output_report(field->report, &data[0]); + + output_report_enum = &dj_rcv_hiddev->report_enum[HID_OUTPUT_REPORT]; + report = output_report_enum->report_id_hash[REPORT_ID_DJ_SHORT]; + hid_set_field(report->field[0], 0, dj_dev->device_index); + hid_set_field(report->field[0], 1, REPORT_TYPE_LEDS); + hid_set_field(report->field[0], 2, data[1]); + + usbhid_submit_report(dj_rcv_hiddev, report, USB_DIR_OUT); + + return 0; + +} + +static int logi_dj_ll_start(struct hid_device *hid) +{ + dbg_hid("%s\n", __func__); + return 0; +} + +static void logi_dj_ll_stop(struct hid_device *hid) +{ + dbg_hid("%s\n", __func__); +} + + +static struct hid_ll_driver logi_dj_ll_driver = { + .parse = logi_dj_ll_parse, + .start = logi_dj_ll_start, + .stop = logi_dj_ll_stop, + .open = logi_dj_ll_open, + .close = logi_dj_ll_close, + .hidinput_input_event = logi_dj_ll_input_event, +}; + + +static int logi_dj_raw_event(struct hid_device *hdev, + struct hid_report *report, u8 *data, + int size) +{ + struct dj_receiver_dev *djrcv_dev = hid_get_drvdata(hdev); + struct dj_report *dj_report = (struct dj_report *) data; + unsigned long flags; + bool report_processed = false; + + dbg_hid("%s, size:%d\n", __func__, size); + + /* Here we receive all data coming from iface 2, there are 4 cases: + * + * 1) Data should continue its normal processing i.e. data does not + * come from the DJ collection, in which case we do nothing and + * return 0, so hid-core can continue normal processing (will forward + * to associated hidraw device) + * + * 2) Data is from DJ collection, and is intended for this driver i. e. + * data contains arrival, departure, etc notifications, in which case + * we queue them for delayed processing by the work queue. We return 1 + * to hid-core as no further processing is required from it. + * + * 3) Data is from DJ collection, and informs a connection change, + * if the change means rf link loss, then we must send a null report + * to the upper layer to discard potentially pressed keys that may be + * repeated forever by the input layer. Return 1 to hid-core as no + * further processing is required. + * + * 4) Data is from DJ collection and is an actual input event from + * a paired DJ device in which case we forward it to the correct hid + * device (via hid_input_report() ) and return 1 so hid-core does not do + * anything else with it. + */ + + spin_lock_irqsave(&djrcv_dev->lock, flags); + if (dj_report->report_id == REPORT_ID_DJ_SHORT) { + switch (dj_report->report_type) { + case REPORT_TYPE_NOTIF_DEVICE_PAIRED: + case REPORT_TYPE_NOTIF_DEVICE_UNPAIRED: + logi_dj_recv_queue_notification(djrcv_dev, dj_report); + break; + case REPORT_TYPE_NOTIF_CONNECTION_STATUS: + if (dj_report->report_params[CONNECTION_STATUS_PARAM_STATUS] == + STATUS_LINKLOSS) { + logi_dj_recv_forward_null_report(djrcv_dev, dj_report); + } + break; + default: + logi_dj_recv_forward_report(djrcv_dev, dj_report); + } + report_processed = true; + } + spin_unlock_irqrestore(&djrcv_dev->lock, flags); + + return report_processed; +} + +static int logi_dj_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct dj_receiver_dev *djrcv_dev; + int retval; + + if (is_dj_device((struct dj_device *)hdev->driver_data)) + return -ENODEV; + + dbg_hid("%s called for ifnum %d\n", __func__, + intf->cur_altsetting->desc.bInterfaceNumber); + + /* Ignore interfaces 0 and 1, they will not carry any data, dont create + * any hid_device for them */ + if (intf->cur_altsetting->desc.bInterfaceNumber != + LOGITECH_DJ_INTERFACE_NUMBER) { + dbg_hid("%s: ignoring ifnum %d\n", __func__, + intf->cur_altsetting->desc.bInterfaceNumber); + return -ENODEV; + } + + /* Treat interface 2 */ + + djrcv_dev = kzalloc(sizeof(struct dj_receiver_dev), GFP_KERNEL); + if (!djrcv_dev) { + dev_err(&hdev->dev, + "%s:failed allocating dj_receiver_dev\n", __func__); + return -ENOMEM; + } + djrcv_dev->hdev = hdev; + INIT_WORK(&djrcv_dev->work, delayedwork_callback); + spin_lock_init(&djrcv_dev->lock); + if (kfifo_alloc(&djrcv_dev->notif_fifo, + DJ_MAX_NUMBER_NOTIFICATIONS * sizeof(struct dj_report), + GFP_KERNEL)) { + dev_err(&hdev->dev, + "%s:failed allocating notif_fifo\n", __func__); + kfree(djrcv_dev); + return -ENOMEM; + } + hid_set_drvdata(hdev, djrcv_dev); + + /* Call to usbhid to fetch the HID descriptors of interface 2 and + * subsequently call to the hid/hid-core to parse the fetched + * descriptors, this will in turn create the hidraw and hiddev nodes + * for interface 2 of the receiver */ + retval = hid_parse(hdev); + if (retval) { + dev_err(&hdev->dev, + "%s:parse of interface 2 failed\n", __func__); + goto hid_parse_fail; + } + + /* Starts the usb device and connects to upper interfaces hiddev and + * hidraw */ + retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (retval) { + dev_err(&hdev->dev, + "%s:hid_hw_start returned error\n", __func__); + goto hid_hw_start_fail; + } + + retval = logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0); + if (retval < 0) { + dev_err(&hdev->dev, + "%s:logi_dj_recv_switch_to_dj_mode returned error:%d\n", + __func__, retval); + goto switch_to_dj_mode_fail; + } + + /* This is enabling the polling urb on the IN endpoint */ + retval = hdev->ll_driver->open(hdev); + if (retval < 0) { + dev_err(&hdev->dev, "%s:hdev->ll_driver->open returned " + "error:%d\n", __func__, retval); + goto llopen_failed; + } + + retval = logi_dj_recv_query_paired_devices(djrcv_dev); + if (retval < 0) { + dev_err(&hdev->dev, "%s:logi_dj_recv_query_paired_devices " + "error:%d\n", __func__, retval); + goto logi_dj_recv_query_paired_devices_failed; + } + + return retval; + +logi_dj_recv_query_paired_devices_failed: + hdev->ll_driver->close(hdev); + +llopen_failed: +switch_to_dj_mode_fail: + hid_hw_stop(hdev); + +hid_hw_start_fail: +hid_parse_fail: + kfifo_free(&djrcv_dev->notif_fifo); + kfree(djrcv_dev); + hid_set_drvdata(hdev, NULL); + return retval; + +} + +#ifdef CONFIG_PM +static int logi_dj_reset_resume(struct hid_device *hdev) +{ + int retval; + struct dj_receiver_dev *djrcv_dev = hid_get_drvdata(hdev); + + retval = logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0); + if (retval < 0) { + dev_err(&hdev->dev, + "%s:logi_dj_recv_switch_to_dj_mode returned error:%d\n", + __func__, retval); + } + + return 0; +} +#endif + +static void logi_dj_remove(struct hid_device *hdev) +{ + struct dj_receiver_dev *djrcv_dev = hid_get_drvdata(hdev); + struct dj_device *dj_dev; + int i; + + dbg_hid("%s\n", __func__); + + cancel_work_sync(&djrcv_dev->work); + + hdev->ll_driver->close(hdev); + hid_hw_stop(hdev); + + /* I suppose that at this point the only context that can access + * the djrecv_data is this thread as the work item is guaranteed to + * have finished and no more raw_event callbacks should arrive after + * the remove callback was triggered so no locks are put around the + * code below */ + for (i = 0; i < DJ_MAX_PAIRED_DEVICES; i++) { + dj_dev = djrcv_dev->paired_dj_devices[i]; + if (dj_dev != NULL) { + hid_destroy_device(dj_dev->hdev); + kfree(dj_dev); + djrcv_dev->paired_dj_devices[i] = NULL; + } + } + + kfifo_free(&djrcv_dev->notif_fifo); + kfree(djrcv_dev); + hid_set_drvdata(hdev, NULL); +} + +static int logi_djdevice_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int ret; + struct dj_device *dj_dev = hdev->driver_data; + + if (!is_dj_device(dj_dev)) + return -ENODEV; + + ret = hid_parse(hdev); + if (!ret) + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + + return ret; +} + +static const struct hid_device_id logi_dj_receivers[] = { + {HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, + USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER)}, + {HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, + USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER_2)}, + {} +}; + +MODULE_DEVICE_TABLE(hid, logi_dj_receivers); + +static struct hid_driver logi_djreceiver_driver = { + .name = "logitech-djreceiver", + .id_table = logi_dj_receivers, + .probe = logi_dj_probe, + .remove = logi_dj_remove, + .raw_event = logi_dj_raw_event, +#ifdef CONFIG_PM + .reset_resume = logi_dj_reset_resume, +#endif +}; + + +static const struct hid_device_id logi_dj_devices[] = { + {HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, + USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER)}, + {HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, + USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER_2)}, + {} +}; + +static struct hid_driver logi_djdevice_driver = { + .name = "logitech-djdevice", + .id_table = logi_dj_devices, + .probe = logi_djdevice_probe, +}; + + +static int __init logi_dj_init(void) +{ + int retval; + + dbg_hid("Logitech-DJ:%s\n", __func__); + + retval = hid_register_driver(&logi_djreceiver_driver); + if (retval) + return retval; + + retval = hid_register_driver(&logi_djdevice_driver); + if (retval) + hid_unregister_driver(&logi_djreceiver_driver); + + return retval; + +} + +static void __exit logi_dj_exit(void) +{ + dbg_hid("Logitech-DJ:%s\n", __func__); + + hid_unregister_driver(&logi_djdevice_driver); + hid_unregister_driver(&logi_djreceiver_driver); + +} + +module_init(logi_dj_init); +module_exit(logi_dj_exit); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Logitech"); +MODULE_AUTHOR("Nestor Lopez Casado"); +MODULE_AUTHOR("nlopezcasad@logitech.com"); diff --git a/drivers/hid/hid-logitech-dj.h b/drivers/hid/hid-logitech-dj.h new file mode 100644 index 000000000000..5982263acd73 --- /dev/null +++ b/drivers/hid/hid-logitech-dj.h @@ -0,0 +1,120 @@ +#ifndef __HID_LOGITECH_DJ_H +#define __HID_LOGITECH_DJ_H + +/* + * HID driver for Logitech Unifying receivers + * + * Copyright (c) 2011 Logitech + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include + +#define DJ_MAX_PAIRED_DEVICES 6 +#define DJ_MAX_NUMBER_NOTIFICATIONS 8 + +#define DJREPORT_SHORT_LENGTH 15 +#define DJREPORT_LONG_LENGTH 32 + +#define REPORT_ID_DJ_SHORT 0x20 +#define REPORT_ID_DJ_LONG 0x21 + +#define REPORT_TYPE_RFREPORT_FIRST 0x01 +#define REPORT_TYPE_RFREPORT_LAST 0x1F + +/* Command Switch to DJ mode */ +#define REPORT_TYPE_CMD_SWITCH 0x80 +#define CMD_SWITCH_PARAM_DEVBITFIELD 0x00 +#define CMD_SWITCH_PARAM_TIMEOUT_SECONDS 0x01 +#define TIMEOUT_NO_KEEPALIVE 0x00 + +/* Command to Get the list of Paired devices */ +#define REPORT_TYPE_CMD_GET_PAIRED_DEVICES 0x81 + +/* Device Paired Notification */ +#define REPORT_TYPE_NOTIF_DEVICE_PAIRED 0x41 +#define SPFUNCTION_MORE_NOTIF_EXPECTED 0x01 +#define SPFUNCTION_DEVICE_LIST_EMPTY 0x02 +#define DEVICE_PAIRED_PARAM_SPFUNCTION 0x00 +#define DEVICE_PAIRED_PARAM_EQUAD_ID_LSB 0x01 +#define DEVICE_PAIRED_PARAM_EQUAD_ID_MSB 0x02 +#define DEVICE_PAIRED_RF_REPORT_TYPE 0x03 + +/* Device Un-Paired Notification */ +#define REPORT_TYPE_NOTIF_DEVICE_UNPAIRED 0x40 + + +/* Connection Status Notification */ +#define REPORT_TYPE_NOTIF_CONNECTION_STATUS 0x42 +#define CONNECTION_STATUS_PARAM_STATUS 0x00 +#define STATUS_LINKLOSS 0x01 + +/* Error Notification */ +#define REPORT_TYPE_NOTIF_ERROR 0x7F +#define NOTIF_ERROR_PARAM_ETYPE 0x00 +#define ETYPE_KEEPALIVE_TIMEOUT 0x01 + +/* supported DJ HID && RF report types */ +#define REPORT_TYPE_KEYBOARD 0x01 +#define REPORT_TYPE_MOUSE 0x02 +#define REPORT_TYPE_CONSUMER_CONTROL 0x03 +#define REPORT_TYPE_SYSTEM_CONTROL 0x04 +#define REPORT_TYPE_MEDIA_CENTER 0x08 +#define REPORT_TYPE_LEDS 0x0E + +/* RF Report types bitfield */ +#define STD_KEYBOARD 0x00000002 +#define STD_MOUSE 0x00000004 +#define MULTIMEDIA 0x00000008 +#define POWER_KEYS 0x00000010 +#define MEDIA_CENTER 0x00000100 +#define KBD_LEDS 0x00004000 + +struct dj_report { + u8 report_id; + u8 device_index; + u8 report_type; + u8 report_params[DJREPORT_SHORT_LENGTH - 3]; +}; + +struct dj_receiver_dev { + struct hid_device *hdev; + struct dj_device *paired_dj_devices[DJ_MAX_PAIRED_DEVICES]; + struct work_struct work; + struct kfifo notif_fifo; + spinlock_t lock; +}; + +struct dj_device { + struct hid_device *hdev; + struct dj_receiver_dev *dj_receiver_dev; + u32 reports_supported; + u8 device_index; +}; + +/** + * is_dj_device - know if the given dj_device is not the receiver. + * @dj_dev: the dj device to test + * + * This macro tests if a struct dj_device pointer is a device created + * by the bus enumarator. + */ +#define is_dj_device(dj_dev) \ + (&(dj_dev)->dj_receiver_dev->hdev->dev == (dj_dev)->hdev->dev.parent) + +#endif -- cgit v1.2.3-55-g7522 From a062cc5a76fa1d12f0821e56e3746cad2dc2fc65 Mon Sep 17 00:00:00 2001 From: Stephane Chatty Date: Sat, 17 Sep 2011 22:27:30 +0200 Subject: HID: hid-multitouch: add support for the IDEACOM 6650 chip The IDEACOM 6650 multitouch chip, present in various all-in-one computers, uses the serial version of the HID multitouch protocol. No existing class supports this. In principle, the new MT_CLS_SERIAL should work for other serial panels as well, perhaps including some eGalax panels. Signed-off-by: Stephane Chatty Signed-off-by: Jiri Kosina --- drivers/hid/hid-core.c | 1 + drivers/hid/hid-ids.h | 3 +++ drivers/hid/hid-multitouch.c | 31 +++++++++++++++++++++---------- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 11b3b39401ee..956f84968fa4 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1388,6 +1388,7 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_GYRATION, USB_DEVICE_ID_GYRATION_REMOTE_2) }, { HID_USB_DEVICE(USB_VENDOR_ID_GYRATION, USB_DEVICE_ID_GYRATION_REMOTE_3) }, { HID_USB_DEVICE(USB_VENDOR_ID_HANVON, USB_DEVICE_ID_HANVON_MULTITOUCH) }, + { HID_USB_DEVICE(USB_VENDOR_ID_IDEACOM, USB_DEVICE_ID_IDEACOM_IDC6650) }, { HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK, USB_DEVICE_ID_HOLTEK_ON_LINE_GRIP) }, { HID_USB_DEVICE(USB_VENDOR_ID_ILITEK, USB_DEVICE_ID_ILITEK_MULTITOUCH) }, { HID_USB_DEVICE(USB_VENDOR_ID_IRTOUCHSYSTEMS, USB_DEVICE_ID_IRTOUCH_INFRARED_USB) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 637e6e96a0d7..33b983413dbe 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -347,6 +347,9 @@ #define USB_DEVICE_ID_UGCI_FLYING 0x0020 #define USB_DEVICE_ID_UGCI_FIGHTING 0x0030 +#define USB_VENDOR_ID_IDEACOM 0x1cb6 +#define USB_DEVICE_ID_IDEACOM_IDC6650 0x6650 + #define USB_VENDOR_ID_ILITEK 0x222a #define USB_DEVICE_ID_ILITEK_MULTITOUCH 0x0001 diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index b03a0b0e9b63..f1c909f1b239 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -47,10 +47,11 @@ MODULE_LICENSE("GPL"); #define MT_QUIRK_SLOT_IS_CONTACTID (1 << 1) #define MT_QUIRK_CYPRESS (1 << 2) #define MT_QUIRK_SLOT_IS_CONTACTNUMBER (1 << 3) -#define MT_QUIRK_VALID_IS_INRANGE (1 << 4) -#define MT_QUIRK_VALID_IS_CONFIDENCE (1 << 5) -#define MT_QUIRK_EGALAX_XYZ_FIXUP (1 << 6) -#define MT_QUIRK_SLOT_IS_CONTACTID_MINUS_ONE (1 << 7) +#define MT_QUIRK_ALWAYS_VALID (1 << 4) +#define MT_QUIRK_VALID_IS_INRANGE (1 << 5) +#define MT_QUIRK_VALID_IS_CONFIDENCE (1 << 6) +#define MT_QUIRK_EGALAX_XYZ_FIXUP (1 << 7) +#define MT_QUIRK_SLOT_IS_CONTACTID_MINUS_ONE (1 << 8) struct mt_slot { __s32 x, y, p, w, h; @@ -86,11 +87,12 @@ struct mt_class { /* classes of device behavior */ #define MT_CLS_DEFAULT 0x0001 -#define MT_CLS_CONFIDENCE 0x0002 -#define MT_CLS_CONFIDENCE_MINUS_ONE 0x0003 -#define MT_CLS_DUAL_INRANGE_CONTACTID 0x0004 -#define MT_CLS_DUAL_INRANGE_CONTACTNUMBER 0x0005 -#define MT_CLS_DUAL_NSMU_CONTACTID 0x0006 +#define MT_CLS_SERIAL 0x0002 +#define MT_CLS_CONFIDENCE 0x0003 +#define MT_CLS_CONFIDENCE_MINUS_ONE 0x0004 +#define MT_CLS_DUAL_INRANGE_CONTACTID 0x0005 +#define MT_CLS_DUAL_INRANGE_CONTACTNUMBER 0x0006 +#define MT_CLS_DUAL_NSMU_CONTACTID 0x0007 /* vendor specific classes */ #define MT_CLS_3M 0x0101 @@ -134,6 +136,8 @@ static int find_slot_from_contactid(struct mt_device *td) struct mt_class mt_classes[] = { { .name = MT_CLS_DEFAULT, .quirks = MT_QUIRK_NOT_SEEN_MEANS_UP }, + { .name = MT_CLS_SERIAL, + .quirks = MT_QUIRK_ALWAYS_VALID}, { .name = MT_CLS_CONFIDENCE, .quirks = MT_QUIRK_VALID_IS_CONFIDENCE }, { .name = MT_CLS_CONFIDENCE_MINUS_ONE, @@ -445,7 +449,9 @@ static int mt_event(struct hid_device *hid, struct hid_field *field, if (hid->claimed & HID_CLAIMED_INPUT && td->slots) { switch (usage->hid) { case HID_DG_INRANGE: - if (quirks & MT_QUIRK_VALID_IS_INRANGE) + if (quirks & MT_QUIRK_ALWAYS_VALID) + td->curvalid = true; + else if (quirks & MT_QUIRK_VALID_IS_INRANGE) td->curvalid = value; break; case HID_DG_TIPSWITCH: @@ -672,6 +678,11 @@ static const struct hid_device_id mt_devices[] = { HID_USB_DEVICE(USB_VENDOR_ID_GOODTOUCH, USB_DEVICE_ID_GOODTOUCH_000f) }, + /* Ideacom panel */ + { .driver_data = MT_CLS_SERIAL, + HID_USB_DEVICE(USB_VENDOR_ID_IDEACOM, + USB_DEVICE_ID_IDEACOM_IDC6650) }, + /* Ilitek dual touch panel */ { .driver_data = MT_CLS_DEFAULT, HID_USB_DEVICE(USB_VENDOR_ID_ILITEK, -- cgit v1.2.3-55-g7522 From 844580ff63ef4eb19eec4cfd8cd6e0b62d81279f Mon Sep 17 00:00:00 2001 From: Nestor Lopez Casado Date: Tue, 20 Sep 2011 15:59:03 +0200 Subject: HID: hid-logitech-dj: fix off by one There is a bug where a device with index 6 would write out of bounds in the array of paired devices. This patch fixes that problem. Reported-by: Dan Carpenter Reviewed-by: Benjamin Tissoires Reviewed-by: Olivier Gay Signed-off-by: Nestor Lopez Casado Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-dj.c | 5 +---- drivers/hid/hid-logitech-dj.h | 5 ++++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index 5bb6f42a4278..38b12e45780c 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -179,9 +179,6 @@ static const u8 hid_reportid_size_map[NUMBER_OF_HID_REPORTS] = { #define LOGITECH_DJ_INTERFACE_NUMBER 0x02 -#define DJ_DEVICE_INDEX_MIN 1 -#define DJ_DEVICE_INDEX_MAX 6 - static struct hid_ll_driver logi_dj_ll_driver; static int logi_dj_output_hidraw_report(struct hid_device *hid, u8 * buf, @@ -823,7 +820,7 @@ static void logi_dj_remove(struct hid_device *hdev) * have finished and no more raw_event callbacks should arrive after * the remove callback was triggered so no locks are put around the * code below */ - for (i = 0; i < DJ_MAX_PAIRED_DEVICES; i++) { + for (i = 0; i < (DJ_MAX_PAIRED_DEVICES + DJ_DEVICE_INDEX_MIN); i++) { dj_dev = djrcv_dev->paired_dj_devices[i]; if (dj_dev != NULL) { hid_destroy_device(dj_dev->hdev); diff --git a/drivers/hid/hid-logitech-dj.h b/drivers/hid/hid-logitech-dj.h index 5982263acd73..fd28a5e0ca3b 100644 --- a/drivers/hid/hid-logitech-dj.h +++ b/drivers/hid/hid-logitech-dj.h @@ -27,6 +27,8 @@ #define DJ_MAX_PAIRED_DEVICES 6 #define DJ_MAX_NUMBER_NOTIFICATIONS 8 +#define DJ_DEVICE_INDEX_MIN 1 +#define DJ_DEVICE_INDEX_MAX 6 #define DJREPORT_SHORT_LENGTH 15 #define DJREPORT_LONG_LENGTH 32 @@ -94,7 +96,8 @@ struct dj_report { struct dj_receiver_dev { struct hid_device *hdev; - struct dj_device *paired_dj_devices[DJ_MAX_PAIRED_DEVICES]; + struct dj_device *paired_dj_devices[DJ_MAX_PAIRED_DEVICES + + DJ_DEVICE_INDEX_MIN]; struct work_struct work; struct kfifo notif_fifo; spinlock_t lock; -- cgit v1.2.3-55-g7522 From e00ddc9b8f978ffbc6c212f780a2bb83aafe2fbd Mon Sep 17 00:00:00 2001 From: Michal Malý Date: Sat, 10 Sep 2011 21:28:23 +0200 Subject: HID: "hid-logitech" driver with Logitech Driving Force GT There's been a small oversight when adding support for Logitech Driving Force GT. Entry in hid-core was missing so the generic driver instead of hid-logitech was being used. Signed-off-by: Michal Malý Signed-off-by: Jiri Kosina --- drivers/hid/hid-core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index ab4ae12e0a01..73e67872ff1d 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1417,6 +1417,7 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOMO_WHEEL) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DFP_WHEEL) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DFGT_WHEEL) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G25_WHEEL) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G27_WHEEL) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER) }, -- cgit v1.2.3-55-g7522 From b77c3920e90e96103e4f41442999402925fe5f73 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 21 Sep 2011 16:56:54 +0200 Subject: HID: add autodetection of multitouch devices As mentioned by http://www.microsoft.com/whdc/device/input/DigitizerDrvs_touch.mspx multitouch devices are those that have the input report HID_CONTACTID. This patch detects this and unloads the generic-usb driver. Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-core.c | 6 ++++++ drivers/hid/hid-input.c | 11 +++++++++++ include/linux/hid.h | 1 + 3 files changed, 18 insertions(+) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 956f84968fa4..f869ab35e6d6 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1212,6 +1212,12 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask) if ((connect_mask & HID_CONNECT_HIDINPUT) && !hidinput_connect(hdev, connect_mask & HID_CONNECT_HIDINPUT_FORCE)) hdev->claimed |= HID_CLAIMED_INPUT; + if (hdev->quirks & HID_QUIRK_MULTITOUCH) { + /* this device should be handled by hid-multitouch, skip it */ + hdev->quirks &= ~HID_QUIRK_MULTITOUCH; + return -ENODEV; + } + if ((connect_mask & HID_CONNECT_HIDDEV) && hdev->hiddev_connect && !hdev->hiddev_connect(hdev, connect_mask & HID_CONNECT_HIDDEV_FORCE)) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 6559e2e3364e..f333139d1a48 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -474,6 +474,10 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel map_key_clear(BTN_STYLUS2); break; + case 0x51: /* ContactID */ + device->quirks |= HID_QUIRK_MULTITOUCH; + goto unknown; + default: goto unknown; } break; @@ -978,6 +982,13 @@ int hidinput_connect(struct hid_device *hid, unsigned int force) } } + if (hid->quirks & HID_QUIRK_MULTITOUCH) { + /* generic hid does not know how to handle multitouch devices */ + if (hidinput) + goto out_cleanup; + goto out_unwind; + } + if (hidinput && input_register_device(hidinput->input)) goto out_cleanup; diff --git a/include/linux/hid.h b/include/linux/hid.h index 9cf8e7ae7450..6fb743d72bfe 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -312,6 +312,7 @@ struct hid_item { #define HID_QUIRK_BADPAD 0x00000020 #define HID_QUIRK_MULTI_INPUT 0x00000040 #define HID_QUIRK_HIDINPUT_FORCE 0x00000080 +#define HID_QUIRK_MULTITOUCH 0x00000100 #define HID_QUIRK_SKIP_OUTPUT_REPORTS 0x00010000 #define HID_QUIRK_FULLSPEED_INTERVAL 0x10000000 #define HID_QUIRK_NO_INIT_REPORTS 0x20000000 -- cgit v1.2.3-55-g7522 From 0db3bfc72adf0cb70f08dfe92e4040f64e25e205 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 21 Sep 2011 16:56:55 +0200 Subject: HID: multitouch: decide if hid-multitouch needs to handle mt devices Now that hid-generic ignores all win7 compatible multitouch devices, this patch allows hid-multitouch to catch them. The idea is to rely on the quirk HID_QUIRK_MULTITOUCH to drop the device if no ContactID is given. There is the need for a blacklist here as other devices may need a special driver (ntrig for instance). Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-multitouch.c | 47 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index f1c909f1b239..fa5d7a1ffa9e 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -291,6 +291,7 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, td->last_slot_field = usage->hid; td->last_field_index = field->index; td->last_mt_collection = usage->collection_index; + hdev->quirks &= ~HID_QUIRK_MULTITOUCH; return 1; case HID_DG_WIDTH: hid_map_usage(hi, usage, bit, max, @@ -529,12 +530,44 @@ static void mt_set_input_mode(struct hid_device *hdev) } } +/* a list of devices for which there is a specialized multitouch driver */ +static const struct hid_device_id mt_have_special_driver[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, 0x0001) }, + { HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, 0x0006) }, + { HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, + USB_DEVICE_ID_PIXART_IMAGING_INC_OPTICAL_TOUCH_SCREEN) }, + { HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, + USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH) }, + { } +}; + +static bool mt_match_one_id(struct hid_device *hdev, + const struct hid_device_id *id) +{ + return id->bus == hdev->bus && + (id->vendor == HID_ANY_ID || id->vendor == hdev->vendor) && + (id->product == HID_ANY_ID || id->product == hdev->product); +} + +static const struct hid_device_id *mt_match_id(struct hid_device *hdev, + const struct hid_device_id *id) +{ + for (; id->bus; id++) + if (mt_match_one_id(hdev, id)) + return id; + + return NULL; +} + static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) { int ret, i; struct mt_device *td; struct mt_class *mtclass = mt_classes; /* MT_CLS_DEFAULT */ + if (mt_match_id(hdev, mt_have_special_driver)) + return -ENODEV; + for (i = 0; mt_classes[i].name ; i++) { if (id->driver_data == mt_classes[i].name) { mtclass = &(mt_classes[i]); @@ -542,10 +575,6 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) } } - /* This allows the driver to correctly support devices - * that emit events over several HID messages. - */ - hdev->quirks |= HID_QUIRK_NO_INPUT_SYNC; td = kzalloc(sizeof(struct mt_device), GFP_KERNEL); if (!td) { @@ -561,10 +590,16 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) if (ret != 0) goto fail; + hdev->quirks |= HID_QUIRK_MULTITOUCH; ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (ret) goto fail; + /* This allows the driver to correctly support devices + * that emit events over several HID messages. + */ + hdev->quirks |= HID_QUIRK_NO_INPUT_SYNC; + td->slots = kzalloc(td->maxcontacts * sizeof(struct mt_slot), GFP_KERNEL); if (!td->slots) { @@ -758,6 +793,10 @@ static const struct hid_device_id mt_devices[] = { HID_USB_DEVICE(USB_VENDOR_ID_XAT, USB_DEVICE_ID_XAT_CSR) }, + /* Rest of the world */ + { .driver_data = MT_CLS_DEFAULT, + HID_USB_DEVICE(HID_ANY_ID, HID_ANY_ID) }, + { } }; MODULE_DEVICE_TABLE(hid, mt_devices); -- cgit v1.2.3-55-g7522