1// SPDX-License-Identifier: GPL-2.0
2/*
3 * HID driver for the Creative SB0540 receiver
4 *
5 * Copyright (C) 2019 Red Hat Inc. All Rights Reserved
6 *
7 */
8
9#include <linux/device.h>
10#include <linux/hid.h>
11#include <linux/module.h>
12#include "hid-ids.h"
13
14MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
15MODULE_DESCRIPTION("HID Creative SB0540 receiver");
16MODULE_LICENSE("GPL");
17
18static const unsigned short creative_sb0540_key_table[] = {
19	KEY_POWER,
20	KEY_RESERVED,		/* text: 24bit */
21	KEY_RESERVED,		/* 24bit wheel up */
22	KEY_RESERVED,		/* 24bit wheel down */
23	KEY_RESERVED,		/* text: CMSS */
24	KEY_RESERVED,		/* CMSS wheel Up */
25	KEY_RESERVED,		/* CMSS wheel Down */
26	KEY_RESERVED,		/* text: EAX */
27	KEY_RESERVED,		/* EAX wheel up */
28	KEY_RESERVED,		/* EAX wheel down */
29	KEY_RESERVED,		/* text: 3D Midi */
30	KEY_RESERVED,		/* 3D Midi wheel up */
31	KEY_RESERVED,		/* 3D Midi wheel down */
32	KEY_MUTE,
33	KEY_VOLUMEUP,
34	KEY_VOLUMEDOWN,
35	KEY_UP,
36	KEY_LEFT,
37	KEY_RIGHT,
38	KEY_REWIND,
39	KEY_OK,
40	KEY_FASTFORWARD,
41	KEY_DOWN,
42	KEY_AGAIN,		/* text: Return, symbol: Jump to */
43	KEY_PLAY,		/* text: Start */
44	KEY_ESC,		/* text: Cancel */
45	KEY_RECORD,
46	KEY_OPTION,
47	KEY_MENU,		/* text: Display */
48	KEY_PREVIOUS,
49	KEY_PLAYPAUSE,
50	KEY_NEXT,
51	KEY_SLOW,
52	KEY_STOP,
53	KEY_NUMERIC_1,
54	KEY_NUMERIC_2,
55	KEY_NUMERIC_3,
56	KEY_NUMERIC_4,
57	KEY_NUMERIC_5,
58	KEY_NUMERIC_6,
59	KEY_NUMERIC_7,
60	KEY_NUMERIC_8,
61	KEY_NUMERIC_9,
62	KEY_NUMERIC_0
63};
64
65/*
66 * Codes and keys from lirc's
67 * remotes/creative/lircd.conf.alsa_usb
68 * order and size must match creative_sb0540_key_table[] above
69 */
70static const unsigned short creative_sb0540_codes[] = {
71	0x619E,
72	0x916E,
73	0x926D,
74	0x936C,
75	0x718E,
76	0x946B,
77	0x956A,
78	0x8C73,
79	0x9669,
80	0x9768,
81	0x9867,
82	0x9966,
83	0x9A65,
84	0x6E91,
85	0x629D,
86	0x639C,
87	0x7B84,
88	0x6B94,
89	0x728D,
90	0x8778,
91	0x817E,
92	0x758A,
93	0x8D72,
94	0x8E71,
95	0x8877,
96	0x7C83,
97	0x738C,
98	0x827D,
99	0x7689,
100	0x7F80,
101	0x7986,
102	0x7A85,
103	0x7D82,
104	0x857A,
105	0x8B74,
106	0x8F70,
107	0x906F,
108	0x8A75,
109	0x847B,
110	0x7887,
111	0x8976,
112	0x837C,
113	0x7788,
114	0x807F
115};
116
117struct creative_sb0540 {
118	struct input_dev *input_dev;
119	struct hid_device *hid;
120	unsigned short keymap[ARRAY_SIZE(creative_sb0540_key_table)];
121};
122
123static inline u64 reverse(u64 data, int bits)
124{
125	int i;
126	u64 c;
127
128	c = 0;
129	for (i = 0; i < bits; i++) {
130		c |= (u64) (((data & (((u64) 1) << i)) ? 1 : 0))
131			<< (bits - 1 - i);
132	}
133	return (c);
134}
135
136static int get_key(struct creative_sb0540 *creative_sb0540, u64 keycode)
137{
138	int i;
139
140	for (i = 0; i < ARRAY_SIZE(creative_sb0540_codes); i++) {
141		if (creative_sb0540_codes[i] == keycode)
142			return creative_sb0540->keymap[i];
143	}
144
145	return 0;
146
147}
148
149static int creative_sb0540_raw_event(struct hid_device *hid,
150	struct hid_report *report, u8 *data, int len)
151{
152	struct creative_sb0540 *creative_sb0540 = hid_get_drvdata(hid);
153	u64 code, main_code;
154	int key;
155
156	if (len != 6)
157		return 0;
158
159	/* From daemons/hw_hiddev.c sb0540_rec() in lirc */
160	code = reverse(data[5], 8);
161	main_code = (code << 8) + ((~code) & 0xff);
162
163	/*
164	 * Flip to get values in the same format as
165	 * remotes/creative/lircd.conf.alsa_usb in lirc
166	 */
167	main_code = ((main_code & 0xff) << 8) +
168		((main_code & 0xff00) >> 8);
169
170	key = get_key(creative_sb0540, main_code);
171	if (key == 0 || key == KEY_RESERVED) {
172		hid_err(hid, "Could not get a key for main_code %llX\n",
173			main_code);
174		return 0;
175	}
176
177	input_report_key(creative_sb0540->input_dev, key, 1);
178	input_report_key(creative_sb0540->input_dev, key, 0);
179	input_sync(creative_sb0540->input_dev);
180
181	/* let hidraw and hiddev handle the report */
182	return 0;
183}
184
185static int creative_sb0540_input_configured(struct hid_device *hid,
186		struct hid_input *hidinput)
187{
188	struct input_dev *input_dev = hidinput->input;
189	struct creative_sb0540 *creative_sb0540 = hid_get_drvdata(hid);
190	int i;
191
192	creative_sb0540->input_dev = input_dev;
193
194	input_dev->keycode = creative_sb0540->keymap;
195	input_dev->keycodesize = sizeof(unsigned short);
196	input_dev->keycodemax = ARRAY_SIZE(creative_sb0540->keymap);
197
198	input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP);
199
200	memcpy(creative_sb0540->keymap, creative_sb0540_key_table,
201		sizeof(creative_sb0540->keymap));
202	for (i = 0; i < ARRAY_SIZE(creative_sb0540_key_table); i++)
203		set_bit(creative_sb0540->keymap[i], input_dev->keybit);
204	clear_bit(KEY_RESERVED, input_dev->keybit);
205
206	return 0;
207}
208
209static int creative_sb0540_input_mapping(struct hid_device *hid,
210		struct hid_input *hi, struct hid_field *field,
211		struct hid_usage *usage, unsigned long **bit, int *max)
212{
213	/*
214	 * We are remapping the keys ourselves, so ignore the hid-input
215	 * keymap processing.
216	 */
217	return -1;
218}
219
220static int creative_sb0540_probe(struct hid_device *hid,
221		const struct hid_device_id *id)
222{
223	int ret;
224	struct creative_sb0540 *creative_sb0540;
225
226	creative_sb0540 = devm_kzalloc(&hid->dev,
227		sizeof(struct creative_sb0540), GFP_KERNEL);
228
229	if (!creative_sb0540)
230		return -ENOMEM;
231
232	creative_sb0540->hid = hid;
233
234	/* force input as some remotes bypass the input registration */
235	hid->quirks |= HID_QUIRK_HIDINPUT_FORCE;
236
237	hid_set_drvdata(hid, creative_sb0540);
238
239	ret = hid_parse(hid);
240	if (ret) {
241		hid_err(hid, "parse failed\n");
242		return ret;
243	}
244
245	ret = hid_hw_start(hid, HID_CONNECT_DEFAULT);
246	if (ret) {
247		hid_err(hid, "hw start failed\n");
248		return ret;
249	}
250
251	return ret;
252}
253
254static const struct hid_device_id creative_sb0540_devices[] = {
255	{ HID_USB_DEVICE(USB_VENDOR_ID_CREATIVELABS, USB_DEVICE_ID_CREATIVE_SB0540) },
256	{ }
257};
258MODULE_DEVICE_TABLE(hid, creative_sb0540_devices);
259
260static struct hid_driver creative_sb0540_driver = {
261	.name = "creative-sb0540",
262	.id_table = creative_sb0540_devices,
263	.raw_event = creative_sb0540_raw_event,
264	.input_configured = creative_sb0540_input_configured,
265	.probe = creative_sb0540_probe,
266	.input_mapping = creative_sb0540_input_mapping,
267};
268module_hid_driver(creative_sb0540_driver);
269