1/* $NetBSD: ims.c,v 1.5 2023/05/10 00:10:02 riastradh Exp $ */
2/* $OpenBSD ims.c,v 1.1 2016/01/12 01:11:15 jcs Exp $ */
3
4/*
5 * HID-over-i2c mouse/trackpad driver
6 *
7 * Copyright (c) 2015, 2016 joshua stein <jcs@openbsd.org>
8 *
9 * Permission to use, copy, modify, and distribute this software for any
10 * purpose with or without fee is hereby granted, provided that the above
11 * copyright notice and this permission notice appear in all copies.
12 *
13 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20 */
21
22#include <sys/cdefs.h>
23__KERNEL_RCSID(0, "$NetBSD: ims.c,v 1.5 2023/05/10 00:10:02 riastradh Exp $");
24
25#include <sys/param.h>
26#include <sys/systm.h>
27#include <sys/kernel.h>
28#include <sys/device.h>
29#include <sys/ioctl.h>
30
31#include <dev/i2c/i2cvar.h>
32#include <dev/i2c/ihidev.h>
33
34#include <dev/hid/hid.h>
35#include <dev/hid/hidms.h>
36
37struct ims_softc {
38	struct ihidev	sc_hdev;
39	struct hidms	sc_ms;
40	bool		sc_enabled;
41};
42
43static void	ims_intr(struct ihidev *addr, void *ibuf, u_int len);
44
45static int	ims_enable(void *);
46static void	ims_disable(void *);
47static int	ims_ioctl(void *, u_long, void *, int, struct lwp *);
48
49const struct wsmouse_accessops ims_accessops = {
50	ims_enable,
51	ims_ioctl,
52	ims_disable,
53};
54
55static int	ims_match(device_t, cfdata_t, void *);
56static void	ims_attach(device_t, device_t, void *);
57static int	ims_detach(device_t, int);
58static void	ims_childdet(device_t, device_t);
59
60CFATTACH_DECL2_NEW(ims, sizeof(struct ims_softc), ims_match, ims_attach,
61    ims_detach, NULL, NULL, ims_childdet);
62
63static int
64ims_match(device_t parent, cfdata_t match, void *aux)
65{
66	struct ihidev_attach_arg *iha = (struct ihidev_attach_arg *)aux;
67	int size;
68	void *desc;
69
70	ihidev_get_report_desc(iha->parent, &desc, &size);
71
72	if (hid_is_collection(desc, size, iha->reportid,
73	    HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_POINTER)))
74		return (IMATCH_IFACECLASS);
75
76	if (hid_is_collection(desc, size, iha->reportid,
77	    HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_MOUSE)))
78		return (IMATCH_IFACECLASS);
79
80	if (hid_is_collection(desc, size, iha->reportid,
81	    HID_USAGE2(HUP_DIGITIZERS, HUD_PEN)))
82		return (IMATCH_IFACECLASS);
83
84	if (hid_is_collection(desc, size, iha->reportid,
85	    HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCH_SCREEN)))
86		return (IMATCH_IFACECLASS);
87
88	return (IMATCH_NONE);
89}
90
91static void
92ims_attach(device_t parent, device_t self, void *aux)
93{
94	struct ims_softc *sc = device_private(self);
95	struct hidms *ms = &sc->sc_ms;
96	struct ihidev_attach_arg *iha = (struct ihidev_attach_arg *)aux;
97	int size, repid;
98	void *desc;
99	struct hid_data * d __debugused;
100	struct hid_item item __debugused;
101
102	sc->sc_hdev.sc_idev = self;
103	sc->sc_hdev.sc_intr = ims_intr;
104	sc->sc_hdev.sc_parent = iha->parent;
105	sc->sc_hdev.sc_report_id = iha->reportid;
106
107	if (!pmf_device_register(self, NULL, NULL))
108		aprint_error_dev(self, "couldn't establish power handler\n");
109
110	ihidev_get_report_desc(iha->parent, &desc, &size);
111	repid = iha->reportid;
112	sc->sc_hdev.sc_isize = hid_report_size(desc, size, hid_input, repid);
113	sc->sc_hdev.sc_osize = hid_report_size(desc, size, hid_output, repid);
114	sc->sc_hdev.sc_fsize = hid_report_size(desc, size, hid_feature, repid);
115
116	if (!hidms_setup(self, ms, iha->reportid, desc, size) != 0)
117		return;
118
119#if defined(DEBUG)
120	/* calibrate the touchscreen */
121	memset(&sc->sc_ms.sc_calibcoords, 0, sizeof(sc->sc_ms.sc_calibcoords));
122	d = hid_start_parse(desc, size, hid_input);
123	if (d != NULL) {
124		while (hid_get_item(d, &item)) {
125			if (item.kind != hid_input
126			    || HID_GET_USAGE_PAGE(item.usage) != HUP_GENERIC_DESKTOP
127			    || item.report_ID != sc->sc_hdev.sc_report_id)
128				continue;
129			if (HID_GET_USAGE(item.usage) == HUG_X) {
130				aprint_normal("X range: %d - %d\n", item.logical_minimum, item.logical_maximum);
131			}
132			if (HID_GET_USAGE(item.usage) == HUG_Y) {
133				aprint_normal("Y range: %d - %d\n", item.logical_minimum, item.logical_maximum);
134			}
135		}
136		hid_end_parse(d);
137	}
138#endif
139	tpcalib_init(&sc->sc_ms.sc_tpcalib);
140	tpcalib_ioctl(&sc->sc_ms.sc_tpcalib, WSMOUSEIO_SCALIBCOORDS,
141	    (void *)&sc->sc_ms.sc_calibcoords, 0, 0);
142
143	hidms_attach(self, ms, &ims_accessops);
144}
145
146static int
147ims_detach(device_t self, int flags)
148{
149	int error;
150
151	/* No need to do reference counting of ums, wsmouse has all the goo. */
152	error = config_detach_children(self, flags);
153	if (error)
154		return error;
155
156	pmf_device_deregister(self);
157	return 0;
158}
159
160void
161ims_childdet(device_t self, device_t child)
162{
163	struct ims_softc *sc = device_private(self);
164
165	KASSERT(KERNEL_LOCKED_P());
166
167	KASSERT(sc->sc_ms.hidms_wsmousedev == child);
168	sc->sc_ms.hidms_wsmousedev = NULL;
169}
170
171
172static void
173ims_intr(struct ihidev *addr, void *buf, u_int len)
174{
175	struct ims_softc *sc = (struct ims_softc *)addr;
176	struct hidms *ms = &sc->sc_ms;
177
178	if (sc->sc_enabled)
179		hidms_intr(ms, buf, len);
180}
181
182static int
183ims_enable(void *v)
184{
185	struct ims_softc *sc = v;
186	int error;
187
188	KASSERT(KERNEL_LOCKED_P());
189
190	if (sc->sc_enabled)
191		return EBUSY;
192
193	sc->sc_enabled = 1;
194	sc->sc_ms.hidms_buttons = 0;
195
196	error = ihidev_open(&sc->sc_hdev);
197	if (error)
198		sc->sc_enabled = 0;
199	return error;
200}
201
202static void
203ims_disable(void *v)
204{
205	struct ims_softc *sc = v;
206
207	KASSERT(KERNEL_LOCKED_P());
208
209#ifdef DIAGNOSTIC
210	if (!sc->sc_enabled) {
211		printf("ums_disable: not enabled\n");
212		return;
213	}
214#endif
215
216	if (sc->sc_enabled) {
217		sc->sc_enabled = 0;
218		ihidev_close(&sc->sc_hdev);
219	}
220
221}
222
223static int
224ims_ioctl(void *v, u_long cmd, void *data, int flag, struct lwp *l)
225{
226	struct ims_softc *sc = v;
227
228	switch (cmd) {
229	case WSMOUSEIO_GTYPE:
230		if (sc->sc_ms.flags & HIDMS_ABS) {
231			*(u_int *)data = WSMOUSE_TYPE_TPANEL;
232		} else {
233			/* XXX: should we set something else? */
234			*(u_int *)data = WSMOUSE_TYPE_USB;
235		}
236		return 0;
237	case WSMOUSEIO_SCALIBCOORDS:
238	case WSMOUSEIO_GCALIBCOORDS:
239		return tpcalib_ioctl(&sc->sc_ms.sc_tpcalib, cmd, data, flag, l);
240	}
241	return EPASSTHROUGH;
242}
243