wmt(4): Add support for touchpads

Obtained from:	sysutils/iichid
This commit is contained in:
Vladimir Kondratyev 2020-12-24 01:18:18 +03:00
parent 7eae6aab7d
commit 8de78df54d
2 changed files with 184 additions and 19 deletions

View File

@ -73,10 +73,6 @@ driver was written by
.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org .
.Sh BUGS
.Nm
works only with touchscreens now.
Neither pens nor touchpads are supported.
.Pp
.Nm
cannot act like
.Xr sysmouse 4 ,
as

View File

@ -69,12 +69,26 @@ SYSCTL_INT(_hw_usb_wmt, OID_AUTO, debug, CTLFLAG_RWTUN,
#endif
#define WMT_BSIZE 1024 /* bytes, buffer size */
#define WMT_BTN_MAX 8 /* Number of buttons supported */
enum {
WMT_INTR_DT,
WMT_N_TRANSFER,
};
enum wmt_type {
WMT_TYPE_UNKNOWN = 0, /* HID report descriptor is not probed */
WMT_TYPE_UNSUPPORTED, /* Repdescr does not belong to MT device */
WMT_TYPE_TOUCHPAD,
WMT_TYPE_TOUCHSCREEN,
};
enum wmt_input_mode {
WMT_INPUT_MODE_MOUSE = 0x0,
WMT_INPUT_MODE_MT_TOUCHSCREEN = 0x2,
WMT_INPUT_MODE_MT_TOUCHPAD = 0x3,
};
enum {
WMT_TIP_SWITCH,
#define WMT_SLOT WMT_TIP_SWITCH
@ -187,29 +201,41 @@ struct wmt_absinfo {
struct wmt_softc {
device_t dev;
bool supported;
enum wmt_type type;
struct mtx mtx;
struct wmt_absinfo ai[WMT_N_USAGES];
struct hid_location locs[MAX_MT_SLOTS][WMT_N_USAGES];
struct hid_location cont_count_loc;
struct hid_location btn_loc[WMT_BTN_MAX];
struct hid_location int_btn_loc;
struct usb_xfer *xfer[WMT_N_TRANSFER];
struct evdev_dev *evdev;
uint32_t slot_data[WMT_N_USAGES];
uint32_t caps;
uint32_t buttons;
uint32_t isize;
uint32_t nconts_per_report;
uint32_t nconts_todo;
uint32_t report_len;
uint8_t report_id;
uint32_t max_button;
bool has_int_button;
bool is_clickpad;
struct hid_location cont_max_loc;
uint32_t cont_max_rlen;
uint8_t cont_max_rid;
struct hid_location btn_type_loc;
uint32_t btn_type_rlen;
uint8_t btn_type_rid;
uint32_t thqa_cert_rlen;
uint8_t thqa_cert_rid;
struct hid_location input_mode_loc;
uint32_t input_mode_rlen;
uint8_t input_mode_rid;
uint8_t buf[WMT_BSIZE] __aligned(4);
};
@ -219,8 +245,9 @@ struct wmt_softc {
for ((usage) = 0; (usage) < WMT_N_USAGES; ++(usage)) \
if (USAGE_SUPPORTED((caps), (usage)))
static bool wmt_hid_parse(struct wmt_softc *, const void *, uint16_t);
static enum wmt_type wmt_hid_parse(struct wmt_softc *, const void *, uint16_t);
static void wmt_cont_max_parse(struct wmt_softc *, const void *, uint16_t);
static int wmt_set_input_mode(struct wmt_softc *, enum wmt_input_mode);
static usb_callback_t wmt_intr_callback;
@ -281,15 +308,16 @@ wmt_probe(device_t dev)
return (ENXIO);
/* Check if report descriptor belongs to a HID multitouch device */
if (!sc->supported)
sc->supported = wmt_hid_parse(sc, d_ptr, d_len);
if (sc->supported)
if (sc->type == WMT_TYPE_UNKNOWN)
sc->type = wmt_hid_parse(sc, d_ptr, d_len);
if (sc->type != WMT_TYPE_UNSUPPORTED)
err = BUS_PROBE_DEFAULT;
else
err = ENXIO;
/* Check HID report length */
if (sc->supported && (sc->isize <= 0 || sc->isize > WMT_BSIZE)) {
if (sc->type != WMT_TYPE_UNSUPPORTED &&
(sc->isize <= 0 || sc->isize > WMT_BSIZE)) {
DPRINTF("Input size invalid or too large: %d\n", sc->isize);
err = ENXIO;
}
@ -303,6 +331,7 @@ wmt_attach(device_t dev)
{
struct usb_attach_arg *uaa = device_get_ivars(dev);
struct wmt_softc *sc = device_get_softc(dev);
int nbuttons, btn;
size_t i;
int err;
@ -323,6 +352,22 @@ wmt_attach(device_t dev)
DPRINTF("Feature report %hhu size invalid or too large: %u\n",
sc->cont_max_rid, sc->cont_max_rlen);
/* Fetch and parse "Button type" feature report */
if (sc->btn_type_rlen > 1 && sc->btn_type_rlen <= WMT_BSIZE &&
sc->btn_type_rid != sc->cont_max_rid) {
bzero(sc->buf, sc->btn_type_rlen);
err = usbd_req_get_report(uaa->device, NULL, sc->buf,
sc->btn_type_rlen, uaa->info.bIfaceIndex,
UHID_FEATURE_REPORT, sc->btn_type_rid);
}
if (sc->btn_type_rlen > 1) {
if (err == 0)
sc->is_clickpad = hid_get_data_unsigned(sc->buf + 1,
sc->btn_type_rlen - 1, &sc->btn_type_loc) == 0;
else
DPRINTF("usbd_req_get_report error=%d\n", err);
}
/* Fetch THQA certificate to enable some devices like WaveShare */
if (sc->thqa_cert_rlen > 0 && sc->thqa_cert_rlen <= WMT_BSIZE &&
sc->thqa_cert_rid != sc->cont_max_rid)
@ -330,6 +375,13 @@ wmt_attach(device_t dev)
sc->thqa_cert_rlen, uaa->info.bIfaceIndex,
UHID_FEATURE_REPORT, sc->thqa_cert_rid);
/* Switch touchpad in to absolute multitouch mode */
if (sc->type == WMT_TYPE_TOUCHPAD) {
err = wmt_set_input_mode(sc, WMT_INPUT_MODE_MT_TOUCHPAD);
if (err != 0)
DPRINTF("Failed to set input mode: %d\n", err);
}
mtx_init(&sc->mtx, "wmt lock", NULL, MTX_DEF);
err = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex,
@ -347,9 +399,28 @@ wmt_attach(device_t dev)
evdev_set_serial(sc->evdev, usb_get_serial(uaa->device));
evdev_set_methods(sc->evdev, sc, &wmt_evdev_methods);
evdev_set_flag(sc->evdev, EVDEV_FLAG_MT_STCOMPAT);
evdev_support_prop(sc->evdev, INPUT_PROP_DIRECT);
switch (sc->type) {
case WMT_TYPE_TOUCHSCREEN:
evdev_support_prop(sc->evdev, INPUT_PROP_DIRECT);
break;
case WMT_TYPE_TOUCHPAD:
evdev_support_prop(sc->evdev, INPUT_PROP_POINTER);
if (sc->is_clickpad)
evdev_support_prop(sc->evdev, INPUT_PROP_BUTTONPAD);
break;
default:
KASSERT(0, ("wmt_attach: unsupported touch device type"));
}
evdev_support_event(sc->evdev, EV_SYN);
evdev_support_event(sc->evdev, EV_ABS);
if (sc->max_button != 0 || sc->has_int_button) {
evdev_support_event(sc->evdev, EV_KEY);
if (sc->has_int_button)
evdev_support_key(sc->evdev, BTN_LEFT);
for (btn = 0; btn < sc->max_button; ++btn)
if (USAGE_SUPPORTED(sc->buttons, btn))
evdev_support_key(sc->evdev, BTN_MOUSE + btn);
}
WMT_FOREACH_USAGE(sc->caps, i) {
if (wmt_hid_map[i].code != WMT_NO_CODE)
evdev_support_abs(sc->evdev, wmt_hid_map[i].code, 0,
@ -361,6 +432,11 @@ wmt_attach(device_t dev)
goto detach;
/* Announce information about the touch device */
nbuttons = bitcount32(sc->buttons);
device_printf(sc->dev, "Multitouch %s with %d external button%s%s\n",
sc->type == WMT_TYPE_TOUCHSCREEN ? "touchscreen" : "touchpad",
nbuttons, nbuttons != 1 ? "s" : "",
sc->is_clickpad ? ", click-pad" : "");
device_printf(sc->dev,
"%d contacts and [%s%s%s%s%s]. Report range [%d:%d] - [%d:%d]\n",
(int)sc->ai[WMT_SLOT].max + 1,
@ -395,10 +471,12 @@ wmt_process_report(struct wmt_softc *sc, uint8_t *buf, int len)
{
size_t usage;
uint32_t *slot_data = sc->slot_data;
uint32_t cont;
uint32_t cont, btn;
uint32_t cont_count;
uint32_t width;
uint32_t height;
uint32_t int_btn = 0;
uint32_t left_btn = 0;
int32_t slot;
/*
@ -496,8 +574,25 @@ wmt_process_report(struct wmt_softc *sc, uint8_t *buf, int len)
}
sc->nconts_todo -= cont_count;
if (sc->nconts_todo == 0)
if (sc->nconts_todo == 0) {
/* Report both the click and external left btns as BTN_LEFT */
if (sc->has_int_button)
int_btn = hid_get_data(buf, len, &sc->int_btn_loc);
if (sc->max_button != 0 && (sc->buttons & 1 << 0) != 0)
left_btn = hid_get_data(buf, len, &sc->btn_loc[0]);
if (sc->has_int_button ||
(sc->max_button != 0 && (sc->buttons & 1 << 0) != 0))
evdev_push_key(sc->evdev, BTN_LEFT,
int_btn != 0 | left_btn != 0);
for (btn = 1; btn < sc->max_button; ++btn) {
if ((sc->buttons & 1 << btn) != 0)
evdev_push_key(sc->evdev, BTN_MOUSE + btn,
hid_get_data(buf,
len,
&sc->btn_loc[btn]) != 0);
}
evdev_sync(sc->evdev);
}
}
static void
@ -640,19 +735,22 @@ wmt_hid_report_size(const void *buf, uint16_t len, enum hid_kind k, uint8_t id)
return ((temp + 7) / 8 + report_id);
}
static bool
static enum wmt_type
wmt_hid_parse(struct wmt_softc *sc, const void *d_ptr, uint16_t d_len)
{
struct hid_item hi;
struct hid_data *hd;
size_t i;
size_t cont = 0;
enum wmt_type type = WMT_TYPE_UNSUPPORTED;
uint32_t left_btn, btn;
int32_t cont_count_max = 0;
uint8_t report_id = 0;
bool touch_coll = false;
bool finger_coll = false;
bool cont_count_found = false;
bool scan_time_found = false;
bool has_int_button = false;
#define WMT_HI_ABSOLUTE(hi) \
(((hi).flags & (HIO_CONST|HIO_VARIABLE|HIO_RELATIVE)) == HIO_VARIABLE)
@ -664,8 +762,18 @@ wmt_hid_parse(struct wmt_softc *sc, const void *d_ptr, uint16_t d_len)
switch (hi.kind) {
case hid_collection:
if (hi.collevel == 1 && hi.usage ==
HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCHSCREEN))
HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCHSCREEN)) {
touch_coll = true;
type = WMT_TYPE_TOUCHSCREEN;
left_btn = 1;
break;
}
if (hi.collevel == 1 && hi.usage ==
HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCHPAD)) {
touch_coll = true;
type = WMT_TYPE_TOUCHPAD;
left_btn = 2;
}
break;
case hid_endcollection:
if (hi.collevel == 0 && touch_coll)
@ -682,6 +790,12 @@ wmt_hid_parse(struct wmt_softc *sc, const void *d_ptr, uint16_t d_len)
cont_count_max = hi.logical_maximum;
sc->cont_max_rid = hi.report_ID;
sc->cont_max_loc = hi.loc;
break;
}
if (hi.collevel == 1 && touch_coll && hi.usage ==
HID_USAGE2(HUP_DIGITIZERS, HUD_BUTTON_TYPE)) {
sc->btn_type_rid = hi.report_ID;
sc->btn_type_loc = hi.loc;
}
break;
default:
@ -690,9 +804,11 @@ wmt_hid_parse(struct wmt_softc *sc, const void *d_ptr, uint16_t d_len)
}
hid_end_parse(hd);
if (type == WMT_TYPE_UNSUPPORTED)
return (WMT_TYPE_UNSUPPORTED);
/* Maximum contact count is required usage */
if (sc->cont_max_rid == 0)
return (false);
return (WMT_TYPE_UNSUPPORTED);
touch_coll = false;
@ -727,6 +843,22 @@ wmt_hid_parse(struct wmt_softc *sc, const void *d_ptr, uint16_t d_len)
else
break;
if (hi.collevel == 1 && left_btn == 2 &&
hi.usage == HID_USAGE2(HUP_BUTTON, 1)) {
has_int_button = true;
sc->int_btn_loc = hi.loc;
break;
}
if (hi.collevel == 1 &&
hi.usage >= HID_USAGE2(HUP_BUTTON, left_btn) &&
hi.usage <= HID_USAGE2(HUP_BUTTON, WMT_BTN_MAX)) {
btn = (hi.usage & 0xFFFF) - left_btn;
sc->buttons |= 1 << btn;
sc->btn_loc[btn] = hi.loc;
if (btn >= sc->max_button)
sc->max_button = btn + 1;
break;
}
if (hi.collevel == 1 && hi.usage ==
HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTCOUNT)) {
cont_count_found = true;
@ -783,12 +915,16 @@ wmt_hid_parse(struct wmt_softc *sc, const void *d_ptr, uint16_t d_len)
/* Check for required HID Usages */
if (!cont_count_found || !scan_time_found || cont == 0)
return (false);
return (WMT_TYPE_UNSUPPORTED);
for (i = 0; i < WMT_N_USAGES; i++) {
if (wmt_hid_map[i].required && !USAGE_SUPPORTED(sc->caps, i))
return (false);
return (WMT_TYPE_UNSUPPORTED);
}
/* Touchpads must have at least one button */
if (type == WMT_TYPE_TOUCHPAD && !sc->max_button && !has_int_button)
return (WMT_TYPE_UNSUPPORTED);
/*
* According to specifications 'Contact Count Maximum' should be read
* from Feature Report rather than from HID descriptor. Set sane
@ -820,14 +956,18 @@ wmt_hid_parse(struct wmt_softc *sc, const void *d_ptr, uint16_t d_len)
report_id);
sc->cont_max_rlen = wmt_hid_report_size(d_ptr, d_len, hid_feature,
sc->cont_max_rid);
if (sc->btn_type_rid > 0)
sc->btn_type_rlen = wmt_hid_report_size(d_ptr, d_len,
hid_feature, sc->btn_type_rid);
if (sc->thqa_cert_rid > 0)
sc->thqa_cert_rlen = wmt_hid_report_size(d_ptr, d_len,
hid_feature, sc->thqa_cert_rid);
sc->report_id = report_id;
sc->nconts_per_report = cont;
sc->has_int_button = has_int_button;
return (true);
return (type);
}
static void
@ -851,6 +991,35 @@ wmt_cont_max_parse(struct wmt_softc *sc, const void *r_ptr, uint16_t r_len)
}
}
static int
wmt_set_input_mode(struct wmt_softc *sc, enum wmt_input_mode mode)
{
struct usb_attach_arg *uaa = device_get_ivars(sc->dev);
int err;
if (sc->input_mode_rlen < 3 || sc->input_mode_rlen > WMT_BSIZE) {
DPRINTF("Feature report %hhu size invalid or too large: %u\n",
sc->input_mode_rid, sc->input_mode_rlen);
return (USB_ERR_BAD_BUFSIZE);
}
/* Input Mode report is not strictly required to be readable */
err = usbd_req_get_report(uaa->device, NULL, sc->buf,
sc->input_mode_rlen, uaa->info.bIfaceIndex,
UHID_FEATURE_REPORT, sc->input_mode_rid);
if (err != USB_ERR_NORMAL_COMPLETION)
bzero(sc->buf + 1, sc->input_mode_rlen - 1);
sc->buf[0] = sc->input_mode_rid;
hid_put_data_unsigned(sc->buf + 1, sc->input_mode_rlen - 1,
&sc->input_mode_loc, mode);
err = usbd_req_set_report(uaa->device, NULL, sc->buf,
sc->input_mode_rlen, uaa->info.bIfaceIndex,
UHID_FEATURE_REPORT, sc->input_mode_rid);
return (err);
}
static const STRUCT_USB_HOST_ID wmt_devs[] = {
/* generic HID class w/o boot interface */
{USB_IFACE_CLASS(UICLASS_HID),