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