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 | |
14 | MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>" ); |
15 | MODULE_DESCRIPTION("HID Creative SB0540 receiver" ); |
16 | MODULE_LICENSE("GPL" ); |
17 | |
18 | static 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 | */ |
70 | static 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 | |
117 | struct 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 | |
123 | static 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 | |
136 | static 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 | |
149 | static 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(hdev: 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: data[5], bits: 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, keycode: 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(dev: creative_sb0540->input_dev, code: key, value: 1); |
178 | input_report_key(dev: creative_sb0540->input_dev, code: key, value: 0); |
179 | input_sync(dev: creative_sb0540->input_dev); |
180 | |
181 | /* let hidraw and hiddev handle the report */ |
182 | return 0; |
183 | } |
184 | |
185 | static 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(hdev: 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(nr: creative_sb0540->keymap[i], addr: input_dev->keybit); |
204 | clear_bit(KEY_RESERVED, addr: input_dev->keybit); |
205 | |
206 | return 0; |
207 | } |
208 | |
209 | static 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 | |
220 | static 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(dev: &hid->dev, |
227 | size: 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(hdev: hid, data: creative_sb0540); |
238 | |
239 | ret = hid_parse(hdev: hid); |
240 | if (ret) { |
241 | hid_err(hid, "parse failed\n" ); |
242 | return ret; |
243 | } |
244 | |
245 | ret = hid_hw_start(hdev: 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 | |
254 | static const struct hid_device_id creative_sb0540_devices[] = { |
255 | { HID_USB_DEVICE(USB_VENDOR_ID_CREATIVELABS, USB_DEVICE_ID_CREATIVE_SB0540) }, |
256 | { } |
257 | }; |
258 | MODULE_DEVICE_TABLE(hid, creative_sb0540_devices); |
259 | |
260 | static 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 | }; |
268 | module_hid_driver(creative_sb0540_driver); |
269 | |