git: 8de78df54d90 - wmt(4): Add support for touchpads

Vladimir Kondratyev wulf at FreeBSD.org
Wed Dec 23 22:29:38 UTC 2020


The branch main has been updated by wulf:

URL: https://cgit.FreeBSD.org/src/commit/?id=8de78df54d907d4b5aa4b962c2c89259938aeda5

commit 8de78df54d907d4b5aa4b962c2c89259938aeda5
Author:     Vladimir Kondratyev <wulf at FreeBSD.org>
AuthorDate: 2020-12-23 22:18:18 +0000
Commit:     Vladimir Kondratyev <wulf at FreeBSD.org>
CommitDate: 2020-12-23 22:22:56 +0000

    wmt(4): Add support for touchpads
    
    Obtained from:  sysutils/iichid
---
 share/man/man4/wmt.4    |   4 -
 sys/dev/usb/input/wmt.c | 199 ++++++++++++++++++++++++++++++++++++++++++++----
 2 files changed, 184 insertions(+), 19 deletions(-)

diff --git a/share/man/man4/wmt.4 b/share/man/man4/wmt.4
index f778a07a5983..ddea0a6b45d3 100644
--- a/share/man/man4/wmt.4
+++ b/share/man/man4/wmt.4
@@ -73,10 +73,6 @@ driver was written by
 .An Vladimir Kondratyev Aq Mt wulf at 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
diff --git a/sys/dev/usb/input/wmt.c b/sys/dev/usb/input/wmt.c
index b3fbd8c82a13..745d0af9b20e 100644
--- a/sys/dev/usb/input/wmt.c
+++ b/sys/dev/usb/input/wmt.c
@@ -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),


More information about the dev-commits-src-all mailing list