NetBSD/sys/dev/i2c/sgp40.c

803 lines
20 KiB
C
Raw Normal View History

/* $NetBSD: sgp40.c,v 1.1 2021/10/14 13:54:46 brad Exp $ */
/*
* Copyright (c) 2021 Brad Spencer <brad@anduin.eldar.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGL`IGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: sgp40.c,v 1.1 2021/10/14 13:54:46 brad Exp $");
/*
Driver for the Sensirion SGP40 MOx gas sensor for air quality
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/mutex.h>
#include <sys/condvar.h>
#include <sys/kthread.h>
#include <dev/sysmon/sysmonvar.h>
#include <dev/i2c/i2cvar.h>
#include <dev/i2c/sgp40reg.h>
#include <dev/i2c/sgp40var.h>
#include <dev/i2c/sensirion_arch_config.h>
#include <dev/i2c/sensirion_voc_algorithm.h>
static uint8_t sgp40_crc(uint8_t *, size_t);
static int sgp40_cmdr(struct sgp40_sc *, uint16_t, uint8_t *, uint8_t, uint8_t *, size_t);
static int sgp40_poke(i2c_tag_t, i2c_addr_t, bool);
static int sgp40_match(device_t, cfdata_t, void *);
static void sgp40_attach(device_t, device_t, void *);
static int sgp40_detach(device_t, int);
static void sgp40_refresh(struct sysmon_envsys *, envsys_data_t *);
static int sgp40_verify_sysctl(SYSCTLFN_ARGS);
static int sgp40_verify_temp_sysctl(SYSCTLFN_ARGS);
static int sgp40_verify_rh_sysctl(SYSCTLFN_ARGS);
static void sgp40_thread(void *);
static void sgp40_stop_thread(void *);
static void sgp40_take_measurement(void *, VocAlgorithmParams *);
#define SGP40_DEBUG
#ifdef SGP40_DEBUG
#define DPRINTF(s, l, x) \
do { \
if (l <= s->sc_sgp40debug) \
printf x; \
} while (/*CONSTCOND*/0)
#else
#define DPRINTF(s, l, x)
#endif
CFATTACH_DECL_NEW(sgp40mox, sizeof(struct sgp40_sc),
sgp40_match, sgp40_attach, sgp40_detach, NULL);
static struct sgp40_sensor sgp40_sensors[] = {
{
.desc = "VOC index",
.type = ENVSYS_INTEGER,
}
};
static struct sgp40_timing sgp40_timings[] = {
{
.cmd = SGP40_MEASURE_RAW,
.typicaldelay = 25000,
},
{
.cmd = SGP40_MEASURE_TEST,
.typicaldelay = 240000,
},
{
.cmd = SGP40_HEATER_OFF,
.typicaldelay = 100,
},
{
.cmd = SGP40_GET_SERIAL_NUMBER,
.typicaldelay = 100,
},
{
.cmd = SGP40_GET_FEATURESET,
.typicaldelay = 1000,
}
};
void
sgp40_thread(void *aux)
{
struct sgp40_sc *sc = aux;
int rv;
VocAlgorithmParams voc_algorithm_params;
mutex_enter(&sc->sc_threadmutex);
VocAlgorithm_init(&voc_algorithm_params);
while (sc->sc_stopping == false) {
rv = cv_timedwait(&sc->sc_condvar, &sc->sc_threadmutex, mstohz(1000));
if (rv == EWOULDBLOCK && sc->sc_stopping == false) {
sgp40_take_measurement(sc,&voc_algorithm_params);
}
}
mutex_exit(&sc->sc_threadmutex);
kthread_exit(0);
}
static void
sgp40_stop_thread(void *aux)
{
struct sgp40_sc *sc;
sc = aux;
int error;
mutex_enter(&sc->sc_threadmutex);
sc->sc_stopping = true;
cv_signal(&sc->sc_condvar);
mutex_exit(&sc->sc_threadmutex);
/* wait for the thread to exit */
kthread_join(sc->sc_thread);
mutex_enter(&sc->sc_mutex);
error = iic_acquire_bus(sc->sc_tag, 0);
if (error) {
DPRINTF(sc, 2, ("%s: Could not acquire iic bus for heater off in stop thread: %d\n",
device_xname(sc->sc_dev), error));
} else {
error = sgp40_cmdr(sc, SGP40_HEATER_OFF,NULL,0,NULL,0);
if (error) {
DPRINTF(sc, 2, ("%s: Error turning heater off: %d\n",
device_xname(sc->sc_dev), error));
}
iic_release_bus(sc->sc_tag, 0);
}
mutex_exit(&sc->sc_mutex);
}
static int
sgp40_compute_temp_comp(int unconverted)
{
/* The published algorithm for this conversion is:
(temp_in_celcius + 45) * 65535 / 175
However, this did not exactly yield the results that
the example in the data sheet, so something a little
different was done.
(temp_in_celcius + 45) * 65536 / 175
This was also scaled up by 10^2 and then scaled back to
preserve some percision. 37449 is simply (65536 * 100) / 175
and rounded.
*/
return (((unconverted + 45) * 100) * 37449) / 10000;
}
static int
sgp40_compute_rh_comp(int unconverted)
{
int q;
/* The published algorithm for this conversion is:
%rh * 65535 / 100
However, this did not exactly yield the results that
the example in the data sheet, so something a little
different was done.
%rh * 65536 / 100
This was also scaled up by 10^2 and then scaled back to
preserve some percision. The value is also latched to 65535
as an upper limit.
*/
q = ((unconverted * 100) * 65536) / 10000;
if (q > 65535)
q = 65535;
return q;
}
static void
sgp40_take_measurement(void *aux, VocAlgorithmParams* params)
{
struct sgp40_sc *sc;
sc = aux;
uint8_t args[6];
uint8_t buf[3];
uint16_t rawmeasurement;
int error;
uint8_t crc;
uint16_t convertedrh, convertedtemp;
int32_t voc_index;
mutex_enter(&sc->sc_mutex);
convertedrh = (uint16_t)sgp40_compute_rh_comp(sc->sc_rhcomp);
convertedtemp = (uint16_t)sgp40_compute_temp_comp(sc->sc_tempcomp);
DPRINTF(sc, 2, ("%s: Converted RH and Temp: %04x %04x\n",
device_xname(sc->sc_dev), convertedrh, convertedtemp));
args[0] = convertedrh >> 8;
args[1] = convertedrh & 0x00ff;
args[2] = sgp40_crc(&args[0],2);
args[3] = convertedtemp >> 8;
args[4] = convertedtemp & 0x00ff;
args[5] = sgp40_crc(&args[3],2);
/* The VOC algoritm has a black out time when it first starts to run
and does not return any indicator that is going on, so voc_index
in that case would be 0.. however, that is also a valid response
otherwise, although an unlikely one.
*/
error = iic_acquire_bus(sc->sc_tag, 0);
if (error) {
DPRINTF(sc, 2, ("%s: Could not acquire iic bus for take measurement: %d\n",
device_xname(sc->sc_dev), error));
sc->sc_voc = 0;
sc->sc_vocvalid = false;
} else {
error = sgp40_cmdr(sc, SGP40_MEASURE_RAW, args, 6, buf, 3);
iic_release_bus(sc->sc_tag, 0);
if (error == 0) {
crc = sgp40_crc(&buf[0],2);
DPRINTF(sc, 2, ("%s: Raw ticks and crc: %02x%02x %02x %02x\n",
device_xname(sc->sc_dev), buf[0], buf[1], buf[2],crc));
if (buf[2] == crc) {
rawmeasurement = buf[0] << 8;
rawmeasurement |= buf[1];
VocAlgorithm_process(params, rawmeasurement, &voc_index);
DPRINTF(sc, 2, ("%s: VOC index: %d\n",
device_xname(sc->sc_dev), voc_index));
sc->sc_voc = voc_index;
sc->sc_vocvalid = true;
} else {
sc->sc_voc = 0;
sc->sc_vocvalid = false;
}
} else {
DPRINTF(sc, 2, ("%s: Failed to get measurement %d\n",
device_xname(sc->sc_dev), error));
sc->sc_voc = 0;
sc->sc_vocvalid = false;
}
}
mutex_exit(&sc->sc_mutex);
}
int
sgp40_verify_sysctl(SYSCTLFN_ARGS)
{
int error, t;
struct sysctlnode node;
node = *rnode;
t = *(int *)rnode->sysctl_data;
node.sysctl_data = &t;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if (error || newp == NULL)
return error;
if (t < 0)
return EINVAL;
*(int *)rnode->sysctl_data = t;
return 0;
}
int
sgp40_verify_temp_sysctl(SYSCTLFN_ARGS)
{
int error, t;
struct sysctlnode node;
node = *rnode;
t = *(int *)rnode->sysctl_data;
node.sysctl_data = &t;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if (error || newp == NULL)
return error;
if (t < -45 || t > 130)
return EINVAL;
*(int *)rnode->sysctl_data = t;
return 0;
}
int
sgp40_verify_rh_sysctl(SYSCTLFN_ARGS)
{
int error, t;
struct sysctlnode node;
node = *rnode;
t = *(int *)rnode->sysctl_data;
node.sysctl_data = &t;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if (error || newp == NULL)
return error;
if (t < 0 || t > 100)
return EINVAL;
*(int *)rnode->sysctl_data = t;
return 0;
}
static int
sgp40_cmddelay(uint16_t cmd)
{
int r = -1;
for(int i = 0;i < __arraycount(sgp40_timings);i++) {
if (cmd == sgp40_timings[i].cmd) {
r = sgp40_timings[i].typicaldelay;
break;
}
}
if (r == -1) {
panic("Bad command look up in cmd delay: cmd: %d\n",cmd);
}
return r;
}
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)
{
int error;
int cmddelay;
uint16_t cmd16;
cmd16 = cmd[0] << 8;
cmd16 = cmd16 | cmd[1];
error = iic_exec(tag,I2C_OP_WRITE_WITH_STOP,addr,cmd,clen,NULL,0,0);
/* Every command returns something except for turning the heater off
and the general soft reset which returns nothing. */
if (error == 0 && cmd16 != SGP40_HEATER_OFF) {
/* Every command has a particular delay for how long
it typically takes and the max time it will take. */
cmddelay = sgp40_cmddelay(cmd16);
delay(cmddelay);
for (int aint = 0; aint < readattempts; aint++) {
error = iic_exec(tag,I2C_OP_READ_WITH_STOP,addr,NULL,0,buf,blen,0);
if (error == 0)
break;
delay(1000);
}
}
return error;
}
static int
sgp40_cmdr(struct sgp40_sc *sc, uint16_t cmd, uint8_t *extraargs, uint8_t argslen, uint8_t *buf, size_t blen)
{
uint8_t fullcmd[8];
uint8_t cmdlen;
int n;
/* The biggest documented command + arguments is 8 uint8_t bytes long. */
/* Catch anything that ties to have an arglen more than 6 */
KASSERT(argslen <= 6);
memset(fullcmd, 0, 8);
fullcmd[0] = cmd >> 8;
fullcmd[1] = cmd & 0x00ff;
cmdlen = 2;
n = 0;
while (extraargs != NULL && n < argslen && cmdlen <= 7) {
fullcmd[cmdlen] = extraargs[n];
cmdlen++;
n++;
}
DPRINTF(sc, 2, ("%s: Full command and arguments: %02x %02x %02x %02x %02x %02x %02x %02x\n",
device_xname(sc->sc_dev), fullcmd[0], fullcmd[1],
fullcmd[2], fullcmd[3], fullcmd[4], fullcmd[5],
fullcmd[6], fullcmd[7]));
return sgp40_cmd(sc->sc_tag, sc->sc_addr, fullcmd, cmdlen, buf, blen, sc->sc_readattempts);
}
static uint8_t
sgp40_crc(uint8_t * data, size_t size)
{
uint8_t crc = 0xFF;
for (size_t i = 0; i < size; i++) {
crc ^= data[i];
for (size_t j = 8; j > 0; j--) {
if (crc & 0x80)
crc = (crc << 1) ^ 0x31;
else
crc <<= 1;
}
}
return crc;
}
static int
sgp40_poke(i2c_tag_t tag, i2c_addr_t addr, bool matchdebug)
{
uint8_t reg[2];
uint8_t buf[9];
int error;
/* Possible bug... this command may not work if the chip is not idle,
however, it appears to be used by a lot of other code as a probe.
*/
reg[0] = SGP40_GET_SERIAL_NUMBER >> 8;
reg[1] = SGP40_GET_SERIAL_NUMBER & 0x00ff;
error = sgp40_cmd(tag, addr, reg, 2, buf, 9, 10);
if (matchdebug) {
printf("poke X 1: %d\n", error);
}
return error;
}
static int
sgp40_sysctl_init(struct sgp40_sc *sc)
{
int error;
const struct sysctlnode *cnode;
int sysctlroot_num;
if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode,
0, CTLTYPE_NODE, device_xname(sc->sc_dev),
SYSCTL_DESCR("SGP40 controls"), NULL, 0, NULL, 0, CTL_HW,
CTL_CREATE, CTL_EOL)) != 0)
return error;
sysctlroot_num = cnode->sysctl_num;
#ifdef SGP40_DEBUG
if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode,
CTLFLAG_READWRITE, CTLTYPE_INT, "debug",
SYSCTL_DESCR("Debug level"), sgp40_verify_sysctl, 0,
&sc->sc_sgp40debug, 0, CTL_HW, sysctlroot_num, CTL_CREATE,
CTL_EOL)) != 0)
return error;
#endif
if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode,
CTLFLAG_READWRITE, CTLTYPE_INT, "readattempts",
SYSCTL_DESCR("The number of times to attempt to read the values"),
sgp40_verify_sysctl, 0, &sc->sc_readattempts, 0, CTL_HW,
sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
return error;
if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode,
CTLFLAG_READWRITE, CTLTYPE_BOOL, "ignorecrc",
SYSCTL_DESCR("Ignore the CRC byte"), NULL, 0, &sc->sc_ignorecrc,
0, CTL_HW, sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
return error;
if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode,
0, CTLTYPE_NODE, "compensation",
SYSCTL_DESCR("SGP40 measurement compensations"), NULL, 0, NULL, 0, CTL_HW,
sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
return error;
int compensation_num = cnode->sysctl_num;
if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode,
CTLFLAG_READWRITE, CTLTYPE_INT, "temperature",
SYSCTL_DESCR("Temperature compensation in celsius"),
sgp40_verify_temp_sysctl, 0, &sc->sc_tempcomp, 0, CTL_HW,
sysctlroot_num, compensation_num, CTL_CREATE, CTL_EOL)) != 0)
return error;
if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode,
CTLFLAG_READWRITE, CTLTYPE_INT, "humidity",
SYSCTL_DESCR("Humidity compensation in %RH"),
sgp40_verify_rh_sysctl, 0, &sc->sc_rhcomp, 0, CTL_HW,
sysctlroot_num, compensation_num, CTL_CREATE, CTL_EOL)) != 0)
return error;
return 0;
}
static int
sgp40_match(device_t parent, cfdata_t match, void *aux)
{
struct i2c_attach_args *ia = aux;
int error, match_result;
const bool matchdebug = false;
if (matchdebug)
printf("in match\n");
if (iic_use_direct_match(ia, match, NULL, &match_result))
return match_result;
/* indirect config - check for configured address */
if (ia->ia_addr != SGP40_TYPICAL_ADDR)
return 0;
/*
* Check to see if something is really at this i2c address. This will
* keep phantom devices from appearing
*/
if (iic_acquire_bus(ia->ia_tag, 0) != 0) {
if (matchdebug)
printf("in match acquire bus failed\n");
return 0;
}
error = sgp40_poke(ia->ia_tag, ia->ia_addr, matchdebug);
iic_release_bus(ia->ia_tag, 0);
return error == 0 ? I2C_MATCH_ADDRESS_AND_PROBE : 0;
}
static void
sgp40_attach(device_t parent, device_t self, void *aux)
{
struct sgp40_sc *sc;
struct i2c_attach_args *ia;
int error, i;
int ecount = 0;
uint8_t buf[9];
uint8_t tstcrc;
uint16_t chiptestvalue;
uint64_t serial_number = 0;
uint8_t sn_crc1, sn_crc2, sn_crc3, sn_crcv1, sn_crcv2, sn_crcv3;
uint8_t fs_crc, fs_crcv;
uint16_t featureset;
ia = aux;
sc = device_private(self);
sc->sc_dev = self;
sc->sc_tag = ia->ia_tag;
sc->sc_addr = ia->ia_addr;
sc->sc_sgp40debug = 0;
sc->sc_readattempts = 10;
sc->sc_ignorecrc = false;
sc->sc_stopping = false;
sc->sc_voc = 0;
sc->sc_vocvalid = false;
sc->sc_tempcomp = SGP40_DEFAULT_TEMP_COMP;
sc->sc_rhcomp = SGP40_DEFAULT_RH_COMP;
sc->sc_sme = NULL;
aprint_normal("\n");
mutex_init(&sc->sc_threadmutex, MUTEX_DEFAULT, IPL_NONE);
mutex_init(&sc->sc_mutex, MUTEX_DEFAULT, IPL_NONE);
cv_init(&sc->sc_condvar, "sgp40cv");
sc->sc_numsensors = __arraycount(sgp40_sensors);
if ((sc->sc_sme = sysmon_envsys_create()) == NULL) {
aprint_error_dev(self,
"Unable to create sysmon structure\n");
sc->sc_sme = NULL;
return;
}
if ((error = sgp40_sysctl_init(sc)) != 0) {
aprint_error_dev(self, "Can't setup sysctl tree (%d)\n", error);
goto out;
}
error = iic_acquire_bus(sc->sc_tag, 0);
if (error) {
aprint_error_dev(self, "Could not acquire iic bus: %d\n",
error);
goto out;
}
/* Usually one would reset the chip here, but that is not possible
without resetting the entire bus, so we won't do that.
What we will do is make sure that the chip is idle by running the
turn-the-heater command.
*/
error = sgp40_cmdr(sc, SGP40_HEATER_OFF, NULL, 0, NULL, 0);
if (error) {
aprint_error_dev(self, "Failed to turn off the heater: %d\n",
error);
ecount++;
}
error = sgp40_cmdr(sc, SGP40_GET_SERIAL_NUMBER, NULL, 0, buf, 9);
if (error) {
aprint_error_dev(self, "Failed to get serial number: %d\n",
error);
ecount++;
}
sn_crc1 = sgp40_crc(&buf[0],2);
sn_crc2 = sgp40_crc(&buf[3],2);
sn_crc3 = sgp40_crc(&buf[6],2);
sn_crcv1 = buf[2];
sn_crcv2 = buf[5];
sn_crcv3 = buf[8];
serial_number = buf[0];
serial_number = (serial_number << 8) | buf[1];
serial_number = (serial_number << 8) | buf[3];
serial_number = (serial_number << 8) | buf[4];
serial_number = (serial_number << 8) | buf[6];
serial_number = (serial_number << 8) | buf[7];
DPRINTF(sc, 2, ("%s: raw serial number: %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
device_xname(sc->sc_dev), buf[0], buf[1], buf[2], buf[3], buf[4],
buf[5], buf[6], buf[7], buf[8]));
error = sgp40_cmdr(sc, SGP40_GET_FEATURESET, NULL, 0, buf, 3);
if (error) {
aprint_error_dev(self, "Failed to get featureset: %d\n",
error);
ecount++;
}
fs_crc = sgp40_crc(&buf[0],2);
fs_crcv = buf[2];
featureset = buf[0];
featureset = (featureset << 8) | buf[1];
DPRINTF(sc, 2, ("%s: raw feature set: %02x %02x %02x\n",
device_xname(sc->sc_dev), buf[0], buf[1], buf[2]));
error = sgp40_cmdr(sc, SGP40_MEASURE_TEST, NULL, 0, buf, 3);
if (error) {
aprint_error_dev(self, "Failed to perform a chip test: %d\n",
error);
ecount++;
}
tstcrc = sgp40_crc(&buf[0],2);
DPRINTF(sc, 2, ("%s: chip test values: %02x%02x - %02x ; %02x\n",
device_xname(sc->sc_dev), buf[0], buf[1], buf[2], tstcrc));
iic_release_bus(sc->sc_tag, 0);
if (error != 0) {
aprint_error_dev(self, "Unable to setup device\n");
goto out;
}
chiptestvalue = buf[0] << 8;
chiptestvalue |= buf[1];
for (i = 0; i < sc->sc_numsensors; i++) {
strlcpy(sc->sc_sensors[i].desc, sgp40_sensors[i].desc,
sizeof(sc->sc_sensors[i].desc));
sc->sc_sensors[i].units = sgp40_sensors[i].type;
sc->sc_sensors[i].state = ENVSYS_SINVALID;
DPRINTF(sc, 2, ("%s: registering sensor %d (%s)\n", __func__, i,
sc->sc_sensors[i].desc));
error = sysmon_envsys_sensor_attach(sc->sc_sme,
&sc->sc_sensors[i]);
if (error) {
aprint_error_dev(self,
"Unable to attach sensor %d: %d\n", i, error);
goto out;
}
}
sc->sc_sme->sme_name = device_xname(sc->sc_dev);
sc->sc_sme->sme_cookie = sc;
sc->sc_sme->sme_refresh = sgp40_refresh;
DPRINTF(sc, 2, ("sgp40_attach: registering with envsys\n"));
if (sysmon_envsys_register(sc->sc_sme)) {
aprint_error_dev(self,
"unable to register with sysmon\n");
sysmon_envsys_destroy(sc->sc_sme);
sc->sc_sme = NULL;
return;
}
error = kthread_create(PRI_NONE, KTHREAD_MUSTJOIN, NULL,
sgp40_thread, sc, &sc->sc_thread,
device_xname(sc->sc_dev));
if (error) {
aprint_error_dev(self,"Unable to create measurement thread\n");
goto out;
}
aprint_normal_dev(self, "Sensirion SGP40, Serial number: %jx%sFeature set word: 0x%jx%s%s%s",
serial_number,
(sn_crc1 == sn_crcv1 && sn_crc2 == sn_crcv2 && sn_crc3 == sn_crcv3) ? ", " : " (bad crc), ",
(uintmax_t)featureset,
(fs_crc == fs_crcv) ? ", " : " (bad crc), ",
(chiptestvalue == SGP40_TEST_RESULTS_ALL_PASSED) ? "All chip tests passed" :
(chiptestvalue == SGP40_TEST_RESULTS_SOME_FAILED) ? "Some chip tests failed" :
"Unknown test results",
(tstcrc == buf[2]) ? "\n" : " (bad crc)\n");
return;
out:
sysmon_envsys_destroy(sc->sc_sme);
sc->sc_sme = NULL;
}
static void
sgp40_refresh(struct sysmon_envsys * sme, envsys_data_t * edata)
{
struct sgp40_sc *sc;
sc = sme->sme_cookie;
mutex_enter(&sc->sc_mutex);
if (sc->sc_vocvalid == true) {
edata->value_cur = (uint32_t)sc->sc_voc;
edata->state = ENVSYS_SVALID;
} else {
edata->state = ENVSYS_SINVALID;
}
mutex_exit(&sc->sc_mutex);
}
static int
sgp40_detach(device_t self, int flags)
{
struct sgp40_sc *sc;
sc = device_private(self);
/* stop the measurement thread */
sgp40_stop_thread(sc);
/* Remove the sensors */
mutex_enter(&sc->sc_mutex);
if (sc->sc_sme != NULL) {
sysmon_envsys_unregister(sc->sc_sme);
sc->sc_sme = NULL;
}
mutex_exit(&sc->sc_mutex);
/* Remove the sysctl tree */
sysctl_teardown(&sc->sc_sgp40log);
/* Remove the mutex */
mutex_destroy(&sc->sc_mutex);
mutex_destroy(&sc->sc_threadmutex);
return 0;
}
MODULE(MODULE_CLASS_DRIVER, sgp40mox, "i2cexec,sysmon_envsys");
#ifdef _MODULE
#include "ioconf.c"
#endif
static int
sgp40mox_modcmd(modcmd_t cmd, void *opaque)
{
switch (cmd) {
case MODULE_CMD_INIT:
#ifdef _MODULE
return config_init_component(cfdriver_ioconf_sgp40mox,
cfattach_ioconf_sgp40mox, cfdata_ioconf_sgp40mox);
#else
return 0;
#endif
case MODULE_CMD_FINI:
#ifdef _MODULE
return config_fini_component(cfdriver_ioconf_sgp40mox,
cfattach_ioconf_sgp40mox, cfdata_ioconf_sgp40mox);
#else
return 0;
#endif
default:
return ENOTTY;
}
}