xref: /openbsd-src/sys/dev/i2c/imt.c (revision 0f9e9ec23bb2b65cc62a3d17df12827a45dae80c)
1*0f9e9ec2Sjsg /* $OpenBSD: imt.c,v 1.6 2024/05/13 01:15:50 jsg Exp $ */
228b578b5Sjcs /*
328b578b5Sjcs  * HID-over-i2c multitouch trackpad driver for devices conforming to
428b578b5Sjcs  * Windows Precision Touchpad standard
528b578b5Sjcs  *
68fabbf99Sjcs  * https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-precision-touchpad-required-hid-top-level-collections
728b578b5Sjcs  *
828b578b5Sjcs  * Copyright (c) 2016 joshua stein <jcs@openbsd.org>
928b578b5Sjcs  *
1028b578b5Sjcs  * Permission to use, copy, modify, and distribute this software for any
1128b578b5Sjcs  * purpose with or without fee is hereby granted, provided that the above
1228b578b5Sjcs  * copyright notice and this permission notice appear in all copies.
1328b578b5Sjcs  *
1428b578b5Sjcs  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1528b578b5Sjcs  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1628b578b5Sjcs  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1728b578b5Sjcs  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1828b578b5Sjcs  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1928b578b5Sjcs  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
2028b578b5Sjcs  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
2128b578b5Sjcs  */
2228b578b5Sjcs 
2328b578b5Sjcs #include <sys/param.h>
2428b578b5Sjcs #include <sys/systm.h>
2528b578b5Sjcs #include <sys/kernel.h>
2628b578b5Sjcs #include <sys/device.h>
2728b578b5Sjcs #include <sys/ioctl.h>
2828b578b5Sjcs 
2928b578b5Sjcs #include <dev/i2c/i2cvar.h>
3028b578b5Sjcs #include <dev/i2c/ihidev.h>
3128b578b5Sjcs 
3228b578b5Sjcs #include <dev/wscons/wsconsio.h>
3328b578b5Sjcs #include <dev/wscons/wsmousevar.h>
3428b578b5Sjcs 
3528b578b5Sjcs #include <dev/hid/hid.h>
3628b578b5Sjcs #include <dev/hid/hidmtvar.h>
3728b578b5Sjcs 
3828b578b5Sjcs struct imt_softc {
3928b578b5Sjcs 	struct ihidev	sc_hdev;
4028b578b5Sjcs 	struct hidmt	sc_mt;
4128b578b5Sjcs 
4228b578b5Sjcs 	int		sc_rep_input;
4328b578b5Sjcs 	int		sc_rep_config;
4428b578b5Sjcs 	int		sc_rep_cap;
4528b578b5Sjcs };
4628b578b5Sjcs 
4728b578b5Sjcs int	imt_enable(void *);
4828b578b5Sjcs void	imt_intr(struct ihidev *, void *, u_int);
4928b578b5Sjcs void	imt_disable(void *);
5028b578b5Sjcs int	imt_ioctl(void *, u_long, caddr_t, int, struct proc *);
5128b578b5Sjcs 
5228b578b5Sjcs const struct wsmouse_accessops imt_accessops = {
5328b578b5Sjcs 	imt_enable,
5428b578b5Sjcs 	imt_ioctl,
5528b578b5Sjcs 	imt_disable,
5628b578b5Sjcs };
5728b578b5Sjcs 
5828b578b5Sjcs int	imt_match(struct device *, void *, void *);
5928b578b5Sjcs int	imt_find_winptp_reports(struct ihidev_softc *, void *, int,
6028b578b5Sjcs 	    struct imt_softc *);
6128b578b5Sjcs void	imt_attach(struct device *, struct device *, void *);
62e4334df5Sjcs int	imt_hidev_get_report(struct device *, int, int, void *, int);
63e4334df5Sjcs int	imt_hidev_set_report(struct device *, int, int, void *, int);
6428b578b5Sjcs int	imt_detach(struct device *, int);
6528b578b5Sjcs 
6628b578b5Sjcs struct cfdriver imt_cd = {
6728b578b5Sjcs 	NULL, "imt", DV_DULL
6828b578b5Sjcs };
6928b578b5Sjcs 
7028b578b5Sjcs const struct cfattach imt_ca = {
7128b578b5Sjcs 	sizeof(struct imt_softc),
7228b578b5Sjcs 	imt_match,
7328b578b5Sjcs 	imt_attach,
7428b578b5Sjcs 	imt_detach
7528b578b5Sjcs };
7628b578b5Sjcs 
7728b578b5Sjcs int
imt_match(struct device * parent,void * match,void * aux)7828b578b5Sjcs imt_match(struct device *parent, void *match, void *aux)
7928b578b5Sjcs {
8028b578b5Sjcs 	struct ihidev_attach_arg *iha = (struct ihidev_attach_arg *)aux;
818fabbf99Sjcs 	struct imt_softc sc;
8228b578b5Sjcs 	int size;
8328b578b5Sjcs 	void *desc;
8428b578b5Sjcs 
858fabbf99Sjcs 	if (iha->reportid == IHIDEV_CLAIM_MULTIPLEID) {
8628b578b5Sjcs 		ihidev_get_report_desc(iha->parent, &desc, &size);
878fabbf99Sjcs 		if (imt_find_winptp_reports(iha->parent, desc, size, &sc)) {
888fabbf99Sjcs 			iha->claims[0] = sc.sc_rep_input;
898fabbf99Sjcs 			iha->claims[1] = sc.sc_rep_config;
908fabbf99Sjcs 			iha->claims[2] = sc.sc_rep_cap;
918fabbf99Sjcs 			iha->nclaims = 3;
9228b578b5Sjcs 			return (IMATCH_DEVCLASS_DEVSUBCLASS);
9328b578b5Sjcs 		}
948fabbf99Sjcs 	}
9528b578b5Sjcs 
9628b578b5Sjcs 	return (IMATCH_NONE);
9728b578b5Sjcs }
9828b578b5Sjcs 
9928b578b5Sjcs int
imt_find_winptp_reports(struct ihidev_softc * parent,void * desc,int size,struct imt_softc * sc)10028b578b5Sjcs imt_find_winptp_reports(struct ihidev_softc *parent, void *desc, int size,
10128b578b5Sjcs     struct imt_softc *sc)
10228b578b5Sjcs {
10328b578b5Sjcs 	int repid;
10428b578b5Sjcs 	int input = 0, conf = 0, cap = 0;
10528b578b5Sjcs 
10628b578b5Sjcs 	if (sc != NULL) {
10728b578b5Sjcs 		sc->sc_rep_input = -1;
10828b578b5Sjcs 		sc->sc_rep_config = -1;
10928b578b5Sjcs 		sc->sc_rep_cap = -1;
11028b578b5Sjcs 	}
11128b578b5Sjcs 
11228b578b5Sjcs 	for (repid = 0; repid < parent->sc_nrepid; repid++) {
11328b578b5Sjcs 		if (hid_report_size(desc, size, hid_input, repid) == 0 &&
11428b578b5Sjcs 		    hid_report_size(desc, size, hid_output, repid) == 0 &&
11528b578b5Sjcs 		    hid_report_size(desc, size, hid_feature, repid) == 0)
11628b578b5Sjcs 			continue;
11728b578b5Sjcs 
11828b578b5Sjcs 		if (hid_is_collection(desc, size, repid,
11928b578b5Sjcs 		    HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCHPAD))) {
12028b578b5Sjcs 			input = 1;
12128b578b5Sjcs 			if (sc != NULL && sc->sc_rep_input == -1)
12228b578b5Sjcs 				sc->sc_rep_input = repid;
12328b578b5Sjcs 		} else if (hid_is_collection(desc, size, repid,
12428b578b5Sjcs 		    HID_USAGE2(HUP_DIGITIZERS, HUD_CONFIG))) {
12528b578b5Sjcs 			conf = 1;
12628b578b5Sjcs 			if (sc != NULL && sc->sc_rep_config == -1)
12728b578b5Sjcs 				sc->sc_rep_config = repid;
12828b578b5Sjcs 		}
12928b578b5Sjcs 
13028b578b5Sjcs 		/* capabilities report could be anywhere */
13128b578b5Sjcs 		if (hid_locate(desc, size, HID_USAGE2(HUP_DIGITIZERS,
13228b578b5Sjcs 		    HUD_CONTACT_MAX), repid, hid_feature, NULL, NULL)) {
13328b578b5Sjcs 			cap = 1;
13428b578b5Sjcs 			if (sc != NULL && sc->sc_rep_cap == -1)
13528b578b5Sjcs 				sc->sc_rep_cap = repid;
13628b578b5Sjcs 		}
13728b578b5Sjcs 	}
13828b578b5Sjcs 
13928b578b5Sjcs 	return (conf && input && cap);
14028b578b5Sjcs }
14128b578b5Sjcs 
14228b578b5Sjcs void
imt_attach(struct device * parent,struct device * self,void * aux)14328b578b5Sjcs imt_attach(struct device *parent, struct device *self, void *aux)
14428b578b5Sjcs {
14528b578b5Sjcs 	struct imt_softc *sc = (struct imt_softc *)self;
14628b578b5Sjcs 	struct hidmt *mt = &sc->sc_mt;
14728b578b5Sjcs 	struct ihidev_attach_arg *iha = (struct ihidev_attach_arg *)aux;
14828b578b5Sjcs 	int size;
14928b578b5Sjcs 	void *desc;
15028b578b5Sjcs 
15128b578b5Sjcs 	sc->sc_hdev.sc_intr = imt_intr;
15228b578b5Sjcs 	sc->sc_hdev.sc_parent = iha->parent;
15328b578b5Sjcs 
15428b578b5Sjcs 	ihidev_get_report_desc(iha->parent, &desc, &size);
15528b578b5Sjcs 	imt_find_winptp_reports(iha->parent, desc, size, sc);
15628b578b5Sjcs 
15728b578b5Sjcs 	memset(mt, 0, sizeof(sc->sc_mt));
15828b578b5Sjcs 
15928b578b5Sjcs 	/* assume everything has "natural scrolling" where Y axis is reversed */
16028b578b5Sjcs 	mt->sc_flags = HIDMT_REVY;
16128b578b5Sjcs 
162253b28d0Sjcs 	mt->hidev_report_type_conv = ihidev_report_type_conv;
163e4334df5Sjcs 	mt->hidev_get_report = imt_hidev_get_report;
164e4334df5Sjcs 	mt->hidev_set_report = imt_hidev_set_report;
16528b578b5Sjcs 	mt->sc_rep_input = sc->sc_rep_input;
16628b578b5Sjcs 	mt->sc_rep_config = sc->sc_rep_config;
16728b578b5Sjcs 	mt->sc_rep_cap = sc->sc_rep_cap;
16828b578b5Sjcs 
16928b578b5Sjcs 	if (hidmt_setup(self, mt, desc, size) != 0)
17028b578b5Sjcs 		return;
17128b578b5Sjcs 
17228b578b5Sjcs 	hidmt_attach(mt, &imt_accessops);
17328b578b5Sjcs }
17428b578b5Sjcs 
17528b578b5Sjcs int
imt_hidev_get_report(struct device * self,int type,int id,void * data,int len)176e4334df5Sjcs imt_hidev_get_report(struct device *self, int type, int id, void *data, int len)
17728b578b5Sjcs {
17828b578b5Sjcs 	struct imt_softc *sc = (struct imt_softc *)self;
17928b578b5Sjcs 
18028b578b5Sjcs 	return ihidev_get_report((struct device *)sc->sc_hdev.sc_parent, type,
18128b578b5Sjcs 	    id, data, len);
18228b578b5Sjcs }
18328b578b5Sjcs 
18428b578b5Sjcs int
imt_hidev_set_report(struct device * self,int type,int id,void * data,int len)185e4334df5Sjcs imt_hidev_set_report(struct device *self, int type, int id, void *data, int len)
18628b578b5Sjcs {
18728b578b5Sjcs 	struct imt_softc *sc = (struct imt_softc *)self;
18828b578b5Sjcs 
18928b578b5Sjcs 	return ihidev_set_report((struct device *)sc->sc_hdev.sc_parent, type,
19028b578b5Sjcs 	    id, data, len);
19128b578b5Sjcs }
19228b578b5Sjcs 
19328b578b5Sjcs int
imt_detach(struct device * self,int flags)19428b578b5Sjcs imt_detach(struct device *self, int flags)
19528b578b5Sjcs {
19628b578b5Sjcs 	struct imt_softc *sc = (struct imt_softc *)self;
19728b578b5Sjcs 	struct hidmt *mt = &sc->sc_mt;
19828b578b5Sjcs 
19928b578b5Sjcs 	return hidmt_detach(mt, flags);
20028b578b5Sjcs }
20128b578b5Sjcs 
20228b578b5Sjcs void
imt_intr(struct ihidev * dev,void * buf,u_int len)20328b578b5Sjcs imt_intr(struct ihidev *dev, void *buf, u_int len)
20428b578b5Sjcs {
20528b578b5Sjcs 	struct imt_softc *sc = (struct imt_softc *)dev;
20628b578b5Sjcs 	struct hidmt *mt = &sc->sc_mt;
20728b578b5Sjcs 
20828b578b5Sjcs 	if (!mt->sc_enabled)
20928b578b5Sjcs 		return;
21028b578b5Sjcs 
21128b578b5Sjcs 	hidmt_input(mt, (uint8_t *)buf, len);
21228b578b5Sjcs }
21328b578b5Sjcs 
21428b578b5Sjcs int
imt_enable(void * v)21528b578b5Sjcs imt_enable(void *v)
21628b578b5Sjcs {
21728b578b5Sjcs 	struct imt_softc *sc = v;
21828b578b5Sjcs 	struct hidmt *mt = &sc->sc_mt;
21928b578b5Sjcs 	int rv;
22028b578b5Sjcs 
22128b578b5Sjcs 	if ((rv = hidmt_enable(mt)) != 0)
22228b578b5Sjcs 		return rv;
22328b578b5Sjcs 
22428b578b5Sjcs 	rv = ihidev_open(&sc->sc_hdev);
22528b578b5Sjcs 
226737d705bSjcs 	hidmt_set_input_mode(mt, HIDMT_INPUT_MODE_MT_TOUCHPAD);
22728b578b5Sjcs 
22828b578b5Sjcs 	return rv;
22928b578b5Sjcs }
23028b578b5Sjcs 
23128b578b5Sjcs void
imt_disable(void * v)23228b578b5Sjcs imt_disable(void *v)
23328b578b5Sjcs {
23428b578b5Sjcs 	struct imt_softc *sc = v;
23528b578b5Sjcs 	struct hidmt *mt = &sc->sc_mt;
23628b578b5Sjcs 
23728b578b5Sjcs 	hidmt_disable(mt);
23828b578b5Sjcs 	ihidev_close(&sc->sc_hdev);
23928b578b5Sjcs }
24028b578b5Sjcs 
24128b578b5Sjcs int
imt_ioctl(void * v,u_long cmd,caddr_t data,int flag,struct proc * p)24228b578b5Sjcs imt_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p)
24328b578b5Sjcs {
24428b578b5Sjcs 	struct imt_softc *sc = v;
24528b578b5Sjcs 	struct hidmt *mt = &sc->sc_mt;
24628b578b5Sjcs 	int rc;
24728b578b5Sjcs 
24828b578b5Sjcs 	rc = ihidev_ioctl(&sc->sc_hdev, cmd, data, flag, p);
24928b578b5Sjcs 	if (rc != -1)
25028b578b5Sjcs 		return rc;
25128b578b5Sjcs 
25228b578b5Sjcs 	return hidmt_ioctl(mt, cmd, data, flag, p);
25328b578b5Sjcs }
254