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