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