xref: /netbsd-src/sys/dev/usb/uintuos.c (revision 57da788e746d15ab69460e76ed92ca1711440ec6)
1 /*	$NetBSD: uintuos.c,v 1.1 2022/06/30 06:30:44 macallan Exp $	*/
2 
3 /*
4  * Copyright (c) 2019 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Yorick Hardy.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 /*
33  *  Wacom Intuos Pen driver.
34  *  (partially based on uep.c and ums.c)
35  */
36 #include <sys/cdefs.h>
37 __KERNEL_RCSID(0, "$NetBSD: uintuos.c,v 1.1 2022/06/30 06:30:44 macallan Exp $");
38 
39 #include <sys/param.h>
40 #include <sys/systm.h>
41 #include <sys/kernel.h>
42 #include <sys/device.h>
43 #include <sys/ioctl.h>
44 #include <sys/vnode.h>
45 
46 #include <dev/usb/usb.h>
47 #include <dev/usb/usbhid.h>
48 
49 #include <dev/usb/usbdi.h>
50 #include <dev/usb/usbdivar.h>
51 #include <dev/usb/usbdi_util.h>
52 #include <dev/usb/usbdevs.h>
53 #include <dev/usb/uhidev.h>
54 
55 #include <dev/wscons/wsconsio.h>
56 #include <dev/wscons/wsmousevar.h>
57 #include <dev/wscons/tpcalibvar.h>
58 
59 struct uintuos_softc {
60 	struct uhidev sc_hdev;
61 
62 	device_t		sc_wsmousedev;	/* wsmouse device */
63 	struct tpcalib_softc	sc_tpcalib;	/* calibration */
64 
65 	u_char sc_enabled;
66 	u_char sc_dying;
67 };
68 
69 Static void uintuos_cth490_intr(struct uhidev *, void *, u_int);
70 Static void uintuos_ctl6100_intr(struct uhidev *, void *, u_int);
71 
72 Static int	uintuos_enable(void *);
73 Static void	uintuos_disable(void *);
74 Static int	uintuos_ioctl(void *, u_long, void *, int, struct lwp *);
75 
76 const struct wsmouse_accessops uintuos_accessops = {
77 	uintuos_enable,
78 	uintuos_ioctl,
79 	uintuos_disable,
80 };
81 
82 int uintuos_match(device_t, cfdata_t, void *);
83 void uintuos_attach(device_t, device_t, void *);
84 void uintuos_childdet(device_t, device_t);
85 int uintuos_detach(device_t, int);
86 int uintuos_activate(device_t, enum devact);
87 
88 CFATTACH_DECL2_NEW(uintuos, sizeof(struct uintuos_softc), uintuos_match, uintuos_attach,
89     uintuos_detach, uintuos_activate, NULL, uintuos_childdet);
90 
91 int
uintuos_match(device_t parent,cfdata_t match,void * aux)92 uintuos_match(device_t parent, cfdata_t match, void *aux)
93 {
94 	struct uhidev_attach_arg *uha = aux;
95 
96 	if ((uha->uiaa->uiaa_vendor == USB_VENDOR_WACOM) &&
97 		(uha->uiaa->uiaa_product == USB_PRODUCT_WACOM_CTH490K0) &&
98 		(uha->reportid == 16))
99 		return UMATCH_VENDOR_PRODUCT;
100 
101 	if ((uha->uiaa->uiaa_vendor == USB_VENDOR_WACOM) &&
102 		(uha->uiaa->uiaa_product == USB_PRODUCT_WACOM_CTL6100WL) &&
103 		(uha->reportid == 16))
104 		return UMATCH_VENDOR_PRODUCT;
105 
106 	return UMATCH_NONE;
107 }
108 
109 void
uintuos_attach(device_t parent,device_t self,void * aux)110 uintuos_attach(device_t parent, device_t self, void *aux)
111 {
112 	struct uintuos_softc *sc = device_private(self);
113 	struct uhidev_attach_arg *uha = aux;
114 	struct wsmousedev_attach_args a;
115 	struct wsmouse_calibcoords default_calib;
116 
117 	aprint_normal("\n");
118 	aprint_naive("\n");
119 
120 	sc->sc_hdev.sc_dev = self;
121 	sc->sc_hdev.sc_parent = uha->parent;
122 	sc->sc_hdev.sc_report_id = uha->reportid;
123 
124 	switch (uha->uiaa->uiaa_product) {
125 	case USB_PRODUCT_WACOM_CTH490K0:
126 		default_calib.minx = 0,
127 		default_calib.miny = 0,
128 		default_calib.maxx = 7600,
129 		default_calib.maxy = 4750,
130 		sc->sc_hdev.sc_intr = uintuos_cth490_intr;
131 		break;
132 	case USB_PRODUCT_WACOM_CTL6100WL:
133 		default_calib.minx = 0,
134 		default_calib.miny = 0,
135 		default_calib.maxx = 21600,
136 		default_calib.maxy = 13471,
137 		sc->sc_hdev.sc_intr = uintuos_ctl6100_intr;
138 		break;
139 	default:
140 		sc->sc_hdev.sc_intr = uintuos_cth490_intr;
141 		aprint_error_dev(self, "unsupported product\n");
142 		break;
143 	}
144 
145 
146 	if (!pmf_device_register(self, NULL, NULL))
147 		aprint_error_dev(self, "couldn't establish power handler\n");
148 
149 	a.accessops = &uintuos_accessops;
150 	a.accesscookie = sc;
151 
152 	sc->sc_wsmousedev = config_found(self, &a, wsmousedevprint);
153 
154 	default_calib.samplelen = WSMOUSE_CALIBCOORDS_RESET,
155 	tpcalib_init(&sc->sc_tpcalib);
156 	tpcalib_ioctl(&sc->sc_tpcalib, WSMOUSEIO_SCALIBCOORDS,
157 		(void *)&default_calib, 0, 0);
158 
159 	return;
160 }
161 
162 int
uintuos_detach(device_t self,int flags)163 uintuos_detach(device_t self, int flags)
164 {
165 	struct uintuos_softc *sc = device_private(self);
166 	int rv = 0;
167 
168 	sc->sc_dying = 1;
169 
170 	if (sc->sc_wsmousedev != NULL)
171 		rv = config_detach(sc->sc_wsmousedev, flags);
172 
173 	pmf_device_deregister(self);
174 
175 	return rv;
176 }
177 
178 void
uintuos_childdet(device_t self,device_t child)179 uintuos_childdet(device_t self, device_t child)
180 {
181 	struct uintuos_softc *sc = device_private(self);
182 
183 	KASSERT(sc->sc_wsmousedev == child);
184 	sc->sc_wsmousedev = NULL;
185 }
186 
187 int
uintuos_activate(device_t self,enum devact act)188 uintuos_activate(device_t self, enum devact act)
189 {
190 	struct uintuos_softc *sc = device_private(self);
191 
192 	switch (act) {
193 	case DVACT_DEACTIVATE:
194 		sc->sc_dying = 1;
195 		return 0;
196 	default:
197 		return EOPNOTSUPP;
198 	}
199 }
200 
201 Static int
uintuos_enable(void * v)202 uintuos_enable(void *v)
203 {
204 	struct uintuos_softc *sc = v;
205 	int error;
206 
207 	if (sc->sc_dying)
208 		return EIO;
209 
210 	if (sc->sc_enabled)
211 		return EBUSY;
212 
213 	sc->sc_enabled = 1;
214 
215 	error = uhidev_open(&sc->sc_hdev);
216 	if (error)
217 		sc->sc_enabled = 0;
218 
219 	return error;
220 }
221 
222 Static void
uintuos_disable(void * v)223 uintuos_disable(void *v)
224 {
225 	struct uintuos_softc *sc = v;
226 
227 	if (!sc->sc_enabled) {
228 		printf("uintuos_disable: not enabled\n");
229 		return;
230 	}
231 
232 	sc->sc_enabled = 0;
233 	uhidev_close(&sc->sc_hdev);
234 }
235 
236 Static int
uintuos_ioctl(void * v,u_long cmd,void * data,int flag,struct lwp * l)237 uintuos_ioctl(void *v, u_long cmd, void *data, int flag, struct lwp *l)
238 {
239 	struct uintuos_softc *sc = v;
240 	struct wsmouse_id *id;
241 
242 	switch (cmd) {
243 	case WSMOUSEIO_GTYPE:
244 		*(u_int *)data = WSMOUSE_TYPE_TPANEL;
245 		return 0;
246 
247 	case WSMOUSEIO_GETID:
248 		id = (struct wsmouse_id *)data;
249 		if (id->type != WSMOUSE_ID_TYPE_UIDSTR)
250 		 	return EINVAL;
251 
252 		snprintf(id->data, WSMOUSE_ID_MAXLEN, "%s %s %s",
253 			sc->sc_hdev.sc_parent->sc_udev->ud_vendor,
254 			sc->sc_hdev.sc_parent->sc_udev->ud_product,
255 			sc->sc_hdev.sc_parent->sc_udev->ud_serial);
256 		id->length = strlen(id->data);
257 		return 0;
258 
259 	case WSMOUSEIO_SCALIBCOORDS:
260 	case WSMOUSEIO_GCALIBCOORDS:
261 		return tpcalib_ioctl(&sc->sc_tpcalib, cmd, data, flag, l);
262 	}
263 
264 	return EPASSTHROUGH;
265 }
266 
267 void
uintuos_cth490_intr(struct uhidev * addr,void * ibuf,u_int len)268 uintuos_cth490_intr(struct uhidev *addr, void *ibuf, u_int len)
269 {
270 	struct uintuos_softc *sc = (struct uintuos_softc *)addr;
271 	u_char *p = ibuf;
272 	u_int btns = 0;
273 	int x = 0, y = 0, z = 0, s;
274 
275 	if (len != 9) {
276 		aprint_error_dev(sc->sc_hdev.sc_dev, "wrong report size - ignoring\n");
277 		return;
278 	}
279 
280 	/*
281 	 * Each report package contains 9 bytes as below (guessed by inspection):
282 	 *
283 	 * Byte 0	?VR? ?21T
284 	 * Byte 1	X coordinate (high byte)
285 	 * Byte 2	X coordinate (low byte)
286 	 * Byte 3	Y coordinate (high byte)
287 	 * Byte 4	Y coordinate (low byte)
288 	 * Byte 5	Pressure (high byte)
289 	 * Byte 6	Pressure (low byte)
290 	 * Byte 7	zero
291 	 * Byte 8	quality
292 	 *
293 	 * V: 1=valid data, 0=don't use
294 	 * R: 1=in range, 2=cannot sense
295 	 * 1: barrel button 1, 1=pressed, 0=not pressed
296 	 * 2: barrel button 2, 1=pressed, 0=not pressed
297 	 * T: 1=touched, 0=not touched (unreliable?)
298 	 * quality: 0 - 255, 255 = most reliable?
299 	 *
300 	 */
301 
302 	/* no valid data or not in range */
303 	if ((p[0] & 0x40) == 0 || (p[0] & 0x20) == 0)
304 		return;
305 
306 	if (sc->sc_wsmousedev != NULL) {
307 		x = (p[1] << 8) | p[2];
308 		y = (p[3] << 8) | p[4];
309 		z = (p[5] << 8) | p[6];
310 
311 		/*
312 		 * The "T" bit seems to require a *lot* of pressure to remain "1",
313 		 * use the pressure value instead (> 255) for button 1
314 		 *
315 		 */
316 		if (p[5] != 0)
317 			btns |= 1;
318 
319 		/* barrel button 1 => button 2 */
320 		if (p[0] & 0x02)
321 			btns |= 2;
322 
323 		/* barrel button 2 => button 3 */
324 		if (p[0] & 0x04)
325 			btns |= 4;
326 
327 		tpcalib_trans(&sc->sc_tpcalib, x, y, &x, &y);
328 
329 		s = spltty();
330 		wsmouse_input(sc->sc_wsmousedev, btns, x, y, z, 0,
331 			WSMOUSE_INPUT_ABSOLUTE_X |
332 			WSMOUSE_INPUT_ABSOLUTE_Y |
333 			WSMOUSE_INPUT_ABSOLUTE_Z);
334 		splx(s);
335 	}
336 }
337 
338 void
uintuos_ctl6100_intr(struct uhidev * addr,void * ibuf,u_int len)339 uintuos_ctl6100_intr(struct uhidev *addr, void *ibuf, u_int len)
340 {
341 	struct uintuos_softc *sc = (struct uintuos_softc *)addr;
342 	u_char *p = ibuf;
343 	u_int btns = 0;
344 	int x = 0, y = 0, z = 0, s;
345 
346 	if (len != 26) {
347 		aprint_error_dev(sc->sc_hdev.sc_dev, "wrong report size - ignoring\n");
348 		return;
349 	}
350 
351 	/*
352 	 * Each report package contains 26 bytes as below (guessed by inspection):
353 	 *
354 	 * Byte 0	?VR? ?21T
355 	 * Byte 1	X coordinate (low byte)
356 	 * Byte 2	X coordinate (high byte)
357 	 * Byte 3	zero
358 	 * Byte 4	Y coordinate (low byte)
359 	 * Byte 5	Y coordinate (high byte)
360 	 * Byte 6	zero
361 	 * Byte 7	Pressure (low byte)
362 	 * Byte 8	Pressure (high byte)
363 	 * Byte 9	zero
364 	 * Byte 10..14	zero
365 	 * Byte 15	quality?
366 	 * Byte 16..25	unknown
367 	 *
368 	 * V: 1=valid data, 0=don't use
369 	 * R: 1=in range, 0=cannot sense
370 	 * 1: barrel button 1, 1=pressed, 0=not pressed
371 	 * 2: barrel button 2, 1=pressed, 0=not pressed
372 	 * T: 1=touched, 0=not touched
373 	 * quality: 0 - 63, 0 = most reliable?
374 	 *
375 	 */
376 
377 	/* no valid data or not in range */
378 	if ((p[0] & 0x40) == 0 || (p[0] & 0x20) == 0)
379 		return;
380 
381 	if (sc->sc_wsmousedev != NULL) {
382 		x = (p[2] << 8) | p[1];
383 		y = (p[5] << 8) | p[4];
384 		z = (p[8] << 8) | p[7];
385 
386 		btns = p[0] & 0x7;
387 
388 		tpcalib_trans(&sc->sc_tpcalib, x, y, &x, &y);
389 
390 		s = spltty();
391 		wsmouse_input(sc->sc_wsmousedev, btns, x, y, z, 0,
392 			WSMOUSE_INPUT_ABSOLUTE_X |
393 			WSMOUSE_INPUT_ABSOLUTE_Y |
394 			WSMOUSE_INPUT_ABSOLUTE_Z);
395 		splx(s);
396 	}
397 }
398