#include <sys/cdefs.h>
#include "opt_hid.h"
#include <sys/param.h>
#include <sys/bus.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 hidwacom_debug
#include <dev/hid/hid.h>
#include <dev/hid/hidbus.h>
#include "usbdevs.h"
#ifdef HID_DEBUG
static int hidwacom_debug = 0;
static SYSCTL_NODE(_hw_hid, OID_AUTO, hidwacom, CTLFLAG_RW, 0,
"Wacom ExpressKey Remote");
SYSCTL_INT(_hw_hid_hidwacom, OID_AUTO, debug, CTLFLAG_RWTUN,
&hidwacom_debug, 0, "Debug level");
#endif
#define WACOM_REPORT_DEVICE_LIST 0x10
#define WACOM_REPORT_REMOTE 0x11
#define WACOM_REMOTE_MIN_LEN 13
#define WACOM_RING_MODE_MASK 0xc0
#define WACOM_RING_MODE_SHIFT 6
#define WACOM_RING_ACTIVE 0x80
#define WACOM_RING_POSITION_MASK 0x7f
#define WACOM_RING_MAX 71
#define WACOM_BATTERY_CHARGING 0x80
#define WACOM_BATTERY_PERCENT_MASK 0x7f
#define WACOM_PAD_DEVICE_ID 0x0f
#define HIDWACOM_DEV_NAME "Wacom ExpressKey Remote"
static const uint16_t hidwacom_buttons[] = {
BTN_0, BTN_1, BTN_2, BTN_3, BTN_4, BTN_5, BTN_6, BTN_7,
BTN_8, BTN_9, BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z,
BTN_BASE, BTN_BASE2,
};
struct hidwacom_softc {
struct mtx sc_mtx;
struct evdev_dev *sc_evdev;
};
static void
hidwacom_intr(void *context, void *data, hid_size_t len)
{
struct hidwacom_softc *sc = context;
uint8_t *buf = data;
uint8_t report_id;
uint8_t b9, b10, b11, ring, battery;
uint32_t serial;
int i, pos;
if (len < 1)
return;
report_id = buf[0];
if (report_id == WACOM_REPORT_DEVICE_LIST) {
DPRINTFN(2, "device list report (pairing)\n");
return;
}
if (report_id != WACOM_REPORT_REMOTE) {
DPRINTFN(1, "unknown report id: 0x%02x\n", report_id);
return;
}
if (len < WACOM_REMOTE_MIN_LEN) {
DPRINTFN(1, "short remote report: %zu\n", (size_t)len);
return;
}
mtx_lock(&sc->sc_mtx);
serial = buf[3] + ((uint32_t)buf[4] << 8) + ((uint32_t)buf[5] << 16);
battery = buf[7] & WACOM_BATTERY_PERCENT_MASK;
b9 = buf[9];
b10 = buf[10];
b11 = buf[11];
for (i = 0; i < 8; i++)
evdev_push_key(sc->sc_evdev, hidwacom_buttons[i],
(b9 >> i) & 1);
for (i = 0; i < 8; i++)
evdev_push_key(sc->sc_evdev, hidwacom_buttons[8 + i],
(b10 >> i) & 1);
evdev_push_key(sc->sc_evdev, BTN_BASE, b11 & 0x01);
evdev_push_key(sc->sc_evdev, BTN_BASE2, b11 & 0x02);
ring = buf[12];
if (ring & WACOM_RING_ACTIVE) {
pos = (ring & WACOM_RING_POSITION_MASK) - 1;
if (pos < 0)
pos = 0;
if (pos > WACOM_RING_MAX)
pos = WACOM_RING_MAX;
evdev_push_abs(sc->sc_evdev, ABS_WHEEL, pos);
evdev_push_abs(sc->sc_evdev, ABS_MISC,
(b11 >> WACOM_RING_MODE_SHIFT) & 0x03);
} else {
evdev_push_abs(sc->sc_evdev, ABS_WHEEL, 0);
evdev_push_abs(sc->sc_evdev, ABS_MISC, 0);
}
evdev_push_event(sc->sc_evdev, EV_MSC, MSC_PULSELED, battery);
evdev_push_event(sc->sc_evdev, EV_MSC, MSC_SERIAL, serial);
evdev_sync(sc->sc_evdev);
mtx_unlock(&sc->sc_mtx);
}
static const struct hid_device_id hidwacom_devs[] = {
{ HID_BVP(BUS_USB, USB_VENDOR_WACOM,
USB_PRODUCT_WACOM_EXPRESSKEY_REMOTE) },
};
static int
hidwacom_probe(device_t dev)
{
int error;
error = HIDBUS_LOOKUP_DRIVER_INFO(dev, hidwacom_devs);
if (error != 0)
return (error);
if (hidbus_get_index(dev) != 0)
return (ENXIO);
hidbus_set_desc(dev, HIDWACOM_DEV_NAME);
return (BUS_PROBE_DEFAULT);
}
static int
hidwacom_attach(device_t dev)
{
struct hidwacom_softc *sc = device_get_softc(dev);
const struct hid_device_info *hw;
int i, error;
hw = hid_get_device_info(dev);
mtx_init(&sc->sc_mtx, "hidwacom", NULL, MTX_DEF);
sc->sc_evdev = evdev_alloc();
evdev_set_name(sc->sc_evdev, HIDWACOM_DEV_NAME);
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_ABS);
evdev_support_event(sc->sc_evdev, EV_MSC);
for (i = 0; i < (int)nitems(hidwacom_buttons); i++)
evdev_support_key(sc->sc_evdev, hidwacom_buttons[i]);
evdev_support_abs(sc->sc_evdev, ABS_WHEEL, 0, WACOM_RING_MAX,
0, 0, 0);
evdev_support_abs(sc->sc_evdev, ABS_MISC, 0, 3, 0, 0, 0);
evdev_support_msc(sc->sc_evdev, MSC_SERIAL);
evdev_support_msc(sc->sc_evdev, MSC_PULSELED);
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, hidwacom_intr, sc);
error = hid_intr_start(dev);
if (error != 0) {
device_printf(dev, "hid_intr_start failed: %d\n", error);
hid_intr_stop(dev);
goto fail;
}
return (0);
fail:
if (sc->sc_evdev != NULL)
evdev_free(sc->sc_evdev);
mtx_destroy(&sc->sc_mtx);
return (error);
}
static int
hidwacom_detach(device_t dev)
{
struct hidwacom_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);
}
evdev_free(sc->sc_evdev);
mtx_destroy(&sc->sc_mtx);
return (0);
}
static device_method_t hidwacom_methods[] = {
DEVMETHOD(device_probe, hidwacom_probe),
DEVMETHOD(device_attach, hidwacom_attach),
DEVMETHOD(device_detach, hidwacom_detach),
DEVMETHOD_END
};
static driver_t hidwacom_driver = {
"hidwacom",
hidwacom_methods,
sizeof(struct hidwacom_softc)
};
DRIVER_MODULE(hidwacom, hidbus, hidwacom_driver, NULL, NULL);
MODULE_DEPEND(hidwacom, hid, 1, 1, 1);
MODULE_DEPEND(hidwacom, hidbus, 1, 1, 1);
MODULE_DEPEND(hidwacom, evdev, 1, 1, 1);
MODULE_VERSION(hidwacom, 1);
HID_PNP_INFO(hidwacom_devs);