Module Name:    src
Committed By:   christos
Date:           Thu Dec 28 23:23:47 UTC 2017

Modified Files:
        src/sys/dev/i2c: files.i2c
Added Files:
        src/sys/dev/i2c: am2315.c am2315reg.h am2315var.h si70xx.c si70xxreg.h
            si70xxvar.h

Log Message:
PR/52848: Brad Spencer: Two environment sensor drivers: AM2315 and SI70xx
XXX: Please check that my refactoring did not break them!


To generate a diff of this commit:
cvs rdiff -u -r0 -r1.1 src/sys/dev/i2c/am2315.c src/sys/dev/i2c/am2315reg.h \
    src/sys/dev/i2c/am2315var.h src/sys/dev/i2c/si70xx.c \
    src/sys/dev/i2c/si70xxreg.h src/sys/dev/i2c/si70xxvar.h
cvs rdiff -u -r1.79 -r1.80 src/sys/dev/i2c/files.i2c

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

Modified files:

Index: src/sys/dev/i2c/files.i2c
diff -u src/sys/dev/i2c/files.i2c:1.79 src/sys/dev/i2c/files.i2c:1.80
--- src/sys/dev/i2c/files.i2c:1.79	Sun Dec 10 12:05:54 2017
+++ src/sys/dev/i2c/files.i2c	Thu Dec 28 18:23:47 2017
@@ -1,4 +1,4 @@
-#	$NetBSD: files.i2c,v 1.79 2017/12/10 17:05:54 bouyer Exp $
+#	$NetBSD: files.i2c,v 1.80 2017/12/28 23:23:47 christos Exp $
 
 obsolete defflag	opt_i2cbus.h		I2C_SCAN
 define	i2cbus { }
@@ -280,6 +280,16 @@ device	tcagpio
 attach	tcagpio at iic
 file	dev/i2c/tcagpio.c			tcagpio
 
+# Silicon Lab SI7013/SI7020/SI7021 Temperature and Humidity sensor
+device si70xxtemp
+attach si70xxtemp at iic
+file dev/i2c/si70xx.c				si70xxtemp
+
+# Aosong AM2315 Temperature and Humidity sensor
+device am2315temp
+attach am2315temp at iic
+file dev/i2c/am2315.c				am2315temp
+
 # Silergy SY8106A regulator
 device	sy8106a
 attach	sy8106a at iic

Added files:

