]>
Commit | Line | Data |
---|---|---|
9c92ab61 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
9a4a5574 BT |
2 | /* |
3 | * HID driver for the apple ir device | |
4 | * | |
5 | * Original driver written by James McKenzie | |
6 | * Ported to recent 2.6 kernel versions by Greg Kroah-Hartman <gregkh@suse.de> | |
7 | * Updated to support newer remotes by Bastien Nocera <hadess@hadess.net> | |
8 | * Ported to HID subsystem by Benjamin Tissoires <benjamin.tissoires@gmail.com> | |
9 | * | |
10 | * Copyright (C) 2006 James McKenzie | |
11 | * Copyright (C) 2008 Greg Kroah-Hartman <greg@kroah.com> | |
12 | * Copyright (C) 2008 Novell Inc. | |
13 | * Copyright (C) 2010, 2012 Bastien Nocera <hadess@hadess.net> | |
14 | * Copyright (C) 2013 Benjamin Tissoires <benjamin.tissoires@gmail.com> | |
15 | * Copyright (C) 2013 Red Hat Inc. All Rights Reserved | |
9a4a5574 BT |
16 | */ |
17 | ||
18 | #include <linux/device.h> | |
19 | #include <linux/hid.h> | |
20 | #include <linux/module.h> | |
21 | #include "hid-ids.h" | |
22 | ||
23 | MODULE_AUTHOR("James McKenzie"); | |
24 | MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@redhat.com>"); | |
25 | MODULE_DESCRIPTION("HID Apple IR remote controls"); | |
26 | MODULE_LICENSE("GPL"); | |
27 | ||
28 | #define KEY_MASK 0x0F | |
29 | #define TWO_PACKETS_MASK 0x40 | |
30 | ||
31 | /* | |
32 | * James McKenzie has two devices both of which report the following | |
33 | * 25 87 ee 83 0a + | |
34 | * 25 87 ee 83 0c - | |
35 | * 25 87 ee 83 09 << | |
36 | * 25 87 ee 83 06 >> | |
37 | * 25 87 ee 83 05 >" | |
38 | * 25 87 ee 83 03 menu | |
39 | * 26 00 00 00 00 for key repeat | |
40 | */ | |
41 | ||
42 | /* | |
43 | * Thomas Glanzmann reports the following responses | |
44 | * 25 87 ee ca 0b + | |
45 | * 25 87 ee ca 0d - | |
46 | * 25 87 ee ca 08 << | |
47 | * 25 87 ee ca 07 >> | |
48 | * 25 87 ee ca 04 >" | |
49 | * 25 87 ee ca 02 menu | |
50 | * 26 00 00 00 00 for key repeat | |
51 | * | |
52 | * He also observes the following event sometimes | |
53 | * sent after a key is release, which I interpret | |
54 | * as a flat battery message | |
55 | * 25 87 e0 ca 06 flat battery | |
56 | */ | |
57 | ||
58 | /* | |
59 | * Alexandre Karpenko reports the following responses for Device ID 0x8242 | |
60 | * 25 87 ee 47 0b + | |
61 | * 25 87 ee 47 0d - | |
62 | * 25 87 ee 47 08 << | |
63 | * 25 87 ee 47 07 >> | |
64 | * 25 87 ee 47 04 >" | |
65 | * 25 87 ee 47 02 menu | |
66 | * 26 87 ee 47 ** for key repeat (** is the code of the key being held) | |
67 | */ | |
68 | ||
69 | /* | |
70 | * Bastien Nocera's remote | |
71 | * 25 87 ee 91 5f followed by | |
72 | * 25 87 ee 91 05 gives you >" | |
73 | * | |
74 | * 25 87 ee 91 5c followed by | |
75 | * 25 87 ee 91 05 gives you the middle button | |
76 | */ | |
77 | ||
78 | /* | |
79 | * Fabien Andre's remote | |
80 | * 25 87 ee a3 5e followed by | |
81 | * 25 87 ee a3 04 gives you >" | |
82 | * | |
83 | * 25 87 ee a3 5d followed by | |
84 | * 25 87 ee a3 04 gives you the middle button | |
85 | */ | |
86 | ||
87 | static const unsigned short appleir_key_table[] = { | |
88 | KEY_RESERVED, | |
89 | KEY_MENU, | |
90 | KEY_PLAYPAUSE, | |
91 | KEY_FORWARD, | |
92 | KEY_BACK, | |
93 | KEY_VOLUMEUP, | |
94 | KEY_VOLUMEDOWN, | |
95 | KEY_RESERVED, | |
96 | KEY_RESERVED, | |
97 | KEY_RESERVED, | |
98 | KEY_RESERVED, | |
99 | KEY_RESERVED, | |
100 | KEY_RESERVED, | |
101 | KEY_RESERVED, | |
102 | KEY_ENTER, | |
103 | KEY_PLAYPAUSE, | |
104 | KEY_RESERVED, | |
105 | }; | |
106 | ||
107 | struct appleir { | |
108 | struct input_dev *input_dev; | |
109 | struct hid_device *hid; | |
110 | unsigned short keymap[ARRAY_SIZE(appleir_key_table)]; | |
111 | struct timer_list key_up_timer; /* timer for key up */ | |
112 | spinlock_t lock; /* protects .current_key */ | |
113 | int current_key; /* the currently pressed key */ | |
114 | int prev_key_idx; /* key index in a 2 packets message */ | |
115 | }; | |
116 | ||
117 | static int get_key(int data) | |
118 | { | |
119 | /* | |
120 | * The key is coded accross bits 2..9: | |
121 | * | |
122 | * 0x00 or 0x01 ( ) key: 0 -> KEY_RESERVED | |
123 | * 0x02 or 0x03 ( menu ) key: 1 -> KEY_MENU | |
124 | * 0x04 or 0x05 ( >" ) key: 2 -> KEY_PLAYPAUSE | |
125 | * 0x06 or 0x07 ( >> ) key: 3 -> KEY_FORWARD | |
126 | * 0x08 or 0x09 ( << ) key: 4 -> KEY_BACK | |
127 | * 0x0a or 0x0b ( + ) key: 5 -> KEY_VOLUMEUP | |
128 | * 0x0c or 0x0d ( - ) key: 6 -> KEY_VOLUMEDOWN | |
129 | * 0x0e or 0x0f ( ) key: 7 -> KEY_RESERVED | |
130 | * 0x50 or 0x51 ( ) key: 8 -> KEY_RESERVED | |
131 | * 0x52 or 0x53 ( ) key: 9 -> KEY_RESERVED | |
132 | * 0x54 or 0x55 ( ) key: 10 -> KEY_RESERVED | |
133 | * 0x56 or 0x57 ( ) key: 11 -> KEY_RESERVED | |
134 | * 0x58 or 0x59 ( ) key: 12 -> KEY_RESERVED | |
135 | * 0x5a or 0x5b ( ) key: 13 -> KEY_RESERVED | |
136 | * 0x5c or 0x5d ( middle ) key: 14 -> KEY_ENTER | |
137 | * 0x5e or 0x5f ( >" ) key: 15 -> KEY_PLAYPAUSE | |
138 | * | |
139 | * Packets starting with 0x5 are part of a two-packets message, | |
140 | * we notify the caller by sending a negative value. | |
141 | */ | |
142 | int key = (data >> 1) & KEY_MASK; | |
143 | ||
144 | if ((data & TWO_PACKETS_MASK)) | |
145 | /* Part of a 2 packets-command */ | |
146 | key = -key; | |
147 | ||
148 | return key; | |
149 | } | |
150 | ||
151 | static void key_up(struct hid_device *hid, struct appleir *appleir, int key) | |
152 | { | |
153 | input_report_key(appleir->input_dev, key, 0); | |
154 | input_sync(appleir->input_dev); | |
155 | } | |
156 | ||
157 | static void key_down(struct hid_device *hid, struct appleir *appleir, int key) | |
158 | { | |
159 | input_report_key(appleir->input_dev, key, 1); | |
160 | input_sync(appleir->input_dev); | |
161 | } | |
162 | ||
163 | static void battery_flat(struct appleir *appleir) | |
164 | { | |
165 | dev_err(&appleir->input_dev->dev, "possible flat battery?\n"); | |
166 | } | |
167 | ||
e99e88a9 | 168 | static void key_up_tick(struct timer_list *t) |
9a4a5574 | 169 | { |
e99e88a9 | 170 | struct appleir *appleir = from_timer(appleir, t, key_up_timer); |
9a4a5574 BT |
171 | struct hid_device *hid = appleir->hid; |
172 | unsigned long flags; | |
173 | ||
174 | spin_lock_irqsave(&appleir->lock, flags); | |
175 | if (appleir->current_key) { | |
176 | key_up(hid, appleir, appleir->current_key); | |
177 | appleir->current_key = 0; | |
178 | } | |
179 | spin_unlock_irqrestore(&appleir->lock, flags); | |
180 | } | |
181 | ||
182 | static int appleir_raw_event(struct hid_device *hid, struct hid_report *report, | |
183 | u8 *data, int len) | |
184 | { | |
185 | struct appleir *appleir = hid_get_drvdata(hid); | |
186 | static const u8 keydown[] = { 0x25, 0x87, 0xee }; | |
187 | static const u8 keyrepeat[] = { 0x26, }; | |
188 | static const u8 flatbattery[] = { 0x25, 0x87, 0xe0 }; | |
189 | unsigned long flags; | |
190 | ||
191 | if (len != 5) | |
192 | goto out; | |
193 | ||
194 | if (!memcmp(data, keydown, sizeof(keydown))) { | |
195 | int index; | |
196 | ||
197 | spin_lock_irqsave(&appleir->lock, flags); | |
198 | /* | |
199 | * If we already have a key down, take it up before marking | |
200 | * this one down | |
201 | */ | |
202 | if (appleir->current_key) | |
203 | key_up(hid, appleir, appleir->current_key); | |
204 | ||
205 | /* Handle dual packet commands */ | |
206 | if (appleir->prev_key_idx > 0) | |
207 | index = appleir->prev_key_idx; | |
208 | else | |
209 | index = get_key(data[4]); | |
210 | ||
211 | if (index >= 0) { | |
212 | appleir->current_key = appleir->keymap[index]; | |
213 | ||
214 | key_down(hid, appleir, appleir->current_key); | |
215 | /* | |
216 | * Remote doesn't do key up, either pull them up, in | |
217 | * the test above, or here set a timer which pulls | |
218 | * them up after 1/8 s | |
219 | */ | |
220 | mod_timer(&appleir->key_up_timer, jiffies + HZ / 8); | |
221 | appleir->prev_key_idx = 0; | |
222 | } else | |
223 | /* Remember key for next packet */ | |
224 | appleir->prev_key_idx = -index; | |
225 | spin_unlock_irqrestore(&appleir->lock, flags); | |
226 | goto out; | |
227 | } | |
228 | ||
229 | appleir->prev_key_idx = 0; | |
230 | ||
231 | if (!memcmp(data, keyrepeat, sizeof(keyrepeat))) { | |
232 | key_down(hid, appleir, appleir->current_key); | |
233 | /* | |
234 | * Remote doesn't do key up, either pull them up, in the test | |
235 | * above, or here set a timer which pulls them up after 1/8 s | |
236 | */ | |
237 | mod_timer(&appleir->key_up_timer, jiffies + HZ / 8); | |
238 | goto out; | |
239 | } | |
240 | ||
241 | if (!memcmp(data, flatbattery, sizeof(flatbattery))) { | |
242 | battery_flat(appleir); | |
243 | /* Fall through */ | |
244 | } | |
245 | ||
246 | out: | |
247 | /* let hidraw and hiddev handle the report */ | |
248 | return 0; | |
249 | } | |
250 | ||
9154301a | 251 | static int appleir_input_configured(struct hid_device *hid, |
9a4a5574 BT |
252 | struct hid_input *hidinput) |
253 | { | |
254 | struct input_dev *input_dev = hidinput->input; | |
255 | struct appleir *appleir = hid_get_drvdata(hid); | |
256 | int i; | |
257 | ||
258 | appleir->input_dev = input_dev; | |
259 | ||
260 | input_dev->keycode = appleir->keymap; | |
261 | input_dev->keycodesize = sizeof(unsigned short); | |
262 | input_dev->keycodemax = ARRAY_SIZE(appleir->keymap); | |
263 | ||
264 | input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP); | |
265 | ||
266 | memcpy(appleir->keymap, appleir_key_table, sizeof(appleir->keymap)); | |
267 | for (i = 0; i < ARRAY_SIZE(appleir_key_table); i++) | |
268 | set_bit(appleir->keymap[i], input_dev->keybit); | |
269 | clear_bit(KEY_RESERVED, input_dev->keybit); | |
9154301a DT |
270 | |
271 | return 0; | |
9a4a5574 BT |
272 | } |
273 | ||
274 | static int appleir_input_mapping(struct hid_device *hid, | |
275 | struct hid_input *hi, struct hid_field *field, | |
276 | struct hid_usage *usage, unsigned long **bit, int *max) | |
277 | { | |
278 | return -1; | |
279 | } | |
280 | ||
281 | static int appleir_probe(struct hid_device *hid, const struct hid_device_id *id) | |
282 | { | |
283 | int ret; | |
284 | struct appleir *appleir; | |
285 | ||
910a7e89 | 286 | appleir = devm_kzalloc(&hid->dev, sizeof(struct appleir), GFP_KERNEL); |
b08e8d8a LT |
287 | if (!appleir) |
288 | return -ENOMEM; | |
9a4a5574 BT |
289 | |
290 | appleir->hid = hid; | |
291 | ||
3d18bd41 BT |
292 | /* force input as some remotes bypass the input registration */ |
293 | hid->quirks |= HID_QUIRK_HIDINPUT_FORCE; | |
294 | ||
9a4a5574 | 295 | spin_lock_init(&appleir->lock); |
e99e88a9 | 296 | timer_setup(&appleir->key_up_timer, key_up_tick, 0); |
9a4a5574 BT |
297 | |
298 | hid_set_drvdata(hid, appleir); | |
299 | ||
300 | ret = hid_parse(hid); | |
301 | if (ret) { | |
302 | hid_err(hid, "parse failed\n"); | |
303 | goto fail; | |
304 | } | |
305 | ||
306 | ret = hid_hw_start(hid, HID_CONNECT_DEFAULT | HID_CONNECT_HIDDEV_FORCE); | |
307 | if (ret) { | |
308 | hid_err(hid, "hw start failed\n"); | |
309 | goto fail; | |
310 | } | |
311 | ||
312 | return 0; | |
313 | fail: | |
910a7e89 | 314 | devm_kfree(&hid->dev, appleir); |
9a4a5574 BT |
315 | return ret; |
316 | } | |
317 | ||
318 | static void appleir_remove(struct hid_device *hid) | |
319 | { | |
320 | struct appleir *appleir = hid_get_drvdata(hid); | |
321 | hid_hw_stop(hid); | |
322 | del_timer_sync(&appleir->key_up_timer); | |
9a4a5574 BT |
323 | } |
324 | ||
325 | static const struct hid_device_id appleir_devices[] = { | |
326 | { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL) }, | |
327 | { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL2) }, | |
328 | { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL3) }, | |
329 | { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL4) }, | |
330 | { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL5) }, | |
331 | { } | |
332 | }; | |
333 | MODULE_DEVICE_TABLE(hid, appleir_devices); | |
334 | ||
335 | static struct hid_driver appleir_driver = { | |
336 | .name = "appleir", | |
337 | .id_table = appleir_devices, | |
338 | .raw_event = appleir_raw_event, | |
339 | .input_configured = appleir_input_configured, | |
340 | .probe = appleir_probe, | |
341 | .remove = appleir_remove, | |
342 | .input_mapping = appleir_input_mapping, | |
343 | }; | |
344 | module_hid_driver(appleir_driver); |