1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 *  HID driver for some samsung "special" devices
4 *
5 *  Copyright (c) 1999 Andreas Gal
6 *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
7 *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
8 *  Copyright (c) 2006-2007 Jiri Kosina
9 *  Copyright (c) 2008 Jiri Slaby
10 *  Copyright (c) 2010 Don Prince <dhprince.devel@yahoo.co.uk>
11 *
12 *  This driver supports several HID devices:
13 *
14 *  [0419:0001] Samsung IrDA remote controller (reports as Cypress USB Mouse).
15 *	various hid report fixups for different variants.
16 *
17 *  [0419:0600] Creative Desktop Wireless 6000 keyboard/mouse combo
18 *	several key mappings used from the consumer usage page
19 *	deviate from the USB HUT 1.12 standard.
20 */
21
22/*
23 */
24
25#include <linux/device.h>
26#include <linux/usb.h>
27#include <linux/hid.h>
28#include <linux/module.h>
29
30#include "hid-ids.h"
31
32/*
33 * There are several variants for 0419:0001:
34 *
35 * 1. 184 byte report descriptor
36 * Vendor specific report #4 has a size of 48 bit,
37 * and therefore is not accepted when inspecting the descriptors.
38 * As a workaround we reinterpret the report as:
39 *   Variable type, count 6, size 8 bit, log. maximum 255
40 * The burden to reconstruct the data is moved into user space.
41 *
42 * 2. 203 byte report descriptor
43 * Report #4 has an array field with logical range 0..18 instead of 1..15.
44 *
45 * 3. 135 byte report descriptor
46 * Report #4 has an array field with logical range 0..17 instead of 1..14.
47 *
48 * 4. 171 byte report descriptor
49 * Report #3 has an array field with logical range 0..1 instead of 1..3.
50 */
51static inline void samsung_irda_dev_trace(struct hid_device *hdev,
52		unsigned int rsize)
53{
54	hid_info(hdev, "fixing up Samsung IrDA %d byte report descriptor\n",
55		 rsize);
56}
57
58static __u8 *samsung_irda_report_fixup(struct hid_device *hdev, __u8 *rdesc,
59		unsigned int *rsize)
60{
61	if (*rsize == 184 && !memcmp(&rdesc[175], "\x25\x40\x75\x30\x95\x01", 6) &&
62			rdesc[182] == 0x40) {
63		samsung_irda_dev_trace(hdev, 184);
64		rdesc[176] = 0xff;
65		rdesc[178] = 0x08;
66		rdesc[180] = 0x06;
67		rdesc[182] = 0x42;
68	} else if (*rsize == 203 && !memcmp(&rdesc[192], "\x15\x00\x25\x12", 4)) {
69		samsung_irda_dev_trace(hdev, 203);
70		rdesc[193] = 0x01;
71		rdesc[195] = 0x0f;
72	} else if (*rsize == 135 && !memcmp(&rdesc[124], "\x15\x00\x25\x11", 4)) {
73		samsung_irda_dev_trace(hdev, 135);
74		rdesc[125] = 0x01;
75		rdesc[127] = 0x0e;
76	} else if (*rsize == 171 && !memcmp(&rdesc[160], "\x15\x00\x25\x01", 4)) {
77		samsung_irda_dev_trace(hdev, 171);
78		rdesc[161] = 0x01;
79		rdesc[163] = 0x03;
80	}
81	return rdesc;
82}
83
84#define samsung_kbd_mouse_map_key_clear(c) \
85	hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
86
87static int samsung_kbd_mouse_input_mapping(struct hid_device *hdev,
88	struct hid_input *hi, struct hid_field *field, struct hid_usage *usage,
89	unsigned long **bit, int *max)
90{
91	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
92	unsigned short ifnum = intf->cur_altsetting->desc.bInterfaceNumber;
93
94	if (ifnum != 1 || HID_UP_CONSUMER != (usage->hid & HID_USAGE_PAGE))
95		return 0;
96
97	dbg_hid("samsung wireless keyboard/mouse input mapping event [0x%x]\n",
98		usage->hid & HID_USAGE);
99
100	switch (usage->hid & HID_USAGE) {
101	/* report 2 */
102	case 0x183:
103		samsung_kbd_mouse_map_key_clear(KEY_MEDIA);
104		break;
105	case 0x195:
106		samsung_kbd_mouse_map_key_clear(KEY_EMAIL);
107		break;
108	case 0x196:
109		samsung_kbd_mouse_map_key_clear(KEY_CALC);
110		break;
111	case 0x197:
112		samsung_kbd_mouse_map_key_clear(KEY_COMPUTER);
113		break;
114	case 0x22b:
115		samsung_kbd_mouse_map_key_clear(KEY_SEARCH);
116		break;
117	case 0x22c:
118		samsung_kbd_mouse_map_key_clear(KEY_WWW);
119		break;
120	case 0x22d:
121		samsung_kbd_mouse_map_key_clear(KEY_BACK);
122		break;
123	case 0x22e:
124		samsung_kbd_mouse_map_key_clear(KEY_FORWARD);
125		break;
126	case 0x22f:
127		samsung_kbd_mouse_map_key_clear(KEY_FAVORITES);
128		break;
129	case 0x230:
130		samsung_kbd_mouse_map_key_clear(KEY_REFRESH);
131		break;
132	case 0x231:
133		samsung_kbd_mouse_map_key_clear(KEY_STOP);
134		break;
135	default:
136		return 0;
137	}
138
139	return 1;
140}
141
142static int samsung_kbd_input_mapping(struct hid_device *hdev,
143	struct hid_input *hi, struct hid_field *field, struct hid_usage *usage,
144	unsigned long **bit, int *max)
145{
146	if (!(HID_UP_CONSUMER == (usage->hid & HID_USAGE_PAGE) ||
147			HID_UP_KEYBOARD == (usage->hid & HID_USAGE_PAGE)))
148		return 0;
149
150	dbg_hid("samsung wireless keyboard input mapping event [0x%x]\n",
151		usage->hid & HID_USAGE);
152
153	if (HID_UP_KEYBOARD == (usage->hid & HID_USAGE_PAGE)) {
154		set_bit(EV_REP, hi->input->evbit);
155		switch (usage->hid & HID_USAGE) {
156		case 0x32:
157			samsung_kbd_mouse_map_key_clear(KEY_BACKSLASH);
158			break;
159		case 0x64:
160			samsung_kbd_mouse_map_key_clear(KEY_102ND);
161			break;
162		/* Only for BR keyboard */
163		case 0x87:
164			samsung_kbd_mouse_map_key_clear(KEY_RO);
165			break;
166		default:
167			return 0;
168		}
169	}
170
171	if (HID_UP_CONSUMER == (usage->hid & HID_USAGE_PAGE)) {
172		switch (usage->hid & HID_USAGE) {
173		/* report 2 */
174		/* MENU */
175		case 0x040:
176			samsung_kbd_mouse_map_key_clear(KEY_MENU);
177			break;
178		case 0x18a:
179			samsung_kbd_mouse_map_key_clear(KEY_MAIL);
180			break;
181		case 0x196:
182			samsung_kbd_mouse_map_key_clear(KEY_WWW);
183			break;
184		case 0x19e:
185			samsung_kbd_mouse_map_key_clear(KEY_SCREENLOCK);
186			break;
187		case 0x221:
188			samsung_kbd_mouse_map_key_clear(KEY_SEARCH);
189			break;
190		case 0x223:
191			samsung_kbd_mouse_map_key_clear(KEY_HOMEPAGE);
192			break;
193		/* Smtart Voice Key */
194		case 0x300:
195			samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY13);
196			break;
197		/* RECENTAPPS */
198		case 0x301:
199			samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY1);
200			break;
201		/* APPLICATION */
202		case 0x302:
203			samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY2);
204			break;
205		/* Voice search */
206		case 0x305:
207			samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY4);
208			break;
209		/* QPANEL on/off */
210		case 0x306:
211			samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY5);
212			break;
213		/* SIP on/off */
214		case 0x307:
215			samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY3);
216			break;
217		/* LANG */
218		case 0x308:
219			samsung_kbd_mouse_map_key_clear(KEY_LANGUAGE);
220			break;
221		case 0x30a:
222			samsung_kbd_mouse_map_key_clear(KEY_BRIGHTNESSDOWN);
223			break;
224		case 0x30b:
225			samsung_kbd_mouse_map_key_clear(KEY_BRIGHTNESSUP);
226			break;
227		default:
228			return 0;
229		}
230	}
231
232	return 1;
233}
234
235static int samsung_gamepad_input_mapping(struct hid_device *hdev,
236	struct hid_input *hi, struct hid_field *field, struct hid_usage *usage,
237	unsigned long **bit, int *max)
238{
239	if (!(HID_UP_BUTTON == (usage->hid & HID_USAGE_PAGE) ||
240			HID_UP_CONSUMER == (usage->hid & HID_USAGE_PAGE)))
241		return 0;
242
243	dbg_hid("samsung wireless gamepad input mapping event [0x%x], %ld, %ld, [0x%x]\n",
244		usage->hid & HID_USAGE, hi->input->evbit[0], hi->input->absbit[0], usage->hid & HID_USAGE_PAGE);
245
246	if (HID_UP_BUTTON == (usage->hid & HID_USAGE_PAGE)) {
247		switch (usage->hid & HID_USAGE) {
248		case 0x01:
249			samsung_kbd_mouse_map_key_clear(BTN_A);
250			break;
251		case 0x02:
252			samsung_kbd_mouse_map_key_clear(BTN_B);
253			break;
254		case 0x03:
255			samsung_kbd_mouse_map_key_clear(BTN_C);
256			break;
257		case 0x04:
258			samsung_kbd_mouse_map_key_clear(BTN_X);
259			break;
260		case 0x05:
261			samsung_kbd_mouse_map_key_clear(BTN_Y);
262			break;
263		case 0x06:
264			samsung_kbd_mouse_map_key_clear(BTN_Z);
265			break;
266		case 0x07:
267			samsung_kbd_mouse_map_key_clear(BTN_TL);
268			break;
269		case 0x08:
270			samsung_kbd_mouse_map_key_clear(BTN_TR);
271			break;
272		case 0x09:
273			samsung_kbd_mouse_map_key_clear(BTN_TL2);
274			break;
275		case 0x0a:
276			samsung_kbd_mouse_map_key_clear(BTN_TR2);
277			break;
278		case 0x0b:
279			samsung_kbd_mouse_map_key_clear(BTN_SELECT);
280			break;
281		case 0x0c:
282			samsung_kbd_mouse_map_key_clear(BTN_START);
283			break;
284		case 0x0d:
285			samsung_kbd_mouse_map_key_clear(BTN_MODE);
286			break;
287		case 0x0e:
288			samsung_kbd_mouse_map_key_clear(BTN_THUMBL);
289			break;
290		case 0x0f:
291			samsung_kbd_mouse_map_key_clear(BTN_THUMBR);
292			break;
293		case 0x10:
294			samsung_kbd_mouse_map_key_clear(0x13f);
295			break;
296		default:
297			return 0;
298		}
299	}
300
301	if (HID_UP_CONSUMER == (usage->hid & HID_USAGE_PAGE)) {
302		switch (usage->hid & HID_USAGE) {
303		case 0x040:
304			samsung_kbd_mouse_map_key_clear(KEY_MENU);
305			break;
306		case 0x223:
307			samsung_kbd_mouse_map_key_clear(KEY_HOMEPAGE);
308			break;
309		case 0x224:
310			samsung_kbd_mouse_map_key_clear(KEY_BACK);
311			break;
312
313		/* Screen Capture */
314		case 0x303:
315			samsung_kbd_mouse_map_key_clear(KEY_SYSRQ);
316			break;
317
318		default:
319			return 0;
320		}
321	}
322
323	return 1;
324}
325
326static int samsung_actionmouse_input_mapping(struct hid_device *hdev,
327	struct hid_input *hi, struct hid_field *field, struct hid_usage *usage,
328	unsigned long **bit, int *max)
329{
330
331	dbg_hid("samsung wireless actionmouse input mapping event [0x%x], [0x%x], %ld, %ld, [0x%x]\n",
332			usage->hid, usage->hid & HID_USAGE, hi->input->evbit[0], hi->input->absbit[0],
333			usage->hid & HID_USAGE_PAGE);
334
335	if (((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER) && ((usage->hid & HID_USAGE_PAGE) != HID_UP_BUTTON))
336		return 0;
337
338	switch (usage->hid & HID_USAGE) {
339	case 0x301:
340		samsung_kbd_mouse_map_key_clear(254);
341		break;
342	default:
343		return 0;
344	}
345
346	return 1;
347}
348
349static int samsung_universal_kbd_input_mapping(struct hid_device *hdev,
350	struct hid_input *hi, struct hid_field *field, struct hid_usage *usage,
351	unsigned long **bit, int *max)
352{
353	if (!(HID_UP_CONSUMER == (usage->hid & HID_USAGE_PAGE) ||
354			HID_UP_KEYBOARD == (usage->hid & HID_USAGE_PAGE)))
355		return 0;
356
357	dbg_hid("samsung wireless keyboard input mapping event [0x%x]\n",
358		usage->hid & HID_USAGE);
359
360	if (HID_UP_KEYBOARD == (usage->hid & HID_USAGE_PAGE)) {
361		set_bit(EV_REP, hi->input->evbit);
362		switch (usage->hid & HID_USAGE) {
363		case 0x32:
364			samsung_kbd_mouse_map_key_clear(KEY_BACKSLASH);
365			break;
366		case 0x64:
367			samsung_kbd_mouse_map_key_clear(KEY_102ND);
368			break;
369		/* Only for BR keyboard */
370		case 0x87:
371			samsung_kbd_mouse_map_key_clear(KEY_RO);
372			break;
373		default:
374			return 0;
375		}
376	}
377
378	if (HID_UP_CONSUMER == (usage->hid & HID_USAGE_PAGE)) {
379		switch (usage->hid & HID_USAGE) {
380		/* report 2 */
381		/* MENU */
382		case 0x040:
383			samsung_kbd_mouse_map_key_clear(KEY_MENU);
384			break;
385		case 0x18a:
386			samsung_kbd_mouse_map_key_clear(KEY_MAIL);
387			break;
388		case 0x196:
389			samsung_kbd_mouse_map_key_clear(KEY_WWW);
390			break;
391		case 0x19e:
392			samsung_kbd_mouse_map_key_clear(KEY_SCREENLOCK);
393			break;
394		case 0x221:
395			samsung_kbd_mouse_map_key_clear(KEY_SEARCH);
396			break;
397		case 0x223:
398			samsung_kbd_mouse_map_key_clear(KEY_HOMEPAGE);
399			break;
400		/* RECENTAPPS */
401		case 0x301:
402			samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY1);
403			break;
404		/* APPLICATION */
405		case 0x302:
406			samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY2);
407			break;
408		/* Voice search */
409		case 0x305:
410			samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY4);
411			break;
412		/* QPANEL on/off */
413		case 0x306:
414			samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY5);
415			break;
416		/* SIP on/off */
417		case 0x307:
418			samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY3);
419			break;
420		/* LANG */
421		case 0x308:
422			samsung_kbd_mouse_map_key_clear(KEY_LANGUAGE);
423			break;
424		case 0x30a:
425			samsung_kbd_mouse_map_key_clear(KEY_BRIGHTNESSDOWN);
426			break;
427		case 0x070:
428			samsung_kbd_mouse_map_key_clear(KEY_BRIGHTNESSDOWN);
429			break;
430		case 0x30b:
431			samsung_kbd_mouse_map_key_clear(KEY_BRIGHTNESSUP);
432			break;
433		case 0x06f:
434			samsung_kbd_mouse_map_key_clear(KEY_BRIGHTNESSUP);
435			break;
436		/* S-Finder */
437		case 0x304:
438			samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY7);
439			break;
440		/* Screen Capture */
441		case 0x303:
442			samsung_kbd_mouse_map_key_clear(KEY_SYSRQ);
443			break;
444		/* Multi Window */
445		case 0x309:
446			samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY9);
447			break;
448		/* HotKey App 1 */
449		case 0x071:
450			samsung_kbd_mouse_map_key_clear(0x2f5);
451			break;
452		/* HotKey App 2 */
453		case 0x072:
454			samsung_kbd_mouse_map_key_clear(0x2f6);
455			break;
456		/* HotKey App 3 */
457		case 0x073:
458			samsung_kbd_mouse_map_key_clear(0x2f7);
459			break;
460		/* Dex */
461		case 0x06e:
462			samsung_kbd_mouse_map_key_clear(0x2bd);
463			break;
464		default:
465			return 0;
466		}
467	}
468
469	return 1;
470}
471
472static __u8 *samsung_report_fixup(struct hid_device *hdev, __u8 *rdesc,
473	unsigned int *rsize)
474{
475	if (hdev->product == USB_DEVICE_ID_SAMSUNG_IR_REMOTE && hid_is_usb(hdev))
476		rdesc = samsung_irda_report_fixup(hdev, rdesc, rsize);
477	return rdesc;
478}
479
480static int samsung_input_mapping(struct hid_device *hdev, struct hid_input *hi,
481	struct hid_field *field, struct hid_usage *usage,
482	unsigned long **bit, int *max)
483{
484	int ret = 0;
485
486	if (hdev->product == USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE && hid_is_usb(hdev))
487		ret = samsung_kbd_mouse_input_mapping(hdev,
488			hi, field, usage, bit, max);
489	else if (hdev->product == USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD)
490		ret = samsung_kbd_input_mapping(hdev,
491			hi, field, usage, bit, max);
492	else if (hdev->product == USB_DEVICE_ID_SAMSUNG_WIRELESS_GAMEPAD)
493		ret = samsung_gamepad_input_mapping(hdev,
494			hi, field, usage, bit, max);
495	else if (hdev->product == USB_DEVICE_ID_SAMSUNG_WIRELESS_ACTIONMOUSE)
496		ret = samsung_actionmouse_input_mapping(hdev,
497			hi, field, usage, bit, max);
498	else if (hdev->product == USB_DEVICE_ID_SAMSUNG_WIRELESS_UNIVERSAL_KBD)
499		ret = samsung_universal_kbd_input_mapping(hdev,
500			hi, field, usage, bit, max);
501	else if (hdev->product == USB_DEVICE_ID_SAMSUNG_WIRELESS_MULTI_HOGP_KBD)
502		ret = samsung_universal_kbd_input_mapping(hdev,
503			hi, field, usage, bit, max);
504
505	return ret;
506}
507
508static int samsung_probe(struct hid_device *hdev,
509		const struct hid_device_id *id)
510{
511	int ret;
512	unsigned int cmask = HID_CONNECT_DEFAULT;
513
514	ret = hid_parse(hdev);
515	if (ret) {
516		hid_err(hdev, "parse failed\n");
517		goto err_free;
518	}
519
520	if (hdev->product == USB_DEVICE_ID_SAMSUNG_IR_REMOTE) {
521		if (!hid_is_usb(hdev)) {
522			ret = -EINVAL;
523			goto err_free;
524		}
525		if (hdev->rsize == 184) {
526			/* disable hidinput, force hiddev */
527			cmask = (cmask & ~HID_CONNECT_HIDINPUT) |
528				HID_CONNECT_HIDDEV_FORCE;
529		}
530	}
531
532	ret = hid_hw_start(hdev, cmask);
533	if (ret) {
534		hid_err(hdev, "hw start failed\n");
535		goto err_free;
536	}
537
538	return 0;
539err_free:
540	return ret;
541}
542
543static const struct hid_device_id samsung_devices[] = {
544	{ HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_IR_REMOTE) },
545	{ HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE) },
546	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SAMSUNG_ELECTRONICS, USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD) },
547	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SAMSUNG_ELECTRONICS, USB_DEVICE_ID_SAMSUNG_WIRELESS_GAMEPAD) },
548	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SAMSUNG_ELECTRONICS, USB_DEVICE_ID_SAMSUNG_WIRELESS_ACTIONMOUSE) },
549	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SAMSUNG_ELECTRONICS, USB_DEVICE_ID_SAMSUNG_WIRELESS_UNIVERSAL_KBD) },
550	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SAMSUNG_ELECTRONICS, USB_DEVICE_ID_SAMSUNG_WIRELESS_MULTI_HOGP_KBD) },
551	{ }
552};
553MODULE_DEVICE_TABLE(hid, samsung_devices);
554
555static struct hid_driver samsung_driver = {
556	.name = "samsung",
557	.id_table = samsung_devices,
558	.report_fixup = samsung_report_fixup,
559	.input_mapping = samsung_input_mapping,
560	.probe = samsung_probe,
561};
562module_hid_driver(samsung_driver);
563
564MODULE_LICENSE("GPL");
565