1 1.5 andvar /* $NetBSD: sgp40.c,v 1.5 2022/05/24 06:28:01 andvar Exp $ */ 2 1.1 brad 3 1.1 brad /* 4 1.1 brad * Copyright (c) 2021 Brad Spencer <brad (at) anduin.eldar.org> 5 1.1 brad * 6 1.1 brad * Permission to use, copy, modify, and distribute this software for any 7 1.1 brad * purpose with or without fee is hereby granted, provided that the above 8 1.1 brad * copyright notice and this permission notice appear in all copies. 9 1.1 brad * 10 1.1 brad * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 1.1 brad * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 1.1 brad * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 1.1 brad * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 1.1 brad * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 1.1 brad * ACTION OF CONTRACT, NEGL`IGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 1.1 brad * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 1.1 brad */ 18 1.1 brad 19 1.1 brad #include <sys/cdefs.h> 20 1.5 andvar __KERNEL_RCSID(0, "$NetBSD: sgp40.c,v 1.5 2022/05/24 06:28:01 andvar Exp $"); 21 1.1 brad 22 1.1 brad /* 23 1.1 brad Driver for the Sensirion SGP40 MOx gas sensor for air quality 24 1.1 brad */ 25 1.1 brad 26 1.1 brad #include <sys/param.h> 27 1.1 brad #include <sys/systm.h> 28 1.1 brad #include <sys/kernel.h> 29 1.1 brad #include <sys/device.h> 30 1.1 brad #include <sys/module.h> 31 1.1 brad #include <sys/sysctl.h> 32 1.1 brad #include <sys/mutex.h> 33 1.1 brad #include <sys/condvar.h> 34 1.1 brad #include <sys/kthread.h> 35 1.1 brad 36 1.1 brad #include <dev/sysmon/sysmonvar.h> 37 1.1 brad #include <dev/i2c/i2cvar.h> 38 1.1 brad #include <dev/i2c/sgp40reg.h> 39 1.1 brad #include <dev/i2c/sgp40var.h> 40 1.1 brad 41 1.1 brad #include <dev/i2c/sensirion_arch_config.h> 42 1.1 brad #include <dev/i2c/sensirion_voc_algorithm.h> 43 1.1 brad 44 1.1 brad static uint8_t sgp40_crc(uint8_t *, size_t); 45 1.2 christos static int sgp40_cmdr(struct sgp40_sc *, uint16_t, uint8_t *, uint8_t, 46 1.2 christos uint8_t *, size_t); 47 1.1 brad static int sgp40_poke(i2c_tag_t, i2c_addr_t, bool); 48 1.1 brad static int sgp40_match(device_t, cfdata_t, void *); 49 1.1 brad static void sgp40_attach(device_t, device_t, void *); 50 1.1 brad static int sgp40_detach(device_t, int); 51 1.1 brad static void sgp40_refresh(struct sysmon_envsys *, envsys_data_t *); 52 1.1 brad static int sgp40_verify_sysctl(SYSCTLFN_ARGS); 53 1.1 brad static int sgp40_verify_temp_sysctl(SYSCTLFN_ARGS); 54 1.1 brad static int sgp40_verify_rh_sysctl(SYSCTLFN_ARGS); 55 1.1 brad static void sgp40_thread(void *); 56 1.1 brad static void sgp40_stop_thread(void *); 57 1.1 brad static void sgp40_take_measurement(void *, VocAlgorithmParams *); 58 1.1 brad 59 1.1 brad #define SGP40_DEBUG 60 1.1 brad #ifdef SGP40_DEBUG 61 1.1 brad #define DPRINTF(s, l, x) \ 62 1.1 brad do { \ 63 1.1 brad if (l <= s->sc_sgp40debug) \ 64 1.1 brad printf x; \ 65 1.1 brad } while (/*CONSTCOND*/0) 66 1.1 brad #else 67 1.1 brad #define DPRINTF(s, l, x) 68 1.1 brad #endif 69 1.1 brad 70 1.1 brad CFATTACH_DECL_NEW(sgp40mox, sizeof(struct sgp40_sc), 71 1.1 brad sgp40_match, sgp40_attach, sgp40_detach, NULL); 72 1.1 brad 73 1.1 brad static struct sgp40_sensor sgp40_sensors[] = { 74 1.1 brad { 75 1.1 brad .desc = "VOC index", 76 1.1 brad .type = ENVSYS_INTEGER, 77 1.1 brad } 78 1.1 brad }; 79 1.1 brad 80 1.1 brad static struct sgp40_timing sgp40_timings[] = { 81 1.1 brad { 82 1.1 brad .cmd = SGP40_MEASURE_RAW, 83 1.1 brad .typicaldelay = 25000, 84 1.1 brad }, 85 1.1 brad { 86 1.1 brad .cmd = SGP40_MEASURE_TEST, 87 1.1 brad .typicaldelay = 240000, 88 1.1 brad }, 89 1.1 brad { 90 1.1 brad .cmd = SGP40_HEATER_OFF, 91 1.1 brad .typicaldelay = 100, 92 1.1 brad }, 93 1.1 brad { 94 1.1 brad .cmd = SGP40_GET_SERIAL_NUMBER, 95 1.1 brad .typicaldelay = 100, 96 1.1 brad }, 97 1.1 brad { 98 1.1 brad .cmd = SGP40_GET_FEATURESET, 99 1.1 brad .typicaldelay = 1000, 100 1.1 brad } 101 1.1 brad }; 102 1.1 brad 103 1.1 brad void 104 1.1 brad sgp40_thread(void *aux) 105 1.1 brad { 106 1.1 brad struct sgp40_sc *sc = aux; 107 1.1 brad int rv; 108 1.1 brad VocAlgorithmParams voc_algorithm_params; 109 1.1 brad 110 1.1 brad mutex_enter(&sc->sc_threadmutex); 111 1.1 brad 112 1.1 brad VocAlgorithm_init(&voc_algorithm_params); 113 1.1 brad 114 1.2 christos while (!sc->sc_stopping) { 115 1.2 christos rv = cv_timedwait(&sc->sc_condvar, &sc->sc_threadmutex, 116 1.2 christos mstohz(1000)); 117 1.2 christos if (rv == EWOULDBLOCK && !sc->sc_stopping) { 118 1.1 brad sgp40_take_measurement(sc,&voc_algorithm_params); 119 1.1 brad } 120 1.1 brad } 121 1.1 brad mutex_exit(&sc->sc_threadmutex); 122 1.1 brad kthread_exit(0); 123 1.1 brad } 124 1.1 brad 125 1.1 brad static void 126 1.1 brad sgp40_stop_thread(void *aux) 127 1.1 brad { 128 1.1 brad struct sgp40_sc *sc; 129 1.1 brad sc = aux; 130 1.1 brad int error; 131 1.1 brad 132 1.1 brad mutex_enter(&sc->sc_threadmutex); 133 1.1 brad sc->sc_stopping = true; 134 1.1 brad cv_signal(&sc->sc_condvar); 135 1.1 brad mutex_exit(&sc->sc_threadmutex); 136 1.1 brad 137 1.1 brad /* wait for the thread to exit */ 138 1.1 brad kthread_join(sc->sc_thread); 139 1.1 brad 140 1.1 brad mutex_enter(&sc->sc_mutex); 141 1.1 brad error = iic_acquire_bus(sc->sc_tag, 0); 142 1.1 brad if (error) { 143 1.2 christos DPRINTF(sc, 2, ("%s: Could not acquire iic bus for heater off " 144 1.2 christos "in stop thread: %d\n", device_xname(sc->sc_dev), error)); 145 1.2 christos goto out; 146 1.2 christos } 147 1.2 christos error = sgp40_cmdr(sc, SGP40_HEATER_OFF, NULL, 0, NULL, 0); 148 1.2 christos if (error) { 149 1.2 christos DPRINTF(sc, 2, ("%s: Error turning heater off: %d\n", 150 1.1 brad device_xname(sc->sc_dev), error)); 151 1.1 brad } 152 1.2 christos out: 153 1.2 christos iic_release_bus(sc->sc_tag, 0); 154 1.1 brad mutex_exit(&sc->sc_mutex); 155 1.1 brad } 156 1.1 brad 157 1.1 brad static int 158 1.1 brad sgp40_compute_temp_comp(int unconverted) 159 1.1 brad { 160 1.2 christos /* 161 1.2 christos * The published algorithm for this conversion is: 162 1.5 andvar * (temp_in_celsius + 45) * 65535 / 175 163 1.2 christos * 164 1.2 christos * However, this did not exactly yield the results that 165 1.2 christos * the example in the data sheet, so something a little 166 1.2 christos * different was done. 167 1.2 christos * 168 1.5 andvar * (temp_in_celsius + 45) * 65536 / 175 169 1.2 christos * 170 1.2 christos * This was also scaled up by 10^2 and then scaled back to 171 1.5 andvar * preserve some precision. 37449 is simply (65536 * 100) / 175 172 1.2 christos * and rounded. 173 1.2 christos */ 174 1.1 brad 175 1.1 brad return (((unconverted + 45) * 100) * 37449) / 10000; 176 1.1 brad } 177 1.1 brad 178 1.1 brad static int 179 1.1 brad sgp40_compute_rh_comp(int unconverted) 180 1.1 brad { 181 1.1 brad int q; 182 1.1 brad 183 1.2 christos /* 184 1.2 christos * The published algorithm for this conversion is: 185 1.2 christos * %rh * 65535 / 100 186 1.2 christos * 187 1.2 christos * However, this did not exactly yield the results that 188 1.2 christos * the example in the data sheet, so something a little 189 1.2 christos * different was done. 190 1.2 christos * 191 1.2 christos * %rh * 65536 / 100 192 1.2 christos * 193 1.2 christos * This was also scaled up by 10^2 and then scaled back to 194 1.5 andvar * preserve some precision. The value is also latched to 65535 195 1.2 christos * as an upper limit. 196 1.2 christos */ 197 1.1 brad 198 1.1 brad q = ((unconverted * 100) * 65536) / 10000; 199 1.1 brad if (q > 65535) 200 1.1 brad q = 65535; 201 1.1 brad return q; 202 1.1 brad } 203 1.1 brad 204 1.1 brad static void 205 1.1 brad sgp40_take_measurement(void *aux, VocAlgorithmParams* params) 206 1.1 brad { 207 1.1 brad struct sgp40_sc *sc; 208 1.1 brad sc = aux; 209 1.1 brad uint8_t args[6]; 210 1.1 brad uint8_t buf[3]; 211 1.1 brad uint16_t rawmeasurement; 212 1.1 brad int error; 213 1.1 brad uint8_t crc; 214 1.1 brad uint16_t convertedrh, convertedtemp; 215 1.1 brad int32_t voc_index; 216 1.1 brad 217 1.1 brad mutex_enter(&sc->sc_mutex); 218 1.1 brad convertedrh = (uint16_t)sgp40_compute_rh_comp(sc->sc_rhcomp); 219 1.1 brad convertedtemp = (uint16_t)sgp40_compute_temp_comp(sc->sc_tempcomp); 220 1.1 brad 221 1.1 brad DPRINTF(sc, 2, ("%s: Converted RH and Temp: %04x %04x\n", 222 1.1 brad device_xname(sc->sc_dev), convertedrh, convertedtemp)); 223 1.1 brad 224 1.1 brad args[0] = convertedrh >> 8; 225 1.1 brad args[1] = convertedrh & 0x00ff; 226 1.2 christos args[2] = sgp40_crc(&args[0], 2); 227 1.1 brad args[3] = convertedtemp >> 8; 228 1.1 brad args[4] = convertedtemp & 0x00ff; 229 1.2 christos args[5] = sgp40_crc(&args[3], 2); 230 1.1 brad 231 1.2 christos /* 232 1.4 andvar * The VOC algorithm has a black out time when it first starts to run 233 1.2 christos * and does not return any indicator that is going on, so voc_index 234 1.2 christos * in that case would be 0.. however, that is also a valid response 235 1.2 christos * otherwise, although an unlikely one. 236 1.2 christos */ 237 1.1 brad error = iic_acquire_bus(sc->sc_tag, 0); 238 1.1 brad if (error) { 239 1.2 christos DPRINTF(sc, 2, ("%s: Could not acquire iic bus for take " 240 1.2 christos "measurement: %d\n", device_xname(sc->sc_dev), error)); 241 1.1 brad sc->sc_voc = 0; 242 1.1 brad sc->sc_vocvalid = false; 243 1.2 christos goto out; 244 1.2 christos } 245 1.2 christos 246 1.2 christos error = sgp40_cmdr(sc, SGP40_MEASURE_RAW, args, 6, buf, 3); 247 1.2 christos iic_release_bus(sc->sc_tag, 0); 248 1.2 christos if (error) { 249 1.2 christos DPRINTF(sc, 2, ("%s: Failed to get measurement %d\n", 250 1.2 christos device_xname(sc->sc_dev), error)); 251 1.2 christos goto out; 252 1.1 brad } 253 1.1 brad 254 1.2 christos crc = sgp40_crc(&buf[0], 2); 255 1.2 christos DPRINTF(sc, 2, ("%s: Raw ticks and crc: %02x%02x %02x " 256 1.2 christos "%02x\n", device_xname(sc->sc_dev), buf[0], buf[1], 257 1.2 christos buf[2], crc)); 258 1.2 christos if (buf[2] != crc) 259 1.2 christos goto out; 260 1.2 christos 261 1.2 christos rawmeasurement = buf[0] << 8; 262 1.2 christos rawmeasurement |= buf[1]; 263 1.2 christos VocAlgorithm_process(params, rawmeasurement, 264 1.2 christos &voc_index); 265 1.2 christos DPRINTF(sc, 2, ("%s: VOC index: %d\n", 266 1.2 christos device_xname(sc->sc_dev), voc_index)); 267 1.2 christos sc->sc_voc = voc_index; 268 1.2 christos sc->sc_vocvalid = true; 269 1.2 christos 270 1.2 christos mutex_exit(&sc->sc_mutex); 271 1.2 christos return; 272 1.2 christos out: 273 1.2 christos sc->sc_voc = 0; 274 1.2 christos sc->sc_vocvalid = false; 275 1.1 brad mutex_exit(&sc->sc_mutex); 276 1.1 brad } 277 1.1 brad 278 1.1 brad int 279 1.1 brad sgp40_verify_sysctl(SYSCTLFN_ARGS) 280 1.1 brad { 281 1.1 brad int error, t; 282 1.1 brad struct sysctlnode node; 283 1.1 brad 284 1.1 brad node = *rnode; 285 1.1 brad t = *(int *)rnode->sysctl_data; 286 1.1 brad node.sysctl_data = &t; 287 1.1 brad error = sysctl_lookup(SYSCTLFN_CALL(&node)); 288 1.1 brad if (error || newp == NULL) 289 1.1 brad return error; 290 1.1 brad 291 1.1 brad if (t < 0) 292 1.1 brad return EINVAL; 293 1.1 brad 294 1.1 brad *(int *)rnode->sysctl_data = t; 295 1.1 brad 296 1.1 brad return 0; 297 1.1 brad } 298 1.1 brad 299 1.1 brad int 300 1.1 brad sgp40_verify_temp_sysctl(SYSCTLFN_ARGS) 301 1.1 brad { 302 1.1 brad int error, t; 303 1.1 brad struct sysctlnode node; 304 1.1 brad 305 1.1 brad node = *rnode; 306 1.1 brad t = *(int *)rnode->sysctl_data; 307 1.1 brad node.sysctl_data = &t; 308 1.1 brad error = sysctl_lookup(SYSCTLFN_CALL(&node)); 309 1.1 brad if (error || newp == NULL) 310 1.1 brad return error; 311 1.1 brad 312 1.1 brad if (t < -45 || t > 130) 313 1.1 brad return EINVAL; 314 1.1 brad 315 1.1 brad *(int *)rnode->sysctl_data = t; 316 1.1 brad 317 1.1 brad return 0; 318 1.1 brad } 319 1.1 brad 320 1.1 brad int 321 1.1 brad sgp40_verify_rh_sysctl(SYSCTLFN_ARGS) 322 1.1 brad { 323 1.1 brad int error, t; 324 1.1 brad struct sysctlnode node; 325 1.1 brad 326 1.1 brad node = *rnode; 327 1.1 brad t = *(int *)rnode->sysctl_data; 328 1.1 brad node.sysctl_data = &t; 329 1.1 brad error = sysctl_lookup(SYSCTLFN_CALL(&node)); 330 1.1 brad if (error || newp == NULL) 331 1.1 brad return error; 332 1.1 brad 333 1.1 brad if (t < 0 || t > 100) 334 1.1 brad return EINVAL; 335 1.1 brad 336 1.1 brad *(int *)rnode->sysctl_data = t; 337 1.1 brad 338 1.1 brad return 0; 339 1.1 brad } 340 1.1 brad 341 1.1 brad static int 342 1.1 brad sgp40_cmddelay(uint16_t cmd) 343 1.1 brad { 344 1.1 brad int r = -1; 345 1.1 brad 346 1.1 brad for(int i = 0;i < __arraycount(sgp40_timings);i++) { 347 1.1 brad if (cmd == sgp40_timings[i].cmd) { 348 1.1 brad r = sgp40_timings[i].typicaldelay; 349 1.1 brad break; 350 1.1 brad } 351 1.1 brad } 352 1.1 brad 353 1.1 brad if (r == -1) { 354 1.2 christos panic("sgp40: Bad command look up in cmd delay: cmd: %d\n", 355 1.2 christos cmd); 356 1.1 brad } 357 1.1 brad 358 1.1 brad return r; 359 1.1 brad } 360 1.1 brad 361 1.1 brad static int 362 1.1 brad sgp40_cmd(i2c_tag_t tag, i2c_addr_t addr, uint8_t *cmd, 363 1.1 brad uint8_t clen, uint8_t *buf, size_t blen, int readattempts) 364 1.1 brad { 365 1.1 brad int error; 366 1.1 brad int cmddelay; 367 1.1 brad uint16_t cmd16; 368 1.1 brad 369 1.1 brad cmd16 = cmd[0] << 8; 370 1.1 brad cmd16 = cmd16 | cmd[1]; 371 1.1 brad 372 1.2 christos error = iic_exec(tag, I2C_OP_WRITE_WITH_STOP, addr, cmd, clen, NULL, 0, 373 1.2 christos 0); 374 1.2 christos if (error) 375 1.2 christos return error; 376 1.1 brad 377 1.2 christos /* 378 1.2 christos * Every command returns something except for turning the heater off 379 1.2 christos * and the general soft reset which returns nothing. 380 1.2 christos */ 381 1.2 christos if (cmd16 == SGP40_HEATER_OFF) 382 1.2 christos return 0; 383 1.2 christos /* 384 1.2 christos * Every command has a particular delay for how long 385 1.2 christos * it typically takes and the max time it will take. 386 1.2 christos */ 387 1.2 christos cmddelay = sgp40_cmddelay(cmd16); 388 1.2 christos delay(cmddelay); 389 1.2 christos 390 1.2 christos for (int aint = 0; aint < readattempts; aint++) { 391 1.2 christos error = iic_exec(tag, I2C_OP_READ_WITH_STOP, addr, NULL, 0, 392 1.2 christos buf, blen, 0); 393 1.2 christos if (error == 0) 394 1.2 christos break; 395 1.2 christos delay(1000); 396 1.1 brad } 397 1.1 brad 398 1.1 brad return error; 399 1.1 brad } 400 1.1 brad 401 1.1 brad static int 402 1.2 christos sgp40_cmdr(struct sgp40_sc *sc, uint16_t cmd, uint8_t *extraargs, 403 1.2 christos uint8_t argslen, uint8_t *buf, size_t blen) 404 1.1 brad { 405 1.1 brad uint8_t fullcmd[8]; 406 1.1 brad uint8_t cmdlen; 407 1.1 brad int n; 408 1.1 brad 409 1.2 christos /* 410 1.2 christos * The biggest documented command + arguments is 8 uint8_t bytes long. 411 1.2 christos * Catch anything that ties to have an arglen more than 6 412 1.2 christos */ 413 1.1 brad KASSERT(argslen <= 6); 414 1.1 brad 415 1.1 brad memset(fullcmd, 0, 8); 416 1.1 brad 417 1.1 brad fullcmd[0] = cmd >> 8; 418 1.1 brad fullcmd[1] = cmd & 0x00ff; 419 1.1 brad cmdlen = 2; 420 1.1 brad 421 1.1 brad n = 0; 422 1.1 brad while (extraargs != NULL && n < argslen && cmdlen <= 7) { 423 1.1 brad fullcmd[cmdlen] = extraargs[n]; 424 1.1 brad cmdlen++; 425 1.1 brad n++; 426 1.1 brad } 427 1.2 christos DPRINTF(sc, 2, ("%s: Full command and arguments: %02x %02x %02x %02x " 428 1.2 christos "%02x %02x %02x %02x\n", 429 1.1 brad device_xname(sc->sc_dev), fullcmd[0], fullcmd[1], 430 1.1 brad fullcmd[2], fullcmd[3], fullcmd[4], fullcmd[5], 431 1.1 brad fullcmd[6], fullcmd[7])); 432 1.2 christos return sgp40_cmd(sc->sc_tag, sc->sc_addr, fullcmd, cmdlen, buf, blen, 433 1.2 christos sc->sc_readattempts); 434 1.1 brad } 435 1.1 brad 436 1.1 brad static uint8_t 437 1.1 brad sgp40_crc(uint8_t * data, size_t size) 438 1.1 brad { 439 1.1 brad uint8_t crc = 0xFF; 440 1.1 brad 441 1.1 brad for (size_t i = 0; i < size; i++) { 442 1.1 brad crc ^= data[i]; 443 1.1 brad for (size_t j = 8; j > 0; j--) { 444 1.1 brad if (crc & 0x80) 445 1.1 brad crc = (crc << 1) ^ 0x31; 446 1.1 brad else 447 1.1 brad crc <<= 1; 448 1.1 brad } 449 1.1 brad } 450 1.1 brad return crc; 451 1.1 brad } 452 1.1 brad 453 1.1 brad static int 454 1.1 brad sgp40_poke(i2c_tag_t tag, i2c_addr_t addr, bool matchdebug) 455 1.1 brad { 456 1.1 brad uint8_t reg[2]; 457 1.1 brad uint8_t buf[9]; 458 1.1 brad int error; 459 1.1 brad 460 1.2 christos /* 461 1.2 christos * Possible bug... this command may not work if the chip is not idle, 462 1.2 christos * however, it appears to be used by a lot of other code as a probe. 463 1.2 christos */ 464 1.1 brad reg[0] = SGP40_GET_SERIAL_NUMBER >> 8; 465 1.1 brad reg[1] = SGP40_GET_SERIAL_NUMBER & 0x00ff; 466 1.1 brad 467 1.1 brad error = sgp40_cmd(tag, addr, reg, 2, buf, 9, 10); 468 1.1 brad if (matchdebug) { 469 1.1 brad printf("poke X 1: %d\n", error); 470 1.1 brad } 471 1.1 brad return error; 472 1.1 brad } 473 1.1 brad 474 1.1 brad static int 475 1.1 brad sgp40_sysctl_init(struct sgp40_sc *sc) 476 1.1 brad { 477 1.1 brad int error; 478 1.1 brad const struct sysctlnode *cnode; 479 1.1 brad int sysctlroot_num; 480 1.1 brad 481 1.1 brad if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode, 482 1.1 brad 0, CTLTYPE_NODE, device_xname(sc->sc_dev), 483 1.1 brad SYSCTL_DESCR("SGP40 controls"), NULL, 0, NULL, 0, CTL_HW, 484 1.1 brad CTL_CREATE, CTL_EOL)) != 0) 485 1.1 brad return error; 486 1.1 brad 487 1.1 brad sysctlroot_num = cnode->sysctl_num; 488 1.1 brad 489 1.1 brad #ifdef SGP40_DEBUG 490 1.1 brad if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode, 491 1.1 brad CTLFLAG_READWRITE, CTLTYPE_INT, "debug", 492 1.1 brad SYSCTL_DESCR("Debug level"), sgp40_verify_sysctl, 0, 493 1.1 brad &sc->sc_sgp40debug, 0, CTL_HW, sysctlroot_num, CTL_CREATE, 494 1.1 brad CTL_EOL)) != 0) 495 1.1 brad return error; 496 1.1 brad 497 1.1 brad #endif 498 1.1 brad 499 1.1 brad if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode, 500 1.1 brad CTLFLAG_READWRITE, CTLTYPE_INT, "readattempts", 501 1.1 brad SYSCTL_DESCR("The number of times to attempt to read the values"), 502 1.1 brad sgp40_verify_sysctl, 0, &sc->sc_readattempts, 0, CTL_HW, 503 1.1 brad sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0) 504 1.1 brad return error; 505 1.1 brad 506 1.1 brad if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode, 507 1.1 brad CTLFLAG_READWRITE, CTLTYPE_BOOL, "ignorecrc", 508 1.1 brad SYSCTL_DESCR("Ignore the CRC byte"), NULL, 0, &sc->sc_ignorecrc, 509 1.1 brad 0, CTL_HW, sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0) 510 1.1 brad return error; 511 1.1 brad 512 1.1 brad if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode, 513 1.1 brad 0, CTLTYPE_NODE, "compensation", 514 1.2 christos SYSCTL_DESCR("SGP40 measurement compensations"), NULL, 0, NULL, 0, 515 1.2 christos CTL_HW, sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0) 516 1.1 brad return error; 517 1.1 brad int compensation_num = cnode->sysctl_num; 518 1.1 brad 519 1.1 brad if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode, 520 1.1 brad CTLFLAG_READWRITE, CTLTYPE_INT, "temperature", 521 1.1 brad SYSCTL_DESCR("Temperature compensation in celsius"), 522 1.1 brad sgp40_verify_temp_sysctl, 0, &sc->sc_tempcomp, 0, CTL_HW, 523 1.1 brad sysctlroot_num, compensation_num, CTL_CREATE, CTL_EOL)) != 0) 524 1.1 brad return error; 525 1.1 brad 526 1.1 brad if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode, 527 1.1 brad CTLFLAG_READWRITE, CTLTYPE_INT, "humidity", 528 1.1 brad SYSCTL_DESCR("Humidity compensation in %RH"), 529 1.1 brad sgp40_verify_rh_sysctl, 0, &sc->sc_rhcomp, 0, CTL_HW, 530 1.1 brad sysctlroot_num, compensation_num, CTL_CREATE, CTL_EOL)) != 0) 531 1.1 brad return error; 532 1.1 brad 533 1.1 brad return 0; 534 1.1 brad } 535 1.1 brad 536 1.1 brad static int 537 1.1 brad sgp40_match(device_t parent, cfdata_t match, void *aux) 538 1.1 brad { 539 1.1 brad struct i2c_attach_args *ia = aux; 540 1.1 brad int error, match_result; 541 1.1 brad const bool matchdebug = false; 542 1.1 brad 543 1.1 brad if (matchdebug) 544 1.1 brad printf("in match\n"); 545 1.1 brad 546 1.1 brad if (iic_use_direct_match(ia, match, NULL, &match_result)) 547 1.1 brad return match_result; 548 1.1 brad 549 1.1 brad /* indirect config - check for configured address */ 550 1.1 brad if (ia->ia_addr != SGP40_TYPICAL_ADDR) 551 1.1 brad return 0; 552 1.1 brad 553 1.1 brad /* 554 1.1 brad * Check to see if something is really at this i2c address. This will 555 1.1 brad * keep phantom devices from appearing 556 1.1 brad */ 557 1.1 brad if (iic_acquire_bus(ia->ia_tag, 0) != 0) { 558 1.1 brad if (matchdebug) 559 1.1 brad printf("in match acquire bus failed\n"); 560 1.1 brad return 0; 561 1.1 brad } 562 1.1 brad 563 1.1 brad error = sgp40_poke(ia->ia_tag, ia->ia_addr, matchdebug); 564 1.1 brad iic_release_bus(ia->ia_tag, 0); 565 1.1 brad 566 1.1 brad return error == 0 ? I2C_MATCH_ADDRESS_AND_PROBE : 0; 567 1.1 brad } 568 1.1 brad 569 1.1 brad static void 570 1.1 brad sgp40_attach(device_t parent, device_t self, void *aux) 571 1.1 brad { 572 1.1 brad struct sgp40_sc *sc; 573 1.1 brad struct i2c_attach_args *ia; 574 1.1 brad int error, i; 575 1.1 brad int ecount = 0; 576 1.1 brad uint8_t buf[9]; 577 1.1 brad uint8_t tstcrc; 578 1.1 brad uint16_t chiptestvalue; 579 1.1 brad uint64_t serial_number = 0; 580 1.1 brad uint8_t sn_crc1, sn_crc2, sn_crc3, sn_crcv1, sn_crcv2, sn_crcv3; 581 1.1 brad uint8_t fs_crc, fs_crcv; 582 1.1 brad uint16_t featureset; 583 1.1 brad 584 1.1 brad ia = aux; 585 1.1 brad sc = device_private(self); 586 1.1 brad 587 1.1 brad sc->sc_dev = self; 588 1.1 brad sc->sc_tag = ia->ia_tag; 589 1.1 brad sc->sc_addr = ia->ia_addr; 590 1.1 brad sc->sc_sgp40debug = 0; 591 1.1 brad sc->sc_readattempts = 10; 592 1.1 brad sc->sc_ignorecrc = false; 593 1.1 brad sc->sc_stopping = false; 594 1.1 brad sc->sc_voc = 0; 595 1.1 brad sc->sc_vocvalid = false; 596 1.1 brad sc->sc_tempcomp = SGP40_DEFAULT_TEMP_COMP; 597 1.1 brad sc->sc_rhcomp = SGP40_DEFAULT_RH_COMP; 598 1.1 brad sc->sc_sme = NULL; 599 1.1 brad 600 1.1 brad aprint_normal("\n"); 601 1.1 brad 602 1.1 brad mutex_init(&sc->sc_threadmutex, MUTEX_DEFAULT, IPL_NONE); 603 1.1 brad mutex_init(&sc->sc_mutex, MUTEX_DEFAULT, IPL_NONE); 604 1.1 brad cv_init(&sc->sc_condvar, "sgp40cv"); 605 1.1 brad sc->sc_numsensors = __arraycount(sgp40_sensors); 606 1.1 brad 607 1.1 brad if ((sc->sc_sme = sysmon_envsys_create()) == NULL) { 608 1.1 brad aprint_error_dev(self, 609 1.1 brad "Unable to create sysmon structure\n"); 610 1.1 brad sc->sc_sme = NULL; 611 1.1 brad return; 612 1.1 brad } 613 1.1 brad if ((error = sgp40_sysctl_init(sc)) != 0) { 614 1.1 brad aprint_error_dev(self, "Can't setup sysctl tree (%d)\n", error); 615 1.1 brad goto out; 616 1.1 brad } 617 1.1 brad 618 1.1 brad error = iic_acquire_bus(sc->sc_tag, 0); 619 1.1 brad if (error) { 620 1.1 brad aprint_error_dev(self, "Could not acquire iic bus: %d\n", 621 1.1 brad error); 622 1.1 brad goto out; 623 1.1 brad } 624 1.1 brad 625 1.2 christos /* 626 1.2 christos * Usually one would reset the chip here, but that is not possible 627 1.2 christos * without resetting the entire bus, so we won't do that. 628 1.2 christos * 629 1.2 christos * What we will do is make sure that the chip is idle by running the 630 1.2 christos * turn-the-heater command. 631 1.1 brad */ 632 1.1 brad 633 1.1 brad error = sgp40_cmdr(sc, SGP40_HEATER_OFF, NULL, 0, NULL, 0); 634 1.1 brad if (error) { 635 1.1 brad aprint_error_dev(self, "Failed to turn off the heater: %d\n", 636 1.1 brad error); 637 1.1 brad ecount++; 638 1.1 brad } 639 1.1 brad 640 1.1 brad error = sgp40_cmdr(sc, SGP40_GET_SERIAL_NUMBER, NULL, 0, buf, 9); 641 1.1 brad if (error) { 642 1.1 brad aprint_error_dev(self, "Failed to get serial number: %d\n", 643 1.1 brad error); 644 1.1 brad ecount++; 645 1.1 brad } 646 1.1 brad 647 1.2 christos sn_crc1 = sgp40_crc(&buf[0], 2); 648 1.2 christos sn_crc2 = sgp40_crc(&buf[3], 2); 649 1.2 christos sn_crc3 = sgp40_crc(&buf[6], 2); 650 1.1 brad sn_crcv1 = buf[2]; 651 1.1 brad sn_crcv2 = buf[5]; 652 1.1 brad sn_crcv3 = buf[8]; 653 1.1 brad serial_number = buf[0]; 654 1.1 brad serial_number = (serial_number << 8) | buf[1]; 655 1.1 brad serial_number = (serial_number << 8) | buf[3]; 656 1.1 brad serial_number = (serial_number << 8) | buf[4]; 657 1.1 brad serial_number = (serial_number << 8) | buf[6]; 658 1.1 brad serial_number = (serial_number << 8) | buf[7]; 659 1.1 brad 660 1.2 christos DPRINTF(sc, 2, ("%s: raw serial number: %02x %02x %02x %02x %02x %02x " 661 1.2 christos "%02x %02x %02x\n", 662 1.1 brad device_xname(sc->sc_dev), buf[0], buf[1], buf[2], buf[3], buf[4], 663 1.1 brad buf[5], buf[6], buf[7], buf[8])); 664 1.1 brad 665 1.1 brad error = sgp40_cmdr(sc, SGP40_GET_FEATURESET, NULL, 0, buf, 3); 666 1.1 brad if (error) { 667 1.1 brad aprint_error_dev(self, "Failed to get featureset: %d\n", 668 1.1 brad error); 669 1.1 brad ecount++; 670 1.1 brad } 671 1.1 brad 672 1.2 christos fs_crc = sgp40_crc(&buf[0], 2); 673 1.1 brad fs_crcv = buf[2]; 674 1.1 brad featureset = buf[0]; 675 1.1 brad featureset = (featureset << 8) | buf[1]; 676 1.1 brad 677 1.1 brad DPRINTF(sc, 2, ("%s: raw feature set: %02x %02x %02x\n", 678 1.1 brad device_xname(sc->sc_dev), buf[0], buf[1], buf[2])); 679 1.1 brad 680 1.1 brad error = sgp40_cmdr(sc, SGP40_MEASURE_TEST, NULL, 0, buf, 3); 681 1.1 brad if (error) { 682 1.1 brad aprint_error_dev(self, "Failed to perform a chip test: %d\n", 683 1.1 brad error); 684 1.1 brad ecount++; 685 1.1 brad } 686 1.1 brad 687 1.2 christos tstcrc = sgp40_crc(&buf[0], 2); 688 1.1 brad 689 1.1 brad DPRINTF(sc, 2, ("%s: chip test values: %02x%02x - %02x ; %02x\n", 690 1.1 brad device_xname(sc->sc_dev), buf[0], buf[1], buf[2], tstcrc)); 691 1.1 brad 692 1.1 brad iic_release_bus(sc->sc_tag, 0); 693 1.1 brad if (error != 0) { 694 1.1 brad aprint_error_dev(self, "Unable to setup device\n"); 695 1.1 brad goto out; 696 1.1 brad } 697 1.1 brad 698 1.1 brad chiptestvalue = buf[0] << 8; 699 1.1 brad chiptestvalue |= buf[1]; 700 1.1 brad 701 1.1 brad for (i = 0; i < sc->sc_numsensors; i++) { 702 1.1 brad strlcpy(sc->sc_sensors[i].desc, sgp40_sensors[i].desc, 703 1.1 brad sizeof(sc->sc_sensors[i].desc)); 704 1.1 brad 705 1.1 brad sc->sc_sensors[i].units = sgp40_sensors[i].type; 706 1.1 brad sc->sc_sensors[i].state = ENVSYS_SINVALID; 707 1.1 brad 708 1.1 brad DPRINTF(sc, 2, ("%s: registering sensor %d (%s)\n", __func__, i, 709 1.1 brad sc->sc_sensors[i].desc)); 710 1.1 brad 711 1.1 brad error = sysmon_envsys_sensor_attach(sc->sc_sme, 712 1.1 brad &sc->sc_sensors[i]); 713 1.1 brad if (error) { 714 1.1 brad aprint_error_dev(self, 715 1.1 brad "Unable to attach sensor %d: %d\n", i, error); 716 1.1 brad goto out; 717 1.1 brad } 718 1.1 brad } 719 1.1 brad 720 1.1 brad sc->sc_sme->sme_name = device_xname(sc->sc_dev); 721 1.1 brad sc->sc_sme->sme_cookie = sc; 722 1.1 brad sc->sc_sme->sme_refresh = sgp40_refresh; 723 1.1 brad 724 1.1 brad DPRINTF(sc, 2, ("sgp40_attach: registering with envsys\n")); 725 1.1 brad 726 1.1 brad if (sysmon_envsys_register(sc->sc_sme)) { 727 1.1 brad aprint_error_dev(self, 728 1.1 brad "unable to register with sysmon\n"); 729 1.1 brad sysmon_envsys_destroy(sc->sc_sme); 730 1.1 brad sc->sc_sme = NULL; 731 1.1 brad return; 732 1.1 brad } 733 1.1 brad 734 1.1 brad error = kthread_create(PRI_NONE, KTHREAD_MUSTJOIN, NULL, 735 1.2 christos sgp40_thread, sc, &sc->sc_thread, "%s", device_xname(sc->sc_dev)); 736 1.1 brad if (error) { 737 1.1 brad aprint_error_dev(self,"Unable to create measurement thread\n"); 738 1.1 brad goto out; 739 1.1 brad } 740 1.1 brad 741 1.2 christos aprint_normal_dev(self, "Sensirion SGP40, Serial number: %jx%s" 742 1.2 christos "Feature set word: 0x%jx%s%s%s", serial_number, 743 1.2 christos (sn_crc1 == sn_crcv1 && sn_crc2 == sn_crcv2 && sn_crc3 == sn_crcv3) 744 1.2 christos ? ", " : " (bad crc), ", 745 1.1 brad (uintmax_t)featureset, 746 1.1 brad (fs_crc == fs_crcv) ? ", " : " (bad crc), ", 747 1.2 christos (chiptestvalue == SGP40_TEST_RESULTS_ALL_PASSED) ? 748 1.2 christos "All chip tests passed" : 749 1.2 christos (chiptestvalue == SGP40_TEST_RESULTS_SOME_FAILED) ? 750 1.2 christos "Some chip tests failed" : 751 1.1 brad "Unknown test results", 752 1.1 brad (tstcrc == buf[2]) ? "\n" : " (bad crc)\n"); 753 1.1 brad return; 754 1.1 brad out: 755 1.1 brad sysmon_envsys_destroy(sc->sc_sme); 756 1.1 brad sc->sc_sme = NULL; 757 1.1 brad } 758 1.1 brad 759 1.1 brad static void 760 1.1 brad sgp40_refresh(struct sysmon_envsys * sme, envsys_data_t * edata) 761 1.1 brad { 762 1.1 brad struct sgp40_sc *sc; 763 1.1 brad sc = sme->sme_cookie; 764 1.1 brad 765 1.1 brad mutex_enter(&sc->sc_mutex); 766 1.1 brad if (sc->sc_vocvalid == true) { 767 1.1 brad edata->value_cur = (uint32_t)sc->sc_voc; 768 1.1 brad edata->state = ENVSYS_SVALID; 769 1.1 brad } else { 770 1.1 brad edata->state = ENVSYS_SINVALID; 771 1.1 brad } 772 1.1 brad mutex_exit(&sc->sc_mutex); 773 1.1 brad } 774 1.1 brad 775 1.1 brad static int 776 1.1 brad sgp40_detach(device_t self, int flags) 777 1.1 brad { 778 1.1 brad struct sgp40_sc *sc; 779 1.1 brad 780 1.1 brad sc = device_private(self); 781 1.1 brad 782 1.1 brad /* stop the measurement thread */ 783 1.1 brad sgp40_stop_thread(sc); 784 1.1 brad 785 1.1 brad /* Remove the sensors */ 786 1.1 brad mutex_enter(&sc->sc_mutex); 787 1.1 brad if (sc->sc_sme != NULL) { 788 1.1 brad sysmon_envsys_unregister(sc->sc_sme); 789 1.1 brad sc->sc_sme = NULL; 790 1.1 brad } 791 1.1 brad mutex_exit(&sc->sc_mutex); 792 1.1 brad 793 1.1 brad /* Remove the sysctl tree */ 794 1.1 brad sysctl_teardown(&sc->sc_sgp40log); 795 1.1 brad 796 1.1 brad /* Remove the mutex */ 797 1.1 brad mutex_destroy(&sc->sc_mutex); 798 1.1 brad mutex_destroy(&sc->sc_threadmutex); 799 1.1 brad 800 1.1 brad return 0; 801 1.1 brad } 802 1.1 brad 803 1.3 pgoyette MODULE(MODULE_CLASS_DRIVER, sgp40mox, "iic,sysmon_envsys"); 804 1.1 brad 805 1.1 brad #ifdef _MODULE 806 1.1 brad #include "ioconf.c" 807 1.1 brad #endif 808 1.1 brad 809 1.1 brad static int 810 1.1 brad sgp40mox_modcmd(modcmd_t cmd, void *opaque) 811 1.1 brad { 812 1.1 brad 813 1.1 brad switch (cmd) { 814 1.1 brad case MODULE_CMD_INIT: 815 1.1 brad #ifdef _MODULE 816 1.1 brad return config_init_component(cfdriver_ioconf_sgp40mox, 817 1.1 brad cfattach_ioconf_sgp40mox, cfdata_ioconf_sgp40mox); 818 1.1 brad #else 819 1.1 brad return 0; 820 1.1 brad #endif 821 1.1 brad case MODULE_CMD_FINI: 822 1.1 brad #ifdef _MODULE 823 1.1 brad return config_fini_component(cfdriver_ioconf_sgp40mox, 824 1.1 brad cfattach_ioconf_sgp40mox, cfdata_ioconf_sgp40mox); 825 1.1 brad #else 826 1.1 brad return 0; 827 1.1 brad #endif 828 1.1 brad default: 829 1.1 brad return ENOTTY; 830 1.1 brad } 831 1.1 brad } 832