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