75bd3009d3
Many i2c device drivers used similar code to access registers on the ICs they drive. This commit implements that functionality in libi2cdriver and updates the drivers to use the library instead of their own register access functions. The net result is 375+ fewer lines of code and less work for people developing new drivers. The two exceptions were cat24c256 and parts of tda19988. They access the bus in uncommon ways. It doesn't make sense at this time to move their read/write functions into libi2cdriver. Change-Id: Id8280b71af33b710a49944d7f20a7262be9f5988
533 lines
12 KiB
C
533 lines
12 KiB
C
/* Driver for the SHT21 Relative Humidity and Temperature Sensor */
|
|
|
|
#include <minix/ds.h>
|
|
#include <minix/drivers.h>
|
|
#include <minix/i2c.h>
|
|
#include <minix/i2cdriver.h>
|
|
#include <minix/chardriver.h>
|
|
#include <minix/log.h>
|
|
|
|
#include <time.h>
|
|
|
|
/*
|
|
* Device Commands
|
|
*/
|
|
|
|
/*
|
|
* The trigger commands start a measurement. 'Hold' ties up the bus while the
|
|
* measurement is being performed while 'no hold' requires the driver to poll
|
|
* the chip until the data is ready. Hold is faster and requires less message
|
|
* passing while no hold frees up the bus while the measurement is in progress.
|
|
* The worst case conversion times are 85 ms for temperature and 29 ms for
|
|
* humidity. Typical conversion times are about 75% of the worst case times.
|
|
*
|
|
* The driver uses the 'hold' versions of the trigger commands.
|
|
*/
|
|
#define CMD_TRIG_T_HOLD 0xe3
|
|
#define CMD_TRIG_RH_HOLD 0xe5
|
|
#define CMD_TRIG_T_NOHOLD 0xf3
|
|
#define CMD_TRIG_RH_NOHOLD 0xf5
|
|
|
|
/* Read and write the user register contents */
|
|
#define CMD_WR_USR_REG 0xe6
|
|
#define CMD_RD_USR_REG 0xe7
|
|
|
|
/* Resets the chip */
|
|
#define CMD_SOFT_RESET 0xfe
|
|
|
|
/* Status bits included in the measurement need to be masked in calculation */
|
|
#define STATUS_BITS_MASK 0x0003
|
|
|
|
/*
|
|
* The user register has some reserved bits that the device changes over
|
|
* time. The driver must preserve the value of those bits when writing to
|
|
* the user register.
|
|
*/
|
|
#define USR_REG_RESERVED_MASK ((1<<3)|(1<<4)|(1<<5))
|
|
|
|
/* End of Battery flag is set when the voltage drops below 2.25V. */
|
|
#define USR_REG_EOB_MASK (1<<6)
|
|
|
|
/* When powered up and communicating, the register should have only the
|
|
* 'Disable OTP Reload' bit set
|
|
*/
|
|
#define EXPECTED_PWR_UP_TEST_VAL (1<<1)
|
|
|
|
/* Define some constants for the different sensor types on the chip. */
|
|
enum sht21_sensors
|
|
{ SHT21_T, SHT21_RH };
|
|
|
|
/* logging - use with log_warn(), log_info(), log_debug(), log_trace(), etc */
|
|
static struct log log = {
|
|
.name = "sht21",
|
|
.log_level = LEVEL_INFO,
|
|
.log_func = default_log
|
|
};
|
|
|
|
/* device slave address is fixed at 0x40 */
|
|
static i2c_addr_t valid_addrs[2] = {
|
|
0x40, 0x00
|
|
};
|
|
|
|
/* Buffer to store output string returned when reading from device file. */
|
|
#define BUFFER_LEN 64
|
|
char buffer[BUFFER_LEN + 1];
|
|
|
|
/* the bus that this device is on (counting starting at 1) */
|
|
static uint32_t bus;
|
|
|
|
/* slave address of the device */
|
|
static i2c_addr_t address;
|
|
|
|
/* endpoint for the driver for the bus itself. */
|
|
static endpoint_t bus_endpoint;
|
|
|
|
/* Sampling causes self-heating. To limit the self-heating to < 0.1C, the
|
|
* data sheet suggests limiting sampling to 2 samples per second. Since
|
|
* the driver samples temperature and relative humidity at the same time,
|
|
* it's measure function does at most 1 pair of samples per second. It uses
|
|
* this timestamp to see if a measurement was taken less than 1 second ago.
|
|
*/
|
|
static time_t last_sample_time = 0;
|
|
|
|
/*
|
|
* Cache temperature and relative humidity readings. These values are returned
|
|
* when the last_sample_time == current_time to keep the chip activity below
|
|
* 10% to help prevent self-heating.
|
|
*/
|
|
static int32_t cached_t = 0.0;
|
|
static int32_t cached_rh = 0.0;
|
|
|
|
/*
|
|
* An 8-bit CRC is used to validate the readings.
|
|
*/
|
|
#define CRC8_POLYNOMIAL 0x131
|
|
#define CRC8_INITIAL_CRC 0x00
|
|
|
|
/* main driver functions */
|
|
static int sht21_init(void);
|
|
static int sensor_read(enum sht21_sensors sensor, int32_t * measurement);
|
|
static int measure(void);
|
|
|
|
/* CRC functions */
|
|
static uint8_t crc8(uint8_t crc, uint8_t byte);
|
|
static int checksum(uint8_t * bytes, int nbytes, uint8_t expected_crc);
|
|
|
|
/* libchardriver callbacks */
|
|
static struct device *sht21_prepare(dev_t UNUSED(dev));
|
|
static int sht21_transfer(endpoint_t endpt, int opcode, u64_t position,
|
|
iovec_t * iov, unsigned nr_req, endpoint_t UNUSED(user_endpt),
|
|
unsigned int UNUSED(flags));
|
|
static int sht21_other(message * m);
|
|
|
|
/* SEF functions */
|
|
static int sef_cb_lu_state_save(int);
|
|
static int lu_state_restore(void);
|
|
static int sef_cb_init(int type, sef_init_info_t * info);
|
|
static void sef_local_startup(void);
|
|
|
|
/* Entry points to this driver from libchardriver. */
|
|
static struct chardriver sht21_tab = {
|
|
.cdr_open = do_nop,
|
|
.cdr_close = do_nop,
|
|
.cdr_ioctl = nop_ioctl,
|
|
.cdr_prepare = sht21_prepare,
|
|
.cdr_transfer = sht21_transfer,
|
|
.cdr_cleanup = nop_cleanup,
|
|
.cdr_alarm = nop_alarm,
|
|
.cdr_cancel = nop_cancel,
|
|
.cdr_select = nop_select,
|
|
.cdr_other = sht21_other
|
|
};
|
|
|
|
static struct device sht21_device = {
|
|
.dv_base = 0,
|
|
.dv_size = 0
|
|
};
|
|
|
|
/*
|
|
* Performs a soft reset and reads the contents of the user register to ensure
|
|
* that the chip is in a good state and working properly.
|
|
*/
|
|
static int
|
|
sht21_init(void)
|
|
{
|
|
int r;
|
|
uint8_t usr_reg_val;
|
|
|
|
/* Perform a soft-reset */
|
|
r = i2creg_raw_write8(bus_endpoint, address, CMD_SOFT_RESET);
|
|
if (r != OK) {
|
|
return -1;
|
|
}
|
|
|
|
/* soft reset takes up to 15 ms to complete. */
|
|
micro_delay(15000);
|
|
|
|
log_debug(&log, "Soft Reset Complete\n");
|
|
|
|
r = i2creg_read8(bus_endpoint, address, CMD_RD_USR_REG, &usr_reg_val);
|
|
if (r != OK) {
|
|
return -1;
|
|
}
|
|
|
|
/* Check for End of Battery flag. */
|
|
if ((usr_reg_val & USR_REG_EOB_MASK) == USR_REG_EOB_MASK) {
|
|
log_warn(&log, "End of Battery Alarm\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Check that the non-reserved bits are in the default state. */
|
|
if ((usr_reg_val & ~USR_REG_RESERVED_MASK) != EXPECTED_PWR_UP_TEST_VAL) {
|
|
log_warn(&log, "USR_REG has non-default values after reset\n");
|
|
log_warn(&log, "Expected 0x%x | Actual 0x%x",
|
|
EXPECTED_PWR_UP_TEST_VAL,
|
|
(usr_reg_val & ~USR_REG_RESERVED_MASK));
|
|
return -1;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/*
|
|
* Read from the sensor, check the CRC, convert the ADC value into the final
|
|
* representation, and store the result in measurement.
|
|
*/
|
|
static int
|
|
sensor_read(enum sht21_sensors sensor, int32_t * measurement)
|
|
{
|
|
int r;
|
|
uint8_t cmd;
|
|
uint16_t val;
|
|
uint8_t bytes[2];
|
|
uint32_t val32;
|
|
uint8_t expected_crc;
|
|
|
|
switch (sensor) {
|
|
case SHT21_T:
|
|
cmd = CMD_TRIG_T_HOLD;
|
|
break;
|
|
case SHT21_RH:
|
|
cmd = CMD_TRIG_RH_HOLD;
|
|
break;
|
|
default:
|
|
log_warn(&log, "sensor_read() called with bad sensor type.\n");
|
|
return -1;
|
|
}
|
|
|
|
if (measurement == NULL) {
|
|
log_warn(&log, "sensor_read() called with NULL pointer\n");
|
|
return -1;
|
|
}
|
|
|
|
r = i2creg_read24(bus_endpoint, address, cmd, &val32);
|
|
if (r != OK) {
|
|
log_warn(&log, "sensor_read() failed (r=%d)\n", r);
|
|
return -1;
|
|
}
|
|
|
|
expected_crc = val32 & 0xff;
|
|
val = (val32 >> 8) & 0xffff;
|
|
|
|
bytes[0] = (val >> 8) & 0xff;
|
|
bytes[1] = val & 0xff;
|
|
|
|
r = checksum(bytes, 2, expected_crc);
|
|
if (r != OK) {
|
|
return -1;
|
|
}
|
|
|
|
val &= ~STATUS_BITS_MASK; /* clear status bits */
|
|
|
|
log_debug(&log, "Read VAL:0x%x CRC:0x%x\n", val, expected_crc);
|
|
|
|
/* Convert the ADC value to the actual value. */
|
|
if (cmd == CMD_TRIG_T_HOLD) {
|
|
*measurement = (int32_t)
|
|
((-46.85 + ((175.72 / 65536) * ((float) val))) * 1000.0);
|
|
log_debug(&log, "Measured Temperature %d mC\n", *measurement);
|
|
} else if (cmd == CMD_TRIG_RH_HOLD) {
|
|
*measurement =
|
|
(int32_t) ((-6.0 +
|
|
((125.0 / 65536) * ((float) val))) * 1000.0);
|
|
log_debug(&log, "Measured Humidity %d m%%\n", *measurement);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
static int
|
|
measure(void)
|
|
{
|
|
int r;
|
|
time_t sample_time;
|
|
int32_t t, rh;
|
|
|
|
log_debug(&log, "Taking a measurement...");
|
|
|
|
sample_time = time(NULL);
|
|
if (sample_time == last_sample_time) {
|
|
log_debug(&log, "measure() called too soon, using cache.\n");
|
|
return OK;
|
|
}
|
|
|
|
r = sensor_read(SHT21_T, &t);
|
|
if (r != OK) {
|
|
return -1;
|
|
}
|
|
|
|
r = sensor_read(SHT21_RH, &rh);
|
|
if (r != OK) {
|
|
return -1;
|
|
}
|
|
|
|
/* save measured values */
|
|
cached_t = t;
|
|
cached_rh = rh;
|
|
last_sample_time = time(NULL);
|
|
|
|
log_debug(&log, "Measurement completed\n");
|
|
|
|
return OK;
|
|
}
|
|
|
|
/*
|
|
* Return an updated checksum for the given crc and byte.
|
|
*/
|
|
static uint8_t
|
|
crc8(uint8_t crc, uint8_t byte)
|
|
{
|
|
int i;
|
|
|
|
crc ^= byte;
|
|
|
|
for (i = 0; i < 8; i++) {
|
|
|
|
if ((crc & 0x80) == 0x80) {
|
|
crc = (crc << 1) ^ CRC8_POLYNOMIAL;
|
|
} else {
|
|
crc <<= 1;
|
|
}
|
|
}
|
|
|
|
return crc;
|
|
}
|
|
|
|
/*
|
|
* Compute the CRC of an array of bytes and compare it to expected_crc.
|
|
* If the computed CRC matches expected_crc, then return OK, otherwise EINVAL.
|
|
*/
|
|
static int
|
|
checksum(uint8_t * bytes, int nbytes, uint8_t expected_crc)
|
|
{
|
|
int i;
|
|
uint8_t crc;
|
|
|
|
crc = CRC8_INITIAL_CRC;
|
|
|
|
log_debug(&log, "Checking CRC\n");
|
|
|
|
for (i = 0; i < nbytes; i++) {
|
|
crc = crc8(crc, bytes[i]);
|
|
}
|
|
|
|
if (crc == expected_crc) {
|
|
log_debug(&log, "CRC OK\n");
|
|
return OK;
|
|
} else {
|
|
log_warn(&log,
|
|
"Bad CRC -- Computed CRC: 0x%x | Expected CRC: 0x%x\n",
|
|
crc, expected_crc);
|
|
return EINVAL;
|
|
}
|
|
}
|
|
|
|
static struct device *
|
|
sht21_prepare(dev_t UNUSED(dev))
|
|
{
|
|
return &sht21_device;
|
|
}
|
|
|
|
static int
|
|
sht21_transfer(endpoint_t endpt, int opcode, u64_t position,
|
|
iovec_t * iov, unsigned nr_req, endpoint_t UNUSED(user_endpt),
|
|
unsigned int UNUSED(flags))
|
|
{
|
|
int bytes, r;
|
|
|
|
r = measure();
|
|
if (r != OK) {
|
|
return EIO;
|
|
}
|
|
|
|
memset(buffer, '\0', BUFFER_LEN + 1);
|
|
snprintf(buffer, BUFFER_LEN, "%-16s: %d.%03d\n%-16s: %d.%03d\n",
|
|
"TEMPERATURE", cached_t / 1000, cached_t % 1000, "HUMIDITY",
|
|
cached_rh / 1000, cached_rh % 1000);
|
|
|
|
log_trace(&log, "%s", buffer);
|
|
|
|
bytes = strlen(buffer) - position < iov->iov_size ?
|
|
strlen(buffer) - position : iov->iov_size;
|
|
|
|
if (bytes <= 0) {
|
|
return OK;
|
|
}
|
|
|
|
switch (opcode) {
|
|
case DEV_GATHER_S:
|
|
r = sys_safecopyto(endpt, (cp_grant_id_t) iov->iov_addr, 0,
|
|
(vir_bytes) (buffer + position), bytes);
|
|
iov->iov_size -= bytes;
|
|
break;
|
|
default:
|
|
return EINVAL;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
static int
|
|
sht21_other(message * m)
|
|
{
|
|
int r;
|
|
|
|
switch (m->m_type) {
|
|
case NOTIFY_MESSAGE:
|
|
if (m->m_source == DS_PROC_NR) {
|
|
log_debug(&log,
|
|
"bus driver changed state, update endpoint\n");
|
|
i2cdriver_handle_bus_update(&bus_endpoint, bus,
|
|
address);
|
|
}
|
|
r = OK;
|
|
break;
|
|
default:
|
|
log_warn(&log, "Invalid message type (0x%x)\n", m->m_type);
|
|
r = EINVAL;
|
|
break;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
static int
|
|
sef_cb_lu_state_save(int UNUSED(state))
|
|
{
|
|
ds_publish_u32("bus", bus, DSF_OVERWRITE);
|
|
ds_publish_u32("address", address, DSF_OVERWRITE);
|
|
return OK;
|
|
}
|
|
|
|
static int
|
|
lu_state_restore(void)
|
|
{
|
|
/* Restore the state. */
|
|
u32_t value;
|
|
|
|
ds_retrieve_u32("bus", &value);
|
|
ds_delete_u32("bus");
|
|
bus = (int) value;
|
|
|
|
ds_retrieve_u32("address", &value);
|
|
ds_delete_u32("address");
|
|
address = (int) value;
|
|
|
|
return OK;
|
|
}
|
|
|
|
static int
|
|
sef_cb_init(int type, sef_init_info_t * UNUSED(info))
|
|
{
|
|
int r;
|
|
|
|
if (type == SEF_INIT_LU) {
|
|
/* Restore the state. */
|
|
lu_state_restore();
|
|
}
|
|
|
|
/* look-up the endpoint for the bus driver */
|
|
bus_endpoint = i2cdriver_bus_endpoint(bus);
|
|
if (bus_endpoint == 0) {
|
|
log_warn(&log, "Couldn't find bus driver.\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/* claim the device */
|
|
r = i2cdriver_reserve_device(bus_endpoint, address);
|
|
if (r != OK) {
|
|
log_warn(&log, "Couldn't reserve device 0x%x (r=%d)\n",
|
|
address, r);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
r = sht21_init();
|
|
if (r != OK) {
|
|
log_warn(&log, "Device Init Failed\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (type != SEF_INIT_LU) {
|
|
|
|
/* sign up for updates about the i2c bus going down/up */
|
|
r = i2cdriver_subscribe_bus_updates(bus);
|
|
if (r != OK) {
|
|
log_warn(&log, "Couldn't subscribe to bus updates\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
i2cdriver_announce(bus);
|
|
log_debug(&log, "announced\n");
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
static void
|
|
sef_local_startup(void)
|
|
{
|
|
/*
|
|
* Register init callbacks. Use the same function for all event types
|
|
*/
|
|
sef_setcb_init_fresh(sef_cb_init);
|
|
sef_setcb_init_lu(sef_cb_init);
|
|
sef_setcb_init_restart(sef_cb_init);
|
|
|
|
/*
|
|
* Register live update callbacks.
|
|
*/
|
|
/* Agree to update immediately when LU is requested in a valid state. */
|
|
sef_setcb_lu_prepare(sef_cb_lu_prepare_always_ready);
|
|
/* Support live update starting from any standard state. */
|
|
sef_setcb_lu_state_isvalid(sef_cb_lu_state_isvalid_standard);
|
|
/* Register a custom routine to save the state. */
|
|
sef_setcb_lu_state_save(sef_cb_lu_state_save);
|
|
|
|
/* Let SEF perform startup. */
|
|
sef_startup();
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
int r;
|
|
|
|
env_setargs(argc, argv);
|
|
|
|
r = i2cdriver_env_parse(&bus, &address, valid_addrs);
|
|
if (r < 0) {
|
|
log_warn(&log, "Expecting -args 'bus=X address=0xYY'\n");
|
|
log_warn(&log, "Example -args 'bus=1 address=0x40'\n");
|
|
return EXIT_FAILURE;
|
|
} else if (r > 0) {
|
|
log_warn(&log,
|
|
"Invalid slave address for device, expecting 0x40\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
sef_local_startup();
|
|
|
|
chardriver_task(&sht21_tab, CHARDRIVER_SYNC);
|
|
|
|
return 0;
|
|
}
|