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(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
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(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
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(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
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

source code of linux/drivers/hid/hid-creative-sb0540.c