NetBSD/sys/dev/i2c/sht4x.c

887 lines
22 KiB
C

/* $NetBSD: sht4x.c,v 1.3 2022/03/30 00:06:50 pgoyette Exp $ */
/*
* Copyright (c) 2021 Brad Spencer <brad@anduin.eldar.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: sht4x.c,v 1.3 2022/03/30 00:06:50 pgoyette Exp $");
/*
Driver for the Sensirion SHT40/SHT41/SHT45
*/
#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/sht4xreg.h>
#include <dev/i2c/sht4xvar.h>
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, &reg, 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 = (228.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 = 22815; /* this is scaled up already from 228.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, "iic,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;
}
}