Module Name:    src
Committed By:   brad
Date:           Thu Oct 14 13:54:46 UTC 2021

Modified Files:
        src/distrib/sets/lists/debug: module.mi
        src/distrib/sets/lists/man: mi
        src/distrib/sets/lists/modules: mi
        src/share/man/man4: Makefile
        src/sys/dev/i2c: files.i2c
        src/sys/modules: Makefile
Added Files:
        src/share/man/man4: sgp40mox.4
        src/sys/dev/i2c: sensirion_arch_config.h sensirion_voc_algorithm.c
            sensirion_voc_algorithm.h sgp40.c sgp40reg.h sgp40var.h
        src/sys/modules/sgp40mox: Makefile sgp40mox.ioconf

Log Message:
A driver for the Sensirion SGP40 MOx gas sensor.  An example of this
chip from Adafruit is:

https://www.adafruit.com/product/4829

This is a moderately priced gas sensor that can detect volatile
organic compounds in the air.  The driver uses the 3-clause BSD
licensed VOC algorithm provided by Sensirion to turn the raw sensor
metric into a VOC index which can indicate the quality of the air in a
particular indoor environment.  All published functions of the chip
are supported and one unpublished feature.


To generate a diff of this commit:
cvs rdiff -u -r1.13 -r1.14 src/distrib/sets/lists/debug/module.mi
cvs rdiff -u -r1.1727 -r1.1728 src/distrib/sets/lists/man/mi
cvs rdiff -u -r1.147 -r1.148 src/distrib/sets/lists/modules/mi
cvs rdiff -u -r1.717 -r1.718 src/share/man/man4/Makefile
cvs rdiff -u -r0 -r1.1 src/share/man/man4/sgp40mox.4
cvs rdiff -u -r1.117 -r1.118 src/sys/dev/i2c/files.i2c
cvs rdiff -u -r0 -r1.1 src/sys/dev/i2c/sensirion_arch_config.h \
    src/sys/dev/i2c/sensirion_voc_algorithm.c \
    src/sys/dev/i2c/sensirion_voc_algorithm.h src/sys/dev/i2c/sgp40.c \
    src/sys/dev/i2c/sgp40reg.h src/sys/dev/i2c/sgp40var.h
cvs rdiff -u -r1.258 -r1.259 src/sys/modules/Makefile
cvs rdiff -u -r0 -r1.1 src/sys/modules/sgp40mox/Makefile \
    src/sys/modules/sgp40mox/sgp40mox.ioconf

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/distrib/sets/lists/debug/module.mi
diff -u src/distrib/sets/lists/debug/module.mi:1.13 src/distrib/sets/lists/debug/module.mi:1.14
--- src/distrib/sets/lists/debug/module.mi:1.13	Mon Oct  4 07:04:39 2021
+++ src/distrib/sets/lists/debug/module.mi	Thu Oct 14 13:54:46 2021
@@ -1,4 +1,4 @@
-# $NetBSD: module.mi,v 1.13 2021/10/04 07:04:39 brad Exp $
+# $NetBSD: module.mi,v 1.14 2021/10/14 13:54:46 brad Exp $
 ./usr/libdata/debug/@MODULEDIR@					modules-base-kernel	kmod,debug
 ./usr/libdata/debug/@MODULEDIR@/accf_dataready			modules-base-kernel	kmod,debug
 ./usr/libdata/debug/@MODULEDIR@/accf_dataready/accf_dataready.kmod.debug	modules-base-kernel	kmod,debug
@@ -332,6 +332,8 @@
 ./usr/libdata/debug/@MODULEDIR@/securelevel/securelevel.kmod.debug	modules-base-kernel	kmod,debug
 ./usr/libdata/debug/@MODULEDIR@/sequencer				modules-base-kernel	kmod,debug
 ./usr/libdata/debug/@MODULEDIR@/sequencer/sequencer.kmod.debug		modules-base-kernel	kmod,debug
+./usr/libdata/debug/@MODULEDIR@/sgp40mox				modules-base-kernel	kmod,debug
+./usr/libdata/debug/@MODULEDIR@/sgp40mox/sgp40mox.kmod.debug		modules-base-kernel	kmod,debug
 ./usr/libdata/debug/@MODULEDIR@/sht4xtemp				modules-base-kernel	kmod,debug
 ./usr/libdata/debug/@MODULEDIR@/sht4xtemp/sht4xtemp.kmod.debug		modules-base-kernel	kmod,debug
 ./usr/libdata/debug/@MODULEDIR@/si70xxtemp				modules-base-kernel	kmod,debug

Index: src/distrib/sets/lists/man/mi
diff -u src/distrib/sets/lists/man/mi:1.1727 src/distrib/sets/lists/man/mi:1.1728
--- src/distrib/sets/lists/man/mi:1.1727	Tue Oct 12 04:55:19 2021
+++ src/distrib/sets/lists/man/mi	Thu Oct 14 13:54:46 2021
@@ -1,4 +1,4 @@
-# $NetBSD: mi,v 1.1727 2021/10/12 04:55:19 msaitoh Exp $
+# $NetBSD: mi,v 1.1728 2021/10/14 13:54:46 brad Exp $
 #
 # Note: don't delete entries from here - mark them as "obsolete" instead.
 #
@@ -1720,6 +1720,7 @@
 ./usr/share/man/cat4/sgimips/pic.0		man-sys-catman		.cat
 ./usr/share/man/cat4/sgimips/sq.0		man-sys-catman		.cat
 ./usr/share/man/cat4/sgimips/wdsc.0		man-sys-catman		.cat
+./usr/share/man/cat4/sgp40mox.0			man-sys-catman		.cat
 ./usr/share/man/cat4/sgsmix.0			man-sys-catman		.cat
 ./usr/share/man/cat4/shb.0			man-sys-catman		.cat
 ./usr/share/man/cat4/shmif.0			man-sys-catman		.cat
@@ -4895,6 +4896,7 @@
 ./usr/share/man/html4/sgimips/pic.html		man-sys-htmlman		html
 ./usr/share/man/html4/sgimips/sq.html		man-sys-htmlman		html
 ./usr/share/man/html4/sgimips/wdsc.html		man-sys-htmlman		html
+./usr/share/man/html4/sgp40mox.html		man-sys-htmlman		html
 ./usr/share/man/html4/sgsmix.html		man-sys-htmlman		html
 ./usr/share/man/html4/shb.html			man-sys-htmlman		html
 ./usr/share/man/html4/shmif.html		man-sys-htmlman		html
@@ -7976,6 +7978,7 @@
 ./usr/share/man/man4/sgimips/pic.4		man-sys-man		.man
 ./usr/share/man/man4/sgimips/sq.4		man-sys-man		.man
 ./usr/share/man/man4/sgimips/wdsc.4		man-sys-man		.man
+./usr/share/man/man4/sgp40mox.4			man-sys-man		.man
 ./usr/share/man/man4/sgsmix.4			man-sys-man		.man
 ./usr/share/man/man4/shb.4			man-sys-man		.man
 ./usr/share/man/man4/shmif.4			man-sys-man		.man

Index: src/distrib/sets/lists/modules/mi
diff -u src/distrib/sets/lists/modules/mi:1.147 src/distrib/sets/lists/modules/mi:1.148
--- src/distrib/sets/lists/modules/mi:1.147	Sun Oct  3 17:27:02 2021
+++ src/distrib/sets/lists/modules/mi	Thu Oct 14 13:54:46 2021
@@ -1,4 +1,4 @@
-# $NetBSD: mi,v 1.147 2021/10/03 17:27:02 brad Exp $
+# $NetBSD: mi,v 1.148 2021/10/14 13:54:46 brad Exp $
 #
 # Note: don't delete entries from here - mark them as "obsolete" instead.
 #
@@ -397,6 +397,8 @@
 ./@MODULEDIR@/securelevel/securelevel.kmod	modules-base-kernel	kmod
 ./@MODULEDIR@/sequencer				modules-base-kernel	kmod
 ./@MODULEDIR@/sequencer/sequencer.kmod		modules-base-kernel	kmod
+./@MODULEDIR@/sgp40mox				modules-base-kernel	kmod
+./@MODULEDIR@/sgp40mox/sgp40mox.kmod		modules-base-kernel	kmod
 ./@MODULEDIR@/sht4xtemp				modules-base-kernel     kmod
 ./@MODULEDIR@/sht4xtemp/sht4xtemp.kmod		modules-base-kernel     kmod
 ./@MODULEDIR@/si70xxtemp			modules-base-kernel	kmod

