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