xref: /freebsd/sys/dev/hid/appleir.c (revision a85c4ab626ef52530400ace1957daaa35dd07534)
1*a85c4ab6SAbdelkader Boudih /*-
2*a85c4ab6SAbdelkader Boudih  * SPDX-License-Identifier: BSD-2-Clause
3*a85c4ab6SAbdelkader Boudih  *
4*a85c4ab6SAbdelkader Boudih  * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
5*a85c4ab6SAbdelkader Boudih  */
6*a85c4ab6SAbdelkader Boudih 
7*a85c4ab6SAbdelkader Boudih /*
8*a85c4ab6SAbdelkader Boudih  * Apple IR Remote Control Driver
9*a85c4ab6SAbdelkader Boudih  *
10*a85c4ab6SAbdelkader Boudih  * HID driver for Apple IR receivers (USB HID, vendor 0x05ac).
11*a85c4ab6SAbdelkader Boudih  * Supports Apple Remote and generic IR remotes using NEC protocol.
12*a85c4ab6SAbdelkader Boudih  *
13*a85c4ab6SAbdelkader Boudih  * The Apple Remote protocol was reverse-engineered by James McKenzie and
14*a85c4ab6SAbdelkader Boudih  * others; key codes and packet format constants are derived from that work
15*a85c4ab6SAbdelkader Boudih  * and are factual descriptions of the hardware protocol, not copied code.
16*a85c4ab6SAbdelkader Boudih  * Linux reference (GPL-2.0, no code copied): drivers/hid/hid-appleir.c
17*a85c4ab6SAbdelkader Boudih  *
18*a85c4ab6SAbdelkader Boudih  * Apple Remote Protocol (proprietary):
19*a85c4ab6SAbdelkader Boudih  *   Key down:    [0x25][0x87][0xee][remote_id][key_code]
20*a85c4ab6SAbdelkader Boudih  *   Key repeat:  [0x26][0x87][0xee][remote_id][key_code]
21*a85c4ab6SAbdelkader Boudih  *   Battery low: [0x25][0x87][0xe0][remote_id][0x00]
22*a85c4ab6SAbdelkader Boudih  *   Key decode:  (byte4 >> 1) & 0x0F -> keymap[index]
23*a85c4ab6SAbdelkader Boudih  *   Two-packet:  bit 6 of key_code (0x40) set -> store index, use on next keydown
24*a85c4ab6SAbdelkader Boudih  *
25*a85c4ab6SAbdelkader Boudih  * Generic IR Protocol (NEC-style):
26*a85c4ab6SAbdelkader Boudih  *   Format:     [0x26][0x7f][0x80][code][~code]
27*a85c4ab6SAbdelkader Boudih  *   Checksum:   code + ~code = 0xFF
28*a85c4ab6SAbdelkader Boudih  *
29*a85c4ab6SAbdelkader Boudih  * NO hardware key-up events -- synthesize via 125ms callout timer.
30*a85c4ab6SAbdelkader Boudih  */
31*a85c4ab6SAbdelkader Boudih 
32*a85c4ab6SAbdelkader Boudih #include <sys/cdefs.h>
33*a85c4ab6SAbdelkader Boudih 
34*a85c4ab6SAbdelkader Boudih #include "opt_hid.h"
35*a85c4ab6SAbdelkader Boudih 
36*a85c4ab6SAbdelkader Boudih #include <sys/param.h>
37*a85c4ab6SAbdelkader Boudih #include <sys/bus.h>
38*a85c4ab6SAbdelkader Boudih #include <sys/callout.h>
39*a85c4ab6SAbdelkader Boudih #include <sys/kernel.h>
40*a85c4ab6SAbdelkader Boudih #include <sys/lock.h>
41*a85c4ab6SAbdelkader Boudih #include <sys/malloc.h>
42*a85c4ab6SAbdelkader Boudih #include <sys/module.h>
43*a85c4ab6SAbdelkader Boudih #include <sys/mutex.h>
44*a85c4ab6SAbdelkader Boudih #include <sys/sysctl.h>
45*a85c4ab6SAbdelkader Boudih 
46*a85c4ab6SAbdelkader Boudih #include <dev/evdev/input.h>
47*a85c4ab6SAbdelkader Boudih #include <dev/evdev/evdev.h>
48*a85c4ab6SAbdelkader Boudih 
49*a85c4ab6SAbdelkader Boudih #define HID_DEBUG_VAR	appleir_debug
50*a85c4ab6SAbdelkader Boudih #include <dev/hid/hid.h>
51*a85c4ab6SAbdelkader Boudih #include <dev/hid/hidbus.h>
52*a85c4ab6SAbdelkader Boudih #include "usbdevs.h"
53*a85c4ab6SAbdelkader Boudih 
54*a85c4ab6SAbdelkader Boudih #ifdef HID_DEBUG
55*a85c4ab6SAbdelkader Boudih static int appleir_debug = 0;
56*a85c4ab6SAbdelkader Boudih 
57*a85c4ab6SAbdelkader Boudih static SYSCTL_NODE(_hw_hid, OID_AUTO, appleir, CTLFLAG_RW, 0,
58*a85c4ab6SAbdelkader Boudih     "Apple IR Remote Control");
59*a85c4ab6SAbdelkader Boudih SYSCTL_INT(_hw_hid_appleir, OID_AUTO, debug, CTLFLAG_RWTUN,
60*a85c4ab6SAbdelkader Boudih     &appleir_debug, 0, "Debug level");
61*a85c4ab6SAbdelkader Boudih #endif
62*a85c4ab6SAbdelkader Boudih 
63*a85c4ab6SAbdelkader Boudih /* Protocol constants */
64*a85c4ab6SAbdelkader Boudih #define	APPLEIR_REPORT_LEN	5
65*a85c4ab6SAbdelkader Boudih #define	APPLEIR_KEY_MASK	0x0F
66*a85c4ab6SAbdelkader Boudih #define	APPLEIR_TWO_PKT_FLAG	0x40	/* bit 6: two-packet command */
67*a85c4ab6SAbdelkader Boudih #define	APPLEIR_KEYUP_TICKS	MAX(1, hz / 8)	/* 125ms */
68*a85c4ab6SAbdelkader Boudih #define	APPLEIR_TWOPKT_TICKS	MAX(1, hz / 4)	/* 250ms */
69*a85c4ab6SAbdelkader Boudih 
70*a85c4ab6SAbdelkader Boudih /* Report type markers (byte 0) */
71*a85c4ab6SAbdelkader Boudih #define	APPLEIR_PKT_KEYDOWN	0x25	/* key down / battery low */
72*a85c4ab6SAbdelkader Boudih #define	APPLEIR_PKT_REPEAT	0x26	/* key repeat / NEC generic */
73*a85c4ab6SAbdelkader Boudih 
74*a85c4ab6SAbdelkader Boudih /* Apple Remote signature (bytes 1-2) */
75*a85c4ab6SAbdelkader Boudih #define	APPLEIR_SIG_HI		0x87
76*a85c4ab6SAbdelkader Boudih #define	APPLEIR_SIG_KEYLO	0xee	/* normal key event */
77*a85c4ab6SAbdelkader Boudih #define	APPLEIR_SIG_BATTLO	0xe0	/* battery low event */
78*a85c4ab6SAbdelkader Boudih 
79*a85c4ab6SAbdelkader Boudih /* Generic IR NEC signature (bytes 1-2) */
80*a85c4ab6SAbdelkader Boudih #define	APPLEIR_NEC_HI		0x7f
81*a85c4ab6SAbdelkader Boudih #define	APPLEIR_NEC_LO		0x80
82*a85c4ab6SAbdelkader Boudih #define	APPLEIR_NEC_CHECKSUM	0xFF	/* code + ~code must equal this */
83*a85c4ab6SAbdelkader Boudih 
84*a85c4ab6SAbdelkader Boudih /*
85*a85c4ab6SAbdelkader Boudih  * Apple IR keymap: 17 entries, index = (key_code >> 1) & 0x0F
86*a85c4ab6SAbdelkader Boudih  * Based on Linux driver (hid-appleir.c) keymap.
87*a85c4ab6SAbdelkader Boudih  */
88*a85c4ab6SAbdelkader Boudih static const uint16_t appleir_keymap[] = {
89*a85c4ab6SAbdelkader Boudih 	KEY_RESERVED,		/* 0x00 */
90*a85c4ab6SAbdelkader Boudih 	KEY_MENU,		/* 0x01 - menu */
91*a85c4ab6SAbdelkader Boudih 	KEY_PLAYPAUSE,		/* 0x02 - play/pause */
92*a85c4ab6SAbdelkader Boudih 	KEY_FORWARD,		/* 0x03 - >> */
93*a85c4ab6SAbdelkader Boudih 	KEY_BACK,		/* 0x04 - << */
94*a85c4ab6SAbdelkader Boudih 	KEY_VOLUMEUP,		/* 0x05 - + */
95*a85c4ab6SAbdelkader Boudih 	KEY_VOLUMEDOWN,		/* 0x06 - - */
96*a85c4ab6SAbdelkader Boudih 	KEY_RESERVED,		/* 0x07 */
97*a85c4ab6SAbdelkader Boudih 	KEY_RESERVED,		/* 0x08 */
98*a85c4ab6SAbdelkader Boudih 	KEY_RESERVED,		/* 0x09 */
99*a85c4ab6SAbdelkader Boudih 	KEY_RESERVED,		/* 0x0A */
100*a85c4ab6SAbdelkader Boudih 	KEY_RESERVED,		/* 0x0B */
101*a85c4ab6SAbdelkader Boudih 	KEY_RESERVED,		/* 0x0C */
102*a85c4ab6SAbdelkader Boudih 	KEY_RESERVED,		/* 0x0D */
103*a85c4ab6SAbdelkader Boudih 	KEY_ENTER,		/* 0x0E - middle button (two-packet) */
104*a85c4ab6SAbdelkader Boudih 	KEY_PLAYPAUSE,		/* 0x0F - play/pause (two-packet) */
105*a85c4ab6SAbdelkader Boudih 	KEY_RESERVED,		/* 0x10 - out of range guard */
106*a85c4ab6SAbdelkader Boudih };
107*a85c4ab6SAbdelkader Boudih #define APPLEIR_NKEYS	(nitems(appleir_keymap))
108*a85c4ab6SAbdelkader Boudih 
109*a85c4ab6SAbdelkader Boudih /*
110*a85c4ab6SAbdelkader Boudih  * Generic IR keymap (NEC protocol codes).
111*a85c4ab6SAbdelkader Boudih  * Maps raw NEC codes to evdev KEY_* codes.
112*a85c4ab6SAbdelkader Boudih  */
113*a85c4ab6SAbdelkader Boudih struct generic_ir_map {
114*a85c4ab6SAbdelkader Boudih 	uint8_t		code;		/* NEC IR code */
115*a85c4ab6SAbdelkader Boudih 	uint16_t	key;		/* evdev KEY_* */
116*a85c4ab6SAbdelkader Boudih };
117*a85c4ab6SAbdelkader Boudih 
118*a85c4ab6SAbdelkader Boudih static const struct generic_ir_map generic_keymap[] = {
119*a85c4ab6SAbdelkader Boudih 	{ 0xe1, KEY_VOLUMEUP },
120*a85c4ab6SAbdelkader Boudih 	{ 0xe9, KEY_VOLUMEDOWN },
121*a85c4ab6SAbdelkader Boudih 	{ 0xed, KEY_CHANNELUP },
122*a85c4ab6SAbdelkader Boudih 	{ 0xf3, KEY_CHANNELDOWN },
123*a85c4ab6SAbdelkader Boudih 	{ 0xf5, KEY_PLAYPAUSE },
124*a85c4ab6SAbdelkader Boudih 	{ 0xf9, KEY_POWER },
125*a85c4ab6SAbdelkader Boudih 	{ 0xfb, KEY_MUTE },
126*a85c4ab6SAbdelkader Boudih 	{ 0xfe, KEY_OK },
127*a85c4ab6SAbdelkader Boudih };
128*a85c4ab6SAbdelkader Boudih #define GENERIC_NKEYS	(nitems(generic_keymap))
129*a85c4ab6SAbdelkader Boudih 
130*a85c4ab6SAbdelkader Boudih static uint16_t
generic_ir_lookup(uint8_t code)131*a85c4ab6SAbdelkader Boudih generic_ir_lookup(uint8_t code)
132*a85c4ab6SAbdelkader Boudih {
133*a85c4ab6SAbdelkader Boudih 	int i;
134*a85c4ab6SAbdelkader Boudih 
135*a85c4ab6SAbdelkader Boudih 	for (i = 0; i < GENERIC_NKEYS; i++) {
136*a85c4ab6SAbdelkader Boudih 		if (generic_keymap[i].code == code)
137*a85c4ab6SAbdelkader Boudih 			return (generic_keymap[i].key);
138*a85c4ab6SAbdelkader Boudih 	}
139*a85c4ab6SAbdelkader Boudih 	return (KEY_RESERVED);
140*a85c4ab6SAbdelkader Boudih }
141*a85c4ab6SAbdelkader Boudih 
142*a85c4ab6SAbdelkader Boudih struct appleir_softc {
143*a85c4ab6SAbdelkader Boudih 	device_t		sc_dev;
144*a85c4ab6SAbdelkader Boudih 	struct mtx		sc_mtx;		/* protects below + callout */
145*a85c4ab6SAbdelkader Boudih 	struct evdev_dev	*sc_evdev;
146*a85c4ab6SAbdelkader Boudih 	struct callout		sc_co;		/* key-up timer */
147*a85c4ab6SAbdelkader Boudih 	struct callout		sc_twoco;	/* two-packet timeout */
148*a85c4ab6SAbdelkader Boudih 	uint16_t		sc_current_key;	/* evdev keycode (0=none) */
149*a85c4ab6SAbdelkader Boudih 	int			sc_prev_key_idx;/* two-packet state (0=none) */
150*a85c4ab6SAbdelkader Boudih 	bool			sc_batt_warned;
151*a85c4ab6SAbdelkader Boudih };
152*a85c4ab6SAbdelkader Boudih 
153*a85c4ab6SAbdelkader Boudih 
154*a85c4ab6SAbdelkader Boudih /*
155*a85c4ab6SAbdelkader Boudih  * Callout: synthesize key-up event (no hardware key-up from remote).
156*a85c4ab6SAbdelkader Boudih  * Runs with sc_mtx held (callout_init_mtx).
157*a85c4ab6SAbdelkader Boudih  */
158*a85c4ab6SAbdelkader Boudih static void
appleir_keyup(void * arg)159*a85c4ab6SAbdelkader Boudih appleir_keyup(void *arg)
160*a85c4ab6SAbdelkader Boudih {
161*a85c4ab6SAbdelkader Boudih 	struct appleir_softc *sc = arg;
162*a85c4ab6SAbdelkader Boudih 
163*a85c4ab6SAbdelkader Boudih 	mtx_assert(&sc->sc_mtx, MA_OWNED);
164*a85c4ab6SAbdelkader Boudih 
165*a85c4ab6SAbdelkader Boudih 	if (sc->sc_current_key != 0) {
166*a85c4ab6SAbdelkader Boudih 		evdev_push_key(sc->sc_evdev, sc->sc_current_key, 0);
167*a85c4ab6SAbdelkader Boudih 		evdev_sync(sc->sc_evdev);
168*a85c4ab6SAbdelkader Boudih 		sc->sc_current_key = 0;
169*a85c4ab6SAbdelkader Boudih 		sc->sc_prev_key_idx = 0;
170*a85c4ab6SAbdelkader Boudih 	}
171*a85c4ab6SAbdelkader Boudih }
172*a85c4ab6SAbdelkader Boudih 
173*a85c4ab6SAbdelkader Boudih static void
appleir_twopacket_timeout(void * arg)174*a85c4ab6SAbdelkader Boudih appleir_twopacket_timeout(void *arg)
175*a85c4ab6SAbdelkader Boudih {
176*a85c4ab6SAbdelkader Boudih 	struct appleir_softc *sc = arg;
177*a85c4ab6SAbdelkader Boudih 
178*a85c4ab6SAbdelkader Boudih 	mtx_assert(&sc->sc_mtx, MA_OWNED);
179*a85c4ab6SAbdelkader Boudih 	sc->sc_prev_key_idx = 0;
180*a85c4ab6SAbdelkader Boudih }
181*a85c4ab6SAbdelkader Boudih 
182*a85c4ab6SAbdelkader Boudih /*
183*a85c4ab6SAbdelkader Boudih  * Process 5-byte HID interrupt report.
184*a85c4ab6SAbdelkader Boudih  * Called from hidbus interrupt context.
185*a85c4ab6SAbdelkader Boudih  */
186*a85c4ab6SAbdelkader Boudih static void
appleir_intr(void * context,void * data,hid_size_t len)187*a85c4ab6SAbdelkader Boudih appleir_intr(void *context, void *data, hid_size_t len)
188*a85c4ab6SAbdelkader Boudih {
189*a85c4ab6SAbdelkader Boudih 	struct appleir_softc *sc = context;
190*a85c4ab6SAbdelkader Boudih 	uint8_t *buf = data;
191*a85c4ab6SAbdelkader Boudih 	uint8_t report[APPLEIR_REPORT_LEN];
192*a85c4ab6SAbdelkader Boudih 	int index;
193*a85c4ab6SAbdelkader Boudih 	uint16_t new_key;
194*a85c4ab6SAbdelkader Boudih 
195*a85c4ab6SAbdelkader Boudih 	if (len != APPLEIR_REPORT_LEN) {
196*a85c4ab6SAbdelkader Boudih 		DPRINTFN(1, "bad report len: %zu\n", (size_t)len);
197*a85c4ab6SAbdelkader Boudih 		return;
198*a85c4ab6SAbdelkader Boudih 	}
199*a85c4ab6SAbdelkader Boudih 
200*a85c4ab6SAbdelkader Boudih 	memcpy(report, buf, APPLEIR_REPORT_LEN);
201*a85c4ab6SAbdelkader Boudih 
202*a85c4ab6SAbdelkader Boudih 	mtx_lock(&sc->sc_mtx);
203*a85c4ab6SAbdelkader Boudih 
204*a85c4ab6SAbdelkader Boudih 	/* Battery low: [KEYDOWN][SIG_HI][SIG_BATTLO] -- log and ignore */
205*a85c4ab6SAbdelkader Boudih 	if (report[0] == APPLEIR_PKT_KEYDOWN &&
206*a85c4ab6SAbdelkader Boudih 	    report[1] == APPLEIR_SIG_HI && report[2] == APPLEIR_SIG_BATTLO) {
207*a85c4ab6SAbdelkader Boudih 		if (!sc->sc_batt_warned) {
208*a85c4ab6SAbdelkader Boudih 			device_printf(sc->sc_dev,
209*a85c4ab6SAbdelkader Boudih 			    "remote battery may be low\n");
210*a85c4ab6SAbdelkader Boudih 			sc->sc_batt_warned = true;
211*a85c4ab6SAbdelkader Boudih 		}
212*a85c4ab6SAbdelkader Boudih 		goto done;
213*a85c4ab6SAbdelkader Boudih 	}
214*a85c4ab6SAbdelkader Boudih 
215*a85c4ab6SAbdelkader Boudih 	/* Key down: [KEYDOWN][SIG_HI][SIG_KEYLO][remote_id][key_code] */
216*a85c4ab6SAbdelkader Boudih 	if (report[0] == APPLEIR_PKT_KEYDOWN &&
217*a85c4ab6SAbdelkader Boudih 	    report[1] == APPLEIR_SIG_HI && report[2] == APPLEIR_SIG_KEYLO) {
218*a85c4ab6SAbdelkader Boudih 		/* Release previous key if held */
219*a85c4ab6SAbdelkader Boudih 		if (sc->sc_current_key != 0) {
220*a85c4ab6SAbdelkader Boudih 			evdev_push_key(sc->sc_evdev, sc->sc_current_key, 0);
221*a85c4ab6SAbdelkader Boudih 			evdev_sync(sc->sc_evdev);
222*a85c4ab6SAbdelkader Boudih 			sc->sc_current_key = 0;
223*a85c4ab6SAbdelkader Boudih 		}
224*a85c4ab6SAbdelkader Boudih 
225*a85c4ab6SAbdelkader Boudih 		if (sc->sc_prev_key_idx > 0) {
226*a85c4ab6SAbdelkader Boudih 			/* Second packet of a two-packet command */
227*a85c4ab6SAbdelkader Boudih 			index = sc->sc_prev_key_idx;
228*a85c4ab6SAbdelkader Boudih 			sc->sc_prev_key_idx = 0;
229*a85c4ab6SAbdelkader Boudih 			callout_stop(&sc->sc_twoco);
230*a85c4ab6SAbdelkader Boudih 		} else if (report[4] & APPLEIR_TWO_PKT_FLAG) {
231*a85c4ab6SAbdelkader Boudih 			/* First packet of a two-packet command -- wait for next */
232*a85c4ab6SAbdelkader Boudih 			sc->sc_prev_key_idx = (report[4] >> 1) & APPLEIR_KEY_MASK;
233*a85c4ab6SAbdelkader Boudih 			callout_reset(&sc->sc_twoco, APPLEIR_TWOPKT_TICKS,
234*a85c4ab6SAbdelkader Boudih 			    appleir_twopacket_timeout, sc);
235*a85c4ab6SAbdelkader Boudih 			goto done;
236*a85c4ab6SAbdelkader Boudih 		} else {
237*a85c4ab6SAbdelkader Boudih 			index = (report[4] >> 1) & APPLEIR_KEY_MASK;
238*a85c4ab6SAbdelkader Boudih 		}
239*a85c4ab6SAbdelkader Boudih 
240*a85c4ab6SAbdelkader Boudih 		new_key = (index < APPLEIR_NKEYS) ?
241*a85c4ab6SAbdelkader Boudih 		    appleir_keymap[index] : KEY_RESERVED;
242*a85c4ab6SAbdelkader Boudih 		if (new_key != KEY_RESERVED) {
243*a85c4ab6SAbdelkader Boudih 			sc->sc_current_key = new_key;
244*a85c4ab6SAbdelkader Boudih 			evdev_push_key(sc->sc_evdev, new_key, 1);
245*a85c4ab6SAbdelkader Boudih 			evdev_sync(sc->sc_evdev);
246*a85c4ab6SAbdelkader Boudih 			callout_reset(&sc->sc_co, APPLEIR_KEYUP_TICKS,
247*a85c4ab6SAbdelkader Boudih 			    appleir_keyup, sc);
248*a85c4ab6SAbdelkader Boudih 		}
249*a85c4ab6SAbdelkader Boudih 		goto done;
250*a85c4ab6SAbdelkader Boudih 	}
251*a85c4ab6SAbdelkader Boudih 
252*a85c4ab6SAbdelkader Boudih 	/* Key repeat: [REPEAT][SIG_HI][SIG_KEYLO][remote_id][key_code] */
253*a85c4ab6SAbdelkader Boudih 	if (report[0] == APPLEIR_PKT_REPEAT &&
254*a85c4ab6SAbdelkader Boudih 	    report[1] == APPLEIR_SIG_HI && report[2] == APPLEIR_SIG_KEYLO) {
255*a85c4ab6SAbdelkader Boudih 		uint16_t repeat_key;
256*a85c4ab6SAbdelkader Boudih 		int repeat_idx;
257*a85c4ab6SAbdelkader Boudih 
258*a85c4ab6SAbdelkader Boudih 		if (sc->sc_prev_key_idx > 0)
259*a85c4ab6SAbdelkader Boudih 			goto done;
260*a85c4ab6SAbdelkader Boudih 		if (report[4] & APPLEIR_TWO_PKT_FLAG)
261*a85c4ab6SAbdelkader Boudih 			goto done;
262*a85c4ab6SAbdelkader Boudih 
263*a85c4ab6SAbdelkader Boudih 		repeat_idx = (report[4] >> 1) & APPLEIR_KEY_MASK;
264*a85c4ab6SAbdelkader Boudih 		repeat_key = (repeat_idx < APPLEIR_NKEYS) ?
265*a85c4ab6SAbdelkader Boudih 		    appleir_keymap[repeat_idx] : KEY_RESERVED;
266*a85c4ab6SAbdelkader Boudih 		if (repeat_key == KEY_RESERVED ||
267*a85c4ab6SAbdelkader Boudih 		    repeat_key != sc->sc_current_key)
268*a85c4ab6SAbdelkader Boudih 			goto done;
269*a85c4ab6SAbdelkader Boudih 
270*a85c4ab6SAbdelkader Boudih 		evdev_push_key(sc->sc_evdev, repeat_key, 1);
271*a85c4ab6SAbdelkader Boudih 		evdev_sync(sc->sc_evdev);
272*a85c4ab6SAbdelkader Boudih 		callout_reset(&sc->sc_co, APPLEIR_KEYUP_TICKS,
273*a85c4ab6SAbdelkader Boudih 		    appleir_keyup, sc);
274*a85c4ab6SAbdelkader Boudih 		goto done;
275*a85c4ab6SAbdelkader Boudih 	}
276*a85c4ab6SAbdelkader Boudih 
277*a85c4ab6SAbdelkader Boudih 	/* Generic IR (NEC protocol): [REPEAT][NEC_HI][NEC_LO][code][~code] */
278*a85c4ab6SAbdelkader Boudih 	if (report[0] == APPLEIR_PKT_REPEAT &&
279*a85c4ab6SAbdelkader Boudih 	    report[1] == APPLEIR_NEC_HI && report[2] == APPLEIR_NEC_LO) {
280*a85c4ab6SAbdelkader Boudih 		uint8_t code = report[3];
281*a85c4ab6SAbdelkader Boudih 		uint8_t checksum = report[4];
282*a85c4ab6SAbdelkader Boudih 
283*a85c4ab6SAbdelkader Boudih 		sc->sc_prev_key_idx = 0;
284*a85c4ab6SAbdelkader Boudih 		callout_stop(&sc->sc_twoco);
285*a85c4ab6SAbdelkader Boudih 
286*a85c4ab6SAbdelkader Boudih 		if ((uint8_t)(code + checksum) != APPLEIR_NEC_CHECKSUM) {
287*a85c4ab6SAbdelkader Boudih 			DPRINTFN(1, "generic IR: bad checksum %02x+%02x\n",
288*a85c4ab6SAbdelkader Boudih 			    code, checksum);
289*a85c4ab6SAbdelkader Boudih 			goto done;
290*a85c4ab6SAbdelkader Boudih 		}
291*a85c4ab6SAbdelkader Boudih 
292*a85c4ab6SAbdelkader Boudih 		new_key = generic_ir_lookup(code);
293*a85c4ab6SAbdelkader Boudih 		if (new_key == KEY_RESERVED)
294*a85c4ab6SAbdelkader Boudih 			goto done;
295*a85c4ab6SAbdelkader Boudih 
296*a85c4ab6SAbdelkader Boudih 		if (sc->sc_current_key != new_key) {
297*a85c4ab6SAbdelkader Boudih 			if (sc->sc_current_key != 0)
298*a85c4ab6SAbdelkader Boudih 				evdev_push_key(sc->sc_evdev,
299*a85c4ab6SAbdelkader Boudih 				    sc->sc_current_key, 0);
300*a85c4ab6SAbdelkader Boudih 			sc->sc_current_key = new_key;
301*a85c4ab6SAbdelkader Boudih 			evdev_push_key(sc->sc_evdev, new_key, 1);
302*a85c4ab6SAbdelkader Boudih 			evdev_sync(sc->sc_evdev);
303*a85c4ab6SAbdelkader Boudih 		} else {
304*a85c4ab6SAbdelkader Boudih 			evdev_push_key(sc->sc_evdev, new_key, 1);
305*a85c4ab6SAbdelkader Boudih 			evdev_sync(sc->sc_evdev);
306*a85c4ab6SAbdelkader Boudih 		}
307*a85c4ab6SAbdelkader Boudih 		callout_reset(&sc->sc_co, APPLEIR_KEYUP_TICKS,
308*a85c4ab6SAbdelkader Boudih 		    appleir_keyup, sc);
309*a85c4ab6SAbdelkader Boudih 		goto done;
310*a85c4ab6SAbdelkader Boudih 	}
311*a85c4ab6SAbdelkader Boudih 
312*a85c4ab6SAbdelkader Boudih 	DPRINTFN(1, "unknown report: %02x %02x %02x\n",
313*a85c4ab6SAbdelkader Boudih 	    report[0], report[1], report[2]);
314*a85c4ab6SAbdelkader Boudih 
315*a85c4ab6SAbdelkader Boudih done:
316*a85c4ab6SAbdelkader Boudih 	mtx_unlock(&sc->sc_mtx);
317*a85c4ab6SAbdelkader Boudih }
318*a85c4ab6SAbdelkader Boudih 
319*a85c4ab6SAbdelkader Boudih /* Apple IR receiver device IDs */
320*a85c4ab6SAbdelkader Boudih static const struct hid_device_id appleir_devs[] = {
321*a85c4ab6SAbdelkader Boudih 	{ HID_BVP(BUS_USB, USB_VENDOR_APPLE, 0x8240) },
322*a85c4ab6SAbdelkader Boudih 	{ HID_BVP(BUS_USB, USB_VENDOR_APPLE, 0x8241) },
323*a85c4ab6SAbdelkader Boudih 	{ HID_BVP(BUS_USB, USB_VENDOR_APPLE, 0x8242) },
324*a85c4ab6SAbdelkader Boudih 	{ HID_BVP(BUS_USB, USB_VENDOR_APPLE, 0x8243) },
325*a85c4ab6SAbdelkader Boudih 	{ HID_BVP(BUS_USB, USB_VENDOR_APPLE, 0x1440) },
326*a85c4ab6SAbdelkader Boudih };
327*a85c4ab6SAbdelkader Boudih 
328*a85c4ab6SAbdelkader Boudih static int
appleir_probe(device_t dev)329*a85c4ab6SAbdelkader Boudih appleir_probe(device_t dev)
330*a85c4ab6SAbdelkader Boudih {
331*a85c4ab6SAbdelkader Boudih 	int error;
332*a85c4ab6SAbdelkader Boudih 
333*a85c4ab6SAbdelkader Boudih 	error = HIDBUS_LOOKUP_DRIVER_INFO(dev, appleir_devs);
334*a85c4ab6SAbdelkader Boudih 	if (error != 0)
335*a85c4ab6SAbdelkader Boudih 		return (error);
336*a85c4ab6SAbdelkader Boudih 
337*a85c4ab6SAbdelkader Boudih 	/* Only attach to first top-level collection (TLC index 0) */
338*a85c4ab6SAbdelkader Boudih 	if (hidbus_get_index(dev) != 0)
339*a85c4ab6SAbdelkader Boudih 		return (ENXIO);
340*a85c4ab6SAbdelkader Boudih 
341*a85c4ab6SAbdelkader Boudih 	hidbus_set_desc(dev, "Apple IR Receiver");
342*a85c4ab6SAbdelkader Boudih 	return (BUS_PROBE_DEFAULT);
343*a85c4ab6SAbdelkader Boudih }
344*a85c4ab6SAbdelkader Boudih 
345*a85c4ab6SAbdelkader Boudih static int
appleir_attach(device_t dev)346*a85c4ab6SAbdelkader Boudih appleir_attach(device_t dev)
347*a85c4ab6SAbdelkader Boudih {
348*a85c4ab6SAbdelkader Boudih 	struct appleir_softc *sc = device_get_softc(dev);
349*a85c4ab6SAbdelkader Boudih 	const struct hid_device_info *hw;
350*a85c4ab6SAbdelkader Boudih 	int i, error;
351*a85c4ab6SAbdelkader Boudih 
352*a85c4ab6SAbdelkader Boudih 	sc->sc_dev = dev;
353*a85c4ab6SAbdelkader Boudih 	hw = hid_get_device_info(dev);
354*a85c4ab6SAbdelkader Boudih 	sc->sc_current_key = 0;
355*a85c4ab6SAbdelkader Boudih 	sc->sc_prev_key_idx = 0;
356*a85c4ab6SAbdelkader Boudih 	sc->sc_batt_warned = false;
357*a85c4ab6SAbdelkader Boudih 	mtx_init(&sc->sc_mtx, "appleir", NULL, MTX_DEF);
358*a85c4ab6SAbdelkader Boudih 	callout_init_mtx(&sc->sc_co, &sc->sc_mtx, 0);
359*a85c4ab6SAbdelkader Boudih 	callout_init_mtx(&sc->sc_twoco, &sc->sc_mtx, 0);
360*a85c4ab6SAbdelkader Boudih 
361*a85c4ab6SAbdelkader Boudih 	sc->sc_evdev = evdev_alloc();
362*a85c4ab6SAbdelkader Boudih 	evdev_set_name(sc->sc_evdev, device_get_desc(dev));
363*a85c4ab6SAbdelkader Boudih 	evdev_set_phys(sc->sc_evdev, device_get_nameunit(dev));
364*a85c4ab6SAbdelkader Boudih 	evdev_set_id(sc->sc_evdev, hw->idBus, hw->idVendor, hw->idProduct,
365*a85c4ab6SAbdelkader Boudih 	    hw->idVersion);
366*a85c4ab6SAbdelkader Boudih 	evdev_set_serial(sc->sc_evdev, hw->serial);
367*a85c4ab6SAbdelkader Boudih 	evdev_support_event(sc->sc_evdev, EV_SYN);
368*a85c4ab6SAbdelkader Boudih 	evdev_support_event(sc->sc_evdev, EV_KEY);
369*a85c4ab6SAbdelkader Boudih 	evdev_support_event(sc->sc_evdev, EV_REP);
370*a85c4ab6SAbdelkader Boudih 
371*a85c4ab6SAbdelkader Boudih 	for (i = 0; i < APPLEIR_NKEYS; i++) {
372*a85c4ab6SAbdelkader Boudih 		if (appleir_keymap[i] != KEY_RESERVED)
373*a85c4ab6SAbdelkader Boudih 			evdev_support_key(sc->sc_evdev, appleir_keymap[i]);
374*a85c4ab6SAbdelkader Boudih 	}
375*a85c4ab6SAbdelkader Boudih 	for (i = 0; i < GENERIC_NKEYS; i++)
376*a85c4ab6SAbdelkader Boudih 		evdev_support_key(sc->sc_evdev, generic_keymap[i].key);
377*a85c4ab6SAbdelkader Boudih 
378*a85c4ab6SAbdelkader Boudih 	error = evdev_register_mtx(sc->sc_evdev, &sc->sc_mtx);
379*a85c4ab6SAbdelkader Boudih 	if (error != 0) {
380*a85c4ab6SAbdelkader Boudih 		device_printf(dev, "evdev_register_mtx failed: %d\n", error);
381*a85c4ab6SAbdelkader Boudih 		goto fail;
382*a85c4ab6SAbdelkader Boudih 	}
383*a85c4ab6SAbdelkader Boudih 
384*a85c4ab6SAbdelkader Boudih 	hidbus_set_intr(dev, appleir_intr, sc);
385*a85c4ab6SAbdelkader Boudih 
386*a85c4ab6SAbdelkader Boudih 	error = hid_intr_start(dev);
387*a85c4ab6SAbdelkader Boudih 	if (error != 0) {
388*a85c4ab6SAbdelkader Boudih 		device_printf(dev, "hid_intr_start failed: %d\n", error);
389*a85c4ab6SAbdelkader Boudih 		goto fail;
390*a85c4ab6SAbdelkader Boudih 	}
391*a85c4ab6SAbdelkader Boudih 
392*a85c4ab6SAbdelkader Boudih 	return (0);
393*a85c4ab6SAbdelkader Boudih 
394*a85c4ab6SAbdelkader Boudih fail:
395*a85c4ab6SAbdelkader Boudih 	if (sc->sc_evdev != NULL)
396*a85c4ab6SAbdelkader Boudih 		evdev_free(sc->sc_evdev);
397*a85c4ab6SAbdelkader Boudih 	callout_drain(&sc->sc_co);
398*a85c4ab6SAbdelkader Boudih 	callout_drain(&sc->sc_twoco);
399*a85c4ab6SAbdelkader Boudih 	mtx_destroy(&sc->sc_mtx);
400*a85c4ab6SAbdelkader Boudih 	return (error);
401*a85c4ab6SAbdelkader Boudih }
402*a85c4ab6SAbdelkader Boudih 
403*a85c4ab6SAbdelkader Boudih static int
appleir_detach(device_t dev)404*a85c4ab6SAbdelkader Boudih appleir_detach(device_t dev)
405*a85c4ab6SAbdelkader Boudih {
406*a85c4ab6SAbdelkader Boudih 	struct appleir_softc *sc = device_get_softc(dev);
407*a85c4ab6SAbdelkader Boudih 	int error;
408*a85c4ab6SAbdelkader Boudih 
409*a85c4ab6SAbdelkader Boudih 	error = hid_intr_stop(dev);
410*a85c4ab6SAbdelkader Boudih 	if (error != 0) {
411*a85c4ab6SAbdelkader Boudih 		device_printf(dev, "hid_intr_stop failed: %d\n", error);
412*a85c4ab6SAbdelkader Boudih 		return (error);
413*a85c4ab6SAbdelkader Boudih 	}
414*a85c4ab6SAbdelkader Boudih 	callout_drain(&sc->sc_co);
415*a85c4ab6SAbdelkader Boudih 	callout_drain(&sc->sc_twoco);
416*a85c4ab6SAbdelkader Boudih 	evdev_free(sc->sc_evdev);
417*a85c4ab6SAbdelkader Boudih 	mtx_destroy(&sc->sc_mtx);
418*a85c4ab6SAbdelkader Boudih 
419*a85c4ab6SAbdelkader Boudih 	return (0);
420*a85c4ab6SAbdelkader Boudih }
421*a85c4ab6SAbdelkader Boudih 
422*a85c4ab6SAbdelkader Boudih static device_method_t appleir_methods[] = {
423*a85c4ab6SAbdelkader Boudih 	DEVMETHOD(device_probe,		appleir_probe),
424*a85c4ab6SAbdelkader Boudih 	DEVMETHOD(device_attach,	appleir_attach),
425*a85c4ab6SAbdelkader Boudih 	DEVMETHOD(device_detach,	appleir_detach),
426*a85c4ab6SAbdelkader Boudih 	DEVMETHOD_END
427*a85c4ab6SAbdelkader Boudih };
428*a85c4ab6SAbdelkader Boudih 
429*a85c4ab6SAbdelkader Boudih static driver_t appleir_driver = {
430*a85c4ab6SAbdelkader Boudih 	"appleir",
431*a85c4ab6SAbdelkader Boudih 	appleir_methods,
432*a85c4ab6SAbdelkader Boudih 	sizeof(struct appleir_softc)
433*a85c4ab6SAbdelkader Boudih };
434*a85c4ab6SAbdelkader Boudih 
435*a85c4ab6SAbdelkader Boudih DRIVER_MODULE(appleir, hidbus, appleir_driver, NULL, NULL);
436*a85c4ab6SAbdelkader Boudih MODULE_DEPEND(appleir, hid, 1, 1, 1);
437*a85c4ab6SAbdelkader Boudih MODULE_DEPEND(appleir, hidbus, 1, 1, 1);
438*a85c4ab6SAbdelkader Boudih MODULE_DEPEND(appleir, evdev, 1, 1, 1);
439*a85c4ab6SAbdelkader Boudih MODULE_VERSION(appleir, 1);
440*a85c4ab6SAbdelkader Boudih HID_PNP_INFO(appleir_devs);
441