xref: /openbsd-src/sys/dev/usb/utwitch.c (revision 81508fe356eb7772a68118f65f91723ce5261d7d)
1*81508fe3Sjsg /*	$OpenBSD: utwitch.c,v 1.23 2024/05/23 03:21:09 jsg Exp $ */
2e1c991b8Syuo 
3e1c991b8Syuo /*
4e1c991b8Syuo  * Copyright (c) 2010 Yojiro UO <yuo@nui.org>
5e1c991b8Syuo  *
6e1c991b8Syuo  * Permission to use, copy, modify, and distribute this software for any
7e1c991b8Syuo  * purpose with or without fee is hereby granted, provided that the above
8e1c991b8Syuo  * copyright notice and this permission notice appear in all copies.
9e1c991b8Syuo  *
10e1c991b8Syuo  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCAIMS ALL WARRANTIES
11e1c991b8Syuo  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12e1c991b8Syuo  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13e1c991b8Syuo  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14e1c991b8Syuo  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15e1c991b8Syuo  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16e1c991b8Syuo  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17e1c991b8Syuo  */
18e1c991b8Syuo 
19e1c991b8Syuo /* Driver for Maywa-Denki & KAYAC YUREX BBU sensor */
20230ab8cdSjasper /* this driver was previously known as uyurex(4). */
21e1c991b8Syuo 
22e1c991b8Syuo #include <sys/param.h>
23e1c991b8Syuo #include <sys/systm.h>
24e1c991b8Syuo #include <sys/malloc.h>
25e1c991b8Syuo #include <sys/device.h>
26e1c991b8Syuo #include <sys/sensors.h>
27e1c991b8Syuo 
28e1c991b8Syuo #include <dev/usb/usb.h>
29e1c991b8Syuo #include <dev/usb/usbhid.h>
30e1c991b8Syuo #include <dev/usb/usbdi.h>
31e1c991b8Syuo #include <dev/usb/usbdevs.h>
32e1c991b8Syuo #include <dev/usb/uhidev.h>
338a83145eSjcs 
34e1c991b8Syuo #define	CMD_NONE	0xf0
35e1c991b8Syuo #define CMD_EOF		0x0d
36e1c991b8Syuo #define CMD_ACK		0x21
37e1c991b8Syuo #define	CMD_MODE	0x41 /* XXX */
38e1c991b8Syuo #define	CMD_VALUE	0x43
39e1c991b8Syuo #define CMD_READ	0x52
40e1c991b8Syuo #define CMD_WRITE	0x53
41e1c991b8Syuo #define CMD_PADDING	0xff
42e1c991b8Syuo 
43e1c991b8Syuo #define UPDATE_TICK	5 /* sec */
44e1c991b8Syuo 
45e1c991b8Syuo #ifdef UYUREX_DEBUG
469117c2b1Ssthen #define DPRINTF(x)	do { printf x; } while (0)
47e1c991b8Syuo #else
489117c2b1Ssthen #define DPRINTF(x)
49e1c991b8Syuo #endif
50e1c991b8Syuo 
51e1c991b8Syuo struct utwitch_softc {
52e1c991b8Syuo 	struct uhidev		 sc_hdev;
53ab0b1be7Smglocker 	struct usbd_device	*sc_udev;
54e1c991b8Syuo 
55e1c991b8Syuo 	/* uhidev parameters */
56e1c991b8Syuo 	size_t			 sc_ilen;	/* input report length */
57e1c991b8Syuo 	size_t			 sc_olen;	/* output report length */
58e1c991b8Syuo 
59e1c991b8Syuo 	uint8_t			*sc_ibuf;
60e1c991b8Syuo 
61e1c991b8Syuo 	/* sensor framework */
62e1c991b8Syuo 	struct ksensor		 sc_sensor_val;
63e1c991b8Syuo 	struct ksensor		 sc_sensor_delta;
64e1c991b8Syuo 	struct ksensordev	 sc_sensordev;
65e1c991b8Syuo 	struct sensor_task	*sc_sensortask;
66e1c991b8Syuo 
67e1c991b8Syuo 	/* device private */
68e1c991b8Syuo 	int			 sc_initialized;
69e1c991b8Syuo 	uint8_t			 issueing_cmd;
70e1c991b8Syuo 	uint8_t			 accepted_cmd;
71e1c991b8Syuo 
72e1c991b8Syuo 	uint32_t		 sc_curval;
73e1c991b8Syuo 	uint32_t		 sc_oldval;
74e1c991b8Syuo };
75e1c991b8Syuo 
76e1c991b8Syuo const struct usb_devno utwitch_devs[] = {
77e1c991b8Syuo 	{ USB_VENDOR_MICRODIA, USB_PRODUCT_MICRODIA_YUREX},
78e1c991b8Syuo };
79e1c991b8Syuo 
80e1c991b8Syuo int utwitch_match(struct device *, void *, void *);
81e1c991b8Syuo void utwitch_attach(struct device *, struct device *, void *);
82e1c991b8Syuo int utwitch_detach(struct device *, int);
83e1c991b8Syuo 
84e1c991b8Syuo void utwitch_set_mode(struct utwitch_softc *, uint8_t);
85e1c991b8Syuo void utwitch_read_value_request(struct utwitch_softc *);
86e1c991b8Syuo void utwitch_write_value_request(struct utwitch_softc *, uint32_t);
87e1c991b8Syuo 
88e1c991b8Syuo void utwitch_intr(struct uhidev *, void *, u_int);
89e1c991b8Syuo void utwitch_refresh(void *);
90e1c991b8Syuo 
91e1c991b8Syuo struct cfdriver utwitch_cd = {
92e1c991b8Syuo 	NULL, "utwitch", DV_DULL
93e1c991b8Syuo };
94e1c991b8Syuo 
95e1c991b8Syuo const struct cfattach utwitch_ca = {
96e1c991b8Syuo 	sizeof(struct utwitch_softc),
97e1c991b8Syuo 	utwitch_match,
98e1c991b8Syuo 	utwitch_attach,
999117c2b1Ssthen 	utwitch_detach
100e1c991b8Syuo };
101e1c991b8Syuo 
102e1c991b8Syuo int
utwitch_match(struct device * parent,void * match,void * aux)103e1c991b8Syuo utwitch_match(struct device *parent, void *match, void *aux)
104e1c991b8Syuo {
105f964a65cSmpi 	struct uhidev_attach_arg *uha = aux;
106f964a65cSmpi 
107faac88c0Santon 	if (UHIDEV_CLAIM_MULTIPLE_REPORTID(uha))
108f964a65cSmpi 		return (UMATCH_NONE);
109e1c991b8Syuo 
110230ab8cdSjasper 	return (usb_lookup(utwitch_devs, uha->uaa->vendor, uha->uaa->product) != NULL ?
111230ab8cdSjasper 	    UMATCH_VENDOR_PRODUCT : UMATCH_NONE);
112e1c991b8Syuo }
113e1c991b8Syuo 
114e1c991b8Syuo void
utwitch_attach(struct device * parent,struct device * self,void * aux)115e1c991b8Syuo utwitch_attach(struct device *parent, struct device *self, void *aux)
116e1c991b8Syuo {
117e1c991b8Syuo 	struct utwitch_softc *sc = (struct utwitch_softc *)self;
118e1c991b8Syuo 	struct usb_attach_arg *uaa = aux;
119e1c991b8Syuo 	struct uhidev_attach_arg *uha = (struct uhidev_attach_arg *)uaa;
120ab0b1be7Smglocker 	struct usbd_device *dev = uha->parent->sc_udev;
121e1c991b8Syuo 	int size, repid, err;
122e1c991b8Syuo 	void *desc;
123e1c991b8Syuo 
124e1c991b8Syuo 	sc->sc_udev = dev;
125e1c991b8Syuo 	sc->sc_hdev.sc_intr = utwitch_intr;
126e1c991b8Syuo 	sc->sc_hdev.sc_parent = uha->parent;
127e1c991b8Syuo 	sc->sc_hdev.sc_report_id = uha->reportid;
128e1c991b8Syuo 
129e1c991b8Syuo 	uhidev_get_report_desc(uha->parent, &desc, &size);
130e1c991b8Syuo 	repid = uha->reportid;
131e1c991b8Syuo 	sc->sc_ilen = hid_report_size(desc, size, hid_input, repid);
132e1c991b8Syuo 	sc->sc_olen = hid_report_size(desc, size, hid_output, repid);
133e1c991b8Syuo 
134e1c991b8Syuo 	err = uhidev_open(&sc->sc_hdev);
135e1c991b8Syuo 	if (err) {
1361fc21adeSjasper 		printf("%s: uhidev_open %d\n", __func__, err);
137e1c991b8Syuo 		return;
138e1c991b8Syuo 	}
139e1c991b8Syuo 	sc->sc_ibuf = malloc(sc->sc_ilen, M_USBDEV, M_WAITOK);
140e1c991b8Syuo 
141e1c991b8Syuo 	printf("\n");
142e1c991b8Syuo 
143e1c991b8Syuo 
144e1c991b8Syuo 	/* attach sensor */
145e1c991b8Syuo 	strlcpy(sc->sc_sensordev.xname, sc->sc_hdev.sc_dev.dv_xname,
146e1c991b8Syuo 	    sizeof(sc->sc_sensordev.xname));
147e1c991b8Syuo 
148e1c991b8Syuo 	/* add BBU sensor */
149e1c991b8Syuo 	sc->sc_sensor_val.type = SENSOR_INTEGER;
150e1c991b8Syuo 	sensor_attach(&sc->sc_sensordev, &sc->sc_sensor_val);
151e1c991b8Syuo 	strlcpy(sc->sc_sensor_val.desc, "BBU",
152e1c991b8Syuo 		sizeof(sc->sc_sensor_val.desc));
153e1c991b8Syuo 
154e1c991b8Syuo 	/* add BBU delta sensor */
155e1c991b8Syuo 	sc->sc_sensor_delta.type = SENSOR_INTEGER;
156e1c991b8Syuo 	sensor_attach(&sc->sc_sensordev, &sc->sc_sensor_delta);
157e1c991b8Syuo 	strlcpy(sc->sc_sensor_delta.desc, "mBBU/sec",
158e1c991b8Syuo 		sizeof(sc->sc_sensor_delta.desc));
159e1c991b8Syuo 
160e1c991b8Syuo 	sc->sc_sensortask = sensor_task_register(sc, utwitch_refresh, UPDATE_TICK);
161e1c991b8Syuo 	if (sc->sc_sensortask == NULL) {
162e1c991b8Syuo 		printf(", unable to register update task\n");
163e1c991b8Syuo 		return;
164e1c991b8Syuo 	}
165e1c991b8Syuo 	sensordev_install(&sc->sc_sensordev);
166e1c991b8Syuo 
167e1c991b8Syuo 	DPRINTF(("utwitch_attach: complete\n"));
168e1c991b8Syuo 
169e1c991b8Syuo 	/* init device */ /* XXX */
170e1c991b8Syuo 	utwitch_set_mode(sc, 0);
171e1c991b8Syuo }
172e1c991b8Syuo 
173e1c991b8Syuo int
utwitch_detach(struct device * self,int flags)174e1c991b8Syuo utwitch_detach(struct device *self, int flags)
175e1c991b8Syuo {
176e1c991b8Syuo 	struct utwitch_softc *sc = (struct utwitch_softc *)self;
177e1c991b8Syuo 	int rv = 0;
178e1c991b8Syuo 
179e1c991b8Syuo 	wakeup(&sc->sc_sensortask);
180e1c991b8Syuo 	sensordev_deinstall(&sc->sc_sensordev);
181e1c991b8Syuo 	sensor_detach(&sc->sc_sensordev, &sc->sc_sensor_val);
182e1c991b8Syuo 	sensor_detach(&sc->sc_sensordev, &sc->sc_sensor_delta);
183e1c991b8Syuo 	if (sc->sc_sensortask != NULL)
184e1c991b8Syuo 		sensor_task_unregister(sc->sc_sensortask);
185e1c991b8Syuo 
1869dcdfc61Smpi 	if (sc->sc_hdev.sc_state & UHIDEV_OPEN)
1879dcdfc61Smpi 		uhidev_close(&sc->sc_hdev);
1889dcdfc61Smpi 
189e1c991b8Syuo 	if (sc->sc_ibuf != NULL) {
190234dfda1Sderaadt 		free(sc->sc_ibuf, M_USBDEV, sc->sc_ilen);
191e1c991b8Syuo 		sc->sc_ibuf = NULL;
192e1c991b8Syuo 	}
193e1c991b8Syuo 
194e1c991b8Syuo 	return (rv);
195e1c991b8Syuo }
196e1c991b8Syuo 
197e1c991b8Syuo void
utwitch_intr(struct uhidev * addr,void * ibuf,u_int len)198e1c991b8Syuo utwitch_intr(struct uhidev *addr, void *ibuf, u_int len)
199e1c991b8Syuo {
200e1c991b8Syuo 	struct utwitch_softc *sc = (struct utwitch_softc *)addr;
201e1c991b8Syuo 	uint8_t buf[8];
202e1c991b8Syuo 	uint32_t val;
203e1c991b8Syuo 
204e1c991b8Syuo 	if (sc->sc_ibuf == NULL)
205e1c991b8Syuo 		return;
206e1c991b8Syuo 
207e1c991b8Syuo 	/* process requests */
208e1c991b8Syuo 	memcpy(buf, ibuf, 8);
209e1c991b8Syuo 	DPRINTF(("intr: %.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x\n",
210e1c991b8Syuo 		buf[0], buf[1], buf[2], buf[3],
211e1c991b8Syuo 		buf[4], buf[5], buf[6], buf[7]));
212e1c991b8Syuo 
213e1c991b8Syuo 
214e1c991b8Syuo 	switch (buf[0]) {
215e1c991b8Syuo 	case CMD_ACK:
216e1c991b8Syuo 		if (buf[1] == sc->issueing_cmd) {
217175fa153Sjasper 			DPRINTF(("ack received for cmd 0x%.2x\n", buf[1]));
218e1c991b8Syuo 			sc->accepted_cmd = buf[1];
219e1c991b8Syuo 		} else {
220e1c991b8Syuo 			DPRINTF(("cmd-ack mismatch: recved 0x%.2x, expect 0x%.2x\n",
221e1c991b8Syuo 				buf[1], sc->issueing_cmd));
222e1c991b8Syuo 			/* discard previous command */
223e1c991b8Syuo 			sc->accepted_cmd = CMD_NONE;
224e1c991b8Syuo 			sc->issueing_cmd = CMD_NONE;
225e1c991b8Syuo 		}
226e1c991b8Syuo 		break;
227e1c991b8Syuo 	case CMD_READ:
228e1c991b8Syuo 	case CMD_VALUE:
229e1c991b8Syuo 		val = (buf[2] << 24) + (buf[3] << 16) + (buf[4] << 8)  + buf[5];
230e1c991b8Syuo 		if (!sc->sc_initialized) {
231e1c991b8Syuo 			sc->sc_oldval = val;
232e1c991b8Syuo 			sc->sc_initialized = 1;
233e1c991b8Syuo 		}
234e1c991b8Syuo 		sc->sc_sensor_val.value = val;
235e1c991b8Syuo 		sc->sc_curval = val;
236e1c991b8Syuo 		DPRINTF(("recv value update message: %d\n", val));
237e1c991b8Syuo 		break;
238e1c991b8Syuo 	default:
239e1c991b8Syuo 		DPRINTF(("unknown message: 0x%.2x\n", buf[0]));
240e1c991b8Syuo 	}
241e1c991b8Syuo 
242e1c991b8Syuo 	return;
243e1c991b8Syuo }
244e1c991b8Syuo 
245e1c991b8Syuo void
utwitch_refresh(void * arg)246e1c991b8Syuo utwitch_refresh(void *arg)
247e1c991b8Syuo {
248e1c991b8Syuo 	struct utwitch_softc *sc = arg;
249e1c991b8Syuo 
250e1c991b8Syuo 	if (!sc->sc_initialized) {
251e1c991b8Syuo 		utwitch_read_value_request(sc);
252e1c991b8Syuo 	} else {
253e1c991b8Syuo 		/* calculate delta value */
254e1c991b8Syuo 		sc->sc_sensor_delta.value =
255e1c991b8Syuo 			(1000 * (sc->sc_curval - sc->sc_oldval)) / UPDATE_TICK;
256e1c991b8Syuo 		sc->sc_oldval = sc->sc_curval;
257e1c991b8Syuo 	}
258e1c991b8Syuo }
259e1c991b8Syuo 
260e1c991b8Syuo void
utwitch_set_mode(struct utwitch_softc * sc,uint8_t val)261e1c991b8Syuo utwitch_set_mode(struct utwitch_softc *sc, uint8_t val)
262e1c991b8Syuo {
263e1c991b8Syuo 	uint8_t req[8];
264cc1678f7Smpi 	int olen;
265e1c991b8Syuo 
266cc1678f7Smpi 	olen = MIN(sc->sc_olen, sizeof(req));
267e1c991b8Syuo 	memset(req, CMD_PADDING, sizeof(req));
268e1c991b8Syuo 	req[0] = CMD_MODE;
269e1c991b8Syuo 	req[1] = val;
270e1c991b8Syuo 	req[2] = CMD_EOF;
271e6a02383Smpi 	if (uhidev_set_report(sc->sc_hdev.sc_parent, UHID_OUTPUT_REPORT,
272cc1678f7Smpi 	    sc->sc_hdev.sc_report_id, req, olen) != olen) {
273e1c991b8Syuo 		printf("uhidev_set_report error:EIO\n");
274e1c991b8Syuo 		return;
275e1c991b8Syuo 	}
276e1c991b8Syuo 
277e1c991b8Syuo 	/* wait ack */
278a047a98bScheloha 	tsleep_nsec(&sc->sc_sensortask, 0, "utwitch", MSEC_TO_NSEC(1000));
279e1c991b8Syuo }
280e1c991b8Syuo 
281e1c991b8Syuo void
utwitch_read_value_request(struct utwitch_softc * sc)282e1c991b8Syuo utwitch_read_value_request(struct utwitch_softc *sc)
283e1c991b8Syuo {
284e1c991b8Syuo 	uint8_t req[8];
285cc1678f7Smpi 	int olen;
286e1c991b8Syuo 
287cc1678f7Smpi 	olen = MIN(sc->sc_olen, sizeof(req));
288e1c991b8Syuo 	memset(req, CMD_PADDING, sizeof(req));
289e1c991b8Syuo 	req[0] = CMD_READ;
290e1c991b8Syuo 	req[1] = CMD_EOF;
291e1c991b8Syuo 	sc->issueing_cmd = CMD_READ;
292e1c991b8Syuo 	sc->accepted_cmd = CMD_NONE;
293e6a02383Smpi 	if (uhidev_set_report(sc->sc_hdev.sc_parent, UHID_OUTPUT_REPORT,
294cc1678f7Smpi 	    sc->sc_hdev.sc_report_id, req, olen) != olen)
295e1c991b8Syuo 		return;
296e1c991b8Syuo 
297e1c991b8Syuo 	/* wait till sensor data are updated, 500ms will be enough */
298a047a98bScheloha 	tsleep_nsec(&sc->sc_sensortask, 0, "utwitch", MSEC_TO_NSEC(500));
299e1c991b8Syuo }
300e1c991b8Syuo 
301e1c991b8Syuo void
utwitch_write_value_request(struct utwitch_softc * sc,uint32_t val)302e1c991b8Syuo utwitch_write_value_request(struct utwitch_softc *sc, uint32_t val)
303e1c991b8Syuo {
304e1c991b8Syuo 	uint32_t v;
305e1c991b8Syuo 	uint8_t req[8];
306cc1678f7Smpi 	int olen;
307e1c991b8Syuo 
308cc1678f7Smpi 	olen = MIN(sc->sc_olen, sizeof(req));
309e1c991b8Syuo 	req[0] = CMD_WRITE;
310e1c991b8Syuo 	req[1] = 0;
311e1c991b8Syuo 	req[6] = CMD_EOF;
312e1c991b8Syuo 	req[7] = CMD_PADDING;
313e1c991b8Syuo 	v = htobe32(val);
314e1c991b8Syuo 	memcpy(req + 2, &v, sizeof(uint32_t));
315e1c991b8Syuo 
316e1c991b8Syuo 	sc->issueing_cmd = CMD_WRITE;
317e1c991b8Syuo 	sc->accepted_cmd = CMD_NONE;
318e6a02383Smpi 	if (uhidev_set_report(sc->sc_hdev.sc_parent, UHID_OUTPUT_REPORT,
319cc1678f7Smpi 	    sc->sc_hdev.sc_report_id, req, olen) != olen)
320e1c991b8Syuo 		return;
321e1c991b8Syuo 
322e1c991b8Syuo 	/* wait till sensor data are updated, 250ms will be enough */
323a047a98bScheloha 	tsleep_nsec(&sc->sc_sensortask, 0, "utwitch", MSEC_TO_NSEC(250));
324e1c991b8Syuo }
325