xref: /openbmc/linux/drivers/hid/hid-creative-sb0540.c (revision 976e3645923bdd2fe7893aae33fd7a21098bfb28)
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