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