/* $NetBSD: si70xx.c,v 1.11 2022/03/30 00:06:50 pgoyette Exp $ */ /* * Copyright (c) 2017 Brad Spencer * * 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 __KERNEL_RCSID(0, "$NetBSD: si70xx.c,v 1.11 2022/03/30 00:06:50 pgoyette Exp $"); /* Driver for the Silicon Labs SI7013/SI7020/SI7021, HTU21D and SHT21 */ #include #include #include #include #include #include #include #include #include #include #include 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: 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_READ : 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, ®, 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 (! 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; #ifdef HAVE_I2C_EXECV sc->sc_clockstretch = 2048; #endif 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, 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); /* 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, 8); 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: /* * 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)); 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_NOHOLD, edata); break; case SI70XX_TEMP_SENSOR: 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; } }