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, ®, 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