Index: src/share/man/man4/Makefile
diff -u src/share/man/man4/Makefile:1.717 src/share/man/man4/Makefile:1.718
--- src/share/man/man4/Makefile:1.717	Tue Oct 12 04:55:19 2021
+++ src/share/man/man4/Makefile	Thu Oct 14 13:54:45 2021
@@ -1,4 +1,4 @@
-#	$NetBSD: Makefile,v 1.717 2021/10/12 04:55:19 msaitoh Exp $
+#	$NetBSD: Makefile,v 1.718 2021/10/14 13:54:45 brad Exp $
 #	@(#)Makefile	8.1 (Berkeley) 6/18/93
 
 MAN=	aac.4 ac97.4 acardide.4 aceride.4 acphy.4 \
@@ -56,8 +56,8 @@ MAN=	aac.4 ac97.4 acardide.4 aceride.4 a
 	rnd.4 route.4 rs5c372rtc.4 rtk.4 rtsx.4 rtw.4 rtwn.4 rum.4 run.4 \
 	s390rtc.4 satalink.4 sbus.4 schide.4 \
 	scsi.4 sctp.4 sd.4 se.4 seeprom.4 sem.4 \
-	ses.4 sf.4 sfb.4 sgsmix.4 shb.4 shmif.4 shpcic.4 sht4xtemp.4 si70xxtemp.4 \
-	siisata.4 siop.4 sip.4 siside.4 sk.4 sl.4 slide.4 \
+	ses.4 sf.4 sfb.4 sgp40mox.4 sgsmix.4 shb.4 shmif.4 shpcic.4 sht4xtemp.4 \
+	si70xxtemp.4 siisata.4 siop.4 sip.4 siside.4 sk.4 sl.4 slide.4 \
 	sm.4 smscphy.4 smsh.4 sn.4 sony.4 spc.4 speaker.4 spif.4 sqphy.4 \
 	srt.4 ss.4 \
 	ssdfb.4 st.4 ste.4 stge.4 sti.4 stpcide.4 sv.4 \

Index: src/sys/dev/i2c/files.i2c
diff -u src/sys/dev/i2c/files.i2c:1.117 src/sys/dev/i2c/files.i2c:1.118
--- src/sys/dev/i2c/files.i2c:1.117	Sun Oct  3 17:27:02 2021
+++ src/sys/dev/i2c/files.i2c	Thu Oct 14 13:54:46 2021
@@ -1,4 +1,4 @@
-#	$NetBSD: files.i2c,v 1.117 2021/10/03 17:27:02 brad Exp $
+#	$NetBSD: files.i2c,v 1.118 2021/10/14 13:54:46 brad Exp $
 
 obsolete defflag	opt_i2cbus.h		I2C_SCAN
 define	i2cbus { }
@@ -400,6 +400,12 @@ device sht4xtemp
 attach sht4xtemp at iic
 file dev/i2c/sht4x.c				sht4xtemp
 
+# Sensirion SGP40 MOx gas sensor
+device sgp40mox
+attach sgp40mox at iic
+file dev/i2c/sgp40.c				sgp40mox
+file dev/i2c/sensirion_voc_algorithm.c		sgp40mox
+
 # Philips PCA955x GPIO
 device	pcagpio: leds
 attach	pcagpio at iic

Index: src/sys/modules/Makefile
diff -u src/sys/modules/Makefile:1.258 src/sys/modules/Makefile:1.259
--- src/sys/modules/Makefile:1.258	Sat Oct  9 07:01:34 2021
+++ src/sys/modules/Makefile	Thu Oct 14 13:54:45 2021
@@ -1,4 +1,4 @@
-#	$NetBSD: Makefile,v 1.258 2021/10/09 07:01:34 ryo Exp $
+#	$NetBSD: Makefile,v 1.259 2021/10/14 13:54:45 brad Exp $
 
 .include <bsd.own.mk>
 
@@ -70,6 +70,7 @@ SUBDIR+=	hythygtemp
 SUBDIR+=	si70xxtemp
 SUBDIR+=	am2315temp
 SUBDIR+=	sht4xtemp
+SUBDIR+=	sgp40mox
 SUBDIR+=	i2cexec
 SUBDIR+=	i2c_bitbang
 SUBDIR+=	if_agr

Added files:

Index: src/share/man/man4/sgp40mox.4
diff -u /dev/null src/share/man/man4/sgp40mox.4:1.1
--- /dev/null	Thu Oct 14 13:54:46 2021
+++ src/share/man/man4/sgp40mox.4	Thu Oct 14 13:54:45 2021
@@ -0,0 +1,107 @@
+.\" $NetBSD: sgp40mox.4,v 1.1 2021/10/14 13:54:45 brad Exp $
+.\"
+.\" Copyright (c) 2021 Brad Spencer <b...@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, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd October 7, 2021
+.Dt SGP40MOX 4
+.Os
+.Sh NAME
+.Nm sgp40mox
+.Nd Driver for Sensirion SGP40 MOx gas sensor
+.Sh SYNOPSIS
+.Cd "sgp40mox* at iic? addr 0x59"
+.Sh DESCRIPTION
+The
+.Nm
+driver provides an air quality measurement from the SGP40
+sensor via the
+.Xr envsys 4
+framework.
+The
+.Nm
+.Ar addr
+argument selects the address at the
+.Xr iic 4
+bus.
+The crc validity and temperature and %RH compensation can be changed through
+.Xr sysctl 8
+nodes.
+.Pp
+In order to calculate the VOC index, the volatile organic compounds index, which
+is the measure of air quality the sensor is polled once a second and the raw sensor
+value is fed into the Sensirion VOC algorithm.  This VOC algorithm used in this driver
+is licensed under a 3 clause BSD license and was pulled from the Sensirion Github
+repository at
+.Rs
+.%U https://github.com/Sensirion/embedded-sgp
+.Re
+.Sh SYSCTL VARIABLES
+The following
+.Xr sysctl 3
+variables are provided:
+.Bl -tag -width indent
+.It Li hw.sgp40mox0.compensation.temperature
+This should be set to the temperature in Celsius of the environment that the sensor
+is in.  The valid values are from -45 to 130 degrees Celsius.
+.It Li hw.sgp40mox0.compensation.humidity
+This should be set to the %RH of the environment that the sensor is in.  The valid
+values are from 0 to 100.
+.Pp
+For the best performance of the VOC algorithm it is important that the temperature
+and %RH compensation values be current and set using the
+.Xr sysctl 3
+variables mentioned above.
+This data will need to be pulled from another source, such as a another sensor in
+the environment that the SGP40 is in.
+.It Li hw.sgp40mox0.ignorecrc
+If set, the crc calculation will be ignored on the calls to the chip for the purposes
+of measurement.
+.It Li hw.sgp40mox0.debug
+If the driver is compiled with
+.Dv SGP40_DEBUG ,
+this node will appear and can be used to set the debugging level.
+.It Li hw.sgp40mox0.readattempts
+To read the air quality metric from the chip requires that the command be sent,
+then a delay must be observed before a read can be done to get the values
+back.
+The delays are documented in the data sheet for the chip.
+The driver will attempt to read back the values readattempts number of
+times.
+The default is 10 which should be more than enough for most purposes.
+.El
+.Sh SEE ALSO
+.Xr envsys 4 ,
+.Xr iic 4 ,
+.Xr envstat 8 ,
+.Xr sysctl 8
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Nx 10.0 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+driver was written by
+.An Brad Spencer Aq Mt b...@anduin.eldar.org .
+.Sh BUGS
+The driver does not make complete use of the VOC algorithm.  In particular, there is no
+need to restart the algorithm from scratch if there is a stoppage of polling for less than
+10 minutes.  The driver does not have the ability to determine that, and therefore
+assumes that the sensor is completely cold each time the driver attaches to the chip.
+.Pp
+The temperature and humidity compensation could be allowed to contain fractional degrees Celsius
+and %RH.  The driver only supports setting whole numbers for either of those.

