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