blob: 38d0e8159b1009f2b5c5caa783e509b3d490ba62 [file] [log] [blame]
/*
* Copyright (c) 2021 Leonard Pollak
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT sensirion_sgp40
#include <zephyr/device.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>
#include <zephyr/pm/device.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/crc.h>
#include <zephyr/drivers/sensor/sgp40.h>
#include "sgp40.h"
LOG_MODULE_REGISTER(SGP40, CONFIG_SENSOR_LOG_LEVEL);
static uint8_t sgp40_compute_crc(uint16_t value)
{
uint8_t buf[2];
sys_put_be16(value, buf);
return crc8(buf, 2, SGP40_CRC_POLY, SGP40_CRC_INIT, false);
}
static int sgp40_write_command(const struct device *dev, uint16_t cmd)
{
const struct sgp40_config *cfg = dev->config;
uint8_t tx_buf[2];
sys_put_be16(cmd, tx_buf);
return i2c_write_dt(&cfg->bus, tx_buf, sizeof(tx_buf));
}
static int sgp40_start_measurement(const struct device *dev)
{
const struct sgp40_config *cfg = dev->config;
struct sgp40_data *data = dev->data;
uint8_t tx_buf[8];
sys_put_be16(SGP40_CMD_MEASURE_RAW, tx_buf);
sys_put_be24(sys_get_be24(data->rh_param), &tx_buf[2]);
sys_put_be24(sys_get_be24(data->t_param), &tx_buf[5]);
return i2c_write_dt(&cfg->bus, tx_buf, sizeof(tx_buf));
}
static int sgp40_attr_set(const struct device *dev,
enum sensor_channel chan,
enum sensor_attribute attr,
const struct sensor_value *val)
{
struct sgp40_data *data = dev->data;
/*
* Temperature and RH conversion to ticks as explained in datasheet
* in section "I2C commands"
*/
switch ((enum sensor_attribute_sgp40)attr) {
case SENSOR_ATTR_SGP40_TEMPERATURE:
{
uint16_t t_ticks;
int16_t tmp;
tmp = (int16_t)CLAMP(val->val1, SGP40_COMP_MIN_T, SGP40_COMP_MAX_T);
/* adding +87 to avoid most rounding errors through truncation */
t_ticks = (uint16_t)((((tmp + 45) * 65535) + 87) / 175);
sys_put_be16(t_ticks, data->t_param);
data->t_param[2] = sgp40_compute_crc(t_ticks);
}
break;
case SENSOR_ATTR_SGP40_HUMIDITY:
{
uint16_t rh_ticks;
uint8_t tmp;
tmp = (uint8_t)CLAMP(val->val1, SGP40_COMP_MIN_RH, SGP40_COMP_MAX_RH);
/* adding +50 to eliminate rounding errors through truncation */
rh_ticks = (uint16_t)(((tmp * 65535U) + 50U) / 100U);
sys_put_be16(rh_ticks, data->rh_param);
data->rh_param[2] = sgp40_compute_crc(rh_ticks);
}
break;
default:
return -ENOTSUP;
}
return 0;
}
static int sgp40_selftest(const struct device *dev)
{
const struct sgp40_config *cfg = dev->config;
uint8_t rx_buf[3];
uint16_t raw_sample;
int rc;
rc = sgp40_write_command(dev, SGP40_CMD_MEASURE_TEST);
if (rc < 0) {
LOG_ERR("Failed to start selftest!");
return rc;
}
k_sleep(K_MSEC(SGP40_TEST_WAIT_MS));
rc = i2c_read_dt(&cfg->bus, rx_buf, sizeof(rx_buf));
if (rc < 0) {
LOG_ERR("Failed to read data sample.");
return rc;
}
raw_sample = sys_get_be16(rx_buf);
if (sgp40_compute_crc(raw_sample) != rx_buf[2]) {
LOG_ERR("Received invalid CRC from selftest.");
return -EIO;
}
if (raw_sample != SGP40_TEST_OK) {
LOG_ERR("Selftest failed.");
return -EIO;
}
return 0;
}
static int sgp40_sample_fetch(const struct device *dev,
enum sensor_channel chan)
{
struct sgp40_data *data = dev->data;
const struct sgp40_config *cfg = dev->config;
uint8_t rx_buf[3];
uint16_t raw_sample;
int rc;
if (chan != SENSOR_CHAN_GAS_RES && chan != SENSOR_CHAN_ALL) {
return -ENOTSUP;
}
rc = sgp40_start_measurement(dev);
if (rc < 0) {
LOG_ERR("Failed to start measurement.");
return rc;
}
k_sleep(K_MSEC(SGP40_MEASURE_WAIT_MS));
rc = i2c_read_dt(&cfg->bus, rx_buf, sizeof(rx_buf));
if (rc < 0) {
LOG_ERR("Failed to read data sample.");
return rc;
}
raw_sample = sys_get_be16(rx_buf);
if (sgp40_compute_crc(raw_sample) != rx_buf[2]) {
LOG_ERR("Invalid CRC8 for data sample.");
return -EIO;
}
data->raw_sample = raw_sample;
return 0;
}
static int sgp40_channel_get(const struct device *dev,
enum sensor_channel chan,
struct sensor_value *val)
{
const struct sgp40_data *data = dev->data;
if (chan != SENSOR_CHAN_GAS_RES) {
return -ENOTSUP;
}
val->val1 = data->raw_sample;
val->val2 = 0;
return 0;
}
#ifdef CONFIG_PM_DEVICE
static int sgp40_pm_action(const struct device *dev,
enum pm_device_action action)
{
uint16_t cmd;
switch (action) {
case PM_DEVICE_ACTION_RESUME:
/* activate the hotplate by sending a measure command */
cmd = SGP40_CMD_MEASURE_RAW;
break;
case PM_DEVICE_ACTION_SUSPEND:
cmd = SGP40_CMD_HEATER_OFF;
break;
default:
return -ENOTSUP;
}
return sgp40_write_command(dev, cmd);
}
#endif /* CONFIG_PM_DEVICE */
static int sgp40_init(const struct device *dev)
{
const struct sgp40_config *cfg = dev->config;
struct sensor_value comp_data;
if (!device_is_ready(cfg->bus.bus)) {
LOG_ERR("Device not ready.");
return -ENODEV;
}
if (cfg->selftest) {
int rc = sgp40_selftest(dev);
if (rc < 0) {
LOG_ERR("Selftest failed!");
return rc;
}
LOG_DBG("Selftest succeeded!");
}
comp_data.val1 = SGP40_COMP_DEFAULT_T;
sensor_attr_set(dev,
SENSOR_CHAN_GAS_RES,
(enum sensor_attribute) SENSOR_ATTR_SGP40_TEMPERATURE,
&comp_data);
comp_data.val1 = SGP40_COMP_DEFAULT_RH;
sensor_attr_set(dev,
SENSOR_CHAN_GAS_RES,
(enum sensor_attribute) SENSOR_ATTR_SGP40_HUMIDITY,
&comp_data);
return 0;
}
static const struct sensor_driver_api sgp40_api = {
.sample_fetch = sgp40_sample_fetch,
.channel_get = sgp40_channel_get,
.attr_set = sgp40_attr_set,
};
#define SGP40_INIT(n) \
static struct sgp40_data sgp40_data_##n; \
\
static const struct sgp40_config sgp40_config_##n = { \
.bus = I2C_DT_SPEC_INST_GET(n), \
.selftest = DT_INST_PROP(n, enable_selftest), \
}; \
\
PM_DEVICE_DT_INST_DEFINE(n, sgp40_pm_action); \
\
DEVICE_DT_INST_DEFINE(n, \
sgp40_init, \
PM_DEVICE_DT_INST_GET(n), \
&sgp40_data_##n, \
&sgp40_config_##n, \
POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, \
&sgp40_api);
DT_INST_FOREACH_STATUS_OKAY(SGP40_INIT)