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