Index: src/sys/dev/i2c/sensirion_arch_config.h
diff -u /dev/null src/sys/dev/i2c/sensirion_arch_config.h:1.1
--- /dev/null	Thu Oct 14 13:54:46 2021
+++ src/sys/dev/i2c/sensirion_arch_config.h	Thu Oct 14 13:54:46 2021
@@ -0,0 +1,33 @@
+/*	$NetBSD: sensirion_arch_config.h,v 1.1 2021/10/14 13:54:46 brad Exp $	*/
+
+/*
+ * Copyright (c) 2021 Brad Spencer <b...@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.
+ */
+
+
+/* The Sensirion code wants a file called sensirion_arch_config.h.  This
+   is the shim for NetBSD
+*/
+
+#ifndef SENSIRION_ARCH_CONFIG_H
+#define SENSIRION_ARCH_CONFIG_H
+
+#include <sys/cdefs.h>
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+
+
+#endif /* SENSIRION_ARCH_CONFIG_H */
Index: src/sys/dev/i2c/sensirion_voc_algorithm.c
diff -u /dev/null src/sys/dev/i2c/sensirion_voc_algorithm.c:1.1
--- /dev/null	Thu Oct 14 13:54:46 2021
+++ src/sys/dev/i2c/sensirion_voc_algorithm.c	Thu Oct 14 13:54:46 2021
@@ -0,0 +1,809 @@
+/*
+ *	$NetBSD: sensirion_voc_algorithm.c,v 1.1 2021/10/14 13:54:46 brad Exp $
+ */
+
+/*
+ * Copyright (c) 2021, Sensirion AG
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Sensirion AG nor the names of its
+ *   contributors may be used to endorse or promote products derived from
+ *   this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "sensirion_voc_algorithm.h"
+
+/* The fixed point arithmetic parts of this code were originally created by
+ * https://github.com/PetteriAimonen/libfixmath
+ */
+
+/*!< the maximum value of fix16_t */
+#define FIX16_MAXIMUM 0x7FFFFFFF
+/*!< the minimum value of fix16_t */
+#define FIX16_MINIMUM 0x80000000
+/*!< the value used to indicate overflows when FIXMATH_NO_OVERFLOW is not
+ * specified */
+#define FIX16_OVERFLOW 0x80000000
+/*!< fix16_t value of 1 */
+#define FIX16_ONE 0x00010000
+
+static inline fix16_t fix16_from_int(int32_t a) {
+    return a * FIX16_ONE;
+}
+
+static inline int32_t fix16_cast_to_int(fix16_t a) {
+    return (a >= 0) ? (a >> 16) : -((-a) >> 16);
+}
+
+/*! Multiplies the two given fix16_t's and returns the result. */
+static fix16_t fix16_mul(fix16_t inArg0, fix16_t inArg1);
+
+/*! Divides the first given fix16_t by the second and returns the result. */
+static fix16_t fix16_div(fix16_t inArg0, fix16_t inArg1);
+
+/*! Returns the square root of the given fix16_t. */
+static fix16_t fix16_sqrt(fix16_t inValue);
+
+/*! Returns the exponent (e^) of the given fix16_t. */
+static fix16_t fix16_exp(fix16_t inValue);
+
+static fix16_t fix16_mul(fix16_t inArg0, fix16_t inArg1) {
+    // Each argument is divided to 16-bit parts.
+    //					AB
+    //			*	 CD
+    // -----------
+    //					BD	16 * 16 -> 32 bit products
+    //				 CB
+    //				 AD
+    //				AC
+    //			 |----| 64 bit product
+    uint32_t absArg0 = (uint32_t)((inArg0 >= 0) ? inArg0 : (-inArg0));
+    uint32_t absArg1 = (uint32_t)((inArg1 >= 0) ? inArg1 : (-inArg1));
+    uint32_t A = (absArg0 >> 16), C = (absArg1 >> 16);
+    uint32_t B = (absArg0 & 0xFFFF), D = (absArg1 & 0xFFFF);
+
+    uint32_t AC = A * C;
+    uint32_t AD_CB = A * D + C * B;
+    uint32_t BD = B * D;
+
+    uint32_t product_hi = AC + (AD_CB >> 16);
+
+    // Handle carry from lower 32 bits to upper part of result.
+    uint32_t ad_cb_temp = AD_CB << 16;
+    uint32_t product_lo = BD + ad_cb_temp;
+    if (product_lo < BD)
+        product_hi++;
+
+#ifndef FIXMATH_NO_OVERFLOW
+    // The upper 17 bits should all be zero.
+    if (product_hi >> 15)
+        return (fix16_t)FIX16_OVERFLOW;
+#endif
+
+#ifdef FIXMATH_NO_ROUNDING
+    fix16_t result = (fix16_t)((product_hi << 16) | (product_lo >> 16));
+    if ((inArg0 < 0) != (inArg1 < 0))
+        result = -result;
+    return result;
+#else
+    // Adding 0x8000 (= 0.5) and then using right shift
+    // achieves proper rounding to result.
+    // Handle carry from lower to upper part.
+    uint32_t product_lo_tmp = product_lo;
+    product_lo += 0x8000;
+    if (product_lo < product_lo_tmp)
+        product_hi++;
+
+    // Discard the lowest 16 bits and convert back to signed result.
+    fix16_t result = (fix16_t)((product_hi << 16) | (product_lo >> 16));
+    if ((inArg0 < 0) != (inArg1 < 0))
+        result = -result;
+    return result;
+#endif
+}
+
+static fix16_t fix16_div(fix16_t a, fix16_t b) {
+    // This uses the basic binary restoring division algorithm.
+    // It appears to be faster to do the whole division manually than
+    // trying to compose a 64-bit divide out of 32-bit divisions on
+    // platforms without hardware divide.
+
+    if (b == 0)
+        return (fix16_t)FIX16_MINIMUM;
+
+    uint32_t remainder = (uint32_t)((a >= 0) ? a : (-a));
+    uint32_t divider = (uint32_t)((b >= 0) ? b : (-b));
+
+    uint32_t quotient = 0;
+    uint32_t bit = 0x10000;
+
+    /* The algorithm requires D >= R */
+    while (divider < remainder) {
+        divider <<= 1;
+        bit <<= 1;
+    }
+
+#ifndef FIXMATH_NO_OVERFLOW
+    if (!bit)
+        return (fix16_t)FIX16_OVERFLOW;
+#endif
+
+    if (divider & 0x80000000) {
+        // Perform one step manually to avoid overflows later.
+        // We know that divider's bottom bit is 0 here.
+        if (remainder >= divider) {
+            quotient |= bit;
+            remainder -= divider;
+        }
+        divider >>= 1;
+        bit >>= 1;
+    }
+
+    /* Main division loop */
+    while (bit && remainder) {
+        if (remainder >= divider) {
+            quotient |= bit;
+            remainder -= divider;
+        }
+
+        remainder <<= 1;
+        bit >>= 1;
+    }
+
+#ifndef FIXMATH_NO_ROUNDING
+    if (remainder >= divider) {
+        quotient++;
+    }
+#endif
+
+    fix16_t result = (fix16_t)quotient;
+
+    /* Figure out the sign of result */
+    if ((a < 0) != (b < 0)) {
+#ifndef FIXMATH_NO_OVERFLOW
+        if (result == FIX16_MINIMUM)
+            return (fix16_t)FIX16_OVERFLOW;
+#endif
+
+        result = -result;
+    }
+
+    return result;
+}
+
+static fix16_t fix16_sqrt(fix16_t x) {
+    // It is assumed that x is not negative
+
+    uint32_t num = (uint32_t)x;
+    uint32_t result = 0;
+    uint32_t bit;
+    uint8_t n;
+
+    bit = (uint32_t)1 << 30;
+    while (bit > num)
+        bit >>= 2;
+
+    // The main part is executed twice, in order to avoid
+    // using 64 bit values in computations.
+    for (n = 0; n < 2; n++) {
+        // First we get the top 24 bits of the answer.
+        while (bit) {
+            if (num >= result + bit) {
+                num -= result + bit;
+                result = (result >> 1) + bit;
+            } else {
+                result = (result >> 1);
+            }
+            bit >>= 2;
+        }
+
+        if (n == 0) {
+            // Then process it again to get the lowest 8 bits.
+            if (num > 65535) {
+                // The remainder 'num' is too large to be shifted left
+                // by 16, so we have to add 1 to result manually and
+                // adjust 'num' accordingly.
+                // num = a - (result + 0.5)^2
+                //	 = num + result^2 - (result + 0.5)^2
+                //	 = num - result - 0.5
+                num -= result;
+                num = (num << 16) - 0x8000;
+                result = (result << 16) + 0x8000;
+            } else {
+                num <<= 16;
+                result <<= 16;
+            }
+
+            bit = 1 << 14;
+        }
+    }
+
+#ifndef FIXMATH_NO_ROUNDING
+    // Finally, if next bit would have been 1, round the result upwards.
+    if (num > result) {
+        result++;
+    }
+#endif
+
+    return (fix16_t)result;
+}
+
+static fix16_t fix16_exp(fix16_t x) {
+    // Function to approximate exp(); optimized more for code size than speed
+
+    // exp(x) for x = +/- {1, 1/8, 1/64, 1/512}
+#define NUM_EXP_VALUES 4
+    static const fix16_t exp_pos_values[NUM_EXP_VALUES] = {
+        F16(2.7182818), F16(1.1331485), F16(1.0157477), F16(1.0019550)};
+    static const fix16_t exp_neg_values[NUM_EXP_VALUES] = {
+        F16(0.3678794), F16(0.8824969), F16(0.9844964), F16(0.9980488)};
+    const fix16_t* exp_values;
+
+    fix16_t res, arg;
+    uint16_t i;
+
+    if (x >= F16(10.3972))
+        return FIX16_MAXIMUM;
+    if (x <= F16(-11.7835))
+        return 0;
+
+    if (x < 0) {
+        x = -x;
+        exp_values = exp_neg_values;
+    } else {
+        exp_values = exp_pos_values;
+    }
+
+    res = FIX16_ONE;
+    arg = FIX16_ONE;
+    for (i = 0; i < NUM_EXP_VALUES; i++) {
+        while (x >= arg) {
+            res = fix16_mul(res, exp_values[i]);
+            x -= arg;
+        }
+        arg >>= 3;
+    }
+    return res;
+}
+
+static void VocAlgorithm__init_instances(VocAlgorithmParams* params);
+static void
+VocAlgorithm__mean_variance_estimator__init(VocAlgorithmParams* params);
+static void VocAlgorithm__mean_variance_estimator___init_instances(
+    VocAlgorithmParams* params);
+static void VocAlgorithm__mean_variance_estimator__set_parameters(
+    VocAlgorithmParams* params, fix16_t std_initial,
+    fix16_t tau_mean_variance_hours, fix16_t gating_max_duration_minutes);
+static void
+VocAlgorithm__mean_variance_estimator__set_states(VocAlgorithmParams* params,
+                                                  fix16_t mean, fix16_t std,
+                                                  fix16_t uptime_gamma);
+static fix16_t
+VocAlgorithm__mean_variance_estimator__get_std(VocAlgorithmParams* params);
+static fix16_t
+VocAlgorithm__mean_variance_estimator__get_mean(VocAlgorithmParams* params);
+static void VocAlgorithm__mean_variance_estimator___calculate_gamma(
+    VocAlgorithmParams* params, fix16_t voc_index_from_prior);
+static void VocAlgorithm__mean_variance_estimator__process(
+    VocAlgorithmParams* params, fix16_t sraw, fix16_t voc_index_from_prior);
+static void VocAlgorithm__mean_variance_estimator___sigmoid__init(
+    VocAlgorithmParams* params);
+static void VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
+    VocAlgorithmParams* params, fix16_t L, fix16_t X0, fix16_t K);
+static fix16_t VocAlgorithm__mean_variance_estimator___sigmoid__process(
+    VocAlgorithmParams* params, fix16_t sample);
+static void VocAlgorithm__mox_model__init(VocAlgorithmParams* params);
+static void VocAlgorithm__mox_model__set_parameters(VocAlgorithmParams* params,
+                                                    fix16_t SRAW_STD,
+                                                    fix16_t SRAW_MEAN);
+static fix16_t VocAlgorithm__mox_model__process(VocAlgorithmParams* params,
+                                                fix16_t sraw);
+static void VocAlgorithm__sigmoid_scaled__init(VocAlgorithmParams* params);
+static void
+VocAlgorithm__sigmoid_scaled__set_parameters(VocAlgorithmParams* params,
+                                             fix16_t offset);
+static fix16_t VocAlgorithm__sigmoid_scaled__process(VocAlgorithmParams* params,
+                                                     fix16_t sample);
+static void VocAlgorithm__adaptive_lowpass__init(VocAlgorithmParams* params);
+static void
+VocAlgorithm__adaptive_lowpass__set_parameters(VocAlgorithmParams* params);
+static fix16_t
+VocAlgorithm__adaptive_lowpass__process(VocAlgorithmParams* params,
+                                        fix16_t sample);
+
+void VocAlgorithm_init(VocAlgorithmParams* params) {
+
+    params->mVoc_Index_Offset = F16(VocAlgorithm_VOC_INDEX_OFFSET_DEFAULT);
+    params->mTau_Mean_Variance_Hours =
+        F16(VocAlgorithm_TAU_MEAN_VARIANCE_HOURS);
+    params->mGating_Max_Duration_Minutes =
+        F16(VocAlgorithm_GATING_MAX_DURATION_MINUTES);
+    params->mSraw_Std_Initial = F16(VocAlgorithm_SRAW_STD_INITIAL);
+    params->mUptime = F16(0.);
+    params->mSraw = F16(0.);
+    params->mVoc_Index = 0;
+    VocAlgorithm__init_instances(params);
+}
+
+static void VocAlgorithm__init_instances(VocAlgorithmParams* params) {
+
+    VocAlgorithm__mean_variance_estimator__init(params);
+    VocAlgorithm__mean_variance_estimator__set_parameters(
+        params, params->mSraw_Std_Initial, params->mTau_Mean_Variance_Hours,
+        params->mGating_Max_Duration_Minutes);
+    VocAlgorithm__mox_model__init(params);
+    VocAlgorithm__mox_model__set_parameters(
+        params, VocAlgorithm__mean_variance_estimator__get_std(params),
+        VocAlgorithm__mean_variance_estimator__get_mean(params));
+    VocAlgorithm__sigmoid_scaled__init(params);
+    VocAlgorithm__sigmoid_scaled__set_parameters(params,
+                                                 params->mVoc_Index_Offset);
+    VocAlgorithm__adaptive_lowpass__init(params);
+    VocAlgorithm__adaptive_lowpass__set_parameters(params);
+}
+
+void VocAlgorithm_get_states(VocAlgorithmParams* params, int32_t* state0,
+                             int32_t* state1) {
+
+    *state0 = VocAlgorithm__mean_variance_estimator__get_mean(params);
+    *state1 = VocAlgorithm__mean_variance_estimator__get_std(params);
+    return;
+}
+
+void VocAlgorithm_set_states(VocAlgorithmParams* params, int32_t state0,
+                             int32_t state1) {
+
+    VocAlgorithm__mean_variance_estimator__set_states(
+        params, state0, state1, F16(VocAlgorithm_PERSISTENCE_UPTIME_GAMMA));
+    params->mSraw = state0;
+}
+
+void VocAlgorithm_set_tuning_parameters(VocAlgorithmParams* params,
+                                        int32_t voc_index_offset,
+                                        int32_t learning_time_hours,
+                                        int32_t gating_max_duration_minutes,
+                                        int32_t std_initial) {
+
+    params->mVoc_Index_Offset = (fix16_from_int(voc_index_offset));
+    params->mTau_Mean_Variance_Hours = (fix16_from_int(learning_time_hours));
+    params->mGating_Max_Duration_Minutes =
+        (fix16_from_int(gating_max_duration_minutes));
+    params->mSraw_Std_Initial = (fix16_from_int(std_initial));
+    VocAlgorithm__init_instances(params);
+}
+
+void VocAlgorithm_process(VocAlgorithmParams* params, int32_t sraw,
+                          int32_t* voc_index) {
+
+    if ((params->mUptime <= F16(VocAlgorithm_INITIAL_BLACKOUT))) {
+        params->mUptime =
+            (params->mUptime + F16(VocAlgorithm_SAMPLING_INTERVAL));
+    } else {
+        if (((sraw > 0) && (sraw < 65000))) {
+            if ((sraw < 20001)) {
+                sraw = 20001;
+            } else if ((sraw > 52767)) {
+                sraw = 52767;
+            }
+            params->mSraw = (fix16_from_int((sraw - 20000)));
+        }
+        params->mVoc_Index =
+            VocAlgorithm__mox_model__process(params, params->mSraw);
+        params->mVoc_Index =
+            VocAlgorithm__sigmoid_scaled__process(params, params->mVoc_Index);
+        params->mVoc_Index =
+            VocAlgorithm__adaptive_lowpass__process(params, params->mVoc_Index);
+        if ((params->mVoc_Index < F16(0.5))) {
+            params->mVoc_Index = F16(0.5);
+        }
+        if ((params->mSraw > F16(0.))) {
+            VocAlgorithm__mean_variance_estimator__process(
+                params, params->mSraw, params->mVoc_Index);
+            VocAlgorithm__mox_model__set_parameters(
+                params, VocAlgorithm__mean_variance_estimator__get_std(params),
+                VocAlgorithm__mean_variance_estimator__get_mean(params));
+        }
+    }
+    *voc_index = (fix16_cast_to_int((params->mVoc_Index + F16(0.5))));
+    return;
+}
+
+static void
+VocAlgorithm__mean_variance_estimator__init(VocAlgorithmParams* params) {
+
+    VocAlgorithm__mean_variance_estimator__set_parameters(params, F16(0.),
+                                                          F16(0.), F16(0.));
+    VocAlgorithm__mean_variance_estimator___init_instances(params);
+}
+
+static void VocAlgorithm__mean_variance_estimator___init_instances(
+    VocAlgorithmParams* params) {
+
+    VocAlgorithm__mean_variance_estimator___sigmoid__init(params);
+}
+
+static void VocAlgorithm__mean_variance_estimator__set_parameters(
+    VocAlgorithmParams* params, fix16_t std_initial,
+    fix16_t tau_mean_variance_hours, fix16_t gating_max_duration_minutes) {
+
+    params->m_Mean_Variance_Estimator__Gating_Max_Duration_Minutes =
+        gating_max_duration_minutes;
+    params->m_Mean_Variance_Estimator___Initialized = false;
+    params->m_Mean_Variance_Estimator___Mean = F16(0.);
+    params->m_Mean_Variance_Estimator___Sraw_Offset = F16(0.);
+    params->m_Mean_Variance_Estimator___Std = std_initial;
+    params->m_Mean_Variance_Estimator___Gamma =
+        (fix16_div(F16((VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING *
+                        (VocAlgorithm_SAMPLING_INTERVAL / 3600.))),
+                   (tau_mean_variance_hours +
+                    F16((VocAlgorithm_SAMPLING_INTERVAL / 3600.)))));
+    params->m_Mean_Variance_Estimator___Gamma_Initial_Mean =
+        F16(((VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING *
+              VocAlgorithm_SAMPLING_INTERVAL) /
+             (VocAlgorithm_TAU_INITIAL_MEAN + VocAlgorithm_SAMPLING_INTERVAL)));
+    params->m_Mean_Variance_Estimator___Gamma_Initial_Variance = F16(
+        ((VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING *
+          VocAlgorithm_SAMPLING_INTERVAL) /
+         (VocAlgorithm_TAU_INITIAL_VARIANCE + VocAlgorithm_SAMPLING_INTERVAL)));
+    params->m_Mean_Variance_Estimator__Gamma_Mean = F16(0.);
+    params->m_Mean_Variance_Estimator__Gamma_Variance = F16(0.);
+    params->m_Mean_Variance_Estimator___Uptime_Gamma = F16(0.);
+    params->m_Mean_Variance_Estimator___Uptime_Gating = F16(0.);
+    params->m_Mean_Variance_Estimator___Gating_Duration_Minutes = F16(0.);
+}
+
+static void
+VocAlgorithm__mean_variance_estimator__set_states(VocAlgorithmParams* params,
+                                                  fix16_t mean, fix16_t std,
+                                                  fix16_t uptime_gamma) {
+
+    params->m_Mean_Variance_Estimator___Mean = mean;
+    params->m_Mean_Variance_Estimator___Std = std;
+    params->m_Mean_Variance_Estimator___Uptime_Gamma = uptime_gamma;
+    params->m_Mean_Variance_Estimator___Initialized = true;
+}
+
+static fix16_t
+VocAlgorithm__mean_variance_estimator__get_std(VocAlgorithmParams* params) {
+
+    return params->m_Mean_Variance_Estimator___Std;
+}
+
+static fix16_t
+VocAlgorithm__mean_variance_estimator__get_mean(VocAlgorithmParams* params) {
+
+    return (params->m_Mean_Variance_Estimator___Mean +
+            params->m_Mean_Variance_Estimator___Sraw_Offset);
+}
+
+static void VocAlgorithm__mean_variance_estimator___calculate_gamma(
+    VocAlgorithmParams* params, fix16_t voc_index_from_prior) {
+
+    fix16_t uptime_limit;
+    fix16_t sigmoid_gamma_mean;
+    fix16_t gamma_mean;
+    fix16_t gating_threshold_mean;
+    fix16_t sigmoid_gating_mean;
+    fix16_t sigmoid_gamma_variance;
+    fix16_t gamma_variance;
+    fix16_t gating_threshold_variance;
+    fix16_t sigmoid_gating_variance;
+
+    uptime_limit = F16((VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__FIX16_MAX -
+                        VocAlgorithm_SAMPLING_INTERVAL));
+    if ((params->m_Mean_Variance_Estimator___Uptime_Gamma < uptime_limit)) {
+        params->m_Mean_Variance_Estimator___Uptime_Gamma =
+            (params->m_Mean_Variance_Estimator___Uptime_Gamma +
+             F16(VocAlgorithm_SAMPLING_INTERVAL));
+    }
+    if ((params->m_Mean_Variance_Estimator___Uptime_Gating < uptime_limit)) {
+        params->m_Mean_Variance_Estimator___Uptime_Gating =
+            (params->m_Mean_Variance_Estimator___Uptime_Gating +
+             F16(VocAlgorithm_SAMPLING_INTERVAL));
+    }
+    VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
+        params, F16(1.), F16(VocAlgorithm_INIT_DURATION_MEAN),
+        F16(VocAlgorithm_INIT_TRANSITION_MEAN));
+    sigmoid_gamma_mean =
+        VocAlgorithm__mean_variance_estimator___sigmoid__process(
+            params, params->m_Mean_Variance_Estimator___Uptime_Gamma);
+    gamma_mean =
+        (params->m_Mean_Variance_Estimator___Gamma +
+         (fix16_mul((params->m_Mean_Variance_Estimator___Gamma_Initial_Mean -
+                     params->m_Mean_Variance_Estimator___Gamma),
+                    sigmoid_gamma_mean)));
+    gating_threshold_mean =
+        (F16(VocAlgorithm_GATING_THRESHOLD) +
+         (fix16_mul(
+             F16((VocAlgorithm_GATING_THRESHOLD_INITIAL -
+                  VocAlgorithm_GATING_THRESHOLD)),
+             VocAlgorithm__mean_variance_estimator___sigmoid__process(
+                 params, params->m_Mean_Variance_Estimator___Uptime_Gating))));
+    VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
+        params, F16(1.), gating_threshold_mean,
+        F16(VocAlgorithm_GATING_THRESHOLD_TRANSITION));
+    sigmoid_gating_mean =
+        VocAlgorithm__mean_variance_estimator___sigmoid__process(
+            params, voc_index_from_prior);
+    params->m_Mean_Variance_Estimator__Gamma_Mean =
+        (fix16_mul(sigmoid_gating_mean, gamma_mean));
+    VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
+        params, F16(1.), F16(VocAlgorithm_INIT_DURATION_VARIANCE),
+        F16(VocAlgorithm_INIT_TRANSITION_VARIANCE));
+    sigmoid_gamma_variance =
+        VocAlgorithm__mean_variance_estimator___sigmoid__process(
+            params, params->m_Mean_Variance_Estimator___Uptime_Gamma);
+    gamma_variance =
+        (params->m_Mean_Variance_Estimator___Gamma +
+         (fix16_mul(
+             (params->m_Mean_Variance_Estimator___Gamma_Initial_Variance -
+              params->m_Mean_Variance_Estimator___Gamma),
+             (sigmoid_gamma_variance - sigmoid_gamma_mean))));
+    gating_threshold_variance =
+        (F16(VocAlgorithm_GATING_THRESHOLD) +
+         (fix16_mul(
+             F16((VocAlgorithm_GATING_THRESHOLD_INITIAL -
+                  VocAlgorithm_GATING_THRESHOLD)),
+             VocAlgorithm__mean_variance_estimator___sigmoid__process(
+                 params, params->m_Mean_Variance_Estimator___Uptime_Gating))));
+    VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
+        params, F16(1.), gating_threshold_variance,
+        F16(VocAlgorithm_GATING_THRESHOLD_TRANSITION));
+    sigmoid_gating_variance =
+        VocAlgorithm__mean_variance_estimator___sigmoid__process(
+            params, voc_index_from_prior);
+    params->m_Mean_Variance_Estimator__Gamma_Variance =
+        (fix16_mul(sigmoid_gating_variance, gamma_variance));
+    params->m_Mean_Variance_Estimator___Gating_Duration_Minutes =
+        (params->m_Mean_Variance_Estimator___Gating_Duration_Minutes +
+         (fix16_mul(F16((VocAlgorithm_SAMPLING_INTERVAL / 60.)),
+                    ((fix16_mul((F16(1.) - sigmoid_gating_mean),
+                                F16((1. + VocAlgorithm_GATING_MAX_RATIO)))) -
+                     F16(VocAlgorithm_GATING_MAX_RATIO)))));
+    if ((params->m_Mean_Variance_Estimator___Gating_Duration_Minutes <
+         F16(0.))) {
+        params->m_Mean_Variance_Estimator___Gating_Duration_Minutes = F16(0.);
+    }
+    if ((params->m_Mean_Variance_Estimator___Gating_Duration_Minutes >
+         params->m_Mean_Variance_Estimator__Gating_Max_Duration_Minutes)) {
+        params->m_Mean_Variance_Estimator___Uptime_Gating = F16(0.);
+    }
+}
+
+static void VocAlgorithm__mean_variance_estimator__process(
+    VocAlgorithmParams* params, fix16_t sraw, fix16_t voc_index_from_prior) {
+
+    fix16_t delta_sgp;
+    fix16_t c;
+    fix16_t additional_scaling;
+
+    if ((params->m_Mean_Variance_Estimator___Initialized == false)) {
+        params->m_Mean_Variance_Estimator___Initialized = true;
+        params->m_Mean_Variance_Estimator___Sraw_Offset = sraw;
+        params->m_Mean_Variance_Estimator___Mean = F16(0.);
+    } else {
+        if (((params->m_Mean_Variance_Estimator___Mean >= F16(100.)) ||
+             (params->m_Mean_Variance_Estimator___Mean <= F16(-100.)))) {
+            params->m_Mean_Variance_Estimator___Sraw_Offset =
+                (params->m_Mean_Variance_Estimator___Sraw_Offset +
+                 params->m_Mean_Variance_Estimator___Mean);
+            params->m_Mean_Variance_Estimator___Mean = F16(0.);
+        }
+        sraw = (sraw - params->m_Mean_Variance_Estimator___Sraw_Offset);
+        VocAlgorithm__mean_variance_estimator___calculate_gamma(
+            params, voc_index_from_prior);
+        delta_sgp = (fix16_div(
+            (sraw - params->m_Mean_Variance_Estimator___Mean),
+            F16(VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING)));
+        if ((delta_sgp < F16(0.))) {
+            c = (params->m_Mean_Variance_Estimator___Std - delta_sgp);
+        } else {
+            c = (params->m_Mean_Variance_Estimator___Std + delta_sgp);
+        }
+        additional_scaling = F16(1.);
+        if ((c > F16(1440.))) {
+            additional_scaling = F16(4.);
+        }
+        params->m_Mean_Variance_Estimator___Std = (fix16_mul(
+            fix16_sqrt((fix16_mul(
+                additional_scaling,
+                (F16(VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING) -
+                 params->m_Mean_Variance_Estimator__Gamma_Variance)))),
+            fix16_sqrt((
+                (fix16_mul(
+                    params->m_Mean_Variance_Estimator___Std,
+                    (fix16_div(
+                        params->m_Mean_Variance_Estimator___Std,
+                        (fix16_mul(
+                            F16(VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING),
+                            additional_scaling)))))) +
+                (fix16_mul(
+                    (fix16_div(
+                        (fix16_mul(
+                            params->m_Mean_Variance_Estimator__Gamma_Variance,
+                            delta_sgp)),
+                        additional_scaling)),
+                    delta_sgp))))));
+        params->m_Mean_Variance_Estimator___Mean =
+            (params->m_Mean_Variance_Estimator___Mean +
+             (fix16_mul(params->m_Mean_Variance_Estimator__Gamma_Mean,
+                        delta_sgp)));
+    }
+}
+
+static void VocAlgorithm__mean_variance_estimator___sigmoid__init(
+    VocAlgorithmParams* params) {
+
+    VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
+        params, F16(0.), F16(0.), F16(0.));
+}
+
+static void VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
+    VocAlgorithmParams* params, fix16_t L, fix16_t X0, fix16_t K) {
+
+    params->m_Mean_Variance_Estimator___Sigmoid__L = L;
+    params->m_Mean_Variance_Estimator___Sigmoid__K = K;
+    params->m_Mean_Variance_Estimator___Sigmoid__X0 = X0;
+}
+
+static fix16_t VocAlgorithm__mean_variance_estimator___sigmoid__process(
+    VocAlgorithmParams* params, fix16_t sample) {
+
+    fix16_t x;
+
+    x = (fix16_mul(params->m_Mean_Variance_Estimator___Sigmoid__K,
+                   (sample - params->m_Mean_Variance_Estimator___Sigmoid__X0)));
+    if ((x < F16(-50.))) {
+        return params->m_Mean_Variance_Estimator___Sigmoid__L;
+    } else if ((x > F16(50.))) {
+        return F16(0.);
+    } else {
+        return (fix16_div(params->m_Mean_Variance_Estimator___Sigmoid__L,
+                          (F16(1.) + fix16_exp(x))));
+    }
+}
+
+static void VocAlgorithm__mox_model__init(VocAlgorithmParams* params) {
+
+    VocAlgorithm__mox_model__set_parameters(params, F16(1.), F16(0.));
+}
+
+static void VocAlgorithm__mox_model__set_parameters(VocAlgorithmParams* params,
+                                                    fix16_t SRAW_STD,
+                                                    fix16_t SRAW_MEAN) {
+
+    params->m_Mox_Model__Sraw_Std = SRAW_STD;
+    params->m_Mox_Model__Sraw_Mean = SRAW_MEAN;
+}
+
+static fix16_t VocAlgorithm__mox_model__process(VocAlgorithmParams* params,
+                                                fix16_t sraw) {
+
+    return (fix16_mul((fix16_div((sraw - params->m_Mox_Model__Sraw_Mean),
+                                 (-(params->m_Mox_Model__Sraw_Std +
+                                    F16(VocAlgorithm_SRAW_STD_BONUS))))),
+                      F16(VocAlgorithm_VOC_INDEX_GAIN)));
+}
+
+static void VocAlgorithm__sigmoid_scaled__init(VocAlgorithmParams* params) {
+
+    VocAlgorithm__sigmoid_scaled__set_parameters(params, F16(0.));
+}
+
+static void
+VocAlgorithm__sigmoid_scaled__set_parameters(VocAlgorithmParams* params,
+                                             fix16_t offset) {
+
+    params->m_Sigmoid_Scaled__Offset = offset;
+}
+
+static fix16_t VocAlgorithm__sigmoid_scaled__process(VocAlgorithmParams* params,
+                                                     fix16_t sample) {
+
+    fix16_t x;
+    fix16_t shift;
+
+    x = (fix16_mul(F16(VocAlgorithm_SIGMOID_K),
+                   (sample - F16(VocAlgorithm_SIGMOID_X0))));
+    if ((x < F16(-50.))) {
+        return F16(VocAlgorithm_SIGMOID_L);
+    } else if ((x > F16(50.))) {
+        return F16(0.);
+    } else {
+        if ((sample >= F16(0.))) {
+            shift = (fix16_div(
+                (F16(VocAlgorithm_SIGMOID_L) -
+                 (fix16_mul(F16(5.), params->m_Sigmoid_Scaled__Offset))),
+                F16(4.)));
+            return ((fix16_div((F16(VocAlgorithm_SIGMOID_L) + shift),
+                               (F16(1.) + fix16_exp(x)))) -
+                    shift);
+        } else {
+            return (fix16_mul(
+                (fix16_div(params->m_Sigmoid_Scaled__Offset,
+                           F16(VocAlgorithm_VOC_INDEX_OFFSET_DEFAULT))),
+                (fix16_div(F16(VocAlgorithm_SIGMOID_L),
+                           (F16(1.) + fix16_exp(x))))));
+        }
+    }
+}
+
+static void VocAlgorithm__adaptive_lowpass__init(VocAlgorithmParams* params) {
+
+    VocAlgorithm__adaptive_lowpass__set_parameters(params);
+}
+
+static void
+VocAlgorithm__adaptive_lowpass__set_parameters(VocAlgorithmParams* params) {
+
+    params->m_Adaptive_Lowpass__A1 =
+        F16((VocAlgorithm_SAMPLING_INTERVAL /
+             (VocAlgorithm_LP_TAU_FAST + VocAlgorithm_SAMPLING_INTERVAL)));
+    params->m_Adaptive_Lowpass__A2 =
+        F16((VocAlgorithm_SAMPLING_INTERVAL /
+             (VocAlgorithm_LP_TAU_SLOW + VocAlgorithm_SAMPLING_INTERVAL)));
+    params->m_Adaptive_Lowpass___Initialized = false;
+}
+
+static fix16_t
+VocAlgorithm__adaptive_lowpass__process(VocAlgorithmParams* params,
+                                        fix16_t sample) {
+
+    fix16_t abs_delta;
+    fix16_t F1;
+    fix16_t tau_a;
+    fix16_t a3;
+
+    if ((params->m_Adaptive_Lowpass___Initialized == false)) {
+        params->m_Adaptive_Lowpass___X1 = sample;
+        params->m_Adaptive_Lowpass___X2 = sample;
+        params->m_Adaptive_Lowpass___X3 = sample;
+        params->m_Adaptive_Lowpass___Initialized = true;
+    }
+    params->m_Adaptive_Lowpass___X1 =
+        ((fix16_mul((F16(1.) - params->m_Adaptive_Lowpass__A1),
+                    params->m_Adaptive_Lowpass___X1)) +
+         (fix16_mul(params->m_Adaptive_Lowpass__A1, sample)));
+    params->m_Adaptive_Lowpass___X2 =
+        ((fix16_mul((F16(1.) - params->m_Adaptive_Lowpass__A2),
+                    params->m_Adaptive_Lowpass___X2)) +
+         (fix16_mul(params->m_Adaptive_Lowpass__A2, sample)));
+    abs_delta =
+        (params->m_Adaptive_Lowpass___X1 - params->m_Adaptive_Lowpass___X2);
+    if ((abs_delta < F16(0.))) {
+        abs_delta = (-abs_delta);
+    }
+    F1 = fix16_exp((fix16_mul(F16(VocAlgorithm_LP_ALPHA), abs_delta)));
+    tau_a =
+        ((fix16_mul(F16((VocAlgorithm_LP_TAU_SLOW - VocAlgorithm_LP_TAU_FAST)),
+                    F1)) +
+         F16(VocAlgorithm_LP_TAU_FAST));
+    a3 = (fix16_div(F16(VocAlgorithm_SAMPLING_INTERVAL),
+                    (F16(VocAlgorithm_SAMPLING_INTERVAL) + tau_a)));
+    params->m_Adaptive_Lowpass___X3 =
+        ((fix16_mul((F16(1.) - a3), params->m_Adaptive_Lowpass___X3)) +
+         (fix16_mul(a3, sample)));
+    return params->m_Adaptive_Lowpass___X3;
+}
Index: src/sys/dev/i2c/sensirion_voc_algorithm.h
diff -u /dev/null src/sys/dev/i2c/sensirion_voc_algorithm.h:1.1
--- /dev/null	Thu Oct 14 13:54:46 2021
+++ src/sys/dev/i2c/sensirion_voc_algorithm.h	Thu Oct 14 13:54:46 2021
@@ -0,0 +1,191 @@
+/*
+ *	$NetBSD: sensirion_voc_algorithm.h,v 1.1 2021/10/14 13:54:46 brad Exp $
+ */
+
+/*
+ * Copyright (c) 2021, Sensirion AG
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Sensirion AG nor the names of its
+ *   contributors may be used to endorse or promote products derived from
+ *   this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef VOCALGORITHM_H_
+#define VOCALGORITHM_H_
+
+#include "sensirion_arch_config.h"
+
+/* The fixed point arithmetic parts of this code were originally created by
+ * https://github.com/PetteriAimonen/libfixmath
+ */
+
+typedef int32_t fix16_t;
+
+#define F16(x) \
+    ((fix16_t)(((x) >= 0) ? ((x)*65536.0 + 0.5) : ((x)*65536.0 - 0.5)))
+
+// Should be set by the building toolchain
+#ifndef LIBRARY_VERSION_NAME
+#define LIBRARY_VERSION_NAME "custom build"
+#endif
+
+#define VocAlgorithm_SAMPLING_INTERVAL (1.)
+#define VocAlgorithm_INITIAL_BLACKOUT (45.)
+#define VocAlgorithm_VOC_INDEX_GAIN (230.)
+#define VocAlgorithm_SRAW_STD_INITIAL (50.)
+#define VocAlgorithm_SRAW_STD_BONUS (220.)
+#define VocAlgorithm_TAU_MEAN_VARIANCE_HOURS (12.)
+#define VocAlgorithm_TAU_INITIAL_MEAN (20.)
+#define VocAlgorithm_INIT_DURATION_MEAN ((3600. * 0.75))
+#define VocAlgorithm_INIT_TRANSITION_MEAN (0.01)
+#define VocAlgorithm_TAU_INITIAL_VARIANCE (2500.)
+#define VocAlgorithm_INIT_DURATION_VARIANCE ((3600. * 1.45))
+#define VocAlgorithm_INIT_TRANSITION_VARIANCE (0.01)
+#define VocAlgorithm_GATING_THRESHOLD (340.)
+#define VocAlgorithm_GATING_THRESHOLD_INITIAL (510.)
+#define VocAlgorithm_GATING_THRESHOLD_TRANSITION (0.09)
+#define VocAlgorithm_GATING_MAX_DURATION_MINUTES ((60. * 3.))
+#define VocAlgorithm_GATING_MAX_RATIO (0.3)
+#define VocAlgorithm_SIGMOID_L (500.)
+#define VocAlgorithm_SIGMOID_K (-0.0065)
+#define VocAlgorithm_SIGMOID_X0 (213.)
+#define VocAlgorithm_VOC_INDEX_OFFSET_DEFAULT (100.)
+#define VocAlgorithm_LP_TAU_FAST (20.0)
+#define VocAlgorithm_LP_TAU_SLOW (500.0)
+#define VocAlgorithm_LP_ALPHA (-0.2)
+#define VocAlgorithm_PERSISTENCE_UPTIME_GAMMA ((3. * 3600.))
+#define VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING (64.)
+#define VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__FIX16_MAX (32767.)
+
+/**
+ * Struct to hold all the states of the VOC algorithm.
+ */
+typedef struct {
+    fix16_t mVoc_Index_Offset;
+    fix16_t mTau_Mean_Variance_Hours;
+    fix16_t mGating_Max_Duration_Minutes;
+    fix16_t mSraw_Std_Initial;
+    fix16_t mUptime;
+    fix16_t mSraw;
+    fix16_t mVoc_Index;
+    fix16_t m_Mean_Variance_Estimator__Gating_Max_Duration_Minutes;
+    bool m_Mean_Variance_Estimator___Initialized;
+    fix16_t m_Mean_Variance_Estimator___Mean;
+    fix16_t m_Mean_Variance_Estimator___Sraw_Offset;
+    fix16_t m_Mean_Variance_Estimator___Std;
+    fix16_t m_Mean_Variance_Estimator___Gamma;
+    fix16_t m_Mean_Variance_Estimator___Gamma_Initial_Mean;
+    fix16_t m_Mean_Variance_Estimator___Gamma_Initial_Variance;
+    fix16_t m_Mean_Variance_Estimator__Gamma_Mean;
+    fix16_t m_Mean_Variance_Estimator__Gamma_Variance;
+    fix16_t m_Mean_Variance_Estimator___Uptime_Gamma;
+    fix16_t m_Mean_Variance_Estimator___Uptime_Gating;
+    fix16_t m_Mean_Variance_Estimator___Gating_Duration_Minutes;
+    fix16_t m_Mean_Variance_Estimator___Sigmoid__L;
+    fix16_t m_Mean_Variance_Estimator___Sigmoid__K;
+    fix16_t m_Mean_Variance_Estimator___Sigmoid__X0;
+    fix16_t m_Mox_Model__Sraw_Std;
+    fix16_t m_Mox_Model__Sraw_Mean;
+    fix16_t m_Sigmoid_Scaled__Offset;
+    fix16_t m_Adaptive_Lowpass__A1;
+    fix16_t m_Adaptive_Lowpass__A2;
+    bool m_Adaptive_Lowpass___Initialized;
+    fix16_t m_Adaptive_Lowpass___X1;
+    fix16_t m_Adaptive_Lowpass___X2;
+    fix16_t m_Adaptive_Lowpass___X3;
+} VocAlgorithmParams;
+
+/**
+ * Initialize the VOC algorithm parameters. Call this once at the beginning or
+ * whenever the sensor stopped measurements.
+ * @param params    Pointer to the VocAlgorithmParams struct
+ */
+void VocAlgorithm_init(VocAlgorithmParams* params);
+
+/**
+ * Get current algorithm states. Retrieved values can be used in
+ * VocAlgorithm_set_states() to resume operation after a short interruption,
+ * skipping initial learning phase. This feature can only be used after at least
+ * 3 hours of continuous operation.
+ * @param params    Pointer to the VocAlgorithmParams struct
+ * @param state0    State0 to be stored
+ * @param state1    State1 to be stored
+ */
+void VocAlgorithm_get_states(VocAlgorithmParams* params, int32_t* state0,
+                             int32_t* state1);
+
+/**
+ * Set previously retrieved algorithm states to resume operation after a short
+ * interruption, skipping initial learning phase. This feature should not be
+ * used after inerruptions of more than 10 minutes. Call this once after
+ * VocAlgorithm_init() and the optional VocAlgorithm_set_tuning_parameters(), if
+ * desired. Otherwise, the algorithm will start with initial learning phase.
+ * @param params    Pointer to the VocAlgorithmParams struct
+ * @param state0    State0 to be restored
+ * @param state1    State1 to be restored
+ */
+void VocAlgorithm_set_states(VocAlgorithmParams* params, int32_t state0,
+                             int32_t state1);
+
+/**
+ * Set parameters to customize the VOC algorithm. Call this once after
+ * VocAlgorithm_init(), if desired. Otherwise, the default values will be used.
+ *
+ * @param params                      Pointer to the VocAlgorithmParams struct
+ * @param voc_index_offset            VOC index representing typical (average)
+ *                                    conditions. Range 1..250, default 100
+ * @param learning_time_hours         Time constant of long-term estimator.
+ *                                    Past events will be forgotten after about
+ *                                    twice the learning time.
+ *                                    Range 1..72 [hours], default 12 [hours]
+ * @param gating_max_duration_minutes Maximum duration of gating (freeze of
+ *                                    estimator during high VOC index signal).
+ *                                    0 (no gating) or range 1..720 [minutes],
+ *                                    default 180 [minutes]
+ * @param std_initial                 Initial estimate for standard deviation.
+ *                                    Lower value boosts events during initial
+ *                                    learning period, but may result in larger
+ *                                    device-to-device variations.
+ *                                    Range 10..500, default 50
+ */
+void VocAlgorithm_set_tuning_parameters(VocAlgorithmParams* params,
+                                        int32_t voc_index_offset,
+                                        int32_t learning_time_hours,
+                                        int32_t gating_max_duration_minutes,
+                                        int32_t std_initial);
+
+/**
+ * Calculate the VOC index value from the raw sensor value.
+ *
+ * @param params    Pointer to the VocAlgorithmParams struct
+ * @param sraw      Raw value from the SGP40 sensor
+ * @param voc_index Calculated VOC index value from the raw sensor value. Zero
+ *                  during initial blackout period and 1..500 afterwards
+ */
+void VocAlgorithm_process(VocAlgorithmParams* params, int32_t sraw,
+                          int32_t* voc_index);
+
+#endif /* VOCALGORITHM_H_ */
Index: src/sys/dev/i2c/sgp40.c
diff -u /dev/null src/sys/dev/i2c/sgp40.c:1.1
--- /dev/null	Thu Oct 14 13:54:46 2021
+++ src/sys/dev/i2c/sgp40.c	Thu Oct 14 13:54:46 2021
@@ -0,0 +1,802 @@
+/*	$NetBSD: sgp40.c,v 1.1 2021/10/14 13:54:46 brad Exp $	*/
+
+/*
+ * Copyright (c) 2021 Brad Spencer <b...@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;
+	}
+}
Index: src/sys/dev/i2c/sgp40reg.h
diff -u /dev/null src/sys/dev/i2c/sgp40reg.h:1.1
--- /dev/null	Thu Oct 14 13:54:46 2021
+++ src/sys/dev/i2c/sgp40reg.h	Thu Oct 14 13:54:46 2021
@@ -0,0 +1,50 @@
+/*	$NetBSD: sgp40reg.h,v 1.1 2021/10/14 13:54:46 brad Exp $	*/
+
+/*
+ * Copyright (c) 2021 Brad Spencer <b...@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, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _DEV_I2C_SGP40REG_H_
+#define _DEV_I2C_SGP40REG_H_
+
+#define SGP40_TYPICAL_ADDR	0x59
+
+/* There are three documented commands for this chip, plus one that is called a
+   soft reset.  But really the soft reset is a general reset of all devices on
+   the bus, hence it is not as usable as one would hope
+ */
+
+#define SGP40_MEASURE_RAW 0x260f
+#define SGP40_MEASURE_TEST 0x280e
+#define SGP40_HEATER_OFF 0x3615
+/* The get serial number command is documented in version 1.1 of the
+   datasheet.
+*/
+#define SGP40_GET_SERIAL_NUMBER 0x3682
+/* The get featureset command is not documented in any datasheet that could
+   be found.  However, it is present and used in a lot of example code.
+   It has no additional arguments aside from the command itself, and returns
+   a uint16_t for the data and a uint8_t for the crc.
+*/
+#define SGP40_GET_FEATURESET 0x202f
+
+/* The results of a self test are documented as being either everything is ok, or
+   something failed.
+ */
+
+#define SGP40_TEST_RESULTS_ALL_PASSED 0xd400
+#define SGP40_TEST_RESULTS_SOME_FAILED 0x4b00
+
+#endif
Index: src/sys/dev/i2c/sgp40var.h
diff -u /dev/null src/sys/dev/i2c/sgp40var.h:1.1
--- /dev/null	Thu Oct 14 13:54:46 2021
+++ src/sys/dev/i2c/sgp40var.h	Thu Oct 14 13:54:46 2021
@@ -0,0 +1,61 @@
+/*	$NetBSD: sgp40var.h,v 1.1 2021/10/14 13:54:46 brad Exp $	*/
+
+/*
+ * Copyright (c) 2021 Brad Spencer <b...@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, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _DEV_I2C_SGP40VAR_H_
+#define _DEV_I2C_SGP40VAR_H_
+
+#define SGP40_NUM_SENSORS	1
+#define SGP40_VOC_SENSOR 0
+
+#define SGP40_DEFAULT_TEMP_COMP 25
+#define SGP40_DEFAULT_RH_COMP 50
+
+
+struct sgp40_sc {
+	int 		sc_sgp40debug;
+	device_t 	sc_dev;
+	i2c_tag_t 	sc_tag;
+	i2c_addr_t 	sc_addr;
+	kmutex_t        sc_threadmutex; /* for the measurement kthread */
+	kmutex_t 	sc_mutex; /* for reading the i2c bus */
+	kcondvar_t	sc_condvar;
+	struct lwp      *sc_thread;
+	int 		sc_numsensors;
+	struct sysmon_envsys *sc_sme;
+	struct sysctllog *sc_sgp40log;
+	envsys_data_t 	sc_sensors[SGP40_NUM_SENSORS];
+	uint16_t        sc_voc;
+	bool            sc_vocvalid;
+	bool 		sc_ignorecrc;
+	int 		sc_readattempts;
+	bool            sc_stopping;
+	int             sc_tempcomp;
+	int             sc_rhcomp;
+};
+
+struct sgp40_sensor {
+	const char     *desc;
+	enum envsys_units type;
+};
+
+struct sgp40_timing {
+	uint16_t cmd;
+	int     typicaldelay;
+};
+
+#endif

Index: src/sys/modules/sgp40mox/Makefile
diff -u /dev/null src/sys/modules/sgp40mox/Makefile:1.1
--- /dev/null	Thu Oct 14 13:54:46 2021
+++ src/sys/modules/sgp40mox/Makefile	Thu Oct 14 13:54:46 2021
@@ -0,0 +1,12 @@
+.include "../Makefile.inc"
+
+.PATH:	${S}/dev/i2c
+
+KMOD=	sgp40mox
+IOCONF=	sgp40mox.ioconf
+SRCS=	sgp40.c
+SRCS+=	sensirion_voc_algorithm.c
+
+WARNS=	3
+
+.include <bsd.kmodule.mk>
Index: src/sys/modules/sgp40mox/sgp40mox.ioconf
diff -u /dev/null src/sys/modules/sgp40mox/sgp40mox.ioconf:1.1
--- /dev/null	Thu Oct 14 13:54:46 2021
+++ src/sys/modules/sgp40mox/sgp40mox.ioconf	Thu Oct 14 13:54:46 2021
@@ -0,0 +1,8 @@
+ioconf sgp40mox
+
+include "conf/files"
+
+pseudo-root iic*
+
+sgp40mox* at iic? addr 0x59
+

Reply via email to