/*	$NetBSD: si70xx.c,v 1.12 2025/01/23 19:13:19 brad Exp $	*/

/*
 * Copyright (c) 2017 Brad Spencer <brad@anduin.eldar.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, 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.12 2025/01/23 19:13:19 brad Exp $");

/*
  Driver for the Silicon Labs SI7013/SI7020/SI7021, HTU21D and SHT21
*/

#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 uint8_t
si70xx_dir(uint8_t cmd, size_t len)
{
	switch (cmd) {
	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:
	case SI70XX_MEASURE_RH_HOLD:
	case SI70XX_MEASURE_TEMP_HOLD:
		return I2C_OP_READ_WITH_STOP;
	case SI70XX_WRITE_USER_REG_1:
	case SI70XX_WRITE_HEATER_REG:
	case SI70XX_RESET:
		return I2C_OP_WRITE_WITH_STOP;
	case SI70XX_MEASURE_RH_NOHOLD:
	case SI70XX_MEASURE_TEMP_NOHOLD:
		return len == 0 ? I2C_OP_WRITE : I2C_OP_READ_WITH_STOP;
	default:
		panic("%s: bad command %#x\n", __func__, cmd);
		return 0;
	}
}

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;
	if (clen == 0)
		dir = blen == 0 ? I2C_OP_READ : I2C_OP_READ_WITH_STOP;
	else
		dir = si70xx_dir(cmd[0], blen);

	if (dir == I2C_OP_READ || dir == I2C_OP_READ_WITH_STOP)
		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_HEATER_REG, &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 = 0;
	if (! sc->sc_noheater) {
		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

	if ((error = sysctl_createv(&sc->sc_si70xxlog, 0, NULL, &cnode,
	    CTLFLAG_READWRITE, CTLTYPE_BOOL, "clockstretch",
	    SYSCTL_DESCR("Use clock stretch commands for measurements"), NULL, 0,
	    &sc->sc_clockstretch, 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_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 (! sc->sc_noheater) {
		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;

		if ((error = 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)) != 0)
			return error;
	}

	return 0;
}

static int
si70xx_match(device_t parent, cfdata_t match, void *aux)
{
	struct i2c_attach_args *ia = aux;
	int error, match_result;
	const bool matchdebug = false;

	if (iic_use_direct_match(ia, match, NULL, &match_result))
		return match_result;

	/* 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 ? I2C_MATCH_ADDRESS_AND_PROBE : 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;
	bool validcrcpt1, validcrcpt2;
	uint8_t readcrc1 = 0, readcrc2 = 0;
	uint8_t fwversion = 0, model, heaterregister;

	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;
	sc->sc_clockstretch = false;
	sc->sc_readattempts = 40;
	sc->sc_ignorecrc = false;
	sc->sc_sme = NULL;
	sc->sc_noheater = false;
	sc->sc_nofw = false;

	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;
	}

	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);
	/* A "real" SI70xx has the CRC cover the entire first part of the
	 * serial number.  An HTU21D has the CRC broken out into each
	 * part of the serial number.
	 */
	validcrcpt1 = (readcrc1 == crc1);
	if (! validcrcpt1) {
		validcrcpt1 = (si70xx_crc(&testcrcpt1[0],1) == buf[1] &&
		    si70xx_crc(&testcrcpt1[1],1) == buf[3] &&
		    si70xx_crc(&testcrcpt1[2],1) == buf[5] &&
		    si70xx_crc(&testcrcpt1[3],1) == buf[7]);
		DPRINTF(sc, 2, ("%s: Part 1 SN CRC was not valid for real type, "
		    "check clone: %d\n", device_xname(sc->sc_dev), validcrcpt1));
	}

	DPRINTF(sc, 2, ("%s: read 1 values: %02x%02x%02x%02x%02x%02x%02x%02x "
	    "- %02x -- %d\n", device_xname(sc->sc_dev), buf[0], buf[1],
	    buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
	    crc1, validcrcpt1));

	error = si70xx_cmd2(sc, SI70XX_READ_ID_PT2A, SI70XX_READ_ID_PT2B,
	    buf, 6);
	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);
	/* It is even stranger for this part of the serial number.  A "real"
	 * SI70XX will have a single CRC for the entire second part, but
	 * an HTU21D has a CRC for each word in this case.
	 *
	 * The datasheet actually agrees with the HTU21D case, and not the "real"
	 * chip.
	 */
	validcrcpt2 = (readcrc2 == crc2);
	if (! validcrcpt2) {
		validcrcpt2 = (si70xx_crc(&testcrcpt2[0],2) == buf[2] &&
		    si70xx_crc(&testcrcpt2[2],2) == buf[5]);
		DPRINTF(sc, 2, ("%s: Part 2 SN CRC was not valid for real type, "
		    "check clone: %d\n", device_xname(sc->sc_dev), validcrcpt2));
	}

	DPRINTF(sc, 2, ("%s: read 2 values: %02x%02x%02x%02x%02x%02x - %02x -- %d\n",
	    device_xname(sc->sc_dev), buf[0], buf[1], buf[2],
	    buf[3], buf[4], buf[5], crc2, validcrcpt2));

	error = si70xx_cmd2(sc, SI70XX_READ_FW_VERA, SI70XX_READ_FW_VERB,
	    buf, 1);

	if (error) {
		aprint_error_dev(self, "Failed to read firmware version: Error %d\n",
		    error);
		sc->sc_nofw = true;
	}
	if (! sc->sc_nofw) {
		fwversion = buf[0];
		DPRINTF(sc, 2, ("%s: read fw values: %#x\n", device_xname(sc->sc_dev),
		    fwversion));
	}

	error = si70xx_cmd1(sc, SI70XX_READ_HEATER_REG, &heaterregister, 1);

	if (error) {
		aprint_error_dev(self, "Failed to read heater register: Error %d\n",
		    error);
		sc->sc_noheater = true;
	}

	error = si70xx_update_status(sc);

	iic_release_bus(sc->sc_tag, 0);

	if ((error = si70xx_sysctl_init(sc)) != 0) {
		aprint_error_dev(self, "Can't setup sysctl tree (%d)\n", error);
		goto out;
	}

	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);
			sc->sc_sme = NULL;
			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;
	}

	char modelstr[64];
	switch (model) {
	case 0:
	case 0xff:
		snprintf(modelstr, sizeof(modelstr), "Engineering Sample");
		break;
	case 13:
	case 20:
	case 21:
		snprintf(modelstr, sizeof(modelstr), "SI70%d", model);
		break;
	default:
		snprintf(modelstr, sizeof(modelstr), "Unknown model %d (maybe an HTU21D)", 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],
	    (validcrcpt1 && validcrcpt2) ? "\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:
	case SI70XX_MEASURE_RH_HOLD:
		/*
		 * 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:
	case SI70XX_MEASURE_TEMP_HOLD:
		/*
		 * 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 (sc->sc_clockstretch) {
		error = si70xx_cmd1(sc, cmd, buf, sizeof(buf));
		if (error) {
			DPRINTF(sc, 2, ("%s: Failed to read HOLD %s %d %d\n",
			    device_xname(sc->sc_dev), name, 1, error));
			return error;
		}
	} else {
		error = si70xx_cmd1(sc, cmd, NULL, 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));
			delay(1000);
		}
	}

	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:
		if (sc->sc_clockstretch)
			error = si70xx_exec(sc, SI70XX_MEASURE_RH_HOLD, edata);
		else
			error = si70xx_exec(sc, SI70XX_MEASURE_RH_NOHOLD, edata);
		break;

	case SI70XX_TEMP_SENSOR:
		if (sc->sc_clockstretch)
			error = si70xx_exec(sc, SI70XX_MEASURE_TEMP_HOLD, edata);
		else
			error = si70xx_exec(sc, SI70XX_MEASURE_TEMP_NOHOLD, 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);
	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, "iic,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
		return config_fini_component(cfdriver_ioconf_si70xxtemp,
		      cfattach_ioconf_si70xxtemp, cfdata_ioconf_si70xxtemp);
#else
		return 0;
#endif
	default:
		return ENOTTY;
	}
}
