xref: /openbsd-src/sys/dev/usb/ugold.c (revision 0b7734b3d77bb9b21afec6f4621cae6c805dbd45)
1 /*	$OpenBSD: ugold.c,v 1.12 2016/01/09 04:14:42 jcs 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/malloc.h>
30 #include <sys/device.h>
31 #include <sys/sensors.h>
32 
33 #include <dev/usb/usb.h>
34 #include <dev/usb/usbhid.h>
35 
36 #include <dev/usb/usbdi.h>
37 #include <dev/usb/usbdevs.h>
38 #include <dev/usb/uhidev.h>
39 
40 #define UGOLD_INNER		0
41 #define UGOLD_OUTER		1
42 #define UGOLD_HUM		1
43 #define UGOLD_MAX_SENSORS	2
44 
45 #define UGOLD_CMD_DATA		0x80
46 #define UGOLD_CMD_INIT		0x82
47 
48 #define UGOLD_TYPE_SI7005	1
49 #define UGOLD_TYPE_SI7006	2
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 makes 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 	default:
308 		temp = 0;
309 	}
310 	return temp;
311 }
312 
313 static int
314 ugold_si700x_rhum(int type, uint8_t msb, uint8_t lsb, int temp)
315 {
316 	int rhum = msb * 256 + lsb;
317 
318 	switch (type) { /* convert to m%RH */
319 	case UGOLD_TYPE_SI7005: /* 12bit 16 codes per %RH 0x0000 = -24 %RH */
320 		rhum = (((rhum & 0x0fff) * 1000) / 16) - 24000;
321 #if 0		/* todo: linearization and temperature compensation */
322 		rhum -= -0.00393 * rhum * rhum + 0.4008 * rhum - 4.7844;
323 		rhum += (temp - 30) * (0.00237 * rhum + 0.1973);
324 #endif
325 		break;
326 	case UGOLD_TYPE_SI7006: /* 14bit and status bit */
327 		rhum = (((rhum & ~3) * 15625) / 8192) - 6000;
328 		break;
329 	default:
330 		rhum = 0;
331 	}
332 
333 	/* limit the humidity to valid values */
334 	if (rhum < 0)
335 		rhum = 0;
336 	else if (rhum > 100000)
337 		rhum = 100000;
338 	return rhum;
339 }
340 
341 static void
342 ugold_si700x_type(struct ugold_softc *sc, uint8_t *buf, u_int len)
343 {
344 	if (memcmp(buf, "TEMPerHu", len) == 0)
345 		return; /* skip equal first half of the answer */
346 
347 	printf("%s: %d sensor%s type ", sc->sc_hdev.sc_dev.dv_xname,
348 	    sc->sc_num_sensors, (sc->sc_num_sensors == 1) ? "" : "s");
349 
350 	if (memcmp(buf, "mM12V1.0", len) == 0) {
351 		sc->sc_type = UGOLD_TYPE_SI7005;
352 		printf("si7005 (temperature and humidity)\n");
353 	} else if (memcmp(buf, "mM12V1.2", len) == 0) {
354 		sc->sc_type = UGOLD_TYPE_SI7006;
355 		printf("si7006 (temperature and humidity)\n");
356 	} else {
357 		sc->sc_type = -1;
358 		printf("unknown\n");
359 	}
360 }
361 
362 void
363 ugold_si700x_intr(struct uhidev *addr, void *ibuf, u_int len)
364 {
365 	struct ugold_softc *sc = (struct ugold_softc *)addr;
366 	uint8_t *buf = ibuf;
367 	int i, temp, rhum;
368 
369 	switch (buf[0]) {
370 	case UGOLD_CMD_INIT:
371 		if (sc->sc_num_sensors)
372 			break;
373 
374 		sc->sc_num_sensors = min(buf[1], UGOLD_MAX_SENSORS) /* XXX */;
375 
376 		for (i = 0; i < sc->sc_num_sensors; i++) {
377 			sc->sc_sensor[i].flags |= SENSOR_FINVALID;
378 			sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[i]);
379 		}
380 		break;
381 	case UGOLD_CMD_DATA:
382 		if (buf[1] != 4)
383 			printf("%s: invalid data length (%d bytes)\n",
384 			    sc->sc_hdev.sc_dev.dv_xname, buf[1]);
385 		temp = ugold_si700x_temp(sc->sc_type, buf[2], buf[3]);
386 		sc->sc_sensor[UGOLD_INNER].value = (temp * 1000) + 273150000;
387 		sc->sc_sensor[UGOLD_INNER].flags &= ~SENSOR_FINVALID;
388 		rhum = ugold_si700x_rhum(sc->sc_type, buf[4], buf[5], temp);
389 		sc->sc_sensor[UGOLD_HUM].value = rhum;
390 		sc->sc_sensor[UGOLD_HUM].flags &= ~SENSOR_FINVALID;
391 		break;
392 	default:
393 		if (!sc->sc_type) { /* type command returns arbitrary string */
394 			ugold_si700x_type(sc, buf, len);
395 			break;
396 		}
397 		printf("%s: unknown command 0x%02x\n",
398 		    sc->sc_hdev.sc_dev.dv_xname, buf[0]);
399 	}
400 }
401 
402 void
403 ugold_refresh(void *arg)
404 {
405 	struct ugold_softc *sc = arg;
406 	int i;
407 
408 	if (!sc->sc_num_sensors) {
409 		ugold_issue_cmd(sc, cmd_init, sizeof(cmd_init));
410 		return;
411 	}
412 	if (!sc->sc_type) {
413 		ugold_issue_cmd(sc, cmd_type, sizeof(cmd_type));
414 		return;
415 	}
416 
417 	if (ugold_issue_cmd(sc, cmd_data, sizeof(cmd_data))) {
418 		for (i = 0; i < sc->sc_num_sensors; i++)
419 			sc->sc_sensor[i].flags |= SENSOR_FINVALID;
420 	}
421 }
422 
423 int
424 ugold_issue_cmd(struct ugold_softc *sc, uint8_t *cmd, int len)
425 {
426 	int actlen;
427 
428 	actlen = uhidev_set_report_async(sc->sc_hdev.sc_parent,
429 	    UHID_OUTPUT_REPORT, sc->sc_hdev.sc_report_id, cmd, len);
430 	return (actlen != len);
431 }
432