1 /* $OpenBSD: ugold.c,v 1.15 2020/08/17 04:26:57 gnezdo 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 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 make 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 case UGOLD_TYPE_SHT1X: 308 temp = (temp * 1000) / 256; 309 break; 310 default: 311 temp = 0; 312 } 313 return temp; 314 } 315 316 static int 317 ugold_si700x_rhum(int type, uint8_t msb, uint8_t lsb, int temp) 318 { 319 int rhum = msb * 256 + lsb; 320 321 switch (type) { /* convert to m%RH */ 322 case UGOLD_TYPE_SI7005: /* 12bit 16 codes per %RH 0x0000 = -24 %RH */ 323 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 break; 329 case UGOLD_TYPE_SI7006: /* 14bit and status bit */ 330 rhum = (((rhum & ~3) * 15625) / 8192) - 6000; 331 break; 332 case UGOLD_TYPE_SHT1X: /* 16 bit */ 333 rhum = rhum * 32; 334 break; 335 default: 336 rhum = 0; 337 } 338 339 /* limit the humidity to valid values */ 340 if (rhum < 0) 341 rhum = 0; 342 else if (rhum > 100000) 343 rhum = 100000; 344 return rhum; 345 } 346 347 static void 348 ugold_si700x_type(struct ugold_softc *sc, uint8_t *buf, u_int len) 349 { 350 if (memcmp(buf, "TEMPerHu", len) == 0 || 351 memcmp(buf, "TEMPer1F", len) == 0) 352 return; /* skip equal first half of the answer */ 353 354 printf("%s: %d sensor%s type ", sc->sc_hdev.sc_dev.dv_xname, 355 sc->sc_num_sensors, (sc->sc_num_sensors == 1) ? "" : "s"); 356 357 if (memcmp(buf, "mM12V1.0", len) == 0) { 358 sc->sc_type = UGOLD_TYPE_SI7005; 359 printf("si7005 (temperature and humidity)\n"); 360 } else if (memcmp(buf, "mM12V1.2", len) == 0) { 361 sc->sc_type = UGOLD_TYPE_SI7006; 362 printf("si7006 (temperature and humidity)\n"); 363 } else if (memcmp(buf, "_H1V1.5F", len) == 0) { 364 sc->sc_type = UGOLD_TYPE_SHT1X; 365 printf("sht1x (temperature and humidity)\n"); 366 } else { 367 sc->sc_type = -1; 368 printf("unknown\n"); 369 } 370 } 371 372 void 373 ugold_si700x_intr(struct uhidev *addr, void *ibuf, u_int len) 374 { 375 struct ugold_softc *sc = (struct ugold_softc *)addr; 376 uint8_t *buf = ibuf; 377 int i, temp, rhum; 378 379 switch (buf[0]) { 380 case UGOLD_CMD_INIT: 381 if (sc->sc_num_sensors) 382 break; 383 384 sc->sc_num_sensors = min(buf[1], UGOLD_MAX_SENSORS) /* XXX */; 385 386 for (i = 0; i < sc->sc_num_sensors; i++) { 387 sc->sc_sensor[i].flags |= SENSOR_FINVALID; 388 sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[i]); 389 } 390 break; 391 case UGOLD_CMD_DATA: 392 if (buf[1] != 4) 393 printf("%s: invalid data length (%d bytes)\n", 394 sc->sc_hdev.sc_dev.dv_xname, buf[1]); 395 temp = ugold_si700x_temp(sc->sc_type, buf[2], buf[3]); 396 sc->sc_sensor[UGOLD_INNER].value = (temp * 1000) + 273150000; 397 sc->sc_sensor[UGOLD_INNER].flags &= ~SENSOR_FINVALID; 398 rhum = ugold_si700x_rhum(sc->sc_type, buf[4], buf[5], temp); 399 sc->sc_sensor[UGOLD_HUM].value = rhum; 400 sc->sc_sensor[UGOLD_HUM].flags &= ~SENSOR_FINVALID; 401 break; 402 default: 403 if (!sc->sc_type) { /* type command returns arbitrary string */ 404 ugold_si700x_type(sc, buf, len); 405 break; 406 } 407 printf("%s: unknown command 0x%02x\n", 408 sc->sc_hdev.sc_dev.dv_xname, buf[0]); 409 } 410 } 411 412 void 413 ugold_refresh(void *arg) 414 { 415 struct ugold_softc *sc = arg; 416 int i; 417 418 if (!sc->sc_num_sensors) { 419 ugold_issue_cmd(sc, cmd_init, sizeof(cmd_init)); 420 return; 421 } 422 if (!sc->sc_type) { 423 ugold_issue_cmd(sc, cmd_type, sizeof(cmd_type)); 424 return; 425 } 426 427 if (ugold_issue_cmd(sc, cmd_data, sizeof(cmd_data))) { 428 for (i = 0; i < sc->sc_num_sensors; i++) 429 sc->sc_sensor[i].flags |= SENSOR_FINVALID; 430 } 431 } 432 433 int 434 ugold_issue_cmd(struct ugold_softc *sc, uint8_t *cmd, int len) 435 { 436 int actlen; 437 438 actlen = uhidev_set_report_async(sc->sc_hdev.sc_parent, 439 UHID_OUTPUT_REPORT, sc->sc_hdev.sc_report_id, cmd, len); 440 return (actlen != len); 441 } 442