xref: /openbsd-src/sys/dev/usb/ugold.c (revision 1a8dbaac879b9f3335ad7fb25429ce63ac1d6bac)
1 /*	$OpenBSD: ugold.c,v 1.15 2020/08/17 04:26:57 gnezdo 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 
51 /*
52  * This driver uses three known commands for the TEMPer and TEMPerHUM
53  * devices.
54  *
55  * The first byte of the answer corresponds to the command and the
56  * second one seems to be the size (in bytes) of the answer.
57  *
58  * The device always sends 8 bytes and if the length of the answer
59  * is less than that, it just leaves the last bytes untouched.  That
60  * is why most of the time the last n bytes of the answers are the
61  * same.
62  *
63  * The type command below seems to generate two answers with a
64  * string corresponding to the device, for example:
65  *	'TEMPer1F' and '1.1Per1F' (here Per1F is repeated).
66  */
67 static uint8_t cmd_data[8] = { 0x01, 0x80, 0x33, 0x01, 0x00, 0x00, 0x00, 0x00 };
68 static uint8_t cmd_init[8] = { 0x01, 0x82, 0x77, 0x01, 0x00, 0x00, 0x00, 0x00 };
69 static uint8_t cmd_type[8] = { 0x01, 0x86, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00 };
70 
71 struct ugold_softc {
72 	struct uhidev		 sc_hdev;
73 	struct usbd_device	*sc_udev;
74 
75 	int			 sc_num_sensors;
76 	int			 sc_type;
77 
78 	struct ksensor		 sc_sensor[UGOLD_MAX_SENSORS];
79 	struct ksensordev	 sc_sensordev;
80 	struct sensor_task	*sc_sensortask;
81 };
82 
83 const struct usb_devno ugold_devs[] = {
84 	{ USB_VENDOR_MICRODIA, USB_PRODUCT_MICRODIA_TEMPER },
85 	{ USB_VENDOR_MICRODIA, USB_PRODUCT_MICRODIA_TEMPERHUM },
86 };
87 
88 int 	ugold_match(struct device *, void *, void *);
89 void	ugold_attach(struct device *, struct device *, void *);
90 int 	ugold_detach(struct device *, int);
91 
92 void	ugold_ds75_intr(struct uhidev *, void *, u_int);
93 void	ugold_si700x_intr(struct uhidev *, void *, u_int);
94 void	ugold_refresh(void *);
95 
96 int	ugold_issue_cmd(struct ugold_softc *, uint8_t *, int);
97 
98 struct cfdriver ugold_cd = {
99 	NULL, "ugold", DV_DULL
100 };
101 
102 const struct cfattach ugold_ca = {
103 	sizeof(struct ugold_softc), ugold_match, ugold_attach, ugold_detach,
104 };
105 
106 int
107 ugold_match(struct device *parent, void *match, void *aux)
108 {
109 	struct uhidev_attach_arg *uha = aux;
110 	int size;
111 	void *desc;
112 
113 	if (uha->reportid == UHIDEV_CLAIM_ALLREPORTID)
114 		return (UMATCH_NONE);
115 
116 	if (usb_lookup(ugold_devs, uha->uaa->vendor, uha->uaa->product) == NULL)
117 		return (UMATCH_NONE);
118 
119 	/*
120 	 * XXX Only match the sensor interface.
121 	 *
122 	 * Does it make sense to attach various uhidev(4) to these
123 	 * non-standard HID devices?
124 	 */
125 	uhidev_get_report_desc(uha->parent, &desc, &size);
126 	if (hid_is_collection(desc, size, uha->reportid,
127 	    HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_KEYBOARD)))
128 		return (UMATCH_NONE);
129 
130 	return (UMATCH_VENDOR_PRODUCT);
131 
132 }
133 
134 void
135 ugold_attach(struct device *parent, struct device *self, void *aux)
136 {
137 	struct ugold_softc *sc = (struct ugold_softc *)self;
138 	struct uhidev_attach_arg *uha = aux;
139 	int size, repid;
140 	void *desc;
141 
142 	sc->sc_udev = uha->parent->sc_udev;
143 	sc->sc_hdev.sc_parent = uha->parent;
144 	sc->sc_hdev.sc_report_id = uha->reportid;
145 	switch (uha->uaa->product) {
146 	case USB_PRODUCT_MICRODIA_TEMPER:
147 		sc->sc_hdev.sc_intr = ugold_ds75_intr;
148 		break;
149 	case USB_PRODUCT_MICRODIA_TEMPERHUM:
150 		sc->sc_hdev.sc_intr = ugold_si700x_intr;
151 		break;
152 	default:
153 		printf(", unknown product\n");
154 		return;
155 	}
156 
157 	uhidev_get_report_desc(uha->parent, &desc, &size);
158 	repid = uha->reportid;
159 	sc->sc_hdev.sc_isize = hid_report_size(desc, size, hid_input, repid);
160 	sc->sc_hdev.sc_osize = hid_report_size(desc, size, hid_output, repid);
161 	sc->sc_hdev.sc_fsize = hid_report_size(desc, size, hid_feature, repid);
162 
163 	if (uhidev_open(&sc->sc_hdev)) {
164 		printf(", unable to open interrupt pipe\n");
165 		return;
166 	}
167 
168 	strlcpy(sc->sc_sensordev.xname, sc->sc_hdev.sc_dev.dv_xname,
169 	    sizeof(sc->sc_sensordev.xname));
170 
171 	switch (uha->uaa->product) {
172 	case USB_PRODUCT_MICRODIA_TEMPER:
173 		/* 2 temperature sensors */
174 		sc->sc_sensor[UGOLD_INNER].type = SENSOR_TEMP;
175 		strlcpy(sc->sc_sensor[UGOLD_INNER].desc, "inner",
176 		    sizeof(sc->sc_sensor[UGOLD_INNER].desc));
177 		sc->sc_sensor[UGOLD_OUTER].type = SENSOR_TEMP;
178 		strlcpy(sc->sc_sensor[UGOLD_OUTER].desc, "outer",
179 		    sizeof(sc->sc_sensor[UGOLD_OUTER].desc));
180 		break;
181 	case USB_PRODUCT_MICRODIA_TEMPERHUM:
182 		/* 1 temperature and 1 humidity sensor */
183 		sc->sc_sensor[UGOLD_INNER].type = SENSOR_TEMP;
184 		strlcpy(sc->sc_sensor[UGOLD_INNER].desc, "inner",
185 		    sizeof(sc->sc_sensor[UGOLD_INNER].desc));
186 		sc->sc_sensor[UGOLD_HUM].type = SENSOR_HUMIDITY;
187 		strlcpy(sc->sc_sensor[UGOLD_HUM].desc, "RH",
188 		    sizeof(sc->sc_sensor[UGOLD_HUM].desc));
189 		break;
190 	default:
191 		printf(", unknown product\n");
192 		return;
193 	}
194 
195 	/* 0.1Hz */
196 	sc->sc_sensortask = sensor_task_register(sc, ugold_refresh, 6);
197 	if (sc->sc_sensortask == NULL) {
198 		printf(", unable to register update task\n");
199 		return;
200 	}
201 	printf("\n");
202 
203 	sensordev_install(&sc->sc_sensordev);
204 }
205 
206 int
207 ugold_detach(struct device *self, int flags)
208 {
209 	struct ugold_softc *sc = (struct ugold_softc *)self;
210 	int i;
211 
212 	if (sc->sc_sensortask != NULL) {
213 		sensor_task_unregister(sc->sc_sensortask);
214 		sensordev_deinstall(&sc->sc_sensordev);
215 	}
216 
217 	for (i = 0; i < sc->sc_num_sensors; i++)
218 		sensor_detach(&sc->sc_sensordev, &sc->sc_sensor[i]);
219 
220 	if (sc->sc_hdev.sc_state & UHIDEV_OPEN)
221 		uhidev_close(&sc->sc_hdev);
222 
223 	return (0);
224 }
225 
226 static int
227 ugold_ds75_temp(uint8_t msb, uint8_t lsb)
228 {
229 	/* DS75 12bit precision mode: 0.0625 degrees Celsius ticks */
230 	return (((msb * 100) + ((lsb >> 4) * 25 / 4)) * 10000) + 273150000;
231 }
232 
233 static void
234 ugold_ds75_type(struct ugold_softc *sc, uint8_t *buf, u_int len)
235 {
236 	if (memcmp(buf, "TEMPer1F", len) == 0 ||
237 	    memcmp(buf, "TEMPer2F", len) == 0 ||
238 	    memcmp(buf, "TEMPerF1", len) == 0)
239 		return; /* skip first half of the answer */
240 
241 	printf("%s: %d sensor%s type ds75/12bit (temperature)\n",
242 	    sc->sc_hdev.sc_dev.dv_xname, sc->sc_num_sensors,
243 	    (sc->sc_num_sensors == 1) ? "" : "s");
244 
245 	sc->sc_type = -1; /* ignore type */
246 }
247 
248 void
249 ugold_ds75_intr(struct uhidev *addr, void *ibuf, u_int len)
250 {
251 	struct ugold_softc *sc = (struct ugold_softc *)addr;
252 	uint8_t *buf = ibuf;
253 	int i, temp;
254 
255 	switch (buf[0]) {
256 	case UGOLD_CMD_INIT:
257 		if (sc->sc_num_sensors)
258 			break;
259 
260 		sc->sc_num_sensors = min(buf[1], UGOLD_MAX_SENSORS) /* XXX */;
261 
262 		for (i = 0; i < sc->sc_num_sensors; i++) {
263 			sc->sc_sensor[i].flags |= SENSOR_FINVALID;
264 			sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[i]);
265 		}
266 
267 		break;
268 	case UGOLD_CMD_DATA:
269 		switch (buf[1]) {
270 		case 4:
271 			temp = ugold_ds75_temp(buf[4], buf[5]);
272 			sc->sc_sensor[UGOLD_OUTER].value = temp;
273 			sc->sc_sensor[UGOLD_OUTER].flags &= ~SENSOR_FINVALID;
274 			/* FALLTHROUGH */
275 		case 2:
276 			temp = ugold_ds75_temp(buf[2], buf[3]);
277 			sc->sc_sensor[UGOLD_INNER].value = temp;
278 			sc->sc_sensor[UGOLD_INNER].flags &= ~SENSOR_FINVALID;
279 			break;
280 		default:
281 			printf("%s: invalid data length (%d bytes)\n",
282 				sc->sc_hdev.sc_dev.dv_xname, buf[1]);
283 		}
284 		break;
285 	default:
286 		if (!sc->sc_type) { /* type command returns arbitrary string */
287 			ugold_ds75_type(sc, buf, len);
288 			break;
289 		}
290 		printf("%s: unknown command 0x%02x\n",
291 		    sc->sc_hdev.sc_dev.dv_xname, buf[0]);
292 	}
293 }
294 
295 static int
296 ugold_si700x_temp(int type, uint8_t msb, uint8_t lsb)
297 {
298 	int temp = msb * 256 + lsb;
299 
300 	switch (type) { /* convert to mdegC */
301 	case UGOLD_TYPE_SI7005: /* 14bit 32 codes per degC 0x0000 = -50 degC */
302 		temp = (((temp & 0x3fff) * 1000) / 32) - 50000;
303 		break;
304 	case UGOLD_TYPE_SI7006: /* 14bit and status bit */
305 		temp = (((temp & ~3) * 21965) / 8192) - 46850;
306 		break;
307 	case UGOLD_TYPE_SHT1X:
308 		temp = (temp * 1000) / 256;
309 		break;
310 	default:
311 		temp = 0;
312 	}
313 	return temp;
314 }
315 
316 static int
317 ugold_si700x_rhum(int type, uint8_t msb, uint8_t lsb, int temp)
318 {
319 	int rhum = msb * 256 + lsb;
320 
321 	switch (type) { /* convert to m%RH */
322 	case UGOLD_TYPE_SI7005: /* 12bit 16 codes per %RH 0x0000 = -24 %RH */
323 		rhum = (((rhum & 0x0fff) * 1000) / 16) - 24000;
324 #if 0		/* todo: linearization and temperature compensation */
325 		rhum -= -0.00393 * rhum * rhum + 0.4008 * rhum - 4.7844;
326 		rhum += (temp - 30) * (0.00237 * rhum + 0.1973);
327 #endif
328 		break;
329 	case UGOLD_TYPE_SI7006: /* 14bit and status bit */
330 		rhum = (((rhum & ~3) * 15625) / 8192) - 6000;
331 		break;
332 	case UGOLD_TYPE_SHT1X: /* 16 bit */
333 		rhum = rhum * 32;
334 		break;
335 	default:
336 		rhum = 0;
337 	}
338 
339 	/* limit the humidity to valid values */
340 	if (rhum < 0)
341 		rhum = 0;
342 	else if (rhum > 100000)
343 		rhum = 100000;
344 	return rhum;
345 }
346 
347 static void
348 ugold_si700x_type(struct ugold_softc *sc, uint8_t *buf, u_int len)
349 {
350 	if (memcmp(buf, "TEMPerHu", len) == 0 ||
351 	    memcmp(buf, "TEMPer1F", len) == 0)
352 		return; /* skip equal first half of the answer */
353 
354 	printf("%s: %d sensor%s type ", sc->sc_hdev.sc_dev.dv_xname,
355 	    sc->sc_num_sensors, (sc->sc_num_sensors == 1) ? "" : "s");
356 
357 	if (memcmp(buf, "mM12V1.0", len) == 0) {
358 		sc->sc_type = UGOLD_TYPE_SI7005;
359 		printf("si7005 (temperature and humidity)\n");
360 	} else if (memcmp(buf, "mM12V1.2", len) == 0) {
361 		sc->sc_type = UGOLD_TYPE_SI7006;
362 		printf("si7006 (temperature and humidity)\n");
363 	} else if (memcmp(buf, "_H1V1.5F", len) == 0) {
364 		sc->sc_type = UGOLD_TYPE_SHT1X;
365 		printf("sht1x (temperature and humidity)\n");
366 	} else {
367 		sc->sc_type = -1;
368 		printf("unknown\n");
369 	}
370 }
371 
372 void
373 ugold_si700x_intr(struct uhidev *addr, void *ibuf, u_int len)
374 {
375 	struct ugold_softc *sc = (struct ugold_softc *)addr;
376 	uint8_t *buf = ibuf;
377 	int i, temp, rhum;
378 
379 	switch (buf[0]) {
380 	case UGOLD_CMD_INIT:
381 		if (sc->sc_num_sensors)
382 			break;
383 
384 		sc->sc_num_sensors = min(buf[1], UGOLD_MAX_SENSORS) /* XXX */;
385 
386 		for (i = 0; i < sc->sc_num_sensors; i++) {
387 			sc->sc_sensor[i].flags |= SENSOR_FINVALID;
388 			sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[i]);
389 		}
390 		break;
391 	case UGOLD_CMD_DATA:
392 		if (buf[1] != 4)
393 			printf("%s: invalid data length (%d bytes)\n",
394 			    sc->sc_hdev.sc_dev.dv_xname, buf[1]);
395 		temp = ugold_si700x_temp(sc->sc_type, buf[2], buf[3]);
396 		sc->sc_sensor[UGOLD_INNER].value = (temp * 1000) + 273150000;
397 		sc->sc_sensor[UGOLD_INNER].flags &= ~SENSOR_FINVALID;
398 		rhum = ugold_si700x_rhum(sc->sc_type, buf[4], buf[5], temp);
399 		sc->sc_sensor[UGOLD_HUM].value = rhum;
400 		sc->sc_sensor[UGOLD_HUM].flags &= ~SENSOR_FINVALID;
401 		break;
402 	default:
403 		if (!sc->sc_type) { /* type command returns arbitrary string */
404 			ugold_si700x_type(sc, buf, len);
405 			break;
406 		}
407 		printf("%s: unknown command 0x%02x\n",
408 		    sc->sc_hdev.sc_dev.dv_xname, buf[0]);
409 	}
410 }
411 
412 void
413 ugold_refresh(void *arg)
414 {
415 	struct ugold_softc *sc = arg;
416 	int i;
417 
418 	if (!sc->sc_num_sensors) {
419 		ugold_issue_cmd(sc, cmd_init, sizeof(cmd_init));
420 		return;
421 	}
422 	if (!sc->sc_type) {
423 		ugold_issue_cmd(sc, cmd_type, sizeof(cmd_type));
424 		return;
425 	}
426 
427 	if (ugold_issue_cmd(sc, cmd_data, sizeof(cmd_data))) {
428 		for (i = 0; i < sc->sc_num_sensors; i++)
429 			sc->sc_sensor[i].flags |= SENSOR_FINVALID;
430 	}
431 }
432 
433 int
434 ugold_issue_cmd(struct ugold_softc *sc, uint8_t *cmd, int len)
435 {
436 	int actlen;
437 
438 	actlen = uhidev_set_report_async(sc->sc_hdev.sc_parent,
439 	    UHID_OUTPUT_REPORT, sc->sc_hdev.sc_report_id, cmd, len);
440 	return (actlen != len);
441 }
442