xref: /openbsd-src/sys/dev/usb/ugold.c (revision f6aab3d83b51b91c24247ad2c2573574de475a82)
1 /*	$OpenBSD: ugold.c,v 1.23 2023/04/19 04:51:53 miod Exp $   */
2 
3 /*
4  * Copyright (c) 2013 Takayoshi SASANO <uaa@openbsd.org>
5  * Copyright (c) 2013 Martin Pieuchot <mpi@openbsd.org>
6  * Copyright (c) 2015 Joerg Jung <jung@openbsd.org>
7  *
8  * Permission to use, copy, modify, and distribute this software for any
9  * purpose with or without fee is hereby granted, provided that the above
10  * copyright notice and this permission notice appear in all copies.
11  *
12  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCAIMS ALL WARRANTIES
13  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19  */
20 
21 /*
22  * Driver for Microdia's HID based TEMPer and TEMPerHUM temperature and
23  * humidity sensors
24  */
25 
26 #include <sys/param.h>
27 #include <sys/systm.h>
28 #include <sys/kernel.h>
29 #include <sys/device.h>
30 #include <sys/sensors.h>
31 
32 #include <dev/usb/usb.h>
33 #include <dev/usb/usbhid.h>
34 
35 #include <dev/usb/usbdi.h>
36 #include <dev/usb/usbdevs.h>
37 #include <dev/usb/uhidev.h>
38 
39 #define UGOLD_INNER		0
40 #define UGOLD_OUTER		1
41 #define UGOLD_HUM		1
42 #define UGOLD_MAX_SENSORS	2
43 
44 #define UGOLD_CMD_DATA		0x80
45 #define UGOLD_CMD_INIT		0x82
46 
47 #define UGOLD_TYPE_SI7005	1
48 #define UGOLD_TYPE_SI7006	2
49 #define UGOLD_TYPE_SHT1X	3
50 #define UGOLD_TYPE_GOLD		4
51 #define UGOLD_TYPE_TEMPERX	5
52 
53 /*
54  * This driver uses three known commands for the TEMPer and TEMPerHUM
55  * devices.
56  *
57  * The first byte of the answer corresponds to the command and the
58  * second one seems to be the size (in bytes) of the answer.
59  *
60  * The device always sends 8 bytes and if the length of the answer
61  * is less than that, it just leaves the last bytes untouched.  That
62  * is why most of the time the last n bytes of the answers are the
63  * same.
64  *
65  * The type command below seems to generate two answers with a
66  * string corresponding to the device, for example:
67  *	'TEMPer1F' and '1.1Per1F' (here Per1F is repeated).
68  */
69 static uint8_t cmd_data[8] = { 0x01, 0x80, 0x33, 0x01, 0x00, 0x00, 0x00, 0x00 };
70 static uint8_t cmd_init[8] = { 0x01, 0x82, 0x77, 0x01, 0x00, 0x00, 0x00, 0x00 };
71 static uint8_t cmd_type[8] = { 0x01, 0x86, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00 };
72 
73 struct ugold_softc {
74 	struct uhidev		 sc_hdev;
75 	struct usbd_device	*sc_udev;
76 
77 	int			 sc_num_sensors;
78 	int			 sc_type;
79 
80 	struct ksensor		 sc_sensor[UGOLD_MAX_SENSORS];
81 	struct ksensordev	 sc_sensordev;
82 	struct sensor_task	*sc_sensortask;
83 };
84 
85 const struct usb_devno ugold_devs[] = {
86 	{ USB_VENDOR_MICRODIA, USB_PRODUCT_MICRODIA_TEMPER },
87 	{ USB_VENDOR_MICRODIA, USB_PRODUCT_MICRODIA_TEMPERHUM },
88 	{ USB_VENDOR_PCSENSORS, USB_PRODUCT_PCSENSORS_TEMPER },
89 	{ USB_VENDOR_WCH2, USB_PRODUCT_WCH2_TEMPER },
90 };
91 
92 int 	ugold_match(struct device *, void *, void *);
93 void	ugold_attach(struct device *, struct device *, void *);
94 int 	ugold_detach(struct device *, int);
95 
96 void	ugold_ds75_intr(struct uhidev *, void *, u_int);
97 void	ugold_si700x_intr(struct uhidev *, void *, u_int);
98 void	ugold_refresh(void *);
99 
100 int	ugold_issue_cmd(struct ugold_softc *, uint8_t *, int);
101 
102 struct cfdriver ugold_cd = {
103 	NULL, "ugold", DV_DULL
104 };
105 
106 const struct cfattach ugold_ca = {
107 	sizeof(struct ugold_softc), ugold_match, ugold_attach, ugold_detach,
108 };
109 
110 int
111 ugold_match(struct device *parent, void *match, void *aux)
112 {
113 	struct uhidev_attach_arg *uha = aux;
114 	int size;
115 	void *desc;
116 
117 	if (UHIDEV_CLAIM_MULTIPLE_REPORTID(uha))
118 		return (UMATCH_NONE);
119 
120 	if (usb_lookup(ugold_devs, uha->uaa->vendor, uha->uaa->product) == NULL)
121 		return (UMATCH_NONE);
122 
123 	/*
124 	 * XXX Only match the sensor interface.
125 	 *
126 	 * Does it make sense to attach various uhidev(4) to these
127 	 * non-standard HID devices?
128 	 */
129 	uhidev_get_report_desc(uha->parent, &desc, &size);
130 	if (hid_is_collection(desc, size, uha->reportid,
131 	    HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_KEYBOARD)))
132 		return (UMATCH_NONE);
133 
134 	return (UMATCH_VENDOR_PRODUCT);
135 
136 }
137 
138 void
139 ugold_attach(struct device *parent, struct device *self, void *aux)
140 {
141 	struct ugold_softc *sc = (struct ugold_softc *)self;
142 	struct uhidev_attach_arg *uha = aux;
143 	int size, repid;
144 	void *desc;
145 
146 	sc->sc_udev = uha->parent->sc_udev;
147 	sc->sc_hdev.sc_parent = uha->parent;
148 	sc->sc_hdev.sc_report_id = uha->reportid;
149 	switch (uha->uaa->product) {
150 	case USB_PRODUCT_MICRODIA_TEMPER:
151 		sc->sc_hdev.sc_intr = ugold_ds75_intr;
152 		break;
153 	case USB_PRODUCT_MICRODIA_TEMPERHUM:
154 	case USB_PRODUCT_PCSENSORS_TEMPER:
155 	case USB_PRODUCT_WCH2_TEMPER:
156 		sc->sc_hdev.sc_intr = ugold_si700x_intr;
157 		break;
158 	default:
159 		printf(", unknown product\n");
160 		return;
161 	}
162 
163 	uhidev_get_report_desc(uha->parent, &desc, &size);
164 	repid = uha->reportid;
165 	sc->sc_hdev.sc_isize = hid_report_size(desc, size, hid_input, repid);
166 	sc->sc_hdev.sc_osize = hid_report_size(desc, size, hid_output, repid);
167 	sc->sc_hdev.sc_fsize = hid_report_size(desc, size, hid_feature, repid);
168 
169 	if (uhidev_open(&sc->sc_hdev)) {
170 		printf(", unable to open interrupt pipe\n");
171 		return;
172 	}
173 
174 	strlcpy(sc->sc_sensordev.xname, sc->sc_hdev.sc_dev.dv_xname,
175 	    sizeof(sc->sc_sensordev.xname));
176 
177 	switch (uha->uaa->product) {
178 	case USB_PRODUCT_MICRODIA_TEMPER:
179 		/* 2 temperature sensors */
180 		sc->sc_sensor[UGOLD_INNER].type = SENSOR_TEMP;
181 		strlcpy(sc->sc_sensor[UGOLD_INNER].desc, "inner",
182 		    sizeof(sc->sc_sensor[UGOLD_INNER].desc));
183 		sc->sc_sensor[UGOLD_OUTER].type = SENSOR_TEMP;
184 		strlcpy(sc->sc_sensor[UGOLD_OUTER].desc, "outer",
185 		    sizeof(sc->sc_sensor[UGOLD_OUTER].desc));
186 		break;
187 	case USB_PRODUCT_MICRODIA_TEMPERHUM:
188 	case USB_PRODUCT_PCSENSORS_TEMPER:
189 	case USB_PRODUCT_WCH2_TEMPER:
190 		/* 1 temperature and 1 humidity sensor */
191 		sc->sc_sensor[UGOLD_INNER].type = SENSOR_TEMP;
192 		strlcpy(sc->sc_sensor[UGOLD_INNER].desc, "inner",
193 		    sizeof(sc->sc_sensor[UGOLD_INNER].desc));
194 		sc->sc_sensor[UGOLD_HUM].type = SENSOR_HUMIDITY;
195 		strlcpy(sc->sc_sensor[UGOLD_HUM].desc, "RH",
196 		    sizeof(sc->sc_sensor[UGOLD_HUM].desc));
197 		break;
198 	default:
199 		printf(", unknown product\n");
200 		return;
201 	}
202 
203 	/* 0.1Hz */
204 	sc->sc_sensortask = sensor_task_register(sc, ugold_refresh, 6);
205 	if (sc->sc_sensortask == NULL) {
206 		printf(", unable to register update task\n");
207 		return;
208 	}
209 	printf("\n");
210 
211 	sensordev_install(&sc->sc_sensordev);
212 }
213 
214 int
215 ugold_detach(struct device *self, int flags)
216 {
217 	struct ugold_softc *sc = (struct ugold_softc *)self;
218 	int i;
219 
220 	if (sc->sc_sensortask != NULL) {
221 		sensor_task_unregister(sc->sc_sensortask);
222 		sensordev_deinstall(&sc->sc_sensordev);
223 	}
224 
225 	for (i = 0; i < sc->sc_num_sensors; i++)
226 		sensor_detach(&sc->sc_sensordev, &sc->sc_sensor[i]);
227 
228 	if (sc->sc_hdev.sc_state & UHIDEV_OPEN)
229 		uhidev_close(&sc->sc_hdev);
230 
231 	return (0);
232 }
233 
234 static int
235 ugold_ds75_temp(uint8_t msb, uint8_t lsb)
236 {
237 	/* DS75 12bit precision mode: 0.0625 degrees Celsius ticks */
238 	return (((msb * 100) + ((lsb >> 4) * 25 / 4)) * 10000) + 273150000;
239 }
240 
241 static void
242 ugold_ds75_type(struct ugold_softc *sc, uint8_t *buf, u_int len)
243 {
244 	if (memcmp(buf, "TEMPer1F", len) == 0 ||
245 	    memcmp(buf, "TEMPer2F", len) == 0 ||
246 	    memcmp(buf, "TEMPerF1", len) == 0)
247 		return; /* skip first half of the answer */
248 
249 	printf("%s: %d sensor%s type ds75/12bit (temperature)\n",
250 	    sc->sc_hdev.sc_dev.dv_xname, sc->sc_num_sensors,
251 	    (sc->sc_num_sensors == 1) ? "" : "s");
252 
253 	sc->sc_type = -1; /* ignore type */
254 }
255 
256 void
257 ugold_ds75_intr(struct uhidev *addr, void *ibuf, u_int len)
258 {
259 	struct ugold_softc *sc = (struct ugold_softc *)addr;
260 	uint8_t *buf = ibuf;
261 	int i, temp;
262 
263 	switch (buf[0]) {
264 	case UGOLD_CMD_INIT:
265 		if (sc->sc_num_sensors)
266 			break;
267 
268 		sc->sc_num_sensors = min(buf[1], UGOLD_MAX_SENSORS) /* XXX */;
269 
270 		for (i = 0; i < sc->sc_num_sensors; i++) {
271 			sc->sc_sensor[i].flags |= SENSOR_FINVALID;
272 			sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[i]);
273 		}
274 
275 		break;
276 	case UGOLD_CMD_DATA:
277 		switch (buf[1]) {
278 		case 4:
279 			temp = ugold_ds75_temp(buf[4], buf[5]);
280 			sc->sc_sensor[UGOLD_OUTER].value = temp;
281 			sc->sc_sensor[UGOLD_OUTER].flags &= ~SENSOR_FINVALID;
282 			/* FALLTHROUGH */
283 		case 2:
284 			temp = ugold_ds75_temp(buf[2], buf[3]);
285 			sc->sc_sensor[UGOLD_INNER].value = temp;
286 			sc->sc_sensor[UGOLD_INNER].flags &= ~SENSOR_FINVALID;
287 			break;
288 		default:
289 			printf("%s: invalid data length (%d bytes)\n",
290 				sc->sc_hdev.sc_dev.dv_xname, buf[1]);
291 		}
292 		break;
293 	default:
294 		if (!sc->sc_type) { /* type command returns arbitrary string */
295 			ugold_ds75_type(sc, buf, len);
296 			break;
297 		}
298 		printf("%s: unknown command 0x%02x\n",
299 		    sc->sc_hdev.sc_dev.dv_xname, buf[0]);
300 	}
301 }
302 
303 static int
304 ugold_si700x_temp(int type, uint8_t msb, uint8_t lsb)
305 {
306 	int temp = msb * 256 + lsb;
307 
308 	switch (type) { /* convert to mdegC */
309 	case UGOLD_TYPE_SI7005: /* 14bit 32 codes per degC 0x0000 = -50 degC */
310 		temp = (((temp & 0x3fff) * 1000) / 32) - 50000;
311 		break;
312 	case UGOLD_TYPE_SI7006: /* 14bit and status bit */
313 		temp = (((temp & ~3) * 21965) / 8192) - 46850;
314 		break;
315 	case UGOLD_TYPE_SHT1X:
316 		temp = (temp * 1000) / 256;
317 		break;
318 	case UGOLD_TYPE_GOLD:
319 	case UGOLD_TYPE_TEMPERX:
320 		/* temp = temp / 100 to get degC, then * 1000 to get mdegC */
321 		temp = temp * 10;
322 		break;
323 	default:
324 		temp = 0;
325 	}
326 
327 	return temp;
328 }
329 
330 static int
331 ugold_si700x_rhum(int type, uint8_t msb, uint8_t lsb, int temp)
332 {
333 	int rhum = msb * 256 + lsb;
334 
335 	switch (type) { /* convert to m%RH */
336 	case UGOLD_TYPE_SI7005: /* 12bit 16 codes per %RH 0x0000 = -24 %RH */
337 		rhum = (((rhum & 0x0fff) * 1000) / 16) - 24000;
338 #if 0		/* todo: linearization and temperature compensation */
339 		rhum -= -0.00393 * rhum * rhum + 0.4008 * rhum - 4.7844;
340 		rhum += (temp - 30) * (0.00237 * rhum + 0.1973);
341 #endif
342 		break;
343 	case UGOLD_TYPE_SI7006: /* 14bit and status bit */
344 		rhum = (((rhum & ~3) * 15625) / 8192) - 6000;
345 		break;
346 	case UGOLD_TYPE_SHT1X: /* 16 bit */
347 		rhum = rhum * 32;
348 		break;
349 	case UGOLD_TYPE_TEMPERX:
350 		rhum = rhum * 10;
351 		break;
352 	default:
353 		rhum = 0;
354 	}
355 
356 	/* limit the humidity to valid values */
357 	if (rhum < 0)
358 		rhum = 0;
359 	else if (rhum > 100000)
360 		rhum = 100000;
361 	return rhum;
362 }
363 
364 static void
365 ugold_si700x_type(struct ugold_softc *sc, uint8_t *buf, u_int len)
366 {
367 	if (memcmp(buf, "TEMPerHu", len) == 0 ||
368 	    memcmp(buf, "TEMPer1F", len) == 0 ||
369 	    memcmp(buf, "TEMPerX_", len) == 0 ||
370 	    memcmp(buf, "TEMPerGo", len) == 0)
371 		return; /* skip equal first half of the answer */
372 
373 	printf("%s: %d sensor%s type ", sc->sc_hdev.sc_dev.dv_xname,
374 	    sc->sc_num_sensors, (sc->sc_num_sensors == 1) ? "" : "s");
375 
376 	if (memcmp(buf, "mM12V1.0", len) == 0) {
377 		sc->sc_type = UGOLD_TYPE_SI7005;
378 		printf("si7005 (temperature and humidity)\n");
379 	} else if (memcmp(buf, "mM12V1.2", len) == 0) {
380 		sc->sc_type = UGOLD_TYPE_SI7006;
381 		printf("si7006 (temperature and humidity)\n");
382 	} else if (memcmp(buf, "_H1V1.5F", len) == 0) {
383 		sc->sc_type = UGOLD_TYPE_SHT1X;
384 		printf("sht1x (temperature and humidity)\n");
385 	} else if (memcmp(buf, "V3.1    ", len) == 0) {
386 		sc->sc_type = UGOLD_TYPE_TEMPERX;
387 		printf("temperx (temperature and humidity)\n");
388 	} else if (memcmp(buf, "V3.3    ", len) == 0) {
389 		sc->sc_type = UGOLD_TYPE_TEMPERX;
390 		printf("temperx (temperature and humidity)\n");
391 	} else if (memcmp(buf, "ld_V3.1 ", len) == 0) {
392 		sc->sc_type = UGOLD_TYPE_GOLD;
393 		printf("gold (temperature only)\n");
394 	} else if (memcmp(buf, "ld_V3.4 ", len) == 0) {
395 		sc->sc_type = UGOLD_TYPE_GOLD;
396 		printf("gold (temperature only)\n");
397 	} else {
398 		sc->sc_type = -1;
399 		printf("unknown\n");
400 	}
401 }
402 
403 void
404 ugold_si700x_intr(struct uhidev *addr, void *ibuf, u_int len)
405 {
406 	struct ugold_softc *sc = (struct ugold_softc *)addr;
407 	uint8_t *buf = ibuf;
408 	int i, temp, rhum;
409 
410 	switch (buf[0]) {
411 	case UGOLD_CMD_INIT:
412 		if (sc->sc_num_sensors)
413 			break;
414 
415 		if (sc->sc_type == UGOLD_TYPE_GOLD)
416 			sc->sc_num_sensors = 1;
417 		else
418 			sc->sc_num_sensors = min(buf[1],
419 			    UGOLD_MAX_SENSORS) /* XXX */;
420 
421 		for (i = 0; i < sc->sc_num_sensors; i++) {
422 			sc->sc_sensor[i].flags |= SENSOR_FINVALID;
423 			sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[i]);
424 		}
425 		break;
426 	case UGOLD_CMD_DATA:
427 		if (buf[1] != 4 && buf[1] != 64 && buf[1] != 128)
428 			printf("%s: invalid data length (%d bytes)\n",
429 			    sc->sc_hdev.sc_dev.dv_xname, buf[1]);
430 		temp = ugold_si700x_temp(sc->sc_type, buf[2], buf[3]);
431 		sc->sc_sensor[UGOLD_INNER].value = (temp * 1000) + 273150000;
432 		sc->sc_sensor[UGOLD_INNER].flags &= ~SENSOR_FINVALID;
433 		if (sc->sc_type != UGOLD_TYPE_GOLD) {
434 			rhum = ugold_si700x_rhum(sc->sc_type, buf[4], buf[5], temp);
435 			sc->sc_sensor[UGOLD_HUM].value = rhum;
436 			sc->sc_sensor[UGOLD_HUM].flags &= ~SENSOR_FINVALID;
437 		}
438 		break;
439 	default:
440 		if (!sc->sc_type) { /* type command returns arbitrary string */
441 			ugold_si700x_type(sc, buf, len);
442 			break;
443 		}
444 		printf("%s: unknown command 0x%02x\n",
445 		    sc->sc_hdev.sc_dev.dv_xname, buf[0]);
446 	}
447 }
448 
449 void
450 ugold_refresh(void *arg)
451 {
452 	struct ugold_softc *sc = arg;
453 	int i;
454 
455 	if (!sc->sc_num_sensors) {
456 		ugold_issue_cmd(sc, cmd_init, sizeof(cmd_init));
457 		return;
458 	}
459 	if (!sc->sc_type) {
460 		ugold_issue_cmd(sc, cmd_type, sizeof(cmd_type));
461 		return;
462 	}
463 
464 	if (ugold_issue_cmd(sc, cmd_data, sizeof(cmd_data))) {
465 		for (i = 0; i < sc->sc_num_sensors; i++)
466 			sc->sc_sensor[i].flags |= SENSOR_FINVALID;
467 	}
468 }
469 
470 int
471 ugold_issue_cmd(struct ugold_softc *sc, uint8_t *cmd, int len)
472 {
473 	int actlen;
474 
475 	actlen = uhidev_set_report_async(sc->sc_hdev.sc_parent,
476 	    UHID_OUTPUT_REPORT, sc->sc_hdev.sc_report_id, cmd, len);
477 	return (actlen != len);
478 }
479