Merge tag 'imx-soc-5.11' of git://git.kernel.org/pub/scm/linux/kernel/git/shawnguo...
[linux-2.6-microblaze.git] / drivers / hid / hid-creative-sb0540.c
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(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[5], 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, 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(creative_sb0540->input_dev, key, 1);
178         input_report_key(creative_sb0540->input_dev, key, 0);
179         input_sync(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(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(creative_sb0540->keymap[i], input_dev->keybit);
204         clear_bit(KEY_RESERVED, 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(&hid->dev,
227                 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(hid, creative_sb0540);
238
239         ret = hid_parse(hid);
240         if (ret) {
241                 hid_err(hid, "parse failed\n");
242                 return ret;
243         }
244
245         ret = hid_hw_start(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);