1 /* $NetBSD: aht20.c,v 1.1 2022/11/17 19:20:06 brad Exp $ */ 2 3 /* 4 * Copyright (c) 2022 Brad Spencer <brad@anduin.eldar.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/cdefs.h> 20 __KERNEL_RCSID(0, "$NetBSD: aht20.c,v 1.1 2022/11/17 19:20:06 brad Exp $"); 21 22 /* 23 Driver for the Guangzhou Aosong AHT20 temperature and humidity sensor 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/module.h> 31 #include <sys/sysctl.h> 32 #include <sys/mutex.h> 33 34 #include <dev/sysmon/sysmonvar.h> 35 #include <dev/i2c/i2cvar.h> 36 #include <dev/i2c/aht20reg.h> 37 #include <dev/i2c/aht20var.h> 38 39 40 static uint8_t aht20_crc(uint8_t *, size_t); 41 static int aht20_poke(i2c_tag_t, i2c_addr_t, bool); 42 static int aht20_match(device_t, cfdata_t, void *); 43 static void aht20_attach(device_t, device_t, void *); 44 static int aht20_detach(device_t, int); 45 static void aht20_refresh(struct sysmon_envsys *, envsys_data_t *); 46 static int aht20_verify_sysctl(SYSCTLFN_ARGS); 47 48 #define AHT20_DEBUG 49 #ifdef AHT20_DEBUG 50 #define DPRINTF(s, l, x) \ 51 do { \ 52 if (l <= s->sc_aht20debug) \ 53 printf x; \ 54 } while (/*CONSTCOND*/0) 55 #else 56 #define DPRINTF(s, l, x) 57 #endif 58 59 CFATTACH_DECL_NEW(aht20temp, sizeof(struct aht20_sc), 60 aht20_match, aht20_attach, aht20_detach, NULL); 61 62 static struct aht20_sensor aht20_sensors[] = { 63 { 64 .desc = "humidity", 65 .type = ENVSYS_SRELHUMIDITY, 66 }, 67 { 68 .desc = "temperature", 69 .type = ENVSYS_STEMP, 70 } 71 }; 72 73 /* 74 * The delays are mentioned in the datasheet for the chip, except for 75 * the get status command. 76 */ 77 78 static struct aht20_timing aht20_timings[] = { 79 { 80 .cmd = AHT20_INITIALIZE, 81 .typicaldelay = 10000, 82 }, 83 { 84 .cmd = AHT20_TRIGGER_MEASUREMENT, 85 .typicaldelay = 80000, 86 }, 87 { 88 .cmd = AHT20_GET_STATUS, 89 .typicaldelay = 5000, 90 }, 91 { 92 .cmd = AHT20_SOFT_RESET, 93 .typicaldelay = 20000, 94 } 95 }; 96 97 int 98 aht20_verify_sysctl(SYSCTLFN_ARGS) 99 { 100 int error, t; 101 struct sysctlnode node; 102 103 node = *rnode; 104 t = *(int *)rnode->sysctl_data; 105 node.sysctl_data = &t; 106 error = sysctl_lookup(SYSCTLFN_CALL(&node)); 107 if (error || newp == NULL) 108 return error; 109 110 if (t < 0) 111 return EINVAL; 112 113 *(int *)rnode->sysctl_data = t; 114 115 return 0; 116 } 117 118 static int 119 aht20_cmddelay(uint8_t cmd) 120 { 121 int r = -1; 122 123 for(int i = 0;i < __arraycount(aht20_timings);i++) { 124 if (cmd == aht20_timings[i].cmd) { 125 r = aht20_timings[i].typicaldelay; 126 break; 127 } 128 } 129 130 if (r == -1) { 131 panic("Bad command look up in cmd delay: cmd: %d\n",cmd); 132 } 133 134 return r; 135 } 136 137 static int 138 aht20_cmd(i2c_tag_t tag, i2c_addr_t addr, uint8_t *cmd, 139 uint8_t clen, uint8_t *buf, size_t blen, int readattempts) 140 { 141 int error; 142 int cmddelay; 143 144 error = iic_exec(tag,I2C_OP_WRITE_WITH_STOP,addr,cmd,clen,NULL,0,0); 145 146 /* Every command returns something except for the soft reset and 147 initialize which returns nothing. 148 */ 149 150 if (error == 0) { 151 cmddelay = aht20_cmddelay(cmd[0]); 152 delay(cmddelay); 153 154 if (cmd[0] != AHT20_SOFT_RESET && 155 cmd[0] != AHT20_INITIALIZE) { 156 for (int aint = 0; aint < readattempts; aint++) { 157 error = iic_exec(tag,I2C_OP_READ_WITH_STOP,addr,NULL,0,buf,blen,0); 158 if (error == 0) 159 break; 160 delay(1000); 161 } 162 } 163 } 164 165 return error; 166 } 167 168 static int 169 aht20_cmdr(struct aht20_sc *sc, uint8_t *cmd, uint8_t clen, uint8_t *buf, size_t blen) 170 { 171 KASSERT(clen > 0); 172 173 return aht20_cmd(sc->sc_tag, sc->sc_addr, cmd, clen, buf, blen, sc->sc_readattempts); 174 } 175 176 static uint8_t 177 aht20_crc(uint8_t * data, size_t size) 178 { 179 uint8_t crc = 0xFF; 180 181 for (size_t i = 0; i < size; i++) { 182 crc ^= data[i]; 183 for (size_t j = 8; j > 0; j--) { 184 if (crc & 0x80) 185 crc = (crc << 1) ^ 0x31; 186 else 187 crc <<= 1; 188 } 189 } 190 return crc; 191 } 192 193 static int 194 aht20_poke(i2c_tag_t tag, i2c_addr_t addr, bool matchdebug) 195 { 196 uint8_t reg = AHT20_GET_STATUS; 197 uint8_t buf[6]; 198 int error; 199 200 error = aht20_cmd(tag, addr, ®, 1, buf, 1, 10); 201 if (matchdebug) { 202 printf("poke X 1: %d\n", error); 203 } 204 return error; 205 } 206 207 static int 208 aht20_sysctl_init(struct aht20_sc *sc) 209 { 210 int error; 211 const struct sysctlnode *cnode; 212 int sysctlroot_num; 213 214 if ((error = sysctl_createv(&sc->sc_aht20log, 0, NULL, &cnode, 215 0, CTLTYPE_NODE, device_xname(sc->sc_dev), 216 SYSCTL_DESCR("aht20 controls"), NULL, 0, NULL, 0, CTL_HW, 217 CTL_CREATE, CTL_EOL)) != 0) 218 return error; 219 220 sysctlroot_num = cnode->sysctl_num; 221 222 #ifdef AHT20_DEBUG 223 if ((error = sysctl_createv(&sc->sc_aht20log, 0, NULL, &cnode, 224 CTLFLAG_READWRITE, CTLTYPE_INT, "debug", 225 SYSCTL_DESCR("Debug level"), aht20_verify_sysctl, 0, 226 &sc->sc_aht20debug, 0, CTL_HW, sysctlroot_num, CTL_CREATE, 227 CTL_EOL)) != 0) 228 return error; 229 230 #endif 231 232 if ((error = sysctl_createv(&sc->sc_aht20log, 0, NULL, &cnode, 233 CTLFLAG_READWRITE, CTLTYPE_INT, "readattempts", 234 SYSCTL_DESCR("The number of times to attempt to read the values"), 235 aht20_verify_sysctl, 0, &sc->sc_readattempts, 0, CTL_HW, 236 sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0) 237 return error; 238 239 if ((error = sysctl_createv(&sc->sc_aht20log, 0, NULL, &cnode, 240 CTLFLAG_READWRITE, CTLTYPE_BOOL, "ignorecrc", 241 SYSCTL_DESCR("Ignore the CRC byte"), NULL, 0, &sc->sc_ignorecrc, 242 0, CTL_HW, sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0) 243 return error; 244 245 return 0; 246 } 247 248 static int 249 aht20_match(device_t parent, cfdata_t match, void *aux) 250 { 251 struct i2c_attach_args *ia = aux; 252 int error, match_result; 253 const bool matchdebug = false; 254 255 if (iic_use_direct_match(ia, match, NULL, &match_result)) 256 return match_result; 257 258 /* indirect config - check for configured address */ 259 if (ia->ia_addr != AHT20_TYPICAL_ADDR) 260 return 0; 261 262 /* 263 * Check to see if something is really at this i2c address. This will 264 * keep phantom devices from appearing 265 */ 266 if (iic_acquire_bus(ia->ia_tag, 0) != 0) { 267 if (matchdebug) 268 printf("in match acquire bus failed\n"); 269 return 0; 270 } 271 272 error = aht20_poke(ia->ia_tag, ia->ia_addr, matchdebug); 273 iic_release_bus(ia->ia_tag, 0); 274 275 return error == 0 ? I2C_MATCH_ADDRESS_AND_PROBE : 0; 276 } 277 278 static void 279 aht20_attach(device_t parent, device_t self, void *aux) 280 { 281 struct aht20_sc *sc; 282 struct i2c_attach_args *ia; 283 uint8_t cmd[1]; 284 int error, i; 285 286 ia = aux; 287 sc = device_private(self); 288 289 sc->sc_dev = self; 290 sc->sc_tag = ia->ia_tag; 291 sc->sc_addr = ia->ia_addr; 292 sc->sc_aht20debug = 0; 293 sc->sc_readattempts = 10; 294 sc->sc_ignorecrc = false; 295 sc->sc_sme = NULL; 296 297 aprint_normal("\n"); 298 299 mutex_init(&sc->sc_mutex, MUTEX_DEFAULT, IPL_NONE); 300 sc->sc_numsensors = __arraycount(aht20_sensors); 301 302 if ((sc->sc_sme = sysmon_envsys_create()) == NULL) { 303 aprint_error_dev(self, 304 "Unable to create sysmon structure\n"); 305 sc->sc_sme = NULL; 306 return; 307 } 308 if ((error = aht20_sysctl_init(sc)) != 0) { 309 aprint_error_dev(self, "Can't setup sysctl tree (%d)\n", error); 310 goto out; 311 } 312 313 error = iic_acquire_bus(sc->sc_tag, 0); 314 if (error) { 315 aprint_error_dev(self, "Could not acquire iic bus: %d\n", 316 error); 317 goto out; 318 } 319 320 cmd[0] = AHT20_SOFT_RESET; 321 error = aht20_cmdr(sc, cmd, 1, NULL, 0); 322 if (error != 0) 323 aprint_error_dev(self, "Reset failed: %d\n", error); 324 325 iic_release_bus(sc->sc_tag, 0); 326 327 if (error != 0) { 328 aprint_error_dev(self, "Unable to setup device\n"); 329 goto out; 330 } 331 332 for (i = 0; i < sc->sc_numsensors; i++) { 333 strlcpy(sc->sc_sensors[i].desc, aht20_sensors[i].desc, 334 sizeof(sc->sc_sensors[i].desc)); 335 336 sc->sc_sensors[i].units = aht20_sensors[i].type; 337 sc->sc_sensors[i].state = ENVSYS_SINVALID; 338 339 DPRINTF(sc, 2, ("%s: registering sensor %d (%s)\n", __func__, i, 340 sc->sc_sensors[i].desc)); 341 342 error = sysmon_envsys_sensor_attach(sc->sc_sme, 343 &sc->sc_sensors[i]); 344 if (error) { 345 aprint_error_dev(self, 346 "Unable to attach sensor %d: %d\n", i, error); 347 goto out; 348 } 349 } 350 351 sc->sc_sme->sme_name = device_xname(sc->sc_dev); 352 sc->sc_sme->sme_cookie = sc; 353 sc->sc_sme->sme_refresh = aht20_refresh; 354 355 DPRINTF(sc, 2, ("aht20_attach: registering with envsys\n")); 356 357 if (sysmon_envsys_register(sc->sc_sme)) { 358 aprint_error_dev(self, 359 "unable to register with sysmon\n"); 360 sysmon_envsys_destroy(sc->sc_sme); 361 sc->sc_sme = NULL; 362 return; 363 } 364 365 aprint_normal_dev(self, "Guangzhou Aosong AHT20\n"); 366 367 return; 368 out: 369 sysmon_envsys_destroy(sc->sc_sme); 370 sc->sc_sme = NULL; 371 } 372 373 static void 374 aht20_refresh(struct sysmon_envsys * sme, envsys_data_t * edata) 375 { 376 struct aht20_sc *sc; 377 sc = sme->sme_cookie; 378 int error; 379 uint8_t cmd[3]; 380 uint8_t rawdata[7]; 381 edata->state = ENVSYS_SINVALID; 382 383 mutex_enter(&sc->sc_mutex); 384 error = iic_acquire_bus(sc->sc_tag, 0); 385 if (error) { 386 DPRINTF(sc, 2, ("%s: Could not acquire i2c bus: %x\n", 387 device_xname(sc->sc_dev), error)); 388 goto out; 389 } 390 391 /* 392 The documented conversion calculations for the raw values are as follows: 393 394 %RH = (Srh / 2^20) * 100% 395 396 T in Celsius = ((St / 2^20) * 200) - 50 397 398 It follows then: 399 400 T in Kelvin = ((St / 2^20) * 200) + 223.15 401 402 given the relationship between Celsius and Kelvin. 403 404 What follows reorders the calculation a bit and scales it up to avoid 405 the use of any floating point. All that would really have to happen 406 is a scale up to 10^6 for the sysenv framework, which wants 407 temperature in micro-kelvin and percent relative humidity scaled up 408 10^6, but since this conversion uses 64 bits due to intermediate 409 values that are bigger than 32 bits the conversion first scales up to 410 10^9 and the scales back down by 10^3 at the end. This preserves some 411 precision in the conversion that would otherwise be lost. 412 */ 413 414 cmd[0] = AHT20_TRIGGER_MEASUREMENT; 415 cmd[1] = AHT20_TRIGGER_PARAM1; 416 cmd[2] = AHT20_TRIGGER_PARAM2; 417 error = aht20_cmdr(sc, cmd, 3, rawdata, 7); 418 419 if (error == 0) { 420 if (rawdata[0] & AHT20_STATUS_BUSY_MASK) { 421 aprint_error_dev(sc->sc_dev, 422 "Chip is busy. Status register: %02x\n", 423 rawdata[0]); 424 error = EINVAL; 425 } 426 427 if (error == 0 && 428 rawdata[0] & AHT20_STATUS_CAL_MASK) { 429 430 uint8_t testcrc; 431 432 testcrc = aht20_crc(&rawdata[0],6); 433 434 DPRINTF(sc, 2, ("%s: Raw data: STATUS: %02x - RH: %02x %02x - %02x - TEMP: %02x %02x - CRC: %02x -- %02x\n", 435 device_xname(sc->sc_dev), rawdata[0], rawdata[1], rawdata[2], 436 rawdata[3], rawdata[4], rawdata[5], rawdata[6], testcrc)); 437 438 /* This chip splits the %rh and temp raw files ove the 3 byte returned. Since 439 there is no choice but to get both, split them both apart every time */ 440 441 uint64_t rawhum; 442 uint64_t rawtemp; 443 444 rawhum = (rawdata[1] << 12) | (rawdata[2] << 4) | ((rawdata[3] & 0xf0) >> 4); 445 rawtemp = ((rawdata[3] & 0x0f) << 16) | (rawdata[4] << 8) | rawdata[5]; 446 447 DPRINTF(sc, 2, ("%s: Raw broken data: RH: %04jx (%jd) - TEMP: %04jx (%jd)\n", 448 device_xname(sc->sc_dev), rawhum, rawhum, rawtemp, rawtemp)); 449 450 /* Fake out the CRC check if being asked to ignore CRC */ 451 if (sc->sc_ignorecrc) { 452 testcrc = rawdata[6]; 453 } 454 455 if (rawdata[6] == testcrc) { 456 uint64_t q = 0; 457 458 switch (edata->sensor) { 459 case AHT20_TEMP_SENSOR: 460 q = (((rawtemp * 1000000000) / 10485760) * 2) + 223150000; 461 462 break; 463 case AHT20_HUMIDITY_SENSOR: 464 q = (rawhum * 1000000000) / 10485760; 465 466 break; 467 default: 468 error = EINVAL; 469 break; 470 } 471 472 DPRINTF(sc, 2, ("%s: Computed sensor: %#jx (%jd)\n", 473 device_xname(sc->sc_dev), (uintmax_t)q, (uintmax_t)q)); 474 475 /* The results will fit in 32 bits, so nothing will be lost */ 476 edata->value_cur = (uint32_t) q; 477 edata->state = ENVSYS_SVALID; 478 } else { 479 error = EINVAL; 480 } 481 } else { 482 if (error == 0) { 483 aprint_error_dev(sc->sc_dev,"Calibration needs to be run on the chip.\n"); 484 485 cmd[0] = AHT20_INITIALIZE; 486 cmd[1] = AHT20_INITIALIZE_PARAM1; 487 cmd[2] = AHT20_INITIALIZE_PARAM2; 488 error = aht20_cmdr(sc, cmd, 3, NULL, 0); 489 490 if (error) { 491 DPRINTF(sc, 2, ("%s: Calibration failed to run: %d\n", 492 device_xname(sc->sc_dev), error)); 493 } 494 } 495 } 496 } 497 498 if (error) { 499 DPRINTF(sc, 2, ("%s: Failed to get new status in refresh %d\n", 500 device_xname(sc->sc_dev), error)); 501 } 502 503 iic_release_bus(sc->sc_tag, 0); 504 out: 505 mutex_exit(&sc->sc_mutex); 506 } 507 508 static int 509 aht20_detach(device_t self, int flags) 510 { 511 struct aht20_sc *sc; 512 513 sc = device_private(self); 514 515 mutex_enter(&sc->sc_mutex); 516 517 /* Remove the sensors */ 518 if (sc->sc_sme != NULL) { 519 sysmon_envsys_unregister(sc->sc_sme); 520 sc->sc_sme = NULL; 521 } 522 mutex_exit(&sc->sc_mutex); 523 524 /* Remove the sysctl tree */ 525 sysctl_teardown(&sc->sc_aht20log); 526 527 /* Remove the mutex */ 528 mutex_destroy(&sc->sc_mutex); 529 530 return 0; 531 } 532 533 MODULE(MODULE_CLASS_DRIVER, aht20temp, "iic,sysmon_envsys"); 534 535 #ifdef _MODULE 536 #include "ioconf.c" 537 #endif 538 539 static int 540 aht20temp_modcmd(modcmd_t cmd, void *opaque) 541 { 542 543 switch (cmd) { 544 case MODULE_CMD_INIT: 545 #ifdef _MODULE 546 return config_init_component(cfdriver_ioconf_aht20temp, 547 cfattach_ioconf_aht20temp, cfdata_ioconf_aht20temp); 548 #else 549 return 0; 550 #endif 551 case MODULE_CMD_FINI: 552 #ifdef _MODULE 553 return config_fini_component(cfdriver_ioconf_aht20temp, 554 cfattach_ioconf_aht20temp, cfdata_ioconf_aht20temp); 555 #else 556 return 0; 557 #endif 558 default: 559 return ENOTTY; 560 } 561 } 562