diff --git a/distrib/sets/lists/man/mi b/distrib/sets/lists/man/mi index 171755e6dd52..f70e2b48ffc4 100644 --- a/distrib/sets/lists/man/mi +++ b/distrib/sets/lists/man/mi @@ -1,4 +1,4 @@ -# $NetBSD: mi,v 1.1725 2021/08/01 21:56:26 andvar Exp $ +# $NetBSD: mi,v 1.1726 2021/10/03 17:27:02 brad Exp $ # # Note: don't delete entries from here - mark them as "obsolete" instead. # @@ -1723,6 +1723,7 @@ ./usr/share/man/cat4/shb.0 man-sys-catman .cat ./usr/share/man/cat4/shmif.0 man-sys-catman .cat ./usr/share/man/cat4/shpcic.0 man-sys-catman .cat +./usr/share/man/cat4/sht4xtemp.0 man-sys-catman .cat ./usr/share/man/cat4/si.0 man-sys-catman .cat ./usr/share/man/cat4/si70xxtemp.0 man-sys-catman .cat ./usr/share/man/cat4/siisata.0 man-sys-catman .cat @@ -4896,6 +4897,7 @@ ./usr/share/man/html4/shb.html man-sys-htmlman html ./usr/share/man/html4/shmif.html man-sys-htmlman html ./usr/share/man/html4/shpcic.html man-sys-htmlman html +./usr/share/man/html4/sht4xtemp.html man-sys-htmlman html ./usr/share/man/html4/si.html man-sys-htmlman html ./usr/share/man/html4/si70xxtemp.html man-sys-htmlman html ./usr/share/man/html4/siisata.html man-sys-htmlman html @@ -7975,6 +7977,7 @@ ./usr/share/man/man4/shb.4 man-sys-man .man ./usr/share/man/man4/shmif.4 man-sys-man .man ./usr/share/man/man4/shpcic.4 man-sys-man .man +./usr/share/man/man4/sht4xtemp.4 man-sys-man .man ./usr/share/man/man4/si.4 man-sys-man .man ./usr/share/man/man4/si70xxtemp.4 man-sys-man .man ./usr/share/man/man4/siisata.4 man-sys-man .man diff --git a/distrib/sets/lists/modules/mi b/distrib/sets/lists/modules/mi index 3fab27667d1c..688cac495cce 100644 --- a/distrib/sets/lists/modules/mi +++ b/distrib/sets/lists/modules/mi @@ -1,4 +1,4 @@ -# $NetBSD: mi,v 1.146 2021/09/11 16:10:37 pgoyette Exp $ +# $NetBSD: mi,v 1.147 2021/10/03 17:27:02 brad Exp $ # # Note: don't delete entries from here - mark them as "obsolete" instead. # @@ -397,6 +397,8 @@ ./@MODULEDIR@/securelevel/securelevel.kmod modules-base-kernel kmod ./@MODULEDIR@/sequencer modules-base-kernel kmod ./@MODULEDIR@/sequencer/sequencer.kmod modules-base-kernel kmod +./@MODULEDIR@/sht4xtemp modules-base-kernel kmod +./@MODULEDIR@/sht4xtemp/sht4xtemp.kmod modules-base-kernel kmod ./@MODULEDIR@/si70xxtemp modules-base-kernel kmod ./@MODULEDIR@/si70xxtemp/si70xxtemp.kmod modules-base-kernel kmod ./@MODULEDIR@/skipjack modules-base-kernel kmod diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile index fd1f18c62b3b..ac4fbdb8feb3 100644 --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -1,4 +1,4 @@ -# $NetBSD: Makefile,v 1.715 2021/08/01 21:56:27 andvar Exp $ +# $NetBSD: Makefile,v 1.716 2021/10/03 17:27:02 brad Exp $ # @(#)Makefile 8.1 (Berkeley) 6/18/93 MAN= aac.4 ac97.4 acardide.4 aceride.4 acphy.4 \ @@ -56,7 +56,7 @@ MAN= aac.4 ac97.4 acardide.4 aceride.4 acphy.4 \ rnd.4 route.4 rs5c372rtc.4 rtk.4 rtsx.4 rtw.4 rtwn.4 rum.4 run.4 \ s390rtc.4 satalink.4 sbus.4 schide.4 \ scsi.4 sctp.4 sd.4 se.4 seeprom.4 sem.4 \ - ses.4 sf.4 sfb.4 sgsmix.4 shb.4 shmif.4 shpcic.4 si70xxtemp.4 \ + ses.4 sf.4 sfb.4 sgsmix.4 shb.4 shmif.4 shpcic.4 sht4xtemp.4 si70xxtemp.4 \ siisata.4 siop.4 sip.4 siside.4 sk.4 sl.4 slide.4 \ sm.4 smscphy.4 smsh.4 sn.4 sony.4 spc.4 speaker.4 spif.4 sqphy.4 \ srt.4 ss.4 \ diff --git a/share/man/man4/sht4xtemp.4 b/share/man/man4/sht4xtemp.4 new file mode 100644 index 000000000000..26bc6614807c --- /dev/null +++ b/share/man/man4/sht4xtemp.4 @@ -0,0 +1,93 @@ +.\" $NetBSD: sht4xtemp.4,v 1.1 2021/10/03 17:27:02 brad Exp $ +.\" +.\" Copyright (c) 2021 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. +.\" +.Dd September 28th, 2021 +.Dt SHT4XTEMP 4 +.Os +.Sh NAME +.Nm sht4xtemp +.Nd Driver for Sensirion SHT40/SHT41/SHT45 sensor chip via I2C bus +.Sh SYNOPSIS +.Cd "sht4xtemp* at iic? addr 0x44" +.Sh DESCRIPTION +The +.Nm +driver provides measurements from the SHT40/SHT41/SHT45 humidity/temperature +sensors via the +.Xr envsys 4 +framework. +The +.Nm +.Ar addr +argument selects the address at the +.Xr iic 4 +bus. +The resolution, heater controls and crc validity can be changed through +.Xr sysctl 8 +nodes. +.Sh SYSCTL VARIABLES +The following +.Xr sysctl 3 +variables are provided: +.Bl -tag -width indent +.It hw.sht4xtemp0.resolutions +Lists the resolutions supported by the driver and chip. +.It hw.sht4xtemp0.resolution +Set the resolution, or number of bits, used for %RH and temperature. +Use one of the strings listed in hw.sht4xtemp.resolutions. +.It hw.sht4xtemp0.ignorecrc +If set, the crc calculation for %RH and temperature will be ignored. +.It hw.sht4xtemp0.heateron +Turn the heater on and off. Please note that the heater is turned on right +before the measurement and runs for a pulse width of time. Then the measurement +is taken and the heater is turned off. There is no way to keep the heater running +with this chip. +.It hw.sht4xtemp0.heaterstrength +From 1 to 3, the amount of energy put into the heater. +The higher the number, the more power used. +.It hw.sht4xtemp0.heaterpulses +Lists the valid heater pulses supported by the driver and chip. +.It hw.sht4xtemp0.heaterpulse +Set the heater pulse length. Use one of the strings listed in +hw.sht4xtemp.heaterpulses +.It hw.sht4xtemp0.debug +If the driver is compiled with +.Dv SI70XX_DEBUG , +this node will appear and can be used to set the debugging level. +.It hw.sht4xtemp0.readattempts +To read %RH or temperature the chip requires that the command be sent, +then a delay must be observed before a read can be done to get the values +back. The delays are documented in the datasheet for the chip. +The driver will attempt to read back the values readattempts number of +times. +The default is 10 which should be more than enough for most purposes. +.El +.Sh SEE ALSO +.Xr envsys 4 , +.Xr iic 4 , +.Xr envstat 8 , +.Xr sysctl 8 +.Sh HISTORY +The +.Nm +driver first appeared in +.Nx 10.0 . +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written by +.An Brad Spencer Aq Mt brad@anduin.eldar.org . diff --git a/sys/dev/i2c/files.i2c b/sys/dev/i2c/files.i2c index 6b7699260d35..e46789e86be6 100644 --- a/sys/dev/i2c/files.i2c +++ b/sys/dev/i2c/files.i2c @@ -1,4 +1,4 @@ -# $NetBSD: files.i2c,v 1.116 2021/07/27 20:23:41 macallan Exp $ +# $NetBSD: files.i2c,v 1.117 2021/10/03 17:27:02 brad Exp $ obsolete defflag opt_i2cbus.h I2C_SCAN define i2cbus { } @@ -395,6 +395,11 @@ device cwfg: sysmon_envsys attach cwfg at iic file dev/i2c/cwfg.c cwfg +# Sensirion SHT40/SHT41/SHT45 Temperature and Humidity sensor +device sht4xtemp +attach sht4xtemp at iic +file dev/i2c/sht4x.c sht4xtemp + # Philips PCA955x GPIO device pcagpio: leds attach pcagpio at iic diff --git a/sys/dev/i2c/sht4x.c b/sys/dev/i2c/sht4x.c new file mode 100644 index 000000000000..35db50d32363 --- /dev/null +++ b/sys/dev/i2c/sht4x.c @@ -0,0 +1,886 @@ +/* $NetBSD: sht4x.c,v 1.1 2021/10/03 17:27:02 brad Exp $ */ + +/* + * Copyright (c) 2021 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: sht4x.c,v 1.1 2021/10/03 17:27:02 brad Exp $"); + +/* + Driver for the Sensirion SHT40/SHT41/SHT45 +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +static uint8_t sht4x_crc(uint8_t *, size_t); +static int sht4x_poke(i2c_tag_t, i2c_addr_t, bool); +static int sht4x_match(device_t, cfdata_t, void *); +static void sht4x_attach(device_t, device_t, void *); +static int sht4x_detach(device_t, int); +static void sht4x_refresh(struct sysmon_envsys *, envsys_data_t *); +static int sht4x_verify_sysctl(SYSCTLFN_ARGS); +static int sht4x_verify_sysctl_resolution(SYSCTLFN_ARGS); +static int sht4x_verify_sysctl_heateron(SYSCTLFN_ARGS); +static int sht4x_verify_sysctl_heatervalue(SYSCTLFN_ARGS); +static int sht4x_verify_sysctl_heaterpulse(SYSCTLFN_ARGS); + +#define SHT4X_DEBUG +#ifdef SHT4X_DEBUG +#define DPRINTF(s, l, x) \ + do { \ + if (l <= s->sc_sht4xdebug) \ + printf x; \ + } while (/*CONSTCOND*/0) +#else +#define DPRINTF(s, l, x) +#endif + +CFATTACH_DECL_NEW(sht4xtemp, sizeof(struct sht4x_sc), + sht4x_match, sht4x_attach, sht4x_detach, NULL); + +static struct sht4x_sensor sht4x_sensors[] = { + { + .desc = "humidity", + .type = ENVSYS_SRELHUMIDITY, + }, + { + .desc = "temperature", + .type = ENVSYS_STEMP, + } +}; + +/* The typical delays are documented in the datasheet for the chip. + There is no need to be very accurate with these, just rough estimates + will work fine. +*/ + +static struct sht4x_timing sht4x_timings[] = { + { + .cmd = SHT4X_READ_SERIAL, + .typicaldelay = 5000, + }, + { + .cmd = SHT4X_SOFT_RESET, + .typicaldelay = 1000, + }, + { + .cmd = SHT4X_MEASURE_HIGH_PRECISION, + .typicaldelay = 8000, + }, + { + .cmd = SHT4X_MEASURE_MEDIUM_PRECISION, + .typicaldelay = 4000, + }, + { + .cmd = SHT4X_MEASURE_LOW_PRECISION, + .typicaldelay = 2000, + }, + { + .cmd = SHT4X_MEASURE_HIGH_PRECISION_HIGH_HEAT_1_S, + .typicaldelay = 1000000, + }, + { + .cmd = SHT4X_MEASURE_HIGH_PRECISION_MEDIUM_HEAT_1_S, + .typicaldelay = 1000000, + }, + { + .cmd = SHT4X_MEASURE_HIGH_PRECISION_LOW_HEAT_1_S, + .typicaldelay = 1000000, + }, + { + .cmd = SHT4X_MEASURE_HIGH_PRECISION_HIGH_HEAT_TENTH_S, + .typicaldelay = 100000, + }, + { + .cmd = SHT4X_MEASURE_HIGH_PRECISION_MEDIUM_HEAT_TENTH_S, + .typicaldelay = 100000, + }, + { + .cmd = SHT4X_MEASURE_HIGH_PRECISION_LOW_HEAT_TENTH_S, + .typicaldelay = 100000, + } +}; + +/* Used when the heater is not on to find the command to use for the + * measurement. + */ + +static struct sht4x_resolution sht4x_resolutions[] = { + { + .text = "high", + .cmd = SHT4X_MEASURE_HIGH_PRECISION, + }, + { + .text = "medium", + .cmd = SHT4X_MEASURE_MEDIUM_PRECISION, + }, + { + .text = "low", + .cmd = SHT4X_MEASURE_LOW_PRECISION, + } +}; + +static const char sht4x_resolution_names[] = + "high, medium, low"; + +static struct sht4x_heaterpulse sht4x_heaterpulses[] = { + { + .length = "short", + }, + { + .length = "long", + } +}; + +/* This is consulted when the heater is on for which command is to be + used for the measurement. +*/ + +static struct sht4x_heateron_command sht4x_heateron_commands[] = { + { + .heatervalue = 1, + .pulselength = "short", + .cmd = SHT4X_MEASURE_HIGH_PRECISION_LOW_HEAT_TENTH_S, + }, + { + .heatervalue = 2, + .pulselength = "short", + .cmd = SHT4X_MEASURE_HIGH_PRECISION_MEDIUM_HEAT_TENTH_S, + }, + { + .heatervalue = 3, + .pulselength = "short", + .cmd = SHT4X_MEASURE_HIGH_PRECISION_HIGH_HEAT_TENTH_S, + }, + { + .heatervalue = 1, + .pulselength = "long", + .cmd = SHT4X_MEASURE_HIGH_PRECISION_LOW_HEAT_1_S, + }, + { + .heatervalue = 2, + .pulselength = "long", + .cmd = SHT4X_MEASURE_HIGH_PRECISION_MEDIUM_HEAT_1_S, + }, + { + .heatervalue = 3, + .pulselength = "long", + .cmd = SHT4X_MEASURE_HIGH_PRECISION_HIGH_HEAT_1_S, + } +}; + +static const char sht4x_heaterpulse_names[] = + "short, long"; + +int +sht4x_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; +} + +/* None of the heater and resolutions sysctls change anything on the chip in + real time. The values set are used to send different commands depending on + how they are set up. + + What this implies is that the chip could be reset and the driver would not care. + +*/ + +int +sht4x_verify_sysctl_resolution(SYSCTLFN_ARGS) +{ + char buf[SHT4X_RES_NAME]; + struct sht4x_sc *sc; + struct sysctlnode node; + int error = 0; + size_t i; + + node = *rnode; + sc = node.sysctl_data; + (void) memcpy(buf, sc->sc_resolution, SHT4X_RES_NAME); + node.sysctl_data = buf; + error = sysctl_lookup(SYSCTLFN_CALL(&node)); + if (error || newp == NULL) + return error; + + for (i = 0; i < __arraycount(sht4x_resolutions); i++) { + if (strncmp(node.sysctl_data, sht4x_resolutions[i].text, + SHT4X_RES_NAME) == 0) { + break; + } + } + + if (i == __arraycount(sht4x_resolutions)) + return EINVAL; + (void) memcpy(sc->sc_resolution, node.sysctl_data, SHT4X_RES_NAME); + + return error; +} + +int +sht4x_verify_sysctl_heateron(SYSCTLFN_ARGS) +{ + int error; + bool t; + struct sht4x_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; + + return error; +} + +int +sht4x_verify_sysctl_heatervalue(SYSCTLFN_ARGS) +{ + int error = 0, t; + struct sht4x_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 > 3) + return (EINVAL); + + sc->sc_heaterval = t; + + return error; +} + +int +sht4x_verify_sysctl_heaterpulse(SYSCTLFN_ARGS) +{ + char buf[SHT4X_PULSE_NAME]; + struct sht4x_sc *sc; + struct sysctlnode node; + int error = 0; + size_t i; + + node = *rnode; + sc = node.sysctl_data; + (void) memcpy(buf, sc->sc_heaterpulse, SHT4X_PULSE_NAME); + node.sysctl_data = buf; + error = sysctl_lookup(SYSCTLFN_CALL(&node)); + if (error || newp == NULL) + return error; + + for (i = 0; i < __arraycount(sht4x_heaterpulses); i++) { + if (strncmp(node.sysctl_data, sht4x_heaterpulses[i].length, + SHT4X_RES_NAME) == 0) { + break; + } + } + + if (i == __arraycount(sht4x_heaterpulses)) + return EINVAL; + (void) memcpy(sc->sc_heaterpulse, node.sysctl_data, SHT4X_PULSE_NAME); + + return error; +} + +static int +sht4x_cmddelay(uint8_t cmd) +{ + int r = -1; + + for(int i = 0;i < __arraycount(sht4x_timings);i++) { + if (cmd == sht4x_timings[i].cmd) { + r = sht4x_timings[i].typicaldelay; + break; + } + } + + if (r == -1) { + panic("Bad command look up in cmd delay: cmd: %d\n",cmd); + } + + return r; +} + +static int +sht4x_cmd(i2c_tag_t tag, i2c_addr_t addr, uint8_t *cmd, + uint8_t clen, uint8_t *buf, size_t blen, int readattempts) +{ + int error; + int cmddelay; + + error = iic_exec(tag,I2C_OP_WRITE_WITH_STOP,addr,cmd,clen,NULL,0,0); + + /* Every command returns something except for the soft reset + which returns nothing. This chip is also nice in that pretty + much every command that returns something does it in the same way. + */ + if (error == 0 && cmd[0] != SHT4X_SOFT_RESET) { + cmddelay = sht4x_cmddelay(cmd[0]); + delay(cmddelay); + + for (int aint = 0; aint < readattempts; aint++) { + error = iic_exec(tag,I2C_OP_READ_WITH_STOP,addr,NULL,0,buf,blen,0); + if (error == 0) + break; + delay(1000); + } + } + + return error; +} + +static int +sht4x_cmdr(struct sht4x_sc *sc, uint8_t cmd, uint8_t *buf, size_t blen) +{ + return sht4x_cmd(sc->sc_tag, sc->sc_addr, &cmd, 1, buf, blen, sc->sc_readattempts); +} + +static uint8_t +sht4x_crc(uint8_t * data, size_t size) +{ + uint8_t crc = 0xFF; + + for (size_t i = 0; i < size; i++) { + crc ^= data[i]; + for (size_t j = 8; j > 0; j--) { + if (crc & 0x80) + crc = (crc << 1) ^ 0x131; + else + crc <<= 1; + } + } + return crc; +} + +static int +sht4x_poke(i2c_tag_t tag, i2c_addr_t addr, bool matchdebug) +{ + uint8_t reg = SHT4X_READ_SERIAL; + uint8_t buf[6]; + int error; + + error = sht4x_cmd(tag, addr, ®, 1, buf, 6, 10); + if (matchdebug) { + printf("poke X 1: %d\n", error); + } + return error; +} + +static int +sht4x_sysctl_init(struct sht4x_sc *sc) +{ + int error; + const struct sysctlnode *cnode; + int sysctlroot_num; + + if ((error = sysctl_createv(&sc->sc_sht4xlog, 0, NULL, &cnode, + 0, CTLTYPE_NODE, device_xname(sc->sc_dev), + SYSCTL_DESCR("sht4x controls"), NULL, 0, NULL, 0, CTL_HW, + CTL_CREATE, CTL_EOL)) != 0) + return error; + + sysctlroot_num = cnode->sysctl_num; + +#ifdef SHT4X_DEBUG + if ((error = sysctl_createv(&sc->sc_sht4xlog, 0, NULL, &cnode, + CTLFLAG_READWRITE, CTLTYPE_INT, "debug", + SYSCTL_DESCR("Debug level"), sht4x_verify_sysctl, 0, + &sc->sc_sht4xdebug, 0, CTL_HW, sysctlroot_num, CTL_CREATE, + CTL_EOL)) != 0) + return error; + +#endif + + if ((error = sysctl_createv(&sc->sc_sht4xlog, 0, NULL, &cnode, + CTLFLAG_READWRITE, CTLTYPE_INT, "readattempts", + SYSCTL_DESCR("The number of times to attempt to read the values"), + sht4x_verify_sysctl, 0, &sc->sc_readattempts, 0, CTL_HW, + sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0) + return error; + + if ((error = sysctl_createv(&sc->sc_sht4xlog, 0, NULL, &cnode, + CTLFLAG_READONLY, CTLTYPE_STRING, "resolutions", + SYSCTL_DESCR("Valid resolutions"), 0, 0, + __UNCONST(sht4x_resolution_names), + sizeof(sht4x_resolution_names) + 1, + CTL_HW, sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0) + return error; + + if ((error = sysctl_createv(&sc->sc_sht4xlog, 0, NULL, &cnode, + CTLFLAG_READWRITE, CTLTYPE_STRING, "resolution", + SYSCTL_DESCR("Resolution of RH and Temp"), + sht4x_verify_sysctl_resolution, 0, (void *) sc, + SHT4X_RES_NAME, CTL_HW, sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0) + return error; + + if ((error = sysctl_createv(&sc->sc_sht4xlog, 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_sht4xlog, 0, NULL, &cnode, + CTLFLAG_READWRITE, CTLTYPE_BOOL, "heateron", + SYSCTL_DESCR("Heater on"), sht4x_verify_sysctl_heateron, 0, + (void *)sc, 0, CTL_HW, sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0) + return error; + + if ((error = sysctl_createv(&sc->sc_sht4xlog, 0, NULL, &cnode, + CTLFLAG_READWRITE, CTLTYPE_INT, "heaterstrength", + SYSCTL_DESCR("Heater strength 1 to 3"), + sht4x_verify_sysctl_heatervalue, 0, (void *)sc, 0, CTL_HW, + sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0) + return error; + + if ((error = sysctl_createv(&sc->sc_sht4xlog, 0, NULL, &cnode, + CTLFLAG_READONLY, CTLTYPE_STRING, "heaterpulses", + SYSCTL_DESCR("Valid heater pulse lengths"), 0, 0, + __UNCONST(sht4x_heaterpulse_names), + sizeof(sht4x_heaterpulse_names) + 1, + CTL_HW, sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0) + return error; + + if ((error = sysctl_createv(&sc->sc_sht4xlog, 0, NULL, &cnode, + CTLFLAG_READWRITE, CTLTYPE_STRING, "heaterpulse", + SYSCTL_DESCR("Heater pulse length"), + sht4x_verify_sysctl_heaterpulse, 0, (void *) sc, + SHT4X_RES_NAME, CTL_HW, sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0) + return error; + return 0; +} + +static int +sht4x_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 != SHT4X_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 = sht4x_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 +sht4x_attach(device_t parent, device_t self, void *aux) +{ + struct sht4x_sc *sc; + struct i2c_attach_args *ia; + int error, i; + int ecount = 0; + uint8_t buf[6]; + uint8_t sncrcpt1, sncrcpt2; + + ia = aux; + sc = device_private(self); + + sc->sc_dev = self; + sc->sc_tag = ia->ia_tag; + sc->sc_addr = ia->ia_addr; + sc->sc_sht4xdebug = 0; + strlcpy(sc->sc_resolution,"high",SHT4X_RES_NAME); + sc->sc_readattempts = 10; + sc->sc_ignorecrc = false; + sc->sc_heateron = false; + sc->sc_heaterval = 1; + strlcpy(sc->sc_heaterpulse,"short",SHT4X_PULSE_NAME); + sc->sc_sme = NULL; + + aprint_normal("\n"); + + mutex_init(&sc->sc_mutex, MUTEX_DEFAULT, IPL_NONE); + sc->sc_numsensors = __arraycount(sht4x_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 = sht4x_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 = sht4x_cmdr(sc, SHT4X_SOFT_RESET, NULL, 0); + if (error != 0) + aprint_error_dev(self, "Reset failed: %d\n", error); + + delay(1000); /* 1 ms max */ + + error = sht4x_cmdr(sc, SHT4X_READ_SERIAL, buf, 6); + if (error) { + aprint_error_dev(self, "Failed to read serial number: %d\n", + error); + ecount++; + } + + sncrcpt1 = sht4x_crc(&buf[0],2); + sncrcpt2 = sht4x_crc(&buf[3],2); + + DPRINTF(sc, 2, ("%s: read serial number values: %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], sncrcpt1, sncrcpt2)); + + iic_release_bus(sc->sc_tag, 0); + if (error != 0) { + 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, sht4x_sensors[i].desc, + sizeof(sc->sc_sensors[i].desc)); + + sc->sc_sensors[i].units = sht4x_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 = sht4x_refresh; + + DPRINTF(sc, 2, ("sht4x_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; + } + + /* There is no documented way to ask the chip what version it is. This + is likely fine as the only apparent difference is in how precise the + measurements will be. The actual conversation with the chip is + identical no matter which one you are talking to. + */ + + aprint_normal_dev(self, "Sensirion SHT40/SHT41/SHT45, " + "Serial number: %02x%02x%02x%02x%s", + buf[0], buf[1], buf[3], buf[4], + (sncrcpt1 == buf[2] && sncrcpt2 == buf[5]) ? "\n" : " (bad crc)\n"); + return; +out: + sysmon_envsys_destroy(sc->sc_sme); + sc->sc_sme = NULL; +} + +/* If you use the heater on this chip, there is no documented choice but to use + the highest precision. If the heater is not in use one may select different + precisions or repeatability for the measurement. + + Further, if the heater is used, it will only be active during the measurement. + The use of the heater will add delay to the measurement as chip will not + return anything until the heater pulse time is over. +*/ + +static uint8_t +sht4x_compute_measure_command(char *resolution, bool heateron, + int heatervalue, char *heaterpulse) +{ + int i; + uint8_t r; + + if (heateron == false) { + for (i = 0; i < __arraycount(sht4x_resolutions); i++) { + if (strncmp(resolution, sht4x_resolutions[i].text, + SHT4X_RES_NAME) == 0) { + r = sht4x_resolutions[i].cmd; + break; + } + } + + if (i == __arraycount(sht4x_resolutions)) + panic("Heater off could not find command for resolution: %s\n",resolution); + } else { + for (i = 0; i < __arraycount(sht4x_heateron_commands); i++) { + if (heatervalue == sht4x_heateron_commands[i].heatervalue && + strncmp(heaterpulse, sht4x_heateron_commands[i].pulselength, + SHT4X_PULSE_NAME) == 0) { + r = sht4x_heateron_commands[i].cmd; + break; + } + } + + if (i == __arraycount(sht4x_heateron_commands)) + panic("Heater on could not find command for heatervalue, heaterpulse: %d %s\n", + heatervalue,heaterpulse); + } + + return r; +} + +static void +sht4x_refresh(struct sysmon_envsys * sme, envsys_data_t * edata) +{ + struct sht4x_sc *sc; + sc = sme->sme_cookie; + int error; + uint8_t rawdata[6]; + uint8_t measurement_command; + 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; + } + + /* + The documented conversion calculations for the raw values are as follows: + + %RH = (-6 + 125 * rawvalue / 65535) + + T in Celsius = (-45 + 175 * rawvalue / 65535) + + It follows then: + + T in Kelvin = (229.15 + 175 * rawvalue / 65535) + + given the relationship between Celsius and Kelvin. + + What follows reorders the calculation a bit and scales it up to avoid + the use of any floating point. All that would really have to happen + is a scale up to 10^6 for the sysenv framework, which wants + temperature in micro-kelvin and percent relative humidity scaled up + 10^6, but since this conversion uses 64 bits due to intermediate + values that are bigger than 32 bits the conversion first scales up to + 10^9 and the scales back down by 10^3 at the end. This preserves some + precision in the conversion that would otherwise be lost. + */ + + measurement_command = sht4x_compute_measure_command(sc->sc_resolution, + sc->sc_heateron, sc->sc_heaterval, sc->sc_heaterpulse); + DPRINTF(sc, 2, ("%s: Measurement command: %02x\n", + device_xname(sc->sc_dev), measurement_command)); + + /* This chip is pretty nice in that all commands are the same length and + return the same result. What is not so nice is that you can not ask + for temperature and humidity independently. + + The result will be 16 bits of raw temperature and a CRC byte followed + by 16 bits of humidity followed by a CRC byte. + */ + + error = sht4x_cmdr(sc,measurement_command,rawdata,6); + + if (error == 0) { + DPRINTF(sc, 2, ("%s: Raw data: %02x%02x %02x - %02x%02x %02x\n", + device_xname(sc->sc_dev), rawdata[0], rawdata[1], rawdata[2], + rawdata[3], rawdata[4], rawdata[5])); + + + uint8_t *svalptr; + uint64_t svalue; + int64_t v1; + uint64_t v2; + uint64_t d1 = 65535; + uint64_t mul1; + uint64_t mul2; + uint64_t div1 = 10000; + uint64_t q; + + switch (edata->sensor) { + case SHT4X_TEMP_SENSOR: + svalptr = &rawdata[0]; + v1 = 22915; /* this is scaled up already from 229.15 */ + v2 = 175; + mul1 = 10000000000; + mul2 = 100000000; + break; + case SHT4X_HUMIDITY_SENSOR: + svalptr = &rawdata[3]; + v1 = -6; + v2 = 125; + mul1 = 10000000000; + mul2 = 10000000000; + break; + default: + error = EINVAL; + break; + } + + if (error == 0) { + uint8_t testcrc; + + /* Fake out the CRC check if being asked to ignore CRC */ + if (sc->sc_ignorecrc) { + testcrc = *(svalptr + 2); + } else { + testcrc = sht4x_crc(svalptr,2); + } + + if (*(svalptr + 2) == testcrc) { + svalue = *svalptr << 8 | *(svalptr + 1); + DPRINTF(sc, 2, ("%s: Raw sensor 16 bit: %#jx\n", + device_xname(sc->sc_dev), (uintmax_t)svalue)); + + /* Scale up */ + svalue = svalue * mul1; + v1 = v1 * mul2; + /* Perform the conversion */ + q = ((v2 * (svalue / d1)) + v1) / div1; + + DPRINTF(sc, 2, ("%s: Computed sensor: %#jx\n", + device_xname(sc->sc_dev), (uintmax_t)q)); + /* The results will fit in 32 bits, so nothing will be lost */ + edata->value_cur = (uint32_t) q; + edata->state = ENVSYS_SVALID; + } else { + error = EINVAL; + } + } + } + + if (error) { + DPRINTF(sc, 2, ("%s: Failed to get new status in refresh %d\n", + device_xname(sc->sc_dev), error)); + } + + iic_release_bus(sc->sc_tag, 0); +out: + mutex_exit(&sc->sc_mutex); +} + +static int +sht4x_detach(device_t self, int flags) +{ + struct sht4x_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_sht4xlog); + + /* Remove the mutex */ + mutex_destroy(&sc->sc_mutex); + + return 0; +} + +MODULE(MODULE_CLASS_DRIVER, sht4xtemp, "i2cexec,sysmon_envsys"); + +#ifdef _MODULE +#include "ioconf.c" +#endif + +static int +sht4xtemp_modcmd(modcmd_t cmd, void *opaque) +{ + + switch (cmd) { + case MODULE_CMD_INIT: +#ifdef _MODULE + return config_init_component(cfdriver_ioconf_sht4xtemp, + cfattach_ioconf_sht4xtemp, cfdata_ioconf_sht4xtemp); +#else + return 0; +#endif + case MODULE_CMD_FINI: +#ifdef _MODULE + return config_fini_component(cfdriver_ioconf_sht4xtemp, + cfattach_ioconf_sht4xtemp, cfdata_ioconf_sht4xtemp); +#else + return 0; +#endif + default: + return ENOTTY; + } +} diff --git a/sys/dev/i2c/sht4xreg.h b/sys/dev/i2c/sht4xreg.h new file mode 100644 index 000000000000..91eb9edeabcb --- /dev/null +++ b/sys/dev/i2c/sht4xreg.h @@ -0,0 +1,44 @@ +/* $NetBSD: sht4xreg.h,v 1.1 2021/10/03 17:27:02 brad Exp $ */ + +/* + * Copyright (c) 2021 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. + */ + +#ifndef _DEV_I2C_SHT4XREG_H_ +#define _DEV_I2C_SHT4XREG_H_ + +#define SHT4X_TYPICAL_ADDR 0x44 + +#define SHT4X_READ_SERIAL 0x89 +#define SHT4X_SOFT_RESET 0x94 + +/* If you do not use the heater, you can take measurements at a couple + of different percisions */ +#define SHT4X_MEASURE_HIGH_PRECISION 0xFD +#define SHT4X_MEASURE_MEDIUM_PRECISION 0xF6 +#define SHT4X_MEASURE_LOW_PRECISION 0xE0 + +/* The SHT4X chip only support the heater when reading with the + highest percision and then only when the measurement is happening. + You can have the heater on for 1 second or 1 tenth of a second. + After the measurement the heater will switch itself off */ +#define SHT4X_MEASURE_HIGH_PRECISION_HIGH_HEAT_1_S 0x39 +#define SHT4X_MEASURE_HIGH_PRECISION_HIGH_HEAT_TENTH_S 0x32 +#define SHT4X_MEASURE_HIGH_PRECISION_MEDIUM_HEAT_1_S 0x2F +#define SHT4X_MEASURE_HIGH_PRECISION_MEDIUM_HEAT_TENTH_S 0x24 +#define SHT4X_MEASURE_HIGH_PRECISION_LOW_HEAT_1_S 0x1E +#define SHT4X_MEASURE_HIGH_PRECISION_LOW_HEAT_TENTH_S 0x15 + +#endif diff --git a/sys/dev/i2c/sht4xvar.h b/sys/dev/i2c/sht4xvar.h new file mode 100644 index 000000000000..7414ba8be89f --- /dev/null +++ b/sys/dev/i2c/sht4xvar.h @@ -0,0 +1,72 @@ +/* $NetBSD: sht4xvar.h,v 1.1 2021/10/03 17:27:02 brad Exp $ */ + +/* + * Copyright (c) 2021 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. + */ + +#ifndef _DEV_I2C_SHT4XVAR_H_ +#define _DEV_I2C_SHT4XVAR_H_ + +#define SHT4X_NUM_SENSORS 2 +#define SHT4X_HUMIDITY_SENSOR 0 +#define SHT4X_TEMP_SENSOR 1 + +#define SHT4X_RES_NAME 7 +#define SHT4X_PULSE_NAME 6 + +struct sht4x_sc { + int sc_sht4xdebug; + 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; + struct sysctllog *sc_sht4xlog; + envsys_data_t sc_sensors[SHT4X_NUM_SENSORS]; + bool sc_ignorecrc; + char sc_resolution[SHT4X_RES_NAME]; + int sc_readattempts; + bool sc_heateron; + int sc_heaterval; + char sc_heaterpulse[SHT4X_PULSE_NAME]; +}; + +struct sht4x_sensor { + const char *desc; + enum envsys_units type; +}; + +struct sht4x_timing { + uint8_t cmd; + int typicaldelay; +}; + +struct sht4x_resolution { + const char *text; + uint8_t cmd; +}; + +struct sht4x_heaterpulse { + const char *length; +}; + +struct sht4x_heateron_command { + int heatervalue; + const char *pulselength; + uint8_t cmd; +}; + +#endif diff --git a/sys/modules/Makefile b/sys/modules/Makefile index 28cee291fbbe..96eeac7a77fe 100644 --- a/sys/modules/Makefile +++ b/sys/modules/Makefile @@ -1,4 +1,4 @@ -# $NetBSD: Makefile,v 1.256 2021/09/25 17:55:37 maya Exp $ +# $NetBSD: Makefile,v 1.257 2021/10/03 17:27:02 brad Exp $ .include @@ -69,6 +69,7 @@ SUBDIR+= hfs SUBDIR+= hythygtemp SUBDIR+= si70xxtemp SUBDIR+= am2315temp +SUBDIR+= sht4xtemp SUBDIR+= i2cexec SUBDIR+= i2c_bitbang SUBDIR+= if_agr diff --git a/sys/modules/sht4xtemp/Makefile b/sys/modules/sht4xtemp/Makefile new file mode 100644 index 000000000000..7c0be3c38473 --- /dev/null +++ b/sys/modules/sht4xtemp/Makefile @@ -0,0 +1,11 @@ +.include "../Makefile.inc" + +.PATH: ${S}/dev/i2c + +KMOD= sht4xtemp +IOCONF= sht4xtemp.ioconf +SRCS= sht4x.c + +WARNS= 3 + +.include diff --git a/sys/modules/sht4xtemp/sht4xtemp.ioconf b/sys/modules/sht4xtemp/sht4xtemp.ioconf new file mode 100644 index 000000000000..d5bddcfed33c --- /dev/null +++ b/sys/modules/sht4xtemp/sht4xtemp.ioconf @@ -0,0 +1,7 @@ +ioconf sht4xtemp + +include "conf/files" + +pseudo-root iic* + +sht4xtemp* at iic? addr 0x44