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