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