Index: src/sys/dev/i2c/am2315.c
diff -u /dev/null src/sys/dev/i2c/am2315.c:1.1
--- /dev/null	Thu Dec 28 18:23:47 2017
+++ src/sys/dev/i2c/am2315.c	Thu Dec 28 18:23:47 2017
@@ -0,0 +1,531 @@
+/*	$NetBSD: am2315.c,v 1.1 2017/12/28 23:23:47 christos Exp $	*/
+
+/*
+ * Copyright (c) 2017 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.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: am2315.c,v 1.1 2017/12/28 23:23:47 christos Exp $");
+
+/*
+ * Driver for the Aosong AM2315
+ */
+
+#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/condvar.h>
+#include <sys/mutex.h>
+#include <sys/time.h>
+
+#include <dev/sysmon/sysmonvar.h>
+#include <dev/i2c/i2cvar.h>
+#include <dev/i2c/am2315reg.h>
+#include <dev/i2c/am2315var.h>
+
+static uint16_t am2315_crc(uint8_t *, size_t);
+static int 	am2315_poke(struct am2315_sc *);
+static int 	am2315_poke_m(i2c_tag_t, i2c_addr_t, const char *, bool);
+static int 	am2315_match(device_t, cfdata_t, void *);
+static void 	am2315_attach(device_t, device_t, void *);
+static int 	am2315_detach(device_t, int);
+static void 	am2315_refresh(struct sysmon_envsys *, envsys_data_t *);
+static int 	am2315_verify_sysctl(SYSCTLFN_ARGS);
+
+#define AM2315_DEBUG
+#ifdef AM2315_DEBUG
+#define DPRINTF(s, l, x) \
+    do { \
+	if (l <= s->sc_am2315debug) \
+	printf x; \
+    } while (/*CONSTCOND*/0)
+#else
+#define DPRINTF(s, l, x)
+#endif
+
+CFATTACH_DECL_NEW(am2315temp, sizeof(struct am2315_sc),
+    am2315_match, am2315_attach, am2315_detach, NULL);
+
+static struct am2315_sensor am2315_sensors[] = {
+	{
+		.desc = "humidity",
+		.type = ENVSYS_SRELHUMIDITY,
+	},
+	{
+		.desc = "temperature",
+		.type = ENVSYS_STEMP,
+	}
+};
+
+static uint16_t
+am2315_crc(uint8_t *data, size_t len)
+{
+	uint16_t crc = 0xffff;
+
+	for (size_t j = 0; j < len; j++) {
+		crc ^= data[j];
+		for (size_t i = 0; i < 8; i++) {
+			if (crc & 0x01) {
+				crc >>= 1;
+				crc ^= 0xA001;
+			} else {
+				crc >>= 1;
+			}
+		}
+	}
+
+	return crc;
+}
+
+int 
+am2315_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;
+}
+
+static int
+am2315_cmd(i2c_tag_t tag, i2c_addr_t addr, uint8_t dir, uint8_t cmd,
+    uint8_t clen, uint8_t *buf, size_t blen)
+{
+	uint8_t command[] = { dir, cmd, clen };
+	if (buf)
+		memset(buf, 0xff, blen);
+	uint8_t reg = dir == AM2315_READ_REGISTERS ?
+	    I2C_OP_READ_WITH_STOP : I2C_OP_WRITE_WITH_STOP;
+
+	return iic_exec(tag, reg, addr, command,
+	    __arraycount(command), buf, blen, 0);
+}
+
+static int
+am2315_read_regs(struct am2315_sc *sc, uint8_t cmd, uint8_t clen, uint8_t *buf,
+    size_t blen)
+{
+	return am2315_cmd(sc->sc_tag, sc->sc_addr, AM2315_READ_REGISTERS,
+	    cmd, clen, buf, blen);
+}
+
+static int
+am2315_poke(struct am2315_sc *sc)
+{
+	return am2315_poke_m(sc->sc_tag, sc->sc_addr, device_xname(sc->sc_dev),
+	    sc->sc_am2315debug >= 2);
+}
+
+static int
+am2315_poke_m(i2c_tag_t tag, i2c_addr_t addr, const char *name, bool debug)
+{
+	uint8_t buf[5];
+	int error;
+
+	error = am2315_cmd(tag, addr, AM2315_WRITE_REGISTERS,
+	    AM2315_REGISTER_HIGH_USER1, 1, NULL, 0);
+	if (debug)
+		printf("%s: poke 1: %d\n", name, error);
+
+	if (error != 0)
+		delay(2800);
+
+	error = am2315_cmd(tag, addr, AM2315_READ_REGISTERS,
+	    AM2315_REGISTER_STATUS, 1, buf, __arraycount(buf));
+	if (debug)
+	    printf("%s: poke 2: %d %02x %02x %02x %02x%02x\n", name, error,
+		buf[0], buf[1], buf[2], buf[3], buf[4]);
+
+	if (error != 0)
+		delay(2800);
+	return error;
+}
+
+static int
+am2315_match(device_t parent, cfdata_t match, void *aux)
+{
+	struct i2c_attach_args *ia;
+	int rv;
+	const bool matchdebug = false;
+
+	ia = aux;
+
+	if (ia->ia_name) {
+		/* direct config - check name */
+		if (strcmp(ia->ia_name, "am2315temp") != 0)
+			return 0;
+	} else {
+		/* indirect config - check for configured address */
+		if (ia->ia_addr != AM2315_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;
+	}
+
+	if ((rv = am2315_poke_m(ia->ia_tag, ia->ia_addr, __func__, matchdebug))
+	    != 0) {
+		if (matchdebug)
+			printf("match rv poke %d\n", rv);
+		iic_release_bus(ia->ia_tag, 0);
+		return 0;
+	}
+
+	return 1;
+}
+
+static void
+am2315_attach(device_t parent, device_t self, void *aux)
+{
+	struct am2315_sc *sc = device_private(self);
+	struct i2c_attach_args *ia = aux;
+	uint8_t buf[11];
+	int error;
+	uint16_t crc, readcrc, model;
+	uint8_t chipver;
+	uint32_t id;
+	bool modelgood, chipvergood, idgood;
+
+	sc->sc_dev = self;
+	sc->sc_tag = ia->ia_tag;
+	sc->sc_addr = ia->ia_addr;
+	sc->sc_am2315debug = 0;
+	sc->sc_readcount = 2;
+	sc->sc_readticks = 100;
+	sc->sc_sme = NULL;
+
+	aprint_normal("\n");
+
+	mutex_init(&sc->sc_mutex, MUTEX_DEFAULT, IPL_NONE);
+	mutex_init(&sc->sc_waitmutex, MUTEX_DEFAULT, IPL_NONE);
+	cv_init(&sc->sc_condwait, "am2315wait");
+
+	sc->sc_numsensors = __arraycount(am2315_sensors);
+
+	if ((sc->sc_sme = sysmon_envsys_create()) == NULL) {
+		aprint_error_dev(self, "unable to create sysmon structure\n");
+		return;
+	}
+
+	/* XXX: sysctl's not destroyed on failure */
+	const struct sysctlnode *cnode;
+	int sysctlroot_num;
+	if ((error = sysctl_createv(&sc->sc_am2315log, 0, NULL, &cnode, 0,
+	    CTLTYPE_NODE, device_xname(self),
+	    SYSCTL_DESCR("am2315 controls"), NULL, 0, NULL, 0, CTL_HW,
+	    CTL_CREATE, CTL_EOL)) != 0)
+		goto badsysctl;
+	sysctlroot_num = cnode->sysctl_num;
+
+#ifdef AM2315_DEBUG
+	if ((error = sysctl_createv(&sc->sc_am2315log, 0, NULL, &cnode,
+	    CTLFLAG_READWRITE, CTLTYPE_INT, "debug",
+	    SYSCTL_DESCR("Debug level"), am2315_verify_sysctl, 0,
+	    &sc->sc_am2315debug, 0, CTL_HW, sysctlroot_num, CTL_CREATE,
+	    CTL_EOL)) != 0)
+		goto badsysctl;
+
+#endif
+
+	if ((error = sysctl_createv(&sc->sc_am2315log, 0, NULL, &cnode,
+	    CTLFLAG_READWRITE, CTLTYPE_INT, "readcount",
+	    SYSCTL_DESCR("Number of times to read the sensor"),
+	    am2315_verify_sysctl, 0, &sc->sc_readcount, 0, CTL_HW,
+	    sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
+		goto badsysctl;
+
+	if ((error = sysctl_createv(&sc->sc_am2315log, 0, NULL, &cnode,
+	    CTLFLAG_READWRITE, CTLTYPE_INT, "readticks",
+	    SYSCTL_DESCR("Number of ticks between reads"),
+	    am2315_verify_sysctl, 0, &sc->sc_readticks, 0, CTL_HW,
+	    sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
+		goto badsysctl;
+
+	if ((error = iic_acquire_bus(sc->sc_tag, 0)) != 0) {
+		aprint_error_dev(self,
+		    "Could not acquire iic bus: %d\n", error);
+		return;
+	}
+	am2315_poke(sc);
+
+#define DUMP(a) \
+    DPRINTF(sc, 2, ("%s: read cmd+len+%s+crcl+crch values: %02x %02x " \
+	"%02x%02x %02x%02x -- %02x%02x%02x%02x%02x -- %04x %04x\n", a, \
+	device_xname(self), buf[0], buf[1], buf[2], buf[3], \
+	buf[4], buf[5], buf[6], buf[7], buf[8], buf[9], \
+	buf[10], crc, readcrc))
+
+	error = am2315_read_regs(sc, AM2315_REGISTER_HIGH_MODEL, 2, buf, 6);
+	if (error)
+		aprint_error_dev(sc->sc_dev, "read model: %d\n", error);
+	readcrc = buf[5] << 8 | buf[4];
+	crc = am2315_crc(buf, 4);
+	DUMP("modh+modl");
+	model = buf[2] << 8 | buf[3];
+	modelgood = buf[0] == AM2315_READ_REGISTERS && buf[1] == 2
+	    && crc == readcrc;
+
+	error = am2315_read_regs(sc, AM2315_REGISTER_VERSION, 1, buf, 5);
+	if (error != 0)
+		aprint_error_dev(self, "read chipver: %d\n", error);
+	readcrc = buf[4] << 8 | buf[3];
+	crc = am2315_crc(buf, 3);
+	DUMP("ver");
+	chipver = buf[2];
+	chipvergood = buf[0] == AM2315_READ_REGISTERS && buf[1] == 1
+	    && crc == readcrc;
+
+	error = am2315_read_regs(sc, AM2315_REGISTER_ID_PT_24_31, 2, buf, 6);
+	if (error != 0)
+		aprint_error_dev(self, "read id 1: %d\n", error);
+	readcrc = buf[5] << 8 | buf[4];
+	crc = am2315_crc(buf, 4);
+	DUMP("id1+id2");
+	id = buf[2] << 8 | buf[3];
+	idgood = buf[0] == AM2315_READ_REGISTERS && buf[1] == 2
+	    && crc == readcrc;
+
+	error = am2315_read_regs(sc, AM2315_REGISTER_ID_PT_8_15, 2, buf, 6);
+	if (error != 0)
+		aprint_error_dev(self, "read id 2: %d\n", error);
+	readcrc = buf[5] << 8 | buf[4];
+	crc = am2315_crc(buf, 4);
+	DUMP("id3+id4");
+	id = id << 8 | buf[2];
+	id = id << 8 | buf[3];
+	idgood = buf[0] == AM2315_READ_REGISTERS && buf[1] == 2
+	    && crc == readcrc && idgood;
+
+	iic_release_bus(sc->sc_tag, 0);
+
+	for (int i = 0; i < sc->sc_numsensors; i++) {
+		strlcpy(sc->sc_sensors[i].desc, am2315_sensors[i].desc,
+		    sizeof(sc->sc_sensors[i].desc));
+
+		sc->sc_sensors[i].units = am2315_sensors[i].type;
+		sc->sc_sensors[i].state = ENVSYS_SINVALID;
+
+		DPRINTF(sc, 2, ("am2315_attach: registering sensor %d (%s)\n",
+		    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\n",
+			    error);
+			goto badregister;
+		}
+	}
+
+	sc->sc_sme->sme_name = device_xname(sc->sc_dev);
+	sc->sc_sme->sme_cookie = sc;
+	sc->sc_sme->sme_refresh = am2315_refresh;
+
+	DPRINTF(sc, 2, ("am2315_attach: registering with envsys\n"));
+
+	error = sysmon_envsys_register(sc->sc_sme);
+	if (error) {
+		aprint_error_dev(self, "unable to register with sysmon %d\n",
+		    error);
+		goto badregister;
+	}
+	aprint_normal_dev(self, "Aosong AM2315, Model: %04x%s Version: %02x%s"
+	    " ID: %08x%s",
+	    model, (modelgood ? "," : "(inaccurate),"),
+	    chipver, (chipvergood ? "," : "(inaccurate),"),
+	    id, (idgood ? "\n" : "(inaccurate)\n"));
+	return;
+
+badsysctl:
+	aprint_error_dev(self, ": can't setup sysctl tree (%d)\n", error);
+	return;
+badregister:
+	sysmon_envsys_destroy(sc->sc_sme);
+	sc->sc_sme = NULL;
+}
+
+static void
+am2315_refresh(struct sysmon_envsys * sme, envsys_data_t * edata)
+{
+	struct am2315_sc *sc;
+	uint8_t buf[11], thecommand;
+	uint16_t crc, readcrc;
+	int  error, rv;
+	uint32_t val32;
+	bool istempneg = false;
+
+	sc = sme->sme_cookie;
+	edata->state = ENVSYS_SINVALID;
+
+	mutex_enter(&sc->sc_mutex);
+	error = iic_acquire_bus(sc->sc_tag, 0);
+	if (error == 0) {
+		DPRINTF(sc, 2, ("%s: Could not acquire i2c bus: %d\n",
+		    device_xname(sc->sc_dev), error));
+		goto out;
+	}
+
+	switch (edata->sensor) {
+	case AM2315_HUMIDITY_SENSOR:
+		thecommand = AM2315_REGISTER_HIGH_RH;
+		break;
+	case AM2315_TEMP_SENSOR:
+		thecommand = AM2315_REGISTER_HIGH_TEMP;
+		break;
+	default:
+		DPRINTF(sc, 2, ("%s: bad sensor %d\n",
+		    device_xname(sc->sc_dev), edata->sensor));
+		goto out;
+	}
+
+	for (int count = 0; ;) {
+		am2315_poke(sc);
+
+		if ((error = am2315_read_regs(sc, thecommand, 2, buf, 6)) != 0)
+			aprint_error_dev(sc->sc_dev,
+			    "Read sensor %d error: %d\n", edata->sensor, error);
+
+		readcrc = buf[5] << 8 | buf[4];
+		crc = am2315_crc(buf, 4);
+
+		DPRINTF(sc, 2, ("%s: read cmd+len+dh+dl+crch+crcl values: %02x"
+		    " %02x %02x%02x %02x%02x -- %04x %04x -- %d\n",
+		    device_xname(sc->sc_dev), buf[0], buf[1], buf[2],
+		    buf[3], buf[4], buf[5], crc, readcrc, count));
+		if (++count == sc->sc_readcount)
+			break;
+		mutex_enter(&sc->sc_waitmutex);
+		rv = cv_timedwait(&sc->sc_condwait, &sc->sc_waitmutex,
+		    sc->sc_readticks);
+		DPRINTF(sc, 2, ("%s: wait rv: %d\n", device_xname(sc->sc_dev),
+		    rv));
+		mutex_exit(&sc->sc_waitmutex);
+	}
+
+	iic_release_bus(sc->sc_tag, 0);
+
+	if (buf[0] != AM2315_READ_REGISTERS || buf[1] != 2 ||
+	    crc != readcrc) {
+		DPRINTF(sc, 2, ("%s: Invalid sensor data for %d\n",
+		    device_xname(sc->sc_dev), edata->sensor));
+		goto out;
+	}
+
+	switch (edata->sensor) {
+	case AM2315_HUMIDITY_SENSOR:
+		val32 = buf[2] << 8 | buf[3];
+		val32 = val32 * 100000;
+		DPRINTF(sc, 2, ("%s: read translated values RH: %x\n",
+		    device_xname(sc->sc_dev), val32));
+		edata->value_cur = val32;
+		edata->state = ENVSYS_SVALID;
+		break;
+	case AM2315_TEMP_SENSOR:
+		istempneg = (buf[2] & AM2315_TEMP_NEGATIVE);
+		buf[2] = buf[2] & (~AM2315_TEMP_NEGATIVE);
+		val32 = buf[2] << 8 | buf[3];
+		if (istempneg) {
+			val32 = 273150000 - (val32 * 100000);
+		} else {
+			val32 = (val32 * 100000) + 273150000;
+		}
+		DPRINTF(sc, 2, ("%s: read translated values TEMP: %x\n",
+		    device_xname(sc->sc_dev), val32));
+		edata->value_cur = val32;
+		edata->state = ENVSYS_SVALID;
+		break;
+	default:
+		panic("bad sensor %d\n", edata->sensor);
+	}
+out:
+	mutex_exit(&sc->sc_mutex);
+}
+
+static int
+am2315_detach(device_t self, int flags)
+{
+	struct am2315_sc *sc = device_private(self);
+
+	mutex_enter(&sc->sc_mutex);
+
+	/* Remove the sensors */
+	if (sc->sc_sme != NULL) {
+		sysmon_envsys_unregister(sc->sc_sme);
+		sc->sc_sme = NULL;
+	}
+	mutex_exit(&sc->sc_mutex);
+
+	/* Destroy the wait cond */
+	cv_destroy(&sc->sc_condwait);
+
+	/* Remove the sysctl tree */
+	sysctl_teardown(&sc->sc_am2315log);
+
+	/* Remove the mutex */
+	mutex_destroy(&sc->sc_waitmutex);
+	mutex_destroy(&sc->sc_mutex);
+
+	return 0;
+}
+
+MODULE(MODULE_CLASS_DRIVER, am2315temp, "i2cexec,sysmon_envsys");
+
+#ifdef _MODULE
+#include "ioconf.c"
+#endif
+
+static int
+am2315temp_modcmd(modcmd_t cmd, void *opaque)
+{
+	switch (cmd) {
+	case MODULE_CMD_INIT:
+#ifdef _MODULE
+		return config_init_component(cfdriver_ioconf_am2315temp,
+		    cfattach_ioconf_am2315temp, cfdata_ioconf_am2315temp);
+#else
+		return 0;
+#endif
+	case MODULE_CMD_FINI:
+#ifdef _MODULE
+		return config_fini_component(cfdriver_ioconf_am2315temp,
+		    cfattach_ioconf_am2315temp, cfdata_ioconf_am2315temp);
+#else
+		return 0;
+#endif
+	default:
+		return ENOTTY;
+	}
+}
Index: src/sys/dev/i2c/am2315reg.h
diff -u /dev/null src/sys/dev/i2c/am2315reg.h:1.1
--- /dev/null	Thu Dec 28 18:23:47 2017
+++ src/sys/dev/i2c/am2315reg.h	Thu Dec 28 18:23:47 2017
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2017 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_AM2315REG_H_
+#define _DEV_I2C_AM2315REG_H_
+
+#define AM2315_TYPICAL_ADDR	0x5c
+
+#define AM2315_READ_REGISTERS   0x03
+#define AM2315_WRITE_REGISTERS  0x10
+
+#define AM2315_REGISTER_HIGH_RH 0x00
+#define AM2315_REGISTER_LOW_RH 0x01
+#define AM2315_REGISTER_HIGH_TEMP 0x02
+#define AM2315_REGISTER_LOW_TEMP 0x03
+#define AM2315_REGISTER_RETENTION_04 0x04
+#define AM2315_REGISTER_RETENTION_05 0x05
+#define AM2315_REGISTER_RETENTION_06 0x06
+#define AM2315_REGISTER_RETENTION_07 0x07
+#define AM2315_REGISTER_HIGH_MODEL 0x08
+#define AM2315_REGISTER_LOW_MODEL 0x09
+#define AM2315_REGISTER_VERSION 0x0a
+#define AM2315_REGISTER_ID_PT_24_31 0x0b
+#define AM2315_REGISTER_ID_PT_16_23 0x0c
+#define AM2315_REGISTER_ID_PT_8_15 0x0d
+#define AM2315_REGISTER_ID_PT_0_7 0x0e
+#define AM2315_REGISTER_STATUS 0x0f
+#define AM2315_REGISTER_HIGH_USER1 0x10
+#define AM2315_REGISTER_LOW_USER1 0x11
+#define AM2315_REGISTER_HIGH_USER2 0x12
+#define AM2315_REGISTER_LOW_USER2 0x13
+#define AM2315_REGISTER_RETENTION_14 0x14
+#define AM2315_REGISTER_RETENTION_15 0x15
+#define AM2315_REGISTER_RETENTION_16 0x16
+#define AM2315_REGISTER_RETENTION_17 0x17
+#define AM2315_REGISTER_RETENTION_18 0x18
+#define AM2315_REGISTER_RETENTION_19 0x19
+#define AM2315_REGISTER_RETENTION_1a 0x1a
+#define AM2315_REGISTER_RETENTION_1b 0x1b
+#define AM2315_REGISTER_RETENTION_1c 0x1c
+#define AM2315_REGISTER_RETENTION_1d 0x1d
+#define AM2315_REGISTER_RETENTION_1e 0x1e
+#define AM2315_REGISTER_RETENTION_1f 0x1f
+
+#define AM2315_TEMP_NEGATIVE 0x80
+
+#endif
Index: src/sys/dev/i2c/am2315var.h
diff -u /dev/null src/sys/dev/i2c/am2315var.h:1.1
--- /dev/null	Thu Dec 28 18:23:47 2017
+++ src/sys/dev/i2c/am2315var.h	Thu Dec 28 18:23:47 2017
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2017 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_AM2315VAR_H_
+#define _DEV_I2C_AM2315VAR_H_
+
+#include <sys/time.h>
+
+#define AM2315_NUM_SENSORS	2
+#define AM2315_HUMIDITY_SENSOR 0
+#define AM2315_TEMP_SENSOR 1
+
+struct am2315_sc {
+	int 		sc_am2315debug;
+	device_t 	sc_dev;
+	i2c_tag_t 	sc_tag;
+	i2c_addr_t 	sc_addr;
+	kmutex_t 	sc_mutex;
+	kmutex_t 	sc_waitmutex;
+	kcondvar_t 	sc_condwait;
+	int 		sc_numsensors;
+	struct sysmon_envsys *sc_sme;
+	envsys_data_t 	sc_sensors[AM2315_NUM_SENSORS];
+	struct sysctllog *sc_am2315log;
+	int 		sc_readcount;
+	int 		sc_readticks;
+};
+
+struct am2315_sensor {
+	const char     *desc;
+	enum envsys_units type;
+};
+
+#endif
Index: src/sys/dev/i2c/si70xx.c
diff -u /dev/null src/sys/dev/i2c/si70xx.c:1.1
--- /dev/null	Thu Dec 28 18:23:47 2017
+++ src/sys/dev/i2c/si70xx.c	Thu Dec 28 18:23:47 2017
@@ -0,0 +1,1010 @@
+/*	$NetBSD: si70xx.c,v 1.1 2017/12/28 23:23:47 christos Exp $	*/
+
+/*
+ * Copyright (c) 2017 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.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: si70xx.c,v 1.1 2017/12/28 23:23:47 christos Exp $");
+
+/*
+  Driver for the Silicon Labs SI7013/SI7020/SI7021
+*/
+
+#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 <dev/sysmon/sysmonvar.h>
+#include <dev/i2c/i2cvar.h>
+#include <dev/i2c/si70xxreg.h>
+#include <dev/i2c/si70xxvar.h>
+
+
+static uint8_t 	si70xx_crc(uint8_t *, size_t);
+static int 	si70xx_poke(i2c_tag_t, i2c_addr_t, bool);
+static int 	si70xx_match(device_t, cfdata_t, void *);
+static void 	si70xx_attach(device_t, device_t, void *);
+static int 	si70xx_detach(device_t, int);
+static void 	si70xx_refresh(struct sysmon_envsys *, envsys_data_t *);
+static int 	si70xx_update_status(struct si70xx_sc *);
+static int 	si70xx_set_heateron(struct si70xx_sc *);
+static int 	si70xx_set_resolution(struct si70xx_sc *, size_t);
+static int 	si70xx_set_heatervalue(struct si70xx_sc *, size_t);
+static int 	si70xx_verify_sysctl(SYSCTLFN_ARGS);
+static int 	si70xx_verify_sysctl_resolution(SYSCTLFN_ARGS);
+static int 	si70xx_verify_sysctl_heateron(SYSCTLFN_ARGS);
+static int 	si70xx_verify_sysctl_heatervalue(SYSCTLFN_ARGS);
+
+#define SI70XX_DEBUG
+#ifdef SI70XX_DEBUG
+#define DPRINTF(s, l, x) \
+    do { \
+	if (l <= s->sc_si70xxdebug) \
+	    printf x; \
+    } while (/*CONSTCOND*/0)
+#else
+#define DPRINTF(s, l, x)
+#endif
+
+CFATTACH_DECL_NEW(si70xxtemp, sizeof(struct si70xx_sc),
+    si70xx_match, si70xx_attach, si70xx_detach, NULL);
+
+static struct si70xx_sensor si70xx_sensors[] = {
+	{
+		.desc = "humidity",
+		.type = ENVSYS_SRELHUMIDITY,
+	},
+	{
+		.desc = "temperature",
+		.type = ENVSYS_STEMP,
+	}
+};
+
+static struct si70xx_resolution si70xx_resolutions[] = {
+	{
+		.text = "12bit/14bit",
+		.num = 0x00,
+	},
+	{
+		.text = "8bit/12bit",
+		.num = 0x01,
+	},
+	{
+		.text = "10bit/13bit",
+		.num = 0x80,
+	},
+	{
+		.text = "11bit/11bit",
+		.num = 0x81,
+	}
+};
+
+static const char si70xx_resolution_names[] =
+    "12bit/14bit, 8bit/12bit, 10bit/13bit, 11bit/11bit";
+
+static const int si70xx_heatervalues[] = {
+    0xdeadbeef, 0x00, 0x01, 0x02, 0x04, 0x08, 0x0f
+};
+
+int 
+si70xx_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 
+si70xx_verify_sysctl_resolution(SYSCTLFN_ARGS)
+{
+	char buf[SI70XX_RES_NAME];
+	struct si70xx_sc *sc;
+	struct sysctlnode node;
+	int error = 0;
+	size_t i;
+
+	node = *rnode;
+	sc = node.sysctl_data;
+	(void) memcpy(buf, sc->sc_resolution, SI70XX_RES_NAME);
+	node.sysctl_data = buf;
+	error = sysctl_lookup(SYSCTLFN_CALL(&node));
+	if (error || newp == NULL)
+		return error;
+
+	for (i = 0; i < __arraycount(si70xx_resolutions); i++) {
+		if (memcmp(node.sysctl_data, si70xx_resolutions[i].text,
+		    SI70XX_RES_NAME) == 0)
+			break;
+	}
+
+	if (i == __arraycount(si70xx_resolutions))
+		return EINVAL;
+	(void) memcpy(sc->sc_resolution, node.sysctl_data, SI70XX_RES_NAME);
+
+	error = si70xx_set_resolution(sc, i);
+
+	return error;
+}
+
+int 
+si70xx_verify_sysctl_heateron(SYSCTLFN_ARGS)
+{
+	int 		error;
+	bool 		t;
+	struct si70xx_sc *sc;
+	struct sysctlnode node;
+
+	node = *rnode;
+	sc = node.sysctl_data;
+	t = sc->sc_heateron;
+	node.sysctl_data = &t;
+	error = sysctl_lookup(SYSCTLFN_CALL(&node));
+	if (error || newp == NULL)
+		return error;
+
+	sc->sc_heateron = t;
+	error = si70xx_set_heateron(sc);
+
+	return error;
+}
+
+int 
+si70xx_verify_sysctl_heatervalue(SYSCTLFN_ARGS)
+{
+	int 		error = 0, t;
+	struct si70xx_sc *sc;
+	struct sysctlnode node;
+
+	node = *rnode;
+	sc = node.sysctl_data;
+	t = sc->sc_heaterval;
+	node.sysctl_data = &t;
+	error = sysctl_lookup(SYSCTLFN_CALL(&node));
+	if (error || newp == NULL)
+		return (error);
+
+	if (t < 1 || t >= __arraycount(si70xx_heatervalues))
+		return (EINVAL);
+
+	sc->sc_heaterval = t;
+	error = si70xx_set_heatervalue(sc, t);
+
+	return error;
+}
+
+static int
+si70xx_cmd(i2c_tag_t tag, i2c_addr_t addr, uint8_t *cmd,
+    uint8_t clen, uint8_t *buf, size_t blen)
+{
+	uint8_t dir;
+	switch (cmd[0]) {
+	case SI70XX_READ_USER_REG_1:
+	case SI70XX_READ_HEATER_REG:
+	case SI70XX_READ_ID_PT1A:
+	case SI70XX_READ_ID_PT1B:
+	case SI70XX_READ_ID_PT2A:
+	case SI70XX_READ_ID_PT2B:
+	case SI70XX_READ_FW_VERA:
+	case SI70XX_READ_FW_VERB:
+	    dir = I2C_OP_READ_WITH_STOP;
+	    break;
+	case SI70XX_WRITE_USER_REG_1:
+	case SI70XX_WRITE_HEATER_REG:
+	case SI70XX_RESET:
+	    dir = I2C_OP_WRITE_WITH_STOP;
+	    break;
+	case SI70XX_MEASURE_RH_NOHOLD:
+	case SI70XX_MEASURE_TEMP_NOHOLD:
+	    dir = blen == 0 ? I2C_OP_READ : I2C_OP_READ_WITH_STOP;
+	    break;
+	default:
+	    panic("%s: bad command %#x\n", __func__, cmd[0]);
+	}
+
+	memset(buf, 0, blen);
+	return iic_exec(tag, dir, addr, cmd, clen, buf, blen, 0);
+}
+
+static int
+si70xx_cmd0(struct si70xx_sc *sc, uint8_t *buf, size_t blen)
+{
+	return si70xx_cmd(sc->sc_tag, sc->sc_addr, NULL, 0, buf, blen);
+}
+
+static int
+si70xx_cmd1(struct si70xx_sc *sc, uint8_t cmd, uint8_t *buf, size_t blen)
+{
+	return si70xx_cmd(sc->sc_tag, sc->sc_addr, &cmd, 1, buf, blen);
+}
+
+static int
+si70xx_cmd2(struct si70xx_sc *sc, uint8_t cmd1, uint8_t cmd2, uint8_t *buf,
+    size_t blen)
+{
+	uint8_t cmd[] = { cmd1, cmd2 };
+	return si70xx_cmd(sc->sc_tag, sc->sc_addr, cmd, __arraycount(cmd),
+	    buf, blen);
+}
+
+static int
+si70xx_set_heateron(struct si70xx_sc * sc)
+{
+	int error;
+	uint8_t userregister;
+
+	error = iic_acquire_bus(sc->sc_tag, 0);
+	if (error) {
+		DPRINTF(sc, 2, ("%s:%s: Failed to acquire bus: %d\n",
+		    device_xname(sc->sc_dev), __func__, error));
+		return error;
+	}
+
+	error = si70xx_cmd1(sc, SI70XX_READ_USER_REG_1, &userregister, 1);
+	if (error) {
+		DPRINTF(sc, 2, ("%s: Failed to read user register 1: %d\n",
+		    device_xname(sc->sc_dev), error));
+		goto out;
+	}
+
+	DPRINTF(sc, 2, ("%s:%s: reg 1 values before: %#x\n",
+	    device_xname(sc->sc_dev), __func__, userregister));
+	if (sc->sc_heateron) {
+		userregister |= SI70XX_HTRE_MASK;
+	} else {
+		userregister &= ~SI70XX_HTRE_MASK;
+	}
+	DPRINTF(sc, 2, ("%s:%s: user reg 1 values after: %#x\n",
+	    device_xname(sc->sc_dev), __func__, userregister));
+
+	error = si70xx_cmd1(sc, SI70XX_WRITE_USER_REG_1, &userregister, 1);
+	if (error) {
+		DPRINTF(sc, 2, ("%s: Failed to write user register 1: %d\n",
+		    device_xname(sc->sc_dev), error));
+	}
+out:
+	iic_release_bus(sc->sc_tag, 0);
+	return error;
+}
+
+static int
+si70xx_set_resolution(struct si70xx_sc * sc, size_t index)
+{
+	int error;
+	uint8_t userregister;
+
+	error = iic_acquire_bus(sc->sc_tag, 0);
+	if (error) {
+		DPRINTF(sc, 2, ("%s: Failed to acquire bus: %d\n",
+		    device_xname(sc->sc_dev), error));
+		return error;
+	}
+
+	error = si70xx_cmd1(sc, SI70XX_READ_USER_REG_1, &userregister, 1);
+	if (error) {
+		DPRINTF(sc, 2, ("%s: Failed to read user register 1: %d\n",
+		    device_xname(sc->sc_dev), error));
+		goto out;
+	}
+
+	DPRINTF(sc, 2, ("%s:%s: reg 1 values before: %#x\n",
+	    device_xname(sc->sc_dev), __func__, userregister));
+	userregister &= (~SI70XX_RESOLUTION_MASK);
+	userregister |= si70xx_resolutions[index].num;
+	DPRINTF(sc, 2, ("%s:%s: reg 1 values after: %#x\n",
+	    device_xname(sc->sc_dev), __func__, userregister));
+
+	error = si70xx_cmd1(sc, SI70XX_WRITE_USER_REG_1, &userregister, 1);
+	if (error) {
+		DPRINTF(sc, 2, ("%s: Failed to write user register 1: %d\n",
+		    device_xname(sc->sc_dev), error));
+	}
+out:
+	iic_release_bus(sc->sc_tag, 0);
+	return error;
+}
+
+static int
+si70xx_set_heatervalue(struct si70xx_sc * sc, size_t index)
+{
+	int error;
+	uint8_t heaterregister;
+
+	error = iic_acquire_bus(sc->sc_tag, 0);
+	if (error) {
+		DPRINTF(sc, 2, ("%s: Failed to acquire bus: %d\n",
+		    device_xname(sc->sc_dev), error));
+		return error;
+	}
+	error = si70xx_cmd1(sc, SI70XX_READ_HEATER_REG, &heaterregister, 1);
+	if (error) {
+		DPRINTF(sc, 2, ("%s: Failed to read heater register: %d\n",
+		    device_xname(sc->sc_dev), error));
+		goto out;
+	}
+
+	DPRINTF(sc, 2, ("%s:%s: heater values before: %#x\n",
+	    device_xname(sc->sc_dev), __func__, heaterregister));
+	heaterregister &= ~SI70XX_HEATER_MASK;
+	heaterregister |= si70xx_heatervalues[index];
+	DPRINTF(sc, 2, ("%s:%s: heater values after: %#x\n",
+	    device_xname(sc->sc_dev), __func__, heaterregister));
+
+	error = si70xx_cmd1(sc, SI70XX_WRITE_USER_REG_1, &heaterregister, 1);
+	if (error) {
+		DPRINTF(sc, 2, ("%s: Failed to write heater register: %d\n",
+		    device_xname(sc->sc_dev), error));
+	}
+out:
+	iic_release_bus(sc->sc_tag, 0);
+	return error;
+}
+
+static int
+si70xx_update_heater(struct si70xx_sc *sc)
+{
+	size_t i;
+	int error;
+	uint8_t heaterregister;
+
+	error = si70xx_cmd1(sc, SI70XX_READ_HEATER_REG, &heaterregister, 1);
+	if (error) {
+		DPRINTF(sc, 2, ("%s: Failed to read heater register: %d\n",
+		    device_xname(sc->sc_dev), error));
+		return error;
+	}
+
+	DPRINTF(sc, 2, ("%s: read heater reg values: %02x\n",
+	    device_xname(sc->sc_dev), heaterregister));
+
+	uint8_t heat = heaterregister & SI70XX_HEATER_MASK;
+	for (i = 0; i < __arraycount(si70xx_heatervalues); i++) {
+		if (si70xx_heatervalues[i] == heat)
+			break;
+	}
+	sc->sc_heaterval = i != __arraycount(si70xx_heatervalues) ? i : 0;
+	return 0;
+}
+
+static int
+si70xx_update_user(struct si70xx_sc *sc)
+{
+	size_t i;
+	int error;
+	uint8_t userregister;
+
+	error = si70xx_cmd1(sc, SI70XX_READ_USER_REG_1, &userregister, 1);
+	if (error) {
+		DPRINTF(sc, 2, ("%s: Failed to read user register 1: %d\n",
+		    device_xname(sc->sc_dev), error));
+		return error;
+	}
+	DPRINTF(sc, 2, ("%s: read user reg 1 values: %#x\n",
+	    device_xname(sc->sc_dev), userregister));
+
+	uint8_t res = userregister & SI70XX_RESOLUTION_MASK;
+	for (i = 0; i < __arraycount(si70xx_resolutions); i++) {
+		if (si70xx_resolutions[i].num == res)
+			break;
+	}
+
+	if (i != __arraycount(si70xx_resolutions)) {
+		memcpy(sc->sc_resolution, si70xx_resolutions[i].text,
+		    SI70XX_RES_NAME);
+	} else {
+		snprintf(sc->sc_resolution, SI70XX_RES_NAME, "%02x", res);
+	}
+
+	sc->sc_vddok = (userregister & SI70XX_VDDS_MASK) == 0;
+	sc->sc_heaterval = userregister & SI70XX_HTRE_MASK;
+	return 0;
+}
+
+static int
+si70xx_update_status(struct si70xx_sc *sc)
+{
+	int error1 = si70xx_update_user(sc);
+	int error2 = si70xx_update_heater(sc);
+	return error1 ? error1 : error2;
+}
+
+static	uint8_t
+si70xx_crc(uint8_t * data, size_t size)
+{
+	uint8_t crc = 0;
+
+	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) ^ 0x131;
+			else
+				crc <<= 1;
+		}
+	}
+	return crc;
+}
+
+static int
+si70xx_poke(i2c_tag_t tag, i2c_addr_t addr, bool matchdebug)
+{
+	uint8_t reg = SI70XX_READ_USER_REG_1;
+	uint8_t buf;
+	int error;
+
+	error = si70xx_cmd(tag, addr, &reg, 1, &buf, 1);
+	if (matchdebug) {
+		printf("poke X 1: %d\n", error);
+	}
+	return error;
+}
+
+static int
+si70xx_sysctl_init(struct si70xx_sc *sc)
+{
+	int error;
+	const struct sysctlnode *cnode;
+	int sysctlroot_num;
+
+	if ((error = sysctl_createv(&sc->sc_si70xxlog, 0, NULL, &cnode,
+	    0, CTLTYPE_NODE, device_xname(sc->sc_dev),
+	    SYSCTL_DESCR("si70xx controls"), NULL, 0, NULL, 0, CTL_HW,
+	    CTL_CREATE, CTL_EOL)) != 0)
+		return error;
+
+	sysctlroot_num = cnode->sysctl_num;
+
+#ifdef SI70XX_DEBUG
+	if ((error = sysctl_createv(&sc->sc_si70xxlog, 0, NULL, &cnode,
+	    CTLFLAG_READWRITE, CTLTYPE_INT, "debug",
+	    SYSCTL_DESCR("Debug level"), si70xx_verify_sysctl, 0,
+	    &sc->sc_si70xxdebug, 0, CTL_HW, sysctlroot_num, CTL_CREATE,
+	    CTL_EOL)) != 0)
+		return error;
+
+#endif
+
+#ifdef HAVE_I2C_EXECV
+	if ((error = sysctl_createv(&sc->sc_si70xxlog, 0, NULL, &cnode,
+	    CTLFLAG_READWRITE, CTLTYPE_INT, "clockstretch",
+	    SYSCTL_DESCR("Clockstretch value"), si70xx_verify_sysctl, 0,
+	    &sc->sc_clockstretch, 0, CTL_HW, sysctlroot_num, CTL_CREATE,
+	    CTL_EOL)) != 0)
+		return error;
+#endif
+
+
+	if ((error = sysctl_createv(&sc->sc_si70xxlog, 0, NULL, &cnode,
+	    CTLFLAG_READWRITE, CTLTYPE_INT, "readattempts",
+	    SYSCTL_DESCR("The number of times to attempt to read the values"),
+	    si70xx_verify_sysctl, 0, &sc->sc_readattempts, 0, CTL_HW,
+	    sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
+		return error;
+
+
+	if ((error = sysctl_createv(&sc->sc_si70xxlog, 0, NULL, &cnode,
+	    CTLFLAG_READONLY, CTLTYPE_STRING, "resolutions",
+	    SYSCTL_DESCR("Valid resolutions"), 0, 0,
+	    __UNCONST(si70xx_resolution_names),
+	    sizeof(si70xx_resolution_names) + 1,
+	    CTL_HW, sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
+		return error;
+
+	if ((error = sysctl_createv(&sc->sc_si70xxlog, 0, NULL, &cnode,
+	    CTLFLAG_READWRITE, CTLTYPE_STRING, "resolution",
+	    SYSCTL_DESCR("Resolution of RH and Temp"),
+	    si70xx_verify_sysctl_resolution, 0, (void *) sc,
+	    SI70XX_RES_NAME, CTL_HW, sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
+		return error;
+
+	if ((error = sysctl_createv(&sc->sc_si70xxlog, 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_si70xxlog, 0, NULL, &cnode,
+	    CTLFLAG_READONLY, CTLTYPE_BOOL, "vddok",
+	    SYSCTL_DESCR("Vdd at least 1.9v"), NULL, 0, &sc->sc_vddok, 0,
+	    CTL_HW, sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
+		return error;
+
+	if ((error = sysctl_createv(&sc->sc_si70xxlog, 0, NULL, &cnode,
+	    CTLFLAG_READWRITE, CTLTYPE_BOOL, "heateron",
+	    SYSCTL_DESCR("Heater on"), si70xx_verify_sysctl_heateron, 0,
+	    (void *)sc, 0, CTL_HW, sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
+		return error;
+
+	return sysctl_createv(&sc->sc_si70xxlog, 0, NULL, &cnode,
+	    CTLFLAG_READWRITE, CTLTYPE_INT, "heaterstrength",
+	    SYSCTL_DESCR("Heater strength 1 to 6"),
+	    si70xx_verify_sysctl_heatervalue, 0, (void *)sc, 0, CTL_HW,
+	    sysctlroot_num, CTL_CREATE, CTL_EOL);
+}
+
+static int
+si70xx_match(device_t parent, cfdata_t match, void *aux)
+{
+	struct i2c_attach_args *ia;
+	int error;
+	const bool matchdebug = false;
+
+	ia = aux;
+
+	if (ia->ia_name) {
+		/* direct config - check name */
+		if (strcmp(ia->ia_name, "si70xxtemp") != 0)
+			return 0;
+	} else {
+		/* indirect config - check for configured address */
+		if (ia->ia_addr != SI70XX_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 = si70xx_poke(ia->ia_tag, ia->ia_addr, matchdebug);
+	iic_release_bus(ia->ia_tag, 0);
+
+	return error == 0;
+}
+
+static void
+si70xx_attach(device_t parent, device_t self, void *aux)
+{
+	struct si70xx_sc *sc;
+	struct i2c_attach_args *ia;
+	int error, i;
+	int ecount = 0;
+	uint8_t buf[8];
+	uint8_t testcrcpt1[4];
+	uint8_t testcrcpt2[4];
+	uint8_t crc1 = 0, crc2 = 0;
+	uint8_t readcrc1 = 0, readcrc2 = 0;
+	uint8_t fwversion, model;
+
+	ia = aux;
+	sc = device_private(self);
+
+	sc->sc_dev = self;
+	sc->sc_tag = ia->ia_tag;
+	sc->sc_addr = ia->ia_addr;
+	sc->sc_si70xxdebug = 0;
+#ifdef HAVE_I2C_EXECV
+	sc->sc_clockstretch = 2048;
+#endif
+	sc->sc_readattempts = 15;
+	sc->sc_ignorecrc = false;
+	sc->sc_sme = NULL;
+
+	aprint_normal("\n");
+
+	mutex_init(&sc->sc_mutex, MUTEX_DEFAULT, IPL_NONE);
+	sc->sc_numsensors = __arraycount(si70xx_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 = si70xx_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;
+	}
+	error = si70xx_cmd1(sc, SI70XX_RESET, NULL, 0);
+	if (error != 0)
+		aprint_error_dev(self, "Reset failed: %d\n", error);
+
+	delay(15000);	/* 15 ms max */
+
+	error = si70xx_cmd2(sc, SI70XX_READ_ID_PT1A, SI70XX_READ_ID_PT1B,
+	    buf, 8);
+	if (error) {
+		aprint_error_dev(self, "Failed to read first part of ID: %d\n",
+		    error);
+		ecount++;
+	}
+	testcrcpt1[0] = buf[0];
+	testcrcpt1[1] = buf[2];
+	testcrcpt1[2] = buf[4];
+	testcrcpt1[3] = buf[6];
+	readcrc1 = buf[7];
+	crc1 = si70xx_crc(testcrcpt1, 4);
+
+	DPRINTF(sc, 2, ("%s: read 1 values: %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],
+	    crc1));
+
+	error = si70xx_cmd2(sc, SI70XX_READ_ID_PT2A, SI70XX_READ_ID_PT2B,
+	    buf, 8);
+	if (error != 0) {
+		aprint_error_dev(self, "Failed to read second part of ID: %d\n",
+		    error);
+		ecount++;
+	}
+	model = testcrcpt2[0] = buf[0];
+	testcrcpt2[1] = buf[1];
+	testcrcpt2[2] = buf[3];
+	testcrcpt2[3] = buf[4];
+	readcrc2 = buf[5];
+	crc2 = si70xx_crc(testcrcpt2, 4);
+
+	DPRINTF(sc, 2, ("%s: read 2 values: %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], crc2));
+
+	error = si70xx_cmd2(sc, SI70XX_READ_FW_VERA, SI70XX_READ_FW_VERB,
+	    buf, 8);
+
+	if (error) {
+		aprint_error_dev(self, "Failed to read firware version: %d\n",
+		    error);
+		ecount++;
+	}
+	fwversion = buf[0];
+	DPRINTF(sc, 2, ("%s: read fw values: %#x\n", device_xname(sc->sc_dev),
+	    fwversion));
+
+	error = si70xx_update_status(sc);
+	iic_release_bus(sc->sc_tag, 0);
+	if (error != 0) {
+		aprint_error_dev(self, "Failed to update status: %x\n", error);
+		aprint_error_dev(self, "Unable to setup device\n");
+		goto out;
+	}
+
+	for (i = 0; i < sc->sc_numsensors; i++) {
+		strlcpy(sc->sc_sensors[i].desc, si70xx_sensors[i].desc,
+		    sizeof(sc->sc_sensors[i].desc));
+
+		sc->sc_sensors[i].units = si70xx_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 = si70xx_refresh;
+
+	DPRINTF(sc, 2, ("si70xx_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;
+	}
+	if (ecount != 0) {
+		aprint_normal_dev(self, "Could not read model, "
+		    "probably an HTU21D\n");
+		return;
+	}
+
+	char modelstr[64];
+	switch (model) {
+	case 0:
+	case 0xff:
+		snprintf(modelstr, sizeof(modelstr), "Engineering Sample");
+	case 13:
+	case 20:
+	case 21:
+		snprintf(modelstr, sizeof(modelstr), "SI70%d", model);
+		break;
+	default:
+		snprintf(modelstr, sizeof(modelstr), "Unknown SI70%d", model);
+		break;
+	}
+
+	const char *fwversionstr;
+	switch (fwversion) {
+	case 0xff:
+		fwversionstr = "1.0";
+		break;
+	case 0x20:
+		fwversionstr = "2.0";
+		break;
+	default:
+		fwversionstr = "unknown";
+		break;
+	}
+
+	aprint_normal_dev(self, "Silicon Labs Model: %s, "
+	    "Firmware version: %s, "
+	    "Serial number: %02x%02x%02x%02x%02x%02x%02x%02x%s",
+	    modelstr, fwversionstr, testcrcpt1[0], testcrcpt1[1],
+	    testcrcpt1[2], testcrcpt1[3], testcrcpt2[0], testcrcpt2[1],
+	    testcrcpt2[2], testcrcpt2[3],
+	    (crc1 == readcrc1 && crc2 == readcrc2) ? "\n" : " (bad crc)\n");
+	return;
+out:
+	sysmon_envsys_destroy(sc->sc_sme);
+	sc->sc_sme = NULL;
+}
+
+static int
+si70xx_exec(struct si70xx_sc *sc, uint8_t cmd, envsys_data_t *edata)
+{
+	int error;
+	int xdelay;
+	const char *name;
+	int64_t mul, offs;
+	uint8_t buf[3];
+
+	switch (cmd) {
+	case SI70XX_MEASURE_RH_NOHOLD:
+		/*
+		 * The published conversion for RH is: %RH =
+		 * ((125 * RHCODE) / 65536) - 6
+		 * 
+		 * The sysmon infrastructure for RH wants %RH *
+		 * 10^6 The result will fit in 32 bits, but
+		 * the intermediate values will not.
+		 */
+		mul = 125000000;
+		offs = -6000000;
+		/*
+		 * Conversion times for %RH in ms
+		 * 
+		 *        	Typical Max
+		 * 12-bit	10.0	12.0
+		 * 11-bit	 5.8	 7.0
+		 * 10-bit	 3.7	 4.5
+		 *  8-bit	 2.6	 3.1
+		 * 
+		 * A call to read %RH will also read temperature.  The
+		 * conversion time will be the amount of time above
+		 * plus the amount of time for temperature below
+		 */
+		xdelay = 10500;
+		name = "RH";
+		break;
+	case SI70XX_MEASURE_TEMP_NOHOLD:
+		/*
+		 * The published conversion for temp is:
+		 * degree C = ((175.72 * TEMPCODE) / 65536) -
+		 * 46.85
+		 * 
+		 * The sysmon infrastructure for temp wants
+		 * microkelvin.  This is simple, as degree C
+		 * converts directly with K with simple
+		 * addition. The result will fit in 32 bits,
+		 * but the intermediate values will not.
+		 */
+		mul = 175720000;
+		offs = 226300000;
+		/*
+		 * Conversion times for temperature in ms
+	 	 * 
+		 *		Typical	Max
+		 * 14-bit	7.0	10.8
+		 * 13-bit	4.0	 6.2
+		 * 12-bit	2.4	 3.8
+		 * 11-bit	1.5	 2.4
+		 */
+		xdelay = 4750;
+		name = "TEMP";
+		break;
+	default:
+		return EINVAL;
+	}
+
+#if HAVE_I2C_EXECV
+	memset(buf, 0, sizeof(buf));
+	error = iic_execv(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
+	    &cmd, 1, buf, sizeof(buf), 0, I2C_ATTR_CLOCKSTRETCH,
+	    sc->sc_clockstretch, I2C_ATTR_EOL);
+#else
+	/*
+	 * The lower level driver must support the ability to
+	 * do a zero length read, otherwise this breaks
+	 */
+	error = si70xx_cmd1(sc, cmd, buf, 0);
+	if (error) {
+		DPRINTF(sc, 2, ("%s: Failed to read NO HOLD %s %d %d\n",
+		    device_xname(sc->sc_dev), name, 1, error));
+		return error;
+	}
+
+	/*
+	 * It will probably be at least this long... we would
+	 * not have to do this sort of thing if clock
+	 * stretching worked.  Even this is a problem for the
+	 * RPI without a patch to remove a [apparently] not
+	 * needed KASSERT()
+	 */
+	delay(xdelay);
+
+	for (int aint = 0; aint < sc->sc_readattempts; aint++) {
+		error = si70xx_cmd0(sc, buf, sizeof(buf));
+		if (error == 0)
+			break;
+		DPRINTF(sc, 2, ("%s: Failed to read NO HOLD RH"
+		    " %d %d\n", device_xname(sc->sc_dev), 2, error));
+		if (aint < 10) {
+			delay(1000);
+		}
+	}
+#endif
+
+	DPRINTF(sc, 2, ("%s: %s values: %02x%02x%02x - %02x\n",
+	    device_xname(sc->sc_dev), name, buf[0], buf[1], buf[2],
+	    si70xx_crc(buf, 2)));
+
+	uint8_t crc;
+	if (sc->sc_ignorecrc) {
+		crc = buf[2];
+	} else {
+		crc = si70xx_crc(buf, 2);
+	}
+
+	if (crc != buf[2]) {
+		DPRINTF(sc, 2, ("%s: Bad CRC for %s: %#x and %#x\n",
+		    device_xname(sc->sc_dev), name, crc, buf[2]));
+		return EINVAL;
+	}
+
+	uint16_t val16 = (buf[0] << 8) | buf[1];
+	uint64_t val64 = ((mul * val16) >> 16) + offs;
+	DPRINTF(sc, 2, ("%s: %s calculated values: %x %#jx\n",
+	    device_xname(sc->sc_dev), name, val16, (uintmax_t)val64));
+	edata->value_cur = (uint32_t) val64;
+	edata->state = ENVSYS_SVALID;
+	return 0;
+}
+
+static void
+si70xx_refresh(struct sysmon_envsys * sme, envsys_data_t * edata)
+{
+	struct si70xx_sc *sc;
+	int 		error;
+
+	sc = sme->sme_cookie;
+	edata->state = ENVSYS_SINVALID;
+
+	mutex_enter(&sc->sc_mutex);
+	error = iic_acquire_bus(sc->sc_tag, 0);
+	if (error) {
+		DPRINTF(sc, 2, ("%s: Could not acquire i2c bus: %x\n",
+		    device_xname(sc->sc_dev), error));
+		goto out;
+	}
+	error = si70xx_update_status(sc);
+	if (error) {
+		DPRINTF(sc, 2, ("%s: Failed to update status in refresh %d\n",
+		    device_xname(sc->sc_dev), error));
+		goto out1;
+	}
+	switch (edata->sensor) {
+	case SI70XX_HUMIDITY_SENSOR:
+		error = si70xx_exec(sc, SI70XX_MEASURE_RH_HOLD, edata);
+		break;
+
+	case SI70XX_TEMP_SENSOR:
+		error = si70xx_exec(sc, SI70XX_MEASURE_TEMP_HOLD, edata);
+		break;
+	default:
+		error = EINVAL;
+		break;
+	}
+
+	if (error) {
+		DPRINTF(sc, 2, ("%s: Failed to get new status in refresh %d\n",
+		    device_xname(sc->sc_dev), error));
+	}
+out1:
+	iic_release_bus(sc->sc_tag, 0);
+out:
+	mutex_exit(&sc->sc_mutex);
+}
+
+static int
+si70xx_detach(device_t self, int flags)
+{
+	struct si70xx_sc *sc;
+
+	sc = device_private(self);
+
+	mutex_enter(&sc->sc_mutex);
+
+	/* Remove the sensors */
+	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_si70xxlog);
+
+	/* Remove the mutex */
+	mutex_destroy(&sc->sc_mutex);
+
+	return 0;
+}
+
+MODULE(MODULE_CLASS_DRIVER, si70xxtemp, "i2cexec,sysmon_envsys");
+
+#ifdef _MODULE
+#include "ioconf.c"
+#endif
+
+static int
+si70xxtemp_modcmd(modcmd_t cmd, void *opaque)
+{
+
+	switch (cmd) {
+	case MODULE_CMD_INIT:
+#ifdef _MODULE
+		return config_init_component(cfdriver_ioconf_si70xxtemp,
+		    cfattach_ioconf_si70xxtemp, cfdata_ioconf_si70xxtemp);
+#else
+		return 0;
+#endif
+	case MODULE_CMD_FINI:
+#ifdef _MODULE
+		error = config_fini_component(cfdriver_ioconf_si70xxtemp,
+		      cfattach_ioconf_si70xxtemp, cfdata_ioconf_si70xxtemp);
+#else
+		return 0;
+#endif
+	default:
+		return ENOTTY;
+	}
+}
Index: src/sys/dev/i2c/si70xxreg.h
diff -u /dev/null src/sys/dev/i2c/si70xxreg.h:1.1
--- /dev/null	Thu Dec 28 18:23:47 2017
+++ src/sys/dev/i2c/si70xxreg.h	Thu Dec 28 18:23:47 2017
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2017 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_SI70XXREG_H_
+#define _DEV_I2C_SI70XXREG_H_
+
+#define SI70XX_TYPICAL_ADDR	0x40
+
+#define SI70XX_MEASURE_RH_HOLD 0xE5
+#define SI70XX_MEASURE_RH_NOHOLD 0xF5
+#define SI70XX_MEASURE_TEMP_HOLD 0xE3
+#define SI70XX_MEASURE_TEMP_NOHOLD 0xF3
+#define SI70XX_READ_PREVIOUS_TEMP 0xE0
+#define SI70XX_RESET 0xFE
+#define SI70XX_WRITE_USER_REG_1 0xE6
+#define SI70XX_READ_USER_REG_1 0xE7
+#define SI70XX_WRITE_HEATER_REG 0x51
+#define SI70XX_READ_HEATER_REG 0x11
+#define SI70XX_READ_ID_PT1A 0xFA
+#define SI70XX_READ_ID_PT1B 0x0F
+#define SI70XX_READ_ID_PT2A 0xFC
+#define SI70XX_READ_ID_PT2B 0xC9
+#define SI70XX_READ_FW_VERA 0x84
+#define SI70XX_READ_FW_VERB 0xB8
+
+#define SI70XX_VDDS_MASK 0x40
+#define SI70XX_HTRE_MASK 0x04
+#define SI70XX_RESOLUTION_MASK 0x81
+#define SI70XX_HEATER_MASK 0x0F
+
+#endif
Index: src/sys/dev/i2c/si70xxvar.h
diff -u /dev/null src/sys/dev/i2c/si70xxvar.h:1.1
--- /dev/null	Thu Dec 28 18:23:47 2017
+++ src/sys/dev/i2c/si70xxvar.h	Thu Dec 28 18:23:47 2017
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2017 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_SI70XXVAR_H_
+#define _DEV_I2C_SI70XXVAR_H_
+
+#define SI70XX_NUM_SENSORS	2
+#define SI70XX_HUMIDITY_SENSOR 0
+#define SI70XX_TEMP_SENSOR 1
+
+#define SI70XX_RES_NAME 12
+
+struct si70xx_sc {
+	int 		sc_si70xxdebug;
+	device_t 	sc_dev;
+	i2c_tag_t 	sc_tag;
+	i2c_addr_t 	sc_addr;
+	kmutex_t 	sc_mutex;
+	int 		sc_numsensors;
+	struct sysmon_envsys *sc_sme;
+	envsys_data_t 	sc_sensors[SI70XX_NUM_SENSORS];
+	struct sysctllog *sc_si70xxlog;
+	char 		sc_resolution[SI70XX_RES_NAME];
+	int 		sc_resnumber;
+	bool 		sc_ignorecrc;
+	bool 		sc_vddok;
+	bool 		sc_heateron;
+	int 		sc_heaterval;
+#ifdef HAVE_I2C_EXECV
+	uint32_t 	sc_clockstretch;
+#endif
+	int 		sc_readattempts;
+};
+
+struct si70xx_sensor {
+	const char     *desc;
+	enum envsys_units type;
+};
+
+struct si70xx_resolution {
+	const char     *text;
+	int 		num;
+};
+
+#endif

Reply via email to