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