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