blob: 160c489190813573aa8a5c3bde10d7eda36d9139 [file] [log] [blame]
Jiri Slaby5f22a792008-05-16 11:49:19 +02001/*
2 * HID driver for some logitech "special" devices
3 *
4 * Copyright (c) 1999 Andreas Gal
5 * Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
6 * Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
7 * Copyright (c) 2006-2007 Jiri Kosina
Jiri Slaby5f22a792008-05-16 11:49:19 +02008 * Copyright (c) 2008 Jiri Slaby
Hendrik Iben2c6118e2010-10-04 15:39:49 +02009 * Copyright (c) 2010 Hendrik Iben
Jiri Slaby5f22a792008-05-16 11:49:19 +020010 */
11
12/*
13 * This program is free software; you can redistribute it and/or modify it
14 * under the terms of the GNU General Public License as published by the Free
15 * Software Foundation; either version 2 of the License, or (at your option)
16 * any later version.
17 */
18
19#include <linux/device.h>
20#include <linux/hid.h>
21#include <linux/module.h>
Simon Wood32c88cb2010-09-22 13:19:42 +020022#include <linux/random.h>
23#include <linux/sched.h>
Paul Sbarra54bfe3f2013-02-17 11:53:13 -060024#include <linux/usb.h>
Simon Wood32c88cb2010-09-22 13:19:42 +020025#include <linux/wait.h>
Jiri Slaby5f22a792008-05-16 11:49:19 +020026
Paul Sbarra54bfe3f2013-02-17 11:53:13 -060027#include "usbhid/usbhid.h"
Jiri Slaby5f22a792008-05-16 11:49:19 +020028#include "hid-ids.h"
Jiri Slaby606bd0a2008-07-04 23:06:45 +020029#include "hid-lg.h"
Jiri Slaby5f22a792008-05-16 11:49:19 +020030
31#define LG_RDESC 0x001
32#define LG_BAD_RELATIVE_KEYS 0x002
33#define LG_DUPLICATE_USAGES 0x004
Jiri Slaby5f22a792008-05-16 11:49:19 +020034#define LG_EXPANDED_KEYMAP 0x010
35#define LG_IGNORE_DOUBLED_WHEEL 0x020
36#define LG_WIRELESS 0x040
37#define LG_INVERT_HWHEEL 0x080
38#define LG_NOGET 0x100
Jiri Slaby606bd0a2008-07-04 23:06:45 +020039#define LG_FF 0x200
40#define LG_FF2 0x400
Jiri Kosina24985cf2009-11-13 10:45:53 +010041#define LG_RDESC_REL_ABS 0x800
Gary Stein74f292c2010-01-13 00:25:58 +010042#define LG_FF3 0x1000
Simon Wood32c88cb2010-09-22 13:19:42 +020043#define LG_FF4 0x2000
Jiri Slaby5f22a792008-05-16 11:49:19 +020044
Paul Sbarra54bfe3f2013-02-17 11:53:13 -060045/* Size of the original descriptors of the Driving Force (and Pro) wheels */
46#define DF_RDESC_ORIG_SIZE 130
Michael Bauerdc0a4f02011-06-02 15:40:14 -070047#define DFP_RDESC_ORIG_SIZE 97
Simon Wood270baef2013-02-19 20:25:10 -070048#define MOMO_RDESC_ORIG_SIZE 87
Michael Bauerdc0a4f02011-06-02 15:40:14 -070049
Paul Sbarra54bfe3f2013-02-17 11:53:13 -060050/* Fixed report descriptors for Logitech Driving Force (and Pro)
51 * wheel controllers
Michael Bauerdc0a4f02011-06-02 15:40:14 -070052 *
Paul Sbarra54bfe3f2013-02-17 11:53:13 -060053 * The original descriptors hide the separate throttle and brake axes in
Michael Bauerdc0a4f02011-06-02 15:40:14 -070054 * a custom vendor usage page, providing only a combined value as
55 * GenericDesktop.Y.
Paul Sbarra54bfe3f2013-02-17 11:53:13 -060056 * These descriptors remove the combined Y axis and instead report
Michael Bauerdc0a4f02011-06-02 15:40:14 -070057 * separate throttle (Y) and brake (RZ).
58 */
Paul Sbarra54bfe3f2013-02-17 11:53:13 -060059static __u8 df_rdesc_fixed[] = {
600x05, 0x01, /* Usage Page (Desktop), */
610x09, 0x04, /* Usage (Joystik), */
620xA1, 0x01, /* Collection (Application), */
630xA1, 0x02, /* Collection (Logical), */
640x95, 0x01, /* Report Count (1), */
650x75, 0x0A, /* Report Size (10), */
660x14, /* Logical Minimum (0), */
670x26, 0xFF, 0x03, /* Logical Maximum (1023), */
680x34, /* Physical Minimum (0), */
690x46, 0xFF, 0x03, /* Physical Maximum (1023), */
700x09, 0x30, /* Usage (X), */
710x81, 0x02, /* Input (Variable), */
720x95, 0x0C, /* Report Count (12), */
730x75, 0x01, /* Report Size (1), */
740x25, 0x01, /* Logical Maximum (1), */
750x45, 0x01, /* Physical Maximum (1), */
760x05, 0x09, /* Usage (Buttons), */
770x19, 0x01, /* Usage Minimum (1), */
780x29, 0x0c, /* Usage Maximum (12), */
790x81, 0x02, /* Input (Variable), */
800x95, 0x02, /* Report Count (2), */
810x06, 0x00, 0xFF, /* Usage Page (Vendor: 65280), */
820x09, 0x01, /* Usage (?: 1), */
830x81, 0x02, /* Input (Variable), */
840x05, 0x01, /* Usage Page (Desktop), */
Paul Sbarra54bfe3f2013-02-17 11:53:13 -0600850x26, 0xFF, 0x00, /* Logical Maximum (255), */
860x46, 0xFF, 0x00, /* Physical Maximum (255), */
870x95, 0x01, /* Report Count (1), */
880x75, 0x08, /* Report Size (8), */
890x81, 0x02, /* Input (Variable), */
900x25, 0x07, /* Logical Maximum (7), */
910x46, 0x3B, 0x01, /* Physical Maximum (315), */
920x75, 0x04, /* Report Size (4), */
930x65, 0x14, /* Unit (Degrees), */
940x09, 0x39, /* Usage (Hat Switch), */
950x81, 0x42, /* Input (Variable, Null State), */
960x75, 0x01, /* Report Size (1), */
970x95, 0x04, /* Report Count (4), */
980x65, 0x00, /* Unit (none), */
990x06, 0x00, 0xFF, /* Usage Page (Vendor: 65280), */
1000x09, 0x01, /* Usage (?: 1), */
1010x25, 0x01, /* Logical Maximum (1), */
1020x45, 0x01, /* Physical Maximum (1), */
1030x81, 0x02, /* Input (Variable), */
Paul Sbarra5a9b5712013-02-17 11:53:14 -06001040x05, 0x01, /* Usage Page (Desktop), */
1050x95, 0x01, /* Report Count (1), */
Paul Sbarra54bfe3f2013-02-17 11:53:13 -06001060x75, 0x08, /* Report Size (8), */
1070x26, 0xFF, 0x00, /* Logical Maximum (255), */
1080x46, 0xFF, 0x00, /* Physical Maximum (255), */
Paul Sbarra5a9b5712013-02-17 11:53:14 -06001090x09, 0x31, /* Usage (Y), */
1100x81, 0x02, /* Input (Variable), */
1110x09, 0x35, /* Usage (Rz), */
Paul Sbarra54bfe3f2013-02-17 11:53:13 -06001120x81, 0x02, /* Input (Variable), */
1130xC0, /* End Collection, */
1140xA1, 0x02, /* Collection (Logical), */
1150x26, 0xFF, 0x00, /* Logical Maximum (255), */
1160x46, 0xFF, 0x00, /* Physical Maximum (255), */
1170x95, 0x07, /* Report Count (7), */
1180x75, 0x08, /* Report Size (8), */
1190x09, 0x03, /* Usage (?: 3), */
1200x91, 0x02, /* Output (Variable), */
1210xC0, /* End Collection, */
1220xC0 /* End Collection */
123};
124
Michael Bauerdc0a4f02011-06-02 15:40:14 -0700125static __u8 dfp_rdesc_fixed[] = {
1260x05, 0x01, /* Usage Page (Desktop), */
1270x09, 0x04, /* Usage (Joystik), */
1280xA1, 0x01, /* Collection (Application), */
1290xA1, 0x02, /* Collection (Logical), */
1300x95, 0x01, /* Report Count (1), */
1310x75, 0x0E, /* Report Size (14), */
1320x14, /* Logical Minimum (0), */
1330x26, 0xFF, 0x3F, /* Logical Maximum (16383), */
1340x34, /* Physical Minimum (0), */
1350x46, 0xFF, 0x3F, /* Physical Maximum (16383), */
1360x09, 0x30, /* Usage (X), */
1370x81, 0x02, /* Input (Variable), */
1380x95, 0x0E, /* Report Count (14), */
1390x75, 0x01, /* Report Size (1), */
1400x25, 0x01, /* Logical Maximum (1), */
1410x45, 0x01, /* Physical Maximum (1), */
1420x05, 0x09, /* Usage Page (Button), */
1430x19, 0x01, /* Usage Minimum (01h), */
1440x29, 0x0E, /* Usage Maximum (0Eh), */
1450x81, 0x02, /* Input (Variable), */
1460x05, 0x01, /* Usage Page (Desktop), */
1470x95, 0x01, /* Report Count (1), */
1480x75, 0x04, /* Report Size (4), */
1490x25, 0x07, /* Logical Maximum (7), */
1500x46, 0x3B, 0x01, /* Physical Maximum (315), */
1510x65, 0x14, /* Unit (Degrees), */
1520x09, 0x39, /* Usage (Hat Switch), */
1530x81, 0x42, /* Input (Variable, Nullstate), */
1540x65, 0x00, /* Unit, */
1550x26, 0xFF, 0x00, /* Logical Maximum (255), */
1560x46, 0xFF, 0x00, /* Physical Maximum (255), */
1570x75, 0x08, /* Report Size (8), */
1580x81, 0x01, /* Input (Constant), */
1590x09, 0x31, /* Usage (Y), */
1600x81, 0x02, /* Input (Variable), */
1610x09, 0x35, /* Usage (Rz), */
1620x81, 0x02, /* Input (Variable), */
1630x81, 0x01, /* Input (Constant), */
1640xC0, /* End Collection, */
1650xA1, 0x02, /* Collection (Logical), */
1660x09, 0x02, /* Usage (02h), */
1670x95, 0x07, /* Report Count (7), */
1680x91, 0x02, /* Output (Variable), */
1690xC0, /* End Collection, */
1700xC0 /* End Collection */
171};
172
Simon Wood270baef2013-02-19 20:25:10 -0700173static __u8 momo_rdesc_fixed[] = {
1740x05, 0x01, /* Usage Page (Desktop), */
1750x09, 0x04, /* Usage (Joystik), */
1760xA1, 0x01, /* Collection (Application), */
1770xA1, 0x02, /* Collection (Logical), */
1780x95, 0x01, /* Report Count (1), */
1790x75, 0x0A, /* Report Size (10), */
1800x15, 0x00, /* Logical Minimum (0), */
1810x26, 0xFF, 0x03, /* Logical Maximum (1023), */
1820x35, 0x00, /* Physical Minimum (0), */
1830x46, 0xFF, 0x03, /* Physical Maximum (1023), */
1840x09, 0x30, /* Usage (X), */
1850x81, 0x02, /* Input (Variable), */
1860x95, 0x08, /* Report Count (8), */
1870x75, 0x01, /* Report Size (1), */
1880x25, 0x01, /* Logical Maximum (1), */
1890x45, 0x01, /* Physical Maximum (1), */
1900x05, 0x09, /* Usage Page (Button), */
1910x19, 0x01, /* Usage Minimum (01h), */
1920x29, 0x08, /* Usage Maximum (08h), */
1930x81, 0x02, /* Input (Variable), */
1940x06, 0x00, 0xFF, /* Usage Page (FF00h), */
1950x75, 0x0E, /* Report Size (14), */
1960x95, 0x01, /* Report Count (1), */
1970x26, 0xFF, 0x00, /* Logical Maximum (255), */
1980x46, 0xFF, 0x00, /* Physical Maximum (255), */
1990x09, 0x00, /* Usage (00h), */
2000x81, 0x02, /* Input (Variable), */
2010x05, 0x01, /* Usage Page (Desktop), */
2020x75, 0x08, /* Report Size (8), */
2030x09, 0x31, /* Usage (Y), */
2040x81, 0x02, /* Input (Variable), */
2050x09, 0x32, /* Usage (Z), */
2060x81, 0x02, /* Input (Variable), */
2070x06, 0x00, 0xFF, /* Usage Page (FF00h), */
2080x09, 0x01, /* Usage (01h), */
2090x81, 0x02, /* Input (Variable), */
2100xC0, /* End Collection, */
2110xA1, 0x02, /* Collection (Logical), */
2120x09, 0x02, /* Usage (02h), */
2130x95, 0x07, /* Report Count (7), */
2140x91, 0x02, /* Output (Variable), */
2150xC0, /* End Collection, */
2160xC0 /* End Collection */
217};
218
Jiri Slaby5f22a792008-05-16 11:49:19 +0200219/*
220 * Certain Logitech keyboards send in report #3 keys which are far
221 * above the logical maximum described in descriptor. This extends
222 * the original value of 0x28c of logical maximum to 0x104d
223 */
Nikolai Kondrashov73e40082010-08-06 23:03:06 +0400224static __u8 *lg_report_fixup(struct hid_device *hdev, __u8 *rdesc,
225 unsigned int *rsize)
Jiri Slaby5f22a792008-05-16 11:49:19 +0200226{
Axel Lin25751552012-09-13 14:08:32 +0800227 struct lg_drv_data *drv_data = hid_get_drvdata(hdev);
Paul Sbarra54bfe3f2013-02-17 11:53:13 -0600228 struct usb_device_descriptor *udesc;
229 __u16 bcdDevice, rev_maj, rev_min;
Jiri Slaby5f22a792008-05-16 11:49:19 +0200230
Michal Malý8577dbf2012-03-31 11:17:25 +0200231 if ((drv_data->quirks & LG_RDESC) && *rsize >= 90 && rdesc[83] == 0x26 &&
Jiri Slaby5f22a792008-05-16 11:49:19 +0200232 rdesc[84] == 0x8c && rdesc[85] == 0x02) {
Joe Perches4291ee32010-12-09 19:29:03 -0800233 hid_info(hdev,
234 "fixing up Logitech keyboard report descriptor\n");
Jiri Slaby5f22a792008-05-16 11:49:19 +0200235 rdesc[84] = rdesc[89] = 0x4d;
236 rdesc[85] = rdesc[90] = 0x10;
237 }
Michal Malý8577dbf2012-03-31 11:17:25 +0200238 if ((drv_data->quirks & LG_RDESC_REL_ABS) && *rsize >= 50 &&
Jiri Kosina24985cf2009-11-13 10:45:53 +0100239 rdesc[32] == 0x81 && rdesc[33] == 0x06 &&
240 rdesc[49] == 0x81 && rdesc[50] == 0x06) {
Joe Perches4291ee32010-12-09 19:29:03 -0800241 hid_info(hdev,
242 "fixing up rel/abs in Logitech report descriptor\n");
Jiri Kosina24985cf2009-11-13 10:45:53 +0100243 rdesc[33] = rdesc[50] = 0x02;
244 }
Michal Malý8577dbf2012-03-31 11:17:25 +0200245 if ((drv_data->quirks & LG_FF4) && *rsize >= 101 &&
Simon Wood32c88cb2010-09-22 13:19:42 +0200246 rdesc[41] == 0x95 && rdesc[42] == 0x0B &&
247 rdesc[47] == 0x05 && rdesc[48] == 0x09) {
Joe Perches4291ee32010-12-09 19:29:03 -0800248 hid_info(hdev, "fixing up Logitech Speed Force Wireless button descriptor\n");
Simon Wood32c88cb2010-09-22 13:19:42 +0200249 rdesc[41] = 0x05;
250 rdesc[42] = 0x09;
251 rdesc[47] = 0x95;
252 rdesc[48] = 0x0B;
253 }
Michael Bauerdc0a4f02011-06-02 15:40:14 -0700254
255 switch (hdev->product) {
Paul Sbarra54bfe3f2013-02-17 11:53:13 -0600256
257 /* Several wheels report as this id when operating in emulation mode. */
258 case USB_DEVICE_ID_LOGITECH_WHEEL:
259 udesc = &(hid_to_usb_dev(hdev)->descriptor);
260 if (!udesc) {
261 hid_err(hdev, "NULL USB device descriptor\n");
262 break;
263 }
264 bcdDevice = le16_to_cpu(udesc->bcdDevice);
265 rev_maj = bcdDevice >> 8;
266 rev_min = bcdDevice & 0xff;
267
268 /* Update the report descriptor for only the Driving Force wheel */
269 if (rev_maj == 1 && rev_min == 2 &&
270 *rsize == DF_RDESC_ORIG_SIZE) {
271 hid_info(hdev,
272 "fixing up Logitech Driving Force report descriptor\n");
273 rdesc = df_rdesc_fixed;
274 *rsize = sizeof(df_rdesc_fixed);
275 }
276 break;
277
Simon Wood270baef2013-02-19 20:25:10 -0700278 case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL:
279 if (*rsize == MOMO_RDESC_ORIG_SIZE) {
280 hid_info(hdev,
281 "fixing up Logitech Momo Force (Red) report descriptor\n");
282 rdesc = momo_rdesc_fixed;
283 *rsize = sizeof(momo_rdesc_fixed);
284 }
285 break;
286
Michael Bauerdc0a4f02011-06-02 15:40:14 -0700287 case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
288 if (*rsize == DFP_RDESC_ORIG_SIZE) {
289 hid_info(hdev,
290 "fixing up Logitech Driving Force Pro report descriptor\n");
291 rdesc = dfp_rdesc_fixed;
292 *rsize = sizeof(dfp_rdesc_fixed);
293 }
294 break;
295 }
296
Nikolai Kondrashov73e40082010-08-06 23:03:06 +0400297 return rdesc;
Jiri Slaby5f22a792008-05-16 11:49:19 +0200298}
299
300#define lg_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, \
301 EV_KEY, (c))
302
303static int lg_ultrax_remote_mapping(struct hid_input *hi,
304 struct hid_usage *usage, unsigned long **bit, int *max)
305{
306 if ((usage->hid & HID_USAGE_PAGE) != HID_UP_LOGIVENDOR)
307 return 0;
308
309 set_bit(EV_REP, hi->input->evbit);
310 switch (usage->hid & HID_USAGE) {
311 /* Reported on Logitech Ultra X Media Remote */
312 case 0x004: lg_map_key_clear(KEY_AGAIN); break;
313 case 0x00d: lg_map_key_clear(KEY_HOME); break;
314 case 0x024: lg_map_key_clear(KEY_SHUFFLE); break;
315 case 0x025: lg_map_key_clear(KEY_TV); break;
316 case 0x026: lg_map_key_clear(KEY_MENU); break;
317 case 0x031: lg_map_key_clear(KEY_AUDIO); break;
318 case 0x032: lg_map_key_clear(KEY_TEXT); break;
319 case 0x033: lg_map_key_clear(KEY_LAST); break;
320 case 0x047: lg_map_key_clear(KEY_MP3); break;
321 case 0x048: lg_map_key_clear(KEY_DVD); break;
322 case 0x049: lg_map_key_clear(KEY_MEDIA); break;
323 case 0x04a: lg_map_key_clear(KEY_VIDEO); break;
324 case 0x04b: lg_map_key_clear(KEY_ANGLE); break;
325 case 0x04c: lg_map_key_clear(KEY_LANGUAGE); break;
326 case 0x04d: lg_map_key_clear(KEY_SUBTITLE); break;
327 case 0x051: lg_map_key_clear(KEY_RED); break;
328 case 0x052: lg_map_key_clear(KEY_CLOSE); break;
329
330 default:
331 return 0;
332 }
333 return 1;
334}
335
Jiri Kosina66d61be2009-11-24 18:22:20 +0100336static int lg_dinovo_mapping(struct hid_input *hi, struct hid_usage *usage,
337 unsigned long **bit, int *max)
338{
339 if ((usage->hid & HID_USAGE_PAGE) != HID_UP_LOGIVENDOR)
340 return 0;
341
342 switch (usage->hid & HID_USAGE) {
343
344 case 0x00d: lg_map_key_clear(KEY_MEDIA); break;
345 default:
346 return 0;
347
348 }
349 return 1;
350}
351
Jiri Slaby5f22a792008-05-16 11:49:19 +0200352static int lg_wireless_mapping(struct hid_input *hi, struct hid_usage *usage,
353 unsigned long **bit, int *max)
354{
355 if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER)
356 return 0;
357
358 switch (usage->hid & HID_USAGE) {
359 case 0x1001: lg_map_key_clear(KEY_MESSENGER); break;
360 case 0x1003: lg_map_key_clear(KEY_SOUND); break;
361 case 0x1004: lg_map_key_clear(KEY_VIDEO); break;
362 case 0x1005: lg_map_key_clear(KEY_AUDIO); break;
363 case 0x100a: lg_map_key_clear(KEY_DOCUMENTS); break;
Lorenzo Castelli183922122010-04-16 19:00:31 +0200364 /* The following two entries are Playlist 1 and 2 on the MX3200 */
365 case 0x100f: lg_map_key_clear(KEY_FN_1); break;
366 case 0x1010: lg_map_key_clear(KEY_FN_2); break;
Jiri Slaby5f22a792008-05-16 11:49:19 +0200367 case 0x1011: lg_map_key_clear(KEY_PREVIOUSSONG); break;
368 case 0x1012: lg_map_key_clear(KEY_NEXTSONG); break;
369 case 0x1013: lg_map_key_clear(KEY_CAMERA); break;
370 case 0x1014: lg_map_key_clear(KEY_MESSENGER); break;
371 case 0x1015: lg_map_key_clear(KEY_RECORD); break;
372 case 0x1016: lg_map_key_clear(KEY_PLAYER); break;
373 case 0x1017: lg_map_key_clear(KEY_EJECTCD); break;
374 case 0x1018: lg_map_key_clear(KEY_MEDIA); break;
375 case 0x1019: lg_map_key_clear(KEY_PROG1); break;
376 case 0x101a: lg_map_key_clear(KEY_PROG2); break;
377 case 0x101b: lg_map_key_clear(KEY_PROG3); break;
Lorenzo Castelli183922122010-04-16 19:00:31 +0200378 case 0x101c: lg_map_key_clear(KEY_CYCLEWINDOWS); break;
Jiri Slaby5f22a792008-05-16 11:49:19 +0200379 case 0x101f: lg_map_key_clear(KEY_ZOOMIN); break;
380 case 0x1020: lg_map_key_clear(KEY_ZOOMOUT); break;
381 case 0x1021: lg_map_key_clear(KEY_ZOOMRESET); break;
382 case 0x1023: lg_map_key_clear(KEY_CLOSE); break;
383 case 0x1027: lg_map_key_clear(KEY_MENU); break;
384 /* this one is marked as 'Rotate' */
385 case 0x1028: lg_map_key_clear(KEY_ANGLE); break;
386 case 0x1029: lg_map_key_clear(KEY_SHUFFLE); break;
387 case 0x102a: lg_map_key_clear(KEY_BACK); break;
388 case 0x102b: lg_map_key_clear(KEY_CYCLEWINDOWS); break;
Lorenzo Castelli183922122010-04-16 19:00:31 +0200389 case 0x102d: lg_map_key_clear(KEY_WWW); break;
390 /* The following two are 'Start/answer call' and 'End/reject call'
391 on the MX3200 */
392 case 0x1031: lg_map_key_clear(KEY_OK); break;
393 case 0x1032: lg_map_key_clear(KEY_CANCEL); break;
Jiri Slaby5f22a792008-05-16 11:49:19 +0200394 case 0x1041: lg_map_key_clear(KEY_BATTERY); break;
395 case 0x1042: lg_map_key_clear(KEY_WORDPROCESSOR); break;
396 case 0x1043: lg_map_key_clear(KEY_SPREADSHEET); break;
397 case 0x1044: lg_map_key_clear(KEY_PRESENTATION); break;
398 case 0x1045: lg_map_key_clear(KEY_UNDO); break;
399 case 0x1046: lg_map_key_clear(KEY_REDO); break;
400 case 0x1047: lg_map_key_clear(KEY_PRINT); break;
401 case 0x1048: lg_map_key_clear(KEY_SAVE); break;
402 case 0x1049: lg_map_key_clear(KEY_PROG1); break;
403 case 0x104a: lg_map_key_clear(KEY_PROG2); break;
404 case 0x104b: lg_map_key_clear(KEY_PROG3); break;
405 case 0x104c: lg_map_key_clear(KEY_PROG4); break;
406
407 default:
408 return 0;
409 }
410 return 1;
411}
412
413static int lg_input_mapping(struct hid_device *hdev, struct hid_input *hi,
414 struct hid_field *field, struct hid_usage *usage,
415 unsigned long **bit, int *max)
416{
417 /* extended mapping for certain Logitech hardware (Logitech cordless
418 desktop LX500) */
419 static const u8 e_keymap[] = {
420 0,216, 0,213,175,156, 0, 0, 0, 0,
421 144, 0, 0, 0, 0, 0, 0, 0, 0,212,
422 174,167,152,161,112, 0, 0, 0,154, 0,
423 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
424 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
425 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
426 0, 0, 0, 0, 0,183,184,185,186,187,
427 188,189,190,191,192,193,194, 0, 0, 0
428 };
Axel Lin25751552012-09-13 14:08:32 +0800429 struct lg_drv_data *drv_data = hid_get_drvdata(hdev);
Jiri Slaby5f22a792008-05-16 11:49:19 +0200430 unsigned int hid = usage->hid;
431
432 if (hdev->product == USB_DEVICE_ID_LOGITECH_RECEIVER &&
433 lg_ultrax_remote_mapping(hi, usage, bit, max))
434 return 1;
435
Jiri Kosina66d61be2009-11-24 18:22:20 +0100436 if (hdev->product == USB_DEVICE_ID_DINOVO_MINI &&
437 lg_dinovo_mapping(hi, usage, bit, max))
438 return 1;
439
Michal Malý8577dbf2012-03-31 11:17:25 +0200440 if ((drv_data->quirks & LG_WIRELESS) && lg_wireless_mapping(hi, usage, bit, max))
Jiri Slaby5f22a792008-05-16 11:49:19 +0200441 return 1;
442
443 if ((hid & HID_USAGE_PAGE) != HID_UP_BUTTON)
444 return 0;
445
446 hid &= HID_USAGE;
447
448 /* Special handling for Logitech Cordless Desktop */
449 if (field->application == HID_GD_MOUSE) {
Michal Malý8577dbf2012-03-31 11:17:25 +0200450 if ((drv_data->quirks & LG_IGNORE_DOUBLED_WHEEL) &&
Jiri Slaby5f22a792008-05-16 11:49:19 +0200451 (hid == 7 || hid == 8))
452 return -1;
453 } else {
Michal Malý8577dbf2012-03-31 11:17:25 +0200454 if ((drv_data->quirks & LG_EXPANDED_KEYMAP) &&
Jiri Slaby5f22a792008-05-16 11:49:19 +0200455 hid < ARRAY_SIZE(e_keymap) &&
456 e_keymap[hid] != 0) {
457 hid_map_usage(hi, usage, bit, max, EV_KEY,
458 e_keymap[hid]);
459 return 1;
460 }
461 }
462
463 return 0;
464}
465
466static int lg_input_mapped(struct hid_device *hdev, struct hid_input *hi,
467 struct hid_field *field, struct hid_usage *usage,
468 unsigned long **bit, int *max)
469{
Axel Lin25751552012-09-13 14:08:32 +0800470 struct lg_drv_data *drv_data = hid_get_drvdata(hdev);
Jiri Slaby5f22a792008-05-16 11:49:19 +0200471
Michal Malý8577dbf2012-03-31 11:17:25 +0200472 if ((drv_data->quirks & LG_BAD_RELATIVE_KEYS) && usage->type == EV_KEY &&
Jiri Slaby5f22a792008-05-16 11:49:19 +0200473 (field->flags & HID_MAIN_ITEM_RELATIVE))
474 field->flags &= ~HID_MAIN_ITEM_RELATIVE;
475
Michal Malý8577dbf2012-03-31 11:17:25 +0200476 if ((drv_data->quirks & LG_DUPLICATE_USAGES) && (usage->type == EV_KEY ||
Jiri Slaby5f22a792008-05-16 11:49:19 +0200477 usage->type == EV_REL || usage->type == EV_ABS))
478 clear_bit(usage->code, *bit);
479
480 return 0;
481}
482
483static int lg_event(struct hid_device *hdev, struct hid_field *field,
484 struct hid_usage *usage, __s32 value)
485{
Axel Lin25751552012-09-13 14:08:32 +0800486 struct lg_drv_data *drv_data = hid_get_drvdata(hdev);
Jiri Slaby5f22a792008-05-16 11:49:19 +0200487
Michal Malý8577dbf2012-03-31 11:17:25 +0200488 if ((drv_data->quirks & LG_INVERT_HWHEEL) && usage->code == REL_HWHEEL) {
Jiri Slaby5f22a792008-05-16 11:49:19 +0200489 input_event(field->hidinput->input, usage->type, usage->code,
490 -value);
491 return 1;
492 }
Michal Malý2b24a962012-09-23 22:41:08 +0200493 if (drv_data->quirks & LG_FF4) {
494 return lg4ff_adjust_input_event(hdev, field, usage, value, drv_data);
495 }
Jiri Slaby5f22a792008-05-16 11:49:19 +0200496
497 return 0;
498}
499
500static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id)
501{
Jiri Slaby606bd0a2008-07-04 23:06:45 +0200502 unsigned int connect_mask = HID_CONNECT_DEFAULT;
Michal Malý8577dbf2012-03-31 11:17:25 +0200503 struct lg_drv_data *drv_data;
Jiri Slaby5f22a792008-05-16 11:49:19 +0200504 int ret;
505
Michal Malý8577dbf2012-03-31 11:17:25 +0200506 drv_data = kzalloc(sizeof(struct lg_drv_data), GFP_KERNEL);
507 if (!drv_data) {
508 hid_err(hdev, "Insufficient memory, cannot allocate driver data\n");
509 return -ENOMEM;
510 }
511 drv_data->quirks = id->driver_data;
Michal Malýa80fe5d2012-09-24 01:13:17 +0200512
Michal Malý8577dbf2012-03-31 11:17:25 +0200513 hid_set_drvdata(hdev, (void *)drv_data);
Jiri Slaby5f22a792008-05-16 11:49:19 +0200514
Michal Malý8577dbf2012-03-31 11:17:25 +0200515 if (drv_data->quirks & LG_NOGET)
Jiri Slaby5f22a792008-05-16 11:49:19 +0200516 hdev->quirks |= HID_QUIRK_NOGET;
517
518 ret = hid_parse(hdev);
519 if (ret) {
Joe Perches4291ee32010-12-09 19:29:03 -0800520 hid_err(hdev, "parse failed\n");
Jiri Slaby5f22a792008-05-16 11:49:19 +0200521 goto err_free;
522 }
523
Michal Malý8577dbf2012-03-31 11:17:25 +0200524 if (drv_data->quirks & (LG_FF | LG_FF2 | LG_FF3 | LG_FF4))
Jiri Slaby606bd0a2008-07-04 23:06:45 +0200525 connect_mask &= ~HID_CONNECT_FF;
526
527 ret = hid_hw_start(hdev, connect_mask);
Jiri Slaby5f22a792008-05-16 11:49:19 +0200528 if (ret) {
Joe Perches4291ee32010-12-09 19:29:03 -0800529 hid_err(hdev, "hw start failed\n");
Jiri Slaby5f22a792008-05-16 11:49:19 +0200530 goto err_free;
531 }
532
Michal Malý7362cd22011-08-04 16:16:09 +0200533 /* Setup wireless link with Logitech Wii wheel */
Michal Malýa80fe5d2012-09-24 01:13:17 +0200534 if (hdev->product == USB_DEVICE_ID_LOGITECH_WII_WHEEL) {
Simon Wood32c88cb2010-09-22 13:19:42 +0200535 unsigned char buf[] = { 0x00, 0xAF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
536
537 ret = hdev->hid_output_raw_report(hdev, buf, sizeof(buf), HID_FEATURE_REPORT);
538
539 if (ret >= 0) {
540 /* insert a little delay of 10 jiffies ~ 40ms */
541 wait_queue_head_t wait;
542 init_waitqueue_head (&wait);
543 wait_event_interruptible_timeout(wait, 0, 10);
544
545 /* Select random Address */
546 buf[1] = 0xB2;
547 get_random_bytes(&buf[2], 2);
548
549 ret = hdev->hid_output_raw_report(hdev, buf, sizeof(buf), HID_FEATURE_REPORT);
550 }
551 }
552
Michal Malý8577dbf2012-03-31 11:17:25 +0200553 if (drv_data->quirks & LG_FF)
Jiri Slaby606bd0a2008-07-04 23:06:45 +0200554 lgff_init(hdev);
Michal Malý8577dbf2012-03-31 11:17:25 +0200555 if (drv_data->quirks & LG_FF2)
Jiri Slaby606bd0a2008-07-04 23:06:45 +0200556 lg2ff_init(hdev);
Michal Malý8577dbf2012-03-31 11:17:25 +0200557 if (drv_data->quirks & LG_FF3)
Gary Stein74f292c2010-01-13 00:25:58 +0100558 lg3ff_init(hdev);
Michal Malý8577dbf2012-03-31 11:17:25 +0200559 if (drv_data->quirks & LG_FF4)
Simon Wood32c88cb2010-09-22 13:19:42 +0200560 lg4ff_init(hdev);
Jiri Slaby606bd0a2008-07-04 23:06:45 +0200561
Jiri Slaby5f22a792008-05-16 11:49:19 +0200562 return 0;
563err_free:
Michal Malý8577dbf2012-03-31 11:17:25 +0200564 kfree(drv_data);
Jiri Slaby5f22a792008-05-16 11:49:19 +0200565 return ret;
566}
567
Michal Malý30bb75d2011-08-04 16:20:40 +0200568static void lg_remove(struct hid_device *hdev)
569{
Axel Lin25751552012-09-13 14:08:32 +0800570 struct lg_drv_data *drv_data = hid_get_drvdata(hdev);
Michal Malý8577dbf2012-03-31 11:17:25 +0200571 if (drv_data->quirks & LG_FF4)
Michal Malý30bb75d2011-08-04 16:20:40 +0200572 lg4ff_deinit(hdev);
573
574 hid_hw_stop(hdev);
Michal Malý8577dbf2012-03-31 11:17:25 +0200575 kfree(drv_data);
Michal Malý30bb75d2011-08-04 16:20:40 +0200576}
577
Jiri Slaby5f22a792008-05-16 11:49:19 +0200578static const struct hid_device_id lg_devices[] = {
579 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER),
580 .driver_data = LG_RDESC | LG_WIRELESS },
581 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER),
582 .driver_data = LG_RDESC | LG_WIRELESS },
583 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER_2),
584 .driver_data = LG_RDESC | LG_WIRELESS },
585
586 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RECEIVER),
587 .driver_data = LG_BAD_RELATIVE_KEYS },
588
589 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_DESKTOP),
590 .driver_data = LG_DUPLICATE_USAGES },
591 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_EDGE),
592 .driver_data = LG_DUPLICATE_USAGES },
593 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_MINI),
594 .driver_data = LG_DUPLICATE_USAGES },
595
Jiri Slaby5f22a792008-05-16 11:49:19 +0200596 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_ELITE_KBD),
597 .driver_data = LG_IGNORE_DOUBLED_WHEEL | LG_EXPANDED_KEYMAP },
598 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_CORDLESS_DESKTOP_LX500),
599 .driver_data = LG_IGNORE_DOUBLED_WHEEL | LG_EXPANDED_KEYMAP },
600
Jiri Slaby5f22a792008-05-16 11:49:19 +0200601 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_EXTREME_3D),
602 .driver_data = LG_NOGET },
603 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WHEEL),
Michal Malý7362cd22011-08-04 16:16:09 +0200604 .driver_data = LG_NOGET | LG_FF4 },
Jiri Slaby606bd0a2008-07-04 23:06:45 +0200605
Hendrik Iben2c6118e2010-10-04 15:39:49 +0200606 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD_CORD),
607 .driver_data = LG_FF2 },
Jiri Slaby606bd0a2008-07-04 23:06:45 +0200608 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD),
609 .driver_data = LG_FF },
610 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2_2),
611 .driver_data = LG_FF },
612 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_F3D),
613 .driver_data = LG_FF },
614 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_FORCE3D_PRO),
615 .driver_data = LG_FF },
616 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOMO_WHEEL),
Simon Wood270baef2013-02-19 20:25:10 -0700617 .driver_data = LG_NOGET | LG_FF4 },
Jiri Slaby606bd0a2008-07-04 23:06:45 +0200618 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2),
Michal Malý7362cd22011-08-04 16:16:09 +0200619 .driver_data = LG_FF4 },
Christophe Borivant243b7062009-04-17 11:39:39 +0200620 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G25_WHEEL),
Michal Malý7362cd22011-08-04 16:16:09 +0200621 .driver_data = LG_FF4 },
622 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DFGT_WHEEL),
623 .driver_data = LG_FF4 },
Peter Gundermannfdc68072011-05-03 10:15:03 +0200624 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G27_WHEEL),
Michal Malý7362cd22011-08-04 16:16:09 +0200625 .driver_data = LG_FF4 },
Jiri Kosina5623a242011-03-17 00:43:23 +0100626 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DFP_WHEEL),
Michal Malý7362cd22011-08-04 16:16:09 +0200627 .driver_data = LG_NOGET | LG_FF4 },
Simon Wood32c88cb2010-09-22 13:19:42 +0200628 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WII_WHEEL),
629 .driver_data = LG_FF4 },
Michal Malýa80fe5d2012-09-24 01:13:17 +0200630 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_FFG),
Jiri Kosinafd30ea82009-06-23 12:11:31 +0200631 .driver_data = LG_FF },
Jiri Slaby606bd0a2008-07-04 23:06:45 +0200632 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2),
633 .driver_data = LG_FF2 },
Gary Stein74f292c2010-01-13 00:25:58 +0100634 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_FLIGHT_SYSTEM_G940),
635 .driver_data = LG_FF3 },
Jiri Kosina24985cf2009-11-13 10:45:53 +0100636 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACENAVIGATOR),
637 .driver_data = LG_RDESC_REL_ABS },
638 { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACETRAVELLER),
639 .driver_data = LG_RDESC_REL_ABS },
Jiri Slaby5f22a792008-05-16 11:49:19 +0200640 { }
641};
Jiri Kosina24985cf2009-11-13 10:45:53 +0100642
Jiri Slaby5f22a792008-05-16 11:49:19 +0200643MODULE_DEVICE_TABLE(hid, lg_devices);
644
645static struct hid_driver lg_driver = {
646 .name = "logitech",
647 .id_table = lg_devices,
648 .report_fixup = lg_report_fixup,
649 .input_mapping = lg_input_mapping,
650 .input_mapped = lg_input_mapped,
651 .event = lg_event,
652 .probe = lg_probe,
Michal Malý30bb75d2011-08-04 16:20:40 +0200653 .remove = lg_remove,
Jiri Slaby5f22a792008-05-16 11:49:19 +0200654};
655
Peter Huewea24f4232009-07-02 19:08:38 +0200656static int __init lg_init(void)
Jiri Slaby5f22a792008-05-16 11:49:19 +0200657{
658 return hid_register_driver(&lg_driver);
659}
660
Peter Huewea24f4232009-07-02 19:08:38 +0200661static void __exit lg_exit(void)
Jiri Slaby5f22a792008-05-16 11:49:19 +0200662{
663 hid_unregister_driver(&lg_driver);
664}
665
666module_init(lg_init);
667module_exit(lg_exit);
668MODULE_LICENSE("GPL");