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