xref: /openbsd/sys/dev/usb/ugold.c (revision 73471bf0)
1 /*	$OpenBSD: ugold.c,v 1.20 2021/11/15 15:36:24 anton 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 base 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 };
90 
91 int 	ugold_match(struct device *, void *, void *);
92 void	ugold_attach(struct device *, struct device *, void *);
93 int 	ugold_detach(struct device *, int);
94 
95 void	ugold_ds75_intr(struct uhidev *, void *, u_int);
96 void	ugold_si700x_intr(struct uhidev *, void *, u_int);
97 void	ugold_refresh(void *);
98 
99 int	ugold_issue_cmd(struct ugold_softc *, uint8_t *, int);
100 
101 struct cfdriver ugold_cd = {
102 	NULL, "ugold", DV_DULL
103 };
104 
105 const struct cfattach ugold_ca = {
106 	sizeof(struct ugold_softc), ugold_match, ugold_attach, ugold_detach,
107 };
108 
109 int
110 ugold_match(struct device *parent, void *match, void *aux)
111 {
112 	struct uhidev_attach_arg *uha = aux;
113 	int size;
114 	void *desc;
115 
116 	if (UHIDEV_CLAIM_MULTIPLE_REPORTID(uha))
117 		return (UMATCH_NONE);
118 
119 	if (usb_lookup(ugold_devs, uha->uaa->vendor, uha->uaa->product) == NULL)
120 		return (UMATCH_NONE);
121 
122 	/*
123 	 * XXX Only match the sensor interface.
124 	 *
125 	 * Does it make sense to attach various uhidev(4) to these
126 	 * non-standard HID devices?
127 	 */
128 	uhidev_get_report_desc(uha->parent, &desc, &size);
129 	if (hid_is_collection(desc, size, uha->reportid,
130 	    HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_KEYBOARD)))
131 		return (UMATCH_NONE);
132 
133 	return (UMATCH_VENDOR_PRODUCT);
134 
135 }
136 
137 void
138 ugold_attach(struct device *parent, struct device *self, void *aux)
139 {
140 	struct ugold_softc *sc = (struct ugold_softc *)self;
141 	struct uhidev_attach_arg *uha = aux;
142 	int size, repid;
143 	void *desc;
144 
145 	sc->sc_udev = uha->parent->sc_udev;
146 	sc->sc_hdev.sc_parent = uha->parent;
147 	sc->sc_hdev.sc_report_id = uha->reportid;
148 	switch (uha->uaa->product) {
149 	case USB_PRODUCT_MICRODIA_TEMPER:
150 		sc->sc_hdev.sc_intr = ugold_ds75_intr;
151 		break;
152 	case USB_PRODUCT_MICRODIA_TEMPERHUM:
153 	case USB_PRODUCT_PCSENSORS_TEMPER:
154 		sc->sc_hdev.sc_intr = ugold_si700x_intr;
155 		break;
156 	default:
157 		printf(", unknown product\n");
158 		return;
159 	}
160 
161 	uhidev_get_report_desc(uha->parent, &desc, &size);
162 	repid = uha->reportid;
163 	sc->sc_hdev.sc_isize = hid_report_size(desc, size, hid_input, repid);
164 	sc->sc_hdev.sc_osize = hid_report_size(desc, size, hid_output, repid);
165 	sc->sc_hdev.sc_fsize = hid_report_size(desc, size, hid_feature, repid);
166 
167 	if (uhidev_open(&sc->sc_hdev)) {
168 		printf(", unable to open interrupt pipe\n");
169 		return;
170 	}
171 
172 	strlcpy(sc->sc_sensordev.xname, sc->sc_hdev.sc_dev.dv_xname,
173 	    sizeof(sc->sc_sensordev.xname));
174 
175 	switch (uha->uaa->product) {
176 	case USB_PRODUCT_MICRODIA_TEMPER:
177 		/* 2 temperature sensors */
178 		sc->sc_sensor[UGOLD_INNER].type = SENSOR_TEMP;
179 		strlcpy(sc->sc_sensor[UGOLD_INNER].desc, "inner",
180 		    sizeof(sc->sc_sensor[UGOLD_INNER].desc));
181 		sc->sc_sensor[UGOLD_OUTER].type = SENSOR_TEMP;
182 		strlcpy(sc->sc_sensor[UGOLD_OUTER].desc, "outer",
183 		    sizeof(sc->sc_sensor[UGOLD_OUTER].desc));
184 		break;
185 	case USB_PRODUCT_MICRODIA_TEMPERHUM:
186 	case USB_PRODUCT_PCSENSORS_TEMPER:
187 		/* 1 temperature and 1 humidity sensor */
188 		sc->sc_sensor[UGOLD_INNER].type = SENSOR_TEMP;
189 		strlcpy(sc->sc_sensor[UGOLD_INNER].desc, "inner",
190 		    sizeof(sc->sc_sensor[UGOLD_INNER].desc));
191 		sc->sc_sensor[UGOLD_HUM].type = SENSOR_HUMIDITY;
192 		strlcpy(sc->sc_sensor[UGOLD_HUM].desc, "RH",
193 		    sizeof(sc->sc_sensor[UGOLD_HUM].desc));
194 		break;
195 	default:
196 		printf(", unknown product\n");
197 		return;
198 	}
199 
200 	/* 0.1Hz */
201 	sc->sc_sensortask = sensor_task_register(sc, ugold_refresh, 6);
202 	if (sc->sc_sensortask == NULL) {
203 		printf(", unable to register update task\n");
204 		return;
205 	}
206 	printf("\n");
207 
208 	sensordev_install(&sc->sc_sensordev);
209 }
210 
211 int
212 ugold_detach(struct device *self, int flags)
213 {
214 	struct ugold_softc *sc = (struct ugold_softc *)self;
215 	int i;
216 
217 	if (sc->sc_sensortask != NULL) {
218 		sensor_task_unregister(sc->sc_sensortask);
219 		sensordev_deinstall(&sc->sc_sensordev);
220 	}
221 
222 	for (i = 0; i < sc->sc_num_sensors; i++)
223 		sensor_detach(&sc->sc_sensordev, &sc->sc_sensor[i]);
224 
225 	if (sc->sc_hdev.sc_state & UHIDEV_OPEN)
226 		uhidev_close(&sc->sc_hdev);
227 
228 	return (0);
229 }
230 
231 static int
232 ugold_ds75_temp(uint8_t msb, uint8_t lsb)
233 {
234 	/* DS75 12bit precision mode: 0.0625 degrees Celsius ticks */
235 	return (((msb * 100) + ((lsb >> 4) * 25 / 4)) * 10000) + 273150000;
236 }
237 
238 static void
239 ugold_ds75_type(struct ugold_softc *sc, uint8_t *buf, u_int len)
240 {
241 	if (memcmp(buf, "TEMPer1F", len) == 0 ||
242 	    memcmp(buf, "TEMPer2F", len) == 0 ||
243 	    memcmp(buf, "TEMPerF1", len) == 0)
244 		return; /* skip first half of the answer */
245 
246 	printf("%s: %d sensor%s type ds75/12bit (temperature)\n",
247 	    sc->sc_hdev.sc_dev.dv_xname, sc->sc_num_sensors,
248 	    (sc->sc_num_sensors == 1) ? "" : "s");
249 
250 	sc->sc_type = -1; /* ignore type */
251 }
252 
253 void
254 ugold_ds75_intr(struct uhidev *addr, void *ibuf, u_int len)
255 {
256 	struct ugold_softc *sc = (struct ugold_softc *)addr;
257 	uint8_t *buf = ibuf;
258 	int i, temp;
259 
260 	switch (buf[0]) {
261 	case UGOLD_CMD_INIT:
262 		if (sc->sc_num_sensors)
263 			break;
264 
265 		sc->sc_num_sensors = min(buf[1], UGOLD_MAX_SENSORS) /* XXX */;
266 
267 		for (i = 0; i < sc->sc_num_sensors; i++) {
268 			sc->sc_sensor[i].flags |= SENSOR_FINVALID;
269 			sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[i]);
270 		}
271 
272 		break;
273 	case UGOLD_CMD_DATA:
274 		switch (buf[1]) {
275 		case 4:
276 			temp = ugold_ds75_temp(buf[4], buf[5]);
277 			sc->sc_sensor[UGOLD_OUTER].value = temp;
278 			sc->sc_sensor[UGOLD_OUTER].flags &= ~SENSOR_FINVALID;
279 			/* FALLTHROUGH */
280 		case 2:
281 			temp = ugold_ds75_temp(buf[2], buf[3]);
282 			sc->sc_sensor[UGOLD_INNER].value = temp;
283 			sc->sc_sensor[UGOLD_INNER].flags &= ~SENSOR_FINVALID;
284 			break;
285 		default:
286 			printf("%s: invalid data length (%d bytes)\n",
287 				sc->sc_hdev.sc_dev.dv_xname, buf[1]);
288 		}
289 		break;
290 	default:
291 		if (!sc->sc_type) { /* type command returns arbitrary string */
292 			ugold_ds75_type(sc, buf, len);
293 			break;
294 		}
295 		printf("%s: unknown command 0x%02x\n",
296 		    sc->sc_hdev.sc_dev.dv_xname, buf[0]);
297 	}
298 }
299 
300 static int
301 ugold_si700x_temp(int type, uint8_t msb, uint8_t lsb)
302 {
303 	int temp = msb * 256 + lsb;
304 
305 	switch (type) { /* convert to mdegC */
306 	case UGOLD_TYPE_SI7005: /* 14bit 32 codes per degC 0x0000 = -50 degC */
307 		temp = (((temp & 0x3fff) * 1000) / 32) - 50000;
308 		break;
309 	case UGOLD_TYPE_SI7006: /* 14bit and status bit */
310 		temp = (((temp & ~3) * 21965) / 8192) - 46850;
311 		break;
312 	case UGOLD_TYPE_SHT1X:
313 		temp = (temp * 1000) / 256;
314 		break;
315 	case UGOLD_TYPE_GOLD:
316 	case UGOLD_TYPE_TEMPERX:
317 		/* temp = temp / 100 to get degC, then * 1000 to get mdegC */
318 		temp = temp * 10;
319 		break;
320 	default:
321 		temp = 0;
322 	}
323 
324 	return temp;
325 }
326 
327 static int
328 ugold_si700x_rhum(int type, uint8_t msb, uint8_t lsb, int temp)
329 {
330 	int rhum = msb * 256 + lsb;
331 
332 	switch (type) { /* convert to m%RH */
333 	case UGOLD_TYPE_SI7005: /* 12bit 16 codes per %RH 0x0000 = -24 %RH */
334 		rhum = (((rhum & 0x0fff) * 1000) / 16) - 24000;
335 #if 0		/* todo: linearization and temperature compensation */
336 		rhum -= -0.00393 * rhum * rhum + 0.4008 * rhum - 4.7844;
337 		rhum += (temp - 30) * (0.00237 * rhum + 0.1973);
338 #endif
339 		break;
340 	case UGOLD_TYPE_SI7006: /* 14bit and status bit */
341 		rhum = (((rhum & ~3) * 15625) / 8192) - 6000;
342 		break;
343 	case UGOLD_TYPE_SHT1X: /* 16 bit */
344 		rhum = rhum * 32;
345 		break;
346 	case UGOLD_TYPE_TEMPERX:
347 		rhum = rhum * 10;
348 		break;
349 	default:
350 		rhum = 0;
351 	}
352 
353 	/* limit the humidity to valid values */
354 	if (rhum < 0)
355 		rhum = 0;
356 	else if (rhum > 100000)
357 		rhum = 100000;
358 	return rhum;
359 }
360 
361 static void
362 ugold_si700x_type(struct ugold_softc *sc, uint8_t *buf, u_int len)
363 {
364 	if (memcmp(buf, "TEMPerHu", len) == 0 ||
365 	    memcmp(buf, "TEMPer1F", len) == 0 ||
366 	    memcmp(buf, "TEMPerX_", len) == 0 ||
367 	    memcmp(buf, "TEMPerGo", len) == 0)
368 		return; /* skip equal first half of the answer */
369 
370 	printf("%s: %d sensor%s type ", sc->sc_hdev.sc_dev.dv_xname,
371 	    sc->sc_num_sensors, (sc->sc_num_sensors == 1) ? "" : "s");
372 
373 	if (memcmp(buf, "mM12V1.0", len) == 0) {
374 		sc->sc_type = UGOLD_TYPE_SI7005;
375 		printf("si7005 (temperature and humidity)\n");
376 	} else if (memcmp(buf, "mM12V1.2", len) == 0) {
377 		sc->sc_type = UGOLD_TYPE_SI7006;
378 		printf("si7006 (temperature and humidity)\n");
379 	} else if (memcmp(buf, "_H1V1.5F", len) == 0) {
380 		sc->sc_type = UGOLD_TYPE_SHT1X;
381 		printf("sht1x (temperature and humidity)\n");
382 	} else if (memcmp(buf, "V3.1    ", len) == 0) {
383 		sc->sc_type = UGOLD_TYPE_TEMPERX;
384 		printf("temperx (temperature and humidity)\n");
385 	} else if (memcmp(buf, "V3.3    ", len) == 0) {
386 		sc->sc_type = UGOLD_TYPE_TEMPERX;
387 		printf("temperx (temperature and humidity)\n");
388 	} else if (memcmp(buf, "ld_V3.1 ", len) == 0) {
389 		sc->sc_type = UGOLD_TYPE_GOLD;
390 		printf("gold (temperature only)\n");
391 	} else {
392 		sc->sc_type = -1;
393 		printf("unknown\n");
394 	}
395 }
396 
397 void
398 ugold_si700x_intr(struct uhidev *addr, void *ibuf, u_int len)
399 {
400 	struct ugold_softc *sc = (struct ugold_softc *)addr;
401 	uint8_t *buf = ibuf;
402 	int i, temp, rhum;
403 
404 	switch (buf[0]) {
405 	case UGOLD_CMD_INIT:
406 		if (sc->sc_num_sensors)
407 			break;
408 
409 		if (sc->sc_type == UGOLD_TYPE_GOLD)
410 			sc->sc_num_sensors = 1;
411 		else
412 			sc->sc_num_sensors = min(buf[1],
413 			    UGOLD_MAX_SENSORS) /* XXX */;
414 
415 		for (i = 0; i < sc->sc_num_sensors; i++) {
416 			sc->sc_sensor[i].flags |= SENSOR_FINVALID;
417 			sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[i]);
418 		}
419 		break;
420 	case UGOLD_CMD_DATA:
421 		if (buf[1] != 4 && buf[1] != 64)
422 			printf("%s: invalid data length (%d bytes)\n",
423 			    sc->sc_hdev.sc_dev.dv_xname, buf[1]);
424 		temp = ugold_si700x_temp(sc->sc_type, buf[2], buf[3]);
425 		sc->sc_sensor[UGOLD_INNER].value = (temp * 1000) + 273150000;
426 		sc->sc_sensor[UGOLD_INNER].flags &= ~SENSOR_FINVALID;
427 		if (sc->sc_type != UGOLD_TYPE_GOLD) {
428 			rhum = ugold_si700x_rhum(sc->sc_type, buf[4], buf[5], temp);
429 			sc->sc_sensor[UGOLD_HUM].value = rhum;
430 			sc->sc_sensor[UGOLD_HUM].flags &= ~SENSOR_FINVALID;
431 		}
432 		break;
433 	default:
434 		if (!sc->sc_type) { /* type command returns arbitrary string */
435 			ugold_si700x_type(sc, buf, len);
436 			break;
437 		}
438 		printf("%s: unknown command 0x%02x\n",
439 		    sc->sc_hdev.sc_dev.dv_xname, buf[0]);
440 	}
441 }
442 
443 void
444 ugold_refresh(void *arg)
445 {
446 	struct ugold_softc *sc = arg;
447 	int i;
448 
449 	if (!sc->sc_num_sensors) {
450 		ugold_issue_cmd(sc, cmd_init, sizeof(cmd_init));
451 		return;
452 	}
453 	if (!sc->sc_type) {
454 		ugold_issue_cmd(sc, cmd_type, sizeof(cmd_type));
455 		return;
456 	}
457 
458 	if (ugold_issue_cmd(sc, cmd_data, sizeof(cmd_data))) {
459 		for (i = 0; i < sc->sc_num_sensors; i++)
460 			sc->sc_sensor[i].flags |= SENSOR_FINVALID;
461 	}
462 }
463 
464 int
465 ugold_issue_cmd(struct ugold_softc *sc, uint8_t *cmd, int len)
466 {
467 	int actlen;
468 
469 	actlen = uhidev_set_report_async(sc->sc_hdev.sc_parent,
470 	    UHID_OUTPUT_REPORT, sc->sc_hdev.sc_report_id, cmd, len);
471 	return (actlen != len);
472 }
473