#include <sys/cdefs.h>
#include "opt_hid.h"
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/callout.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/sysctl.h>
#include <dev/evdev/input.h>
#include <dev/evdev/evdev.h>
#define HID_DEBUG_VAR appleir_debug
#include <dev/hid/hid.h>
#include <dev/hid/hidbus.h>
#include "usbdevs.h"
#ifdef HID_DEBUG
static int appleir_debug = 0;
static SYSCTL_NODE(_hw_hid, OID_AUTO, appleir, CTLFLAG_RW, 0,
"Apple IR Remote Control");
SYSCTL_INT(_hw_hid_appleir, OID_AUTO, debug, CTLFLAG_RWTUN,
&appleir_debug, 0, "Debug level");
#endif
#define APPLEIR_REPORT_LEN 5
#define APPLEIR_KEY_MASK 0x0F
#define APPLEIR_TWO_PKT_FLAG 0x40
#define APPLEIR_KEYUP_TICKS MAX(1, hz / 8)
#define APPLEIR_TWOPKT_TICKS MAX(1, hz / 4)
#define APPLEIR_PKT_KEYDOWN 0x25
#define APPLEIR_PKT_REPEAT 0x26
#define APPLEIR_SIG_HI 0x87
#define APPLEIR_SIG_KEYLO 0xee
#define APPLEIR_SIG_BATTLO 0xe0
#define APPLEIR_NEC_HI 0x7f
#define APPLEIR_NEC_LO 0x80
#define APPLEIR_NEC_CHECKSUM 0xFF
static const uint16_t appleir_keymap[] = {
KEY_RESERVED,
KEY_MENU,
KEY_PLAYPAUSE,
KEY_FORWARD,
KEY_BACK,
KEY_VOLUMEUP,
KEY_VOLUMEDOWN,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_ENTER,
KEY_PLAYPAUSE,
KEY_RESERVED,
};
#define APPLEIR_NKEYS (nitems(appleir_keymap))
struct generic_ir_map {
uint8_t code;
uint16_t key;
};
static const struct generic_ir_map generic_keymap[] = {
{ 0xe1, KEY_VOLUMEUP },
{ 0xe9, KEY_VOLUMEDOWN },
{ 0xed, KEY_CHANNELUP },
{ 0xf3, KEY_CHANNELDOWN },
{ 0xf5, KEY_PLAYPAUSE },
{ 0xf9, KEY_POWER },
{ 0xfb, KEY_MUTE },
{ 0xfe, KEY_OK },
};
#define GENERIC_NKEYS (nitems(generic_keymap))
static uint16_t
generic_ir_lookup(uint8_t code)
{
int i;
for (i = 0; i < GENERIC_NKEYS; i++) {
if (generic_keymap[i].code == code)
return (generic_keymap[i].key);
}
return (KEY_RESERVED);
}
struct appleir_softc {
device_t sc_dev;
struct mtx sc_mtx;
struct evdev_dev *sc_evdev;
struct callout sc_co;
struct callout sc_twoco;
uint16_t sc_current_key;
int sc_prev_key_idx;
bool sc_batt_warned;
};
static void
appleir_keyup(void *arg)
{
struct appleir_softc *sc = arg;
mtx_assert(&sc->sc_mtx, MA_OWNED);
if (sc->sc_current_key != 0) {
evdev_push_key(sc->sc_evdev, sc->sc_current_key, 0);
evdev_sync(sc->sc_evdev);
sc->sc_current_key = 0;
sc->sc_prev_key_idx = 0;
}
}
static void
appleir_twopacket_timeout(void *arg)
{
struct appleir_softc *sc = arg;
mtx_assert(&sc->sc_mtx, MA_OWNED);
sc->sc_prev_key_idx = 0;
}
static void
appleir_intr(void *context, void *data, hid_size_t len)
{
struct appleir_softc *sc = context;
uint8_t *buf = data;
uint8_t report[APPLEIR_REPORT_LEN];
int index;
uint16_t new_key;
if (len != APPLEIR_REPORT_LEN) {
DPRINTFN(1, "bad report len: %zu\n", (size_t)len);
return;
}
memcpy(report, buf, APPLEIR_REPORT_LEN);
mtx_lock(&sc->sc_mtx);
if (report[0] == APPLEIR_PKT_KEYDOWN &&
report[1] == APPLEIR_SIG_HI && report[2] == APPLEIR_SIG_BATTLO) {
if (!sc->sc_batt_warned) {
device_printf(sc->sc_dev,
"remote battery may be low\n");
sc->sc_batt_warned = true;
}
goto done;
}
if (report[0] == APPLEIR_PKT_KEYDOWN &&
report[1] == APPLEIR_SIG_HI && report[2] == APPLEIR_SIG_KEYLO) {
if (sc->sc_current_key != 0) {
evdev_push_key(sc->sc_evdev, sc->sc_current_key, 0);
evdev_sync(sc->sc_evdev);
sc->sc_current_key = 0;
}
if (sc->sc_prev_key_idx > 0) {
index = sc->sc_prev_key_idx;
sc->sc_prev_key_idx = 0;
callout_stop(&sc->sc_twoco);
} else if (report[4] & APPLEIR_TWO_PKT_FLAG) {
sc->sc_prev_key_idx = (report[4] >> 1) & APPLEIR_KEY_MASK;
callout_reset(&sc->sc_twoco, APPLEIR_TWOPKT_TICKS,
appleir_twopacket_timeout, sc);
goto done;
} else {
index = (report[4] >> 1) & APPLEIR_KEY_MASK;
}
new_key = (index < APPLEIR_NKEYS) ?
appleir_keymap[index] : KEY_RESERVED;
if (new_key != KEY_RESERVED) {
sc->sc_current_key = new_key;
evdev_push_key(sc->sc_evdev, new_key, 1);
evdev_sync(sc->sc_evdev);
callout_reset(&sc->sc_co, APPLEIR_KEYUP_TICKS,
appleir_keyup, sc);
}
goto done;
}
if (report[0] == APPLEIR_PKT_REPEAT &&
report[1] == APPLEIR_SIG_HI && report[2] == APPLEIR_SIG_KEYLO) {
uint16_t repeat_key;
int repeat_idx;
if (sc->sc_prev_key_idx > 0)
goto done;
if (report[4] & APPLEIR_TWO_PKT_FLAG)
goto done;
repeat_idx = (report[4] >> 1) & APPLEIR_KEY_MASK;
repeat_key = (repeat_idx < APPLEIR_NKEYS) ?
appleir_keymap[repeat_idx] : KEY_RESERVED;
if (repeat_key == KEY_RESERVED ||
repeat_key != sc->sc_current_key)
goto done;
evdev_push_key(sc->sc_evdev, repeat_key, 1);
evdev_sync(sc->sc_evdev);
callout_reset(&sc->sc_co, APPLEIR_KEYUP_TICKS,
appleir_keyup, sc);
goto done;
}
if (report[0] == APPLEIR_PKT_REPEAT &&
report[1] == APPLEIR_NEC_HI && report[2] == APPLEIR_NEC_LO) {
uint8_t code = report[3];
uint8_t checksum = report[4];
sc->sc_prev_key_idx = 0;
callout_stop(&sc->sc_twoco);
if ((uint8_t)(code + checksum) != APPLEIR_NEC_CHECKSUM) {
DPRINTFN(1, "generic IR: bad checksum %02x+%02x\n",
code, checksum);
goto done;
}
new_key = generic_ir_lookup(code);
if (new_key == KEY_RESERVED)
goto done;
if (sc->sc_current_key != new_key) {
if (sc->sc_current_key != 0)
evdev_push_key(sc->sc_evdev,
sc->sc_current_key, 0);
sc->sc_current_key = new_key;
evdev_push_key(sc->sc_evdev, new_key, 1);
evdev_sync(sc->sc_evdev);
} else {
evdev_push_key(sc->sc_evdev, new_key, 1);
evdev_sync(sc->sc_evdev);
}
callout_reset(&sc->sc_co, APPLEIR_KEYUP_TICKS,
appleir_keyup, sc);
goto done;
}
DPRINTFN(1, "unknown report: %02x %02x %02x\n",
report[0], report[1], report[2]);
done:
mtx_unlock(&sc->sc_mtx);
}
static const struct hid_device_id appleir_devs[] = {
{ HID_BVP(BUS_USB, USB_VENDOR_APPLE, 0x8240) },
{ HID_BVP(BUS_USB, USB_VENDOR_APPLE, 0x8241) },
{ HID_BVP(BUS_USB, USB_VENDOR_APPLE, 0x8242) },
{ HID_BVP(BUS_USB, USB_VENDOR_APPLE, 0x8243) },
{ HID_BVP(BUS_USB, USB_VENDOR_APPLE, 0x1440) },
};
static int
appleir_probe(device_t dev)
{
int error;
error = HIDBUS_LOOKUP_DRIVER_INFO(dev, appleir_devs);
if (error != 0)
return (error);
if (hidbus_get_index(dev) != 0)
return (ENXIO);
hidbus_set_desc(dev, "Apple IR Receiver");
return (BUS_PROBE_DEFAULT);
}
static int
appleir_attach(device_t dev)
{
struct appleir_softc *sc = device_get_softc(dev);
const struct hid_device_info *hw;
int i, error;
sc->sc_dev = dev;
hw = hid_get_device_info(dev);
sc->sc_current_key = 0;
sc->sc_prev_key_idx = 0;
sc->sc_batt_warned = false;
mtx_init(&sc->sc_mtx, "appleir", NULL, MTX_DEF);
callout_init_mtx(&sc->sc_co, &sc->sc_mtx, 0);
callout_init_mtx(&sc->sc_twoco, &sc->sc_mtx, 0);
sc->sc_evdev = evdev_alloc();
evdev_set_name(sc->sc_evdev, device_get_desc(dev));
evdev_set_phys(sc->sc_evdev, device_get_nameunit(dev));
evdev_set_id(sc->sc_evdev, hw->idBus, hw->idVendor, hw->idProduct,
hw->idVersion);
evdev_set_serial(sc->sc_evdev, hw->serial);
evdev_support_event(sc->sc_evdev, EV_SYN);
evdev_support_event(sc->sc_evdev, EV_KEY);
evdev_support_event(sc->sc_evdev, EV_REP);
for (i = 0; i < APPLEIR_NKEYS; i++) {
if (appleir_keymap[i] != KEY_RESERVED)
evdev_support_key(sc->sc_evdev, appleir_keymap[i]);
}
for (i = 0; i < GENERIC_NKEYS; i++)
evdev_support_key(sc->sc_evdev, generic_keymap[i].key);
error = evdev_register_mtx(sc->sc_evdev, &sc->sc_mtx);
if (error != 0) {
device_printf(dev, "evdev_register_mtx failed: %d\n", error);
goto fail;
}
hidbus_set_intr(dev, appleir_intr, sc);
error = hid_intr_start(dev);
if (error != 0) {
device_printf(dev, "hid_intr_start failed: %d\n", error);
goto fail;
}
return (0);
fail:
if (sc->sc_evdev != NULL)
evdev_free(sc->sc_evdev);
callout_drain(&sc->sc_co);
callout_drain(&sc->sc_twoco);
mtx_destroy(&sc->sc_mtx);
return (error);
}
static int
appleir_detach(device_t dev)
{
struct appleir_softc *sc = device_get_softc(dev);
int error;
error = hid_intr_stop(dev);
if (error != 0) {
device_printf(dev, "hid_intr_stop failed: %d\n", error);
return (error);
}
callout_drain(&sc->sc_co);
callout_drain(&sc->sc_twoco);
evdev_free(sc->sc_evdev);
mtx_destroy(&sc->sc_mtx);
return (0);
}
static device_method_t appleir_methods[] = {
DEVMETHOD(device_probe, appleir_probe),
DEVMETHOD(device_attach, appleir_attach),
DEVMETHOD(device_detach, appleir_detach),
DEVMETHOD_END
};
static driver_t appleir_driver = {
"appleir",
appleir_methods,
sizeof(struct appleir_softc)
};
DRIVER_MODULE(appleir, hidbus, appleir_driver, NULL, NULL);
MODULE_DEPEND(appleir, hid, 1, 1, 1);
MODULE_DEPEND(appleir, hidbus, 1, 1, 1);
MODULE_DEPEND(appleir, evdev, 1, 1, 1);
MODULE_VERSION(appleir, 1);
HID_PNP_INFO(appleir_devs);