xref: /openbsd/sys/dev/usb/uwacom.c (revision cdf25420)
1 /*	$OpenBSD: uwacom.c,v 1.8 2023/08/12 20:47:06 miod Exp $	*/
2 
3 /*
4  * Copyright (c) 2016 Frank Groeneveld <frank@frankgroeneveld.nl>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 /* Driver for USB Wacom tablets */
20 
21 #include <sys/param.h>
22 #include <sys/systm.h>
23 #include <sys/device.h>
24 
25 #include <dev/usb/usb.h>
26 #include <dev/usb/usbhid.h>
27 
28 #include <dev/usb/usbdi.h>
29 #include <dev/usb/usbdi_util.h>
30 #include <dev/usb/usbdevs.h>
31 #include <dev/usb/uhidev.h>
32 
33 #include <dev/wscons/wsconsio.h>
34 #include <dev/wscons/wsmousevar.h>
35 
36 #include <dev/hid/hidmsvar.h>
37 
38 #define	UWACOM_USE_PRESSURE	0x0001 /* button 0 is flaky, use tip pressure */
39 #define	UWACOM_BIG_ENDIAN	0x0002 /* XY reporting byte order */
40 
41 struct uwacom_softc {
42 	struct uhidev		sc_hdev;
43 	struct hidms		sc_ms;
44 	struct hid_location	sc_loc_tip_press;
45 	int			sc_flags;
46 	int			sc_x, sc_y, sc_z, sc_w;
47 	int			sc_moved;
48 };
49 
50 struct cfdriver uwacom_cd = {
51 	NULL, "uwacom", DV_DULL
52 };
53 
54 const struct usb_devno uwacom_devs[] = {
55 	{ USB_VENDOR_WACOM, USB_PRODUCT_WACOM_INTUOS_DRAW },
56 	{ USB_VENDOR_WACOM, USB_PRODUCT_WACOM_ONE_S },
57 	{ USB_VENDOR_WACOM, USB_PRODUCT_WACOM_ONE_M },
58 	{ USB_VENDOR_WACOM, USB_PRODUCT_WACOM_INTUOS_S }
59 };
60 
61 int	uwacom_match(struct device *, void *, void *);
62 void	uwacom_attach(struct device *, struct device *, void *);
63 int	uwacom_detach(struct device *, int);
64 void	uwacom_intr_legacy(struct uhidev *, void *, u_int);
65 void	uwacom_intr(struct uhidev *, void *, u_int);
66 int	uwacom_enable(void *);
67 void	uwacom_disable(void *);
68 int	uwacom_ioctl(void *, u_long, caddr_t, int, struct proc *);
69 
70 const struct cfattach uwacom_ca = {
71 	sizeof(struct uwacom_softc), uwacom_match, uwacom_attach, uwacom_detach
72 };
73 
74 const struct wsmouse_accessops uwacom_accessops = {
75 	uwacom_enable,
76 	uwacom_ioctl,
77 	uwacom_disable,
78 };
79 
80 int
uwacom_match(struct device * parent,void * match,void * aux)81 uwacom_match(struct device *parent, void *match, void *aux)
82 {
83 	struct uhidev_attach_arg *uha = aux;
84 	int size;
85 	void *desc;
86 
87 	if (UHIDEV_CLAIM_MULTIPLE_REPORTID(uha))
88 		return (UMATCH_NONE);
89 
90 	if (usb_lookup(uwacom_devs, uha->uaa->vendor,
91 	    uha->uaa->product) == NULL)
92 		return (UMATCH_NONE);
93 
94 	uhidev_get_report_desc(uha->parent, &desc, &size);
95 
96 	if (hid_is_collection(desc, size, uha->reportid,
97 	    HID_USAGE2(HUP_WACOM | HUP_DIGITIZERS, HUD_DIGITIZER)))
98 		return (UMATCH_IFACECLASS);
99 	if (!hid_locate(desc, size, HID_USAGE2(HUP_WACOM, HUG_POINTER),
100 	    uha->reportid, hid_input, NULL, NULL))
101 		return (UMATCH_NONE);
102 
103 	return (UMATCH_IFACECLASS);
104 }
105 
106 void
uwacom_attach(struct device * parent,struct device * self,void * aux)107 uwacom_attach(struct device *parent, struct device *self, void *aux)
108 {
109 	struct uwacom_softc *sc = (struct uwacom_softc *)self;
110 	struct hidms *ms = &sc->sc_ms;
111 	struct uhidev_attach_arg *uha = (struct uhidev_attach_arg *)aux;
112 	struct usb_attach_arg *uaa = uha->uaa;
113 	static uByte wacom_report_buf[2] = { 0x02, 0x02 };
114 	int size, repid;
115 	void *desc;
116 
117 	sc->sc_hdev.sc_intr = uwacom_intr_legacy;
118 	sc->sc_hdev.sc_parent = uha->parent;
119 	sc->sc_hdev.sc_udev = uaa->device;
120 	sc->sc_hdev.sc_report_id = uha->reportid;
121 
122 	usbd_set_idle(uha->parent->sc_udev, uha->parent->sc_ifaceno, 0, 0);
123 
124 	uhidev_get_report_desc(uha->parent, &desc, &size);
125 	repid = uha->reportid;
126 
127 	sc->sc_hdev.sc_isize = hid_report_size(desc, size, hid_input, repid);
128 	sc->sc_hdev.sc_osize = hid_report_size(desc, size, hid_output, repid);
129 	sc->sc_hdev.sc_fsize = hid_report_size(desc, size, hid_feature, repid);
130 
131 	ms->sc_device = self;
132 	ms->sc_rawmode = 1;
133 	ms->sc_flags = HIDMS_ABSX | HIDMS_ABSY;
134 	ms->sc_num_buttons = 3;
135 
136 	ms->sc_loc_x.pos = 8;
137 	ms->sc_loc_x.size = 16;
138 	ms->sc_loc_y.pos = 24;
139 	ms->sc_loc_y.size = 16;
140 
141 	ms->sc_tsscale.minx = 0;
142 	ms->sc_tsscale.miny = 0;
143 
144 	ms->sc_loc_btn[0].pos = 0;
145 	ms->sc_loc_btn[0].size = 1;
146 	ms->sc_loc_btn[1].pos = 1;
147 	ms->sc_loc_btn[1].size = 1;
148 	ms->sc_loc_btn[2].pos = 2;
149 	ms->sc_loc_btn[2].size = 1;
150 
151 	switch (uha->uaa->product) {
152 	case USB_PRODUCT_WACOM_ONE_S:
153 	case USB_PRODUCT_WACOM_INTUOS_S:
154 		uhidev_set_report(uha->parent, UHID_FEATURE_REPORT,
155 		    sc->sc_hdev.sc_report_id, &wacom_report_buf,
156 		    sizeof(wacom_report_buf));
157 		sc->sc_hdev.sc_intr = uwacom_intr;
158 		hidms_setup((struct device *)sc, ms, HIDMS_WACOM_SETUP,
159 		    repid, desc, size);
160 		break;
161 	case USB_PRODUCT_WACOM_INTUOS_DRAW:
162 		sc->sc_flags = UWACOM_USE_PRESSURE | UWACOM_BIG_ENDIAN;
163 		sc->sc_loc_tip_press.pos = 43;
164 		sc->sc_loc_tip_press.size = 8;
165 		ms->sc_tsscale.maxx = 7600;
166 		ms->sc_tsscale.maxy = 4750;
167 		break;
168 	}
169 
170 	hidms_attach(ms, &uwacom_accessops);
171 }
172 
173 int
uwacom_detach(struct device * self,int flags)174 uwacom_detach(struct device *self, int flags)
175 {
176 	struct uwacom_softc *sc = (struct uwacom_softc *)self;
177 	struct hidms *ms = &sc->sc_ms;
178 
179 	return hidms_detach(ms, flags);
180 }
181 
182 void
uwacom_intr_legacy(struct uhidev * addr,void * buf,u_int len)183 uwacom_intr_legacy(struct uhidev *addr, void *buf, u_int len)
184 {
185 	struct uwacom_softc *sc = (struct uwacom_softc *)addr;
186 	struct hidms *ms = &sc->sc_ms;
187 	u_int32_t buttons = 0;
188 	uint8_t *data = (uint8_t *)buf;
189 	int i, x, y, pressure;
190 
191 	if (ms->sc_enabled == 0)
192 		return;
193 
194 	/* ignore proximity, it will cause invalid button 2 events */
195 	if ((data[0] & 0xf0) == 0xc0)
196 		return;
197 
198 	x = hid_get_data(data, len, &ms->sc_loc_x);
199 	y = hid_get_data(data, len, &ms->sc_loc_y);
200 
201 	if (sc->sc_flags & UWACOM_BIG_ENDIAN) {
202 		x = be16toh(x);
203 		y = be16toh(y);
204 	}
205 
206 	for (i = 0; i < ms->sc_num_buttons; i++)
207 		if (hid_get_data(data, len, &ms->sc_loc_btn[i]))
208 			buttons |= 1 << i;
209 
210 	if (sc->sc_flags & UWACOM_USE_PRESSURE) {
211 		pressure = hid_get_data(data, len, &sc->sc_loc_tip_press);
212 		if (pressure > 10)
213 			buttons |= 1;
214 		else
215 			buttons &= ~1;
216 	}
217 
218 	if (x != 0 || y != 0 || buttons != ms->sc_buttons) {
219 		wsmouse_position(ms->sc_wsmousedev, x, y);
220 		wsmouse_buttons(ms->sc_wsmousedev, buttons);
221 		wsmouse_input_sync(ms->sc_wsmousedev);
222 	}
223 }
224 
225 void
uwacom_intr(struct uhidev * addr,void * buf,u_int len)226 uwacom_intr(struct uhidev *addr, void *buf, u_int len)
227 {
228 	struct uwacom_softc *sc = (struct uwacom_softc *)addr;
229 	struct hidms *ms = &sc->sc_ms;
230 	u_int32_t buttons = 0;
231 	uint8_t *data = (uint8_t *)buf;
232 	int i, j, x, y, dx, dy, dz, dw, pressure, distance;
233 
234 	if (ms->sc_enabled == 0)
235 		return;
236 
237 	x = hid_get_data(data, len, &ms->sc_loc_x);
238 	y = hid_get_data(data, len, &ms->sc_loc_y);
239 	pressure = hid_get_data(data, len, &ms->sc_loc_z);
240 	distance = hid_get_data(data, len, &ms->sc_loc_w);
241 
242 	if (!sc->sc_moved) {
243 		sc->sc_x = x;
244 		sc->sc_y = y;
245 		sc->sc_z = pressure;
246 		sc->sc_w = distance;
247 		sc->sc_moved = 1;
248 	}
249 
250 	dx = sc->sc_x - x;
251 	dy = sc->sc_y - y;
252 	/* Clamp sensitivity to +/-127 */
253 	dz = sc->sc_z / 32 - pressure / 32;
254 	dw = sc->sc_w - distance;
255 
256 	sc->sc_x = x;
257 	sc->sc_y = y;
258 	sc->sc_z = pressure;
259 	sc->sc_w = distance;
260 
261 	if (sc->sc_flags & UWACOM_BIG_ENDIAN) {
262 		x = be16toh(x);
263 		y = be16toh(y);
264 	}
265 
266 	for (i = 0; i < ms->sc_num_stylus_buttons; i++)
267 		if (hid_get_data(data, len, &ms->sc_loc_btn[i]))
268 			buttons |= 1 << i;
269 
270 	for (j = 0; i < ms->sc_num_buttons; i++, j++)
271 		if (hid_get_data(data, len, &ms->sc_loc_btn[i]))
272 			buttons |= 1 << j;
273 
274 	if (x != 0 || y != 0 || pressure != 0 || distance != 0 ||
275 	    buttons != ms->sc_buttons) {
276 		wsmouse_motion(ms->sc_wsmousedev, -dx, dy, dz, dw);
277 		wsmouse_buttons(ms->sc_wsmousedev, buttons);
278 		wsmouse_input_sync(ms->sc_wsmousedev);
279 	}
280 }
281 
282 int
uwacom_enable(void * v)283 uwacom_enable(void *v)
284 {
285 	struct uwacom_softc *sc = v;
286 	struct hidms *ms = &sc->sc_ms;
287 	int rv;
288 
289 	if (usbd_is_dying(sc->sc_hdev.sc_udev))
290 		return EIO;
291 
292 	if ((rv = hidms_enable(ms)) != 0)
293 		return rv;
294 
295 	return uhidev_open(&sc->sc_hdev);
296 }
297 
298 void
uwacom_disable(void * v)299 uwacom_disable(void *v)
300 {
301 	struct uwacom_softc *sc = v;
302 	struct hidms *ms = &sc->sc_ms;
303 
304 	hidms_disable(ms);
305 	uhidev_close(&sc->sc_hdev);
306 }
307 
308 int
uwacom_ioctl(void * v,u_long cmd,caddr_t data,int flag,struct proc * p)309 uwacom_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p)
310 {
311 	struct uwacom_softc *sc = v;
312 	struct hidms *ms = &sc->sc_ms;
313 	int rc;
314 
315 	switch (cmd) {
316 	case WSMOUSEIO_GTYPE:
317 		*(u_int *)data = WSMOUSE_TYPE_TPANEL;
318 		return 0;
319 	}
320 
321 	rc = uhidev_ioctl(&sc->sc_hdev, cmd, data, flag, p);
322 	if (rc != -1)
323 		return rc;
324 
325 	return hidms_ioctl(ms, cmd, data, flag, p);
326 }
327 
328