12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2980a3da6SJiri Slaby /*
3980a3da6SJiri Slaby * HID driver for some samsung "special" devices
4980a3da6SJiri Slaby *
5980a3da6SJiri Slaby * Copyright (c) 1999 Andreas Gal
6980a3da6SJiri Slaby * Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
7980a3da6SJiri Slaby * Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
8980a3da6SJiri Slaby * Copyright (c) 2006-2007 Jiri Kosina
9980a3da6SJiri Slaby * Copyright (c) 2008 Jiri Slaby
10b355850bSDon Prince * Copyright (c) 2010 Don Prince <dhprince.devel@yahoo.co.uk>
11b355850bSDon Prince *
12b355850bSDon Prince * This driver supports several HID devices:
13b355850bSDon Prince *
14b355850bSDon Prince * [0419:0001] Samsung IrDA remote controller (reports as Cypress USB Mouse).
15b355850bSDon Prince * various hid report fixups for different variants.
16b355850bSDon Prince *
17b355850bSDon Prince * [0419:0600] Creative Desktop Wireless 6000 keyboard/mouse combo
18b355850bSDon Prince * several key mappings used from the consumer usage page
19b355850bSDon Prince * deviate from the USB HUT 1.12 standard.
20980a3da6SJiri Slaby */
21980a3da6SJiri Slaby
22980a3da6SJiri Slaby /*
23980a3da6SJiri Slaby */
24980a3da6SJiri Slaby
25980a3da6SJiri Slaby #include <linux/device.h>
26b355850bSDon Prince #include <linux/usb.h>
27980a3da6SJiri Slaby #include <linux/hid.h>
28980a3da6SJiri Slaby #include <linux/module.h>
29980a3da6SJiri Slaby
30980a3da6SJiri Slaby #include "hid-ids.h"
31980a3da6SJiri Slaby
32980a3da6SJiri Slaby /*
330810b511SRobert Schedel * There are several variants for 0419:0001:
340810b511SRobert Schedel *
350810b511SRobert Schedel * 1. 184 byte report descriptor
36980a3da6SJiri Slaby * Vendor specific report #4 has a size of 48 bit,
37980a3da6SJiri Slaby * and therefore is not accepted when inspecting the descriptors.
38980a3da6SJiri Slaby * As a workaround we reinterpret the report as:
39980a3da6SJiri Slaby * Variable type, count 6, size 8 bit, log. maximum 255
40980a3da6SJiri Slaby * The burden to reconstruct the data is moved into user space.
410810b511SRobert Schedel *
420810b511SRobert Schedel * 2. 203 byte report descriptor
430810b511SRobert Schedel * Report #4 has an array field with logical range 0..18 instead of 1..15.
440810b511SRobert Schedel *
450810b511SRobert Schedel * 3. 135 byte report descriptor
460810b511SRobert Schedel * Report #4 has an array field with logical range 0..17 instead of 1..14.
473975bc56SRobert Schedel *
483975bc56SRobert Schedel * 4. 171 byte report descriptor
493975bc56SRobert Schedel * Report #3 has an array field with logical range 0..1 instead of 1..3.
50980a3da6SJiri Slaby */
samsung_irda_dev_trace(struct hid_device * hdev,unsigned int rsize)51b355850bSDon Prince static inline void samsung_irda_dev_trace(struct hid_device *hdev,
523975bc56SRobert Schedel unsigned int rsize)
533975bc56SRobert Schedel {
544291ee30SJoe Perches hid_info(hdev, "fixing up Samsung IrDA %d byte report descriptor\n",
554291ee30SJoe Perches rsize);
563975bc56SRobert Schedel }
573975bc56SRobert Schedel
samsung_irda_report_fixup(struct hid_device * hdev,__u8 * rdesc,unsigned int * rsize)5873e4008dSNikolai Kondrashov static __u8 *samsung_irda_report_fixup(struct hid_device *hdev, __u8 *rdesc,
5973e4008dSNikolai Kondrashov unsigned int *rsize)
60980a3da6SJiri Slaby {
6173e4008dSNikolai Kondrashov if (*rsize == 184 && rdesc[175] == 0x25 && rdesc[176] == 0x40 &&
62980a3da6SJiri Slaby rdesc[177] == 0x75 && rdesc[178] == 0x30 &&
63980a3da6SJiri Slaby rdesc[179] == 0x95 && rdesc[180] == 0x01 &&
64980a3da6SJiri Slaby rdesc[182] == 0x40) {
65b355850bSDon Prince samsung_irda_dev_trace(hdev, 184);
66980a3da6SJiri Slaby rdesc[176] = 0xff;
67980a3da6SJiri Slaby rdesc[178] = 0x08;
68980a3da6SJiri Slaby rdesc[180] = 0x06;
69980a3da6SJiri Slaby rdesc[182] = 0x42;
700810b511SRobert Schedel } else
7173e4008dSNikolai Kondrashov if (*rsize == 203 && rdesc[192] == 0x15 && rdesc[193] == 0x0 &&
720810b511SRobert Schedel rdesc[194] == 0x25 && rdesc[195] == 0x12) {
73b355850bSDon Prince samsung_irda_dev_trace(hdev, 203);
740810b511SRobert Schedel rdesc[193] = 0x1;
750810b511SRobert Schedel rdesc[195] = 0xf;
760810b511SRobert Schedel } else
7773e4008dSNikolai Kondrashov if (*rsize == 135 && rdesc[124] == 0x15 && rdesc[125] == 0x0 &&
780810b511SRobert Schedel rdesc[126] == 0x25 && rdesc[127] == 0x11) {
79b355850bSDon Prince samsung_irda_dev_trace(hdev, 135);
800810b511SRobert Schedel rdesc[125] = 0x1;
810810b511SRobert Schedel rdesc[127] = 0xe;
823975bc56SRobert Schedel } else
8373e4008dSNikolai Kondrashov if (*rsize == 171 && rdesc[160] == 0x15 && rdesc[161] == 0x0 &&
843975bc56SRobert Schedel rdesc[162] == 0x25 && rdesc[163] == 0x01) {
85b355850bSDon Prince samsung_irda_dev_trace(hdev, 171);
863975bc56SRobert Schedel rdesc[161] = 0x1;
873975bc56SRobert Schedel rdesc[163] = 0x3;
88980a3da6SJiri Slaby }
8973e4008dSNikolai Kondrashov return rdesc;
90980a3da6SJiri Slaby }
91980a3da6SJiri Slaby
92b355850bSDon Prince #define samsung_kbd_mouse_map_key_clear(c) \
93b355850bSDon Prince hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
94b355850bSDon Prince
samsung_kbd_mouse_input_mapping(struct hid_device * hdev,struct hid_input * hi,struct hid_field * field,struct hid_usage * usage,unsigned long ** bit,int * max)95b355850bSDon Prince static int samsung_kbd_mouse_input_mapping(struct hid_device *hdev,
96b355850bSDon Prince struct hid_input *hi, struct hid_field *field, struct hid_usage *usage,
97b355850bSDon Prince unsigned long **bit, int *max)
98b355850bSDon Prince {
99b355850bSDon Prince struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
100b355850bSDon Prince unsigned short ifnum = intf->cur_altsetting->desc.bInterfaceNumber;
101b355850bSDon Prince
102b355850bSDon Prince if (1 != ifnum || HID_UP_CONSUMER != (usage->hid & HID_USAGE_PAGE))
103b355850bSDon Prince return 0;
104b355850bSDon Prince
105b355850bSDon Prince dbg_hid("samsung wireless keyboard/mouse input mapping event [0x%x]\n",
106b355850bSDon Prince usage->hid & HID_USAGE);
107b355850bSDon Prince
108b355850bSDon Prince switch (usage->hid & HID_USAGE) {
109b355850bSDon Prince /* report 2 */
110b355850bSDon Prince case 0x183: samsung_kbd_mouse_map_key_clear(KEY_MEDIA); break;
111b355850bSDon Prince case 0x195: samsung_kbd_mouse_map_key_clear(KEY_EMAIL); break;
112b355850bSDon Prince case 0x196: samsung_kbd_mouse_map_key_clear(KEY_CALC); break;
113b355850bSDon Prince case 0x197: samsung_kbd_mouse_map_key_clear(KEY_COMPUTER); break;
114b355850bSDon Prince case 0x22b: samsung_kbd_mouse_map_key_clear(KEY_SEARCH); break;
115b355850bSDon Prince case 0x22c: samsung_kbd_mouse_map_key_clear(KEY_WWW); break;
116b355850bSDon Prince case 0x22d: samsung_kbd_mouse_map_key_clear(KEY_BACK); break;
117b355850bSDon Prince case 0x22e: samsung_kbd_mouse_map_key_clear(KEY_FORWARD); break;
118b355850bSDon Prince case 0x22f: samsung_kbd_mouse_map_key_clear(KEY_FAVORITES); break;
119b355850bSDon Prince case 0x230: samsung_kbd_mouse_map_key_clear(KEY_REFRESH); break;
120b355850bSDon Prince case 0x231: samsung_kbd_mouse_map_key_clear(KEY_STOP); break;
121b355850bSDon Prince default:
122b355850bSDon Prince return 0;
123b355850bSDon Prince }
124b355850bSDon Prince
125b355850bSDon Prince return 1;
126b355850bSDon Prince }
127b355850bSDon Prince
samsung_report_fixup(struct hid_device * hdev,__u8 * rdesc,unsigned int * rsize)12873e4008dSNikolai Kondrashov static __u8 *samsung_report_fixup(struct hid_device *hdev, __u8 *rdesc,
12973e4008dSNikolai Kondrashov unsigned int *rsize)
130b355850bSDon Prince {
131b355850bSDon Prince if (USB_DEVICE_ID_SAMSUNG_IR_REMOTE == hdev->product)
13273e4008dSNikolai Kondrashov rdesc = samsung_irda_report_fixup(hdev, rdesc, rsize);
13373e4008dSNikolai Kondrashov return rdesc;
134b355850bSDon Prince }
135b355850bSDon Prince
samsung_input_mapping(struct hid_device * hdev,struct hid_input * hi,struct hid_field * field,struct hid_usage * usage,unsigned long ** bit,int * max)136b355850bSDon Prince static int samsung_input_mapping(struct hid_device *hdev, struct hid_input *hi,
137b355850bSDon Prince struct hid_field *field, struct hid_usage *usage,
138b355850bSDon Prince unsigned long **bit, int *max)
139b355850bSDon Prince {
140b355850bSDon Prince int ret = 0;
141b355850bSDon Prince
142b355850bSDon Prince if (USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE == hdev->product)
143b355850bSDon Prince ret = samsung_kbd_mouse_input_mapping(hdev,
144b355850bSDon Prince hi, field, usage, bit, max);
145b355850bSDon Prince
146b355850bSDon Prince return ret;
147b355850bSDon Prince }
148b355850bSDon Prince
samsung_probe(struct hid_device * hdev,const struct hid_device_id * id)149980a3da6SJiri Slaby static int samsung_probe(struct hid_device *hdev,
150980a3da6SJiri Slaby const struct hid_device_id *id)
151980a3da6SJiri Slaby {
152980a3da6SJiri Slaby int ret;
1530810b511SRobert Schedel unsigned int cmask = HID_CONNECT_DEFAULT;
154980a3da6SJiri Slaby
155*93020953SGreg Kroah-Hartman if (!hid_is_usb(hdev))
156*93020953SGreg Kroah-Hartman return -EINVAL;
157*93020953SGreg Kroah-Hartman
158980a3da6SJiri Slaby ret = hid_parse(hdev);
159980a3da6SJiri Slaby if (ret) {
1604291ee30SJoe Perches hid_err(hdev, "parse failed\n");
161980a3da6SJiri Slaby goto err_free;
162980a3da6SJiri Slaby }
163980a3da6SJiri Slaby
164b355850bSDon Prince if (USB_DEVICE_ID_SAMSUNG_IR_REMOTE == hdev->product) {
1650810b511SRobert Schedel if (hdev->rsize == 184) {
1660810b511SRobert Schedel /* disable hidinput, force hiddev */
1670810b511SRobert Schedel cmask = (cmask & ~HID_CONNECT_HIDINPUT) |
1680810b511SRobert Schedel HID_CONNECT_HIDDEV_FORCE;
1690810b511SRobert Schedel }
170b355850bSDon Prince }
1710810b511SRobert Schedel
1720810b511SRobert Schedel ret = hid_hw_start(hdev, cmask);
173980a3da6SJiri Slaby if (ret) {
1744291ee30SJoe Perches hid_err(hdev, "hw start failed\n");
175980a3da6SJiri Slaby goto err_free;
176980a3da6SJiri Slaby }
177980a3da6SJiri Slaby
178980a3da6SJiri Slaby return 0;
179980a3da6SJiri Slaby err_free:
180980a3da6SJiri Slaby return ret;
181980a3da6SJiri Slaby }
182980a3da6SJiri Slaby
183980a3da6SJiri Slaby static const struct hid_device_id samsung_devices[] = {
184980a3da6SJiri Slaby { HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_IR_REMOTE) },
185b355850bSDon Prince { HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE) },
186980a3da6SJiri Slaby { }
187980a3da6SJiri Slaby };
188980a3da6SJiri Slaby MODULE_DEVICE_TABLE(hid, samsung_devices);
189980a3da6SJiri Slaby
190980a3da6SJiri Slaby static struct hid_driver samsung_driver = {
191980a3da6SJiri Slaby .name = "samsung",
192980a3da6SJiri Slaby .id_table = samsung_devices,
193980a3da6SJiri Slaby .report_fixup = samsung_report_fixup,
194b355850bSDon Prince .input_mapping = samsung_input_mapping,
195980a3da6SJiri Slaby .probe = samsung_probe,
196980a3da6SJiri Slaby };
197f425458eSH Hartley Sweeten module_hid_driver(samsung_driver);
198980a3da6SJiri Slaby
199980a3da6SJiri Slaby MODULE_LICENSE("GPL");
200