zephyr/drivers/sensor/ens160/ens160.c

330 lines
8.7 KiB
C

/*
* Copyright (c) 2024 Gustavo Silva
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT sciosense_ens160
#include <zephyr/drivers/sensor/ens160.h>
#include <zephyr/init.h>
#include <zephyr/kernel.h>
#include <zephyr/pm/pm.h>
#include <zephyr/pm/device.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/__assert.h>
#include "ens160.h"
LOG_MODULE_REGISTER(ENS160, CONFIG_SENSOR_LOG_LEVEL);
static int ens160_set_temperature(const struct device *dev, const struct sensor_value *val)
{
struct ens160_data *data = dev->data;
uint8_t buf[2];
int64_t temp;
int ret;
/* Recommended operation: -5 to 60 degrees Celsius */
if (!IN_RANGE(val->val1, -5, 60)) {
LOG_ERR("Invalid temperature value");
return -EINVAL;
}
/* Convert temperature from Celsius to Kelvin */
temp = sensor_value_to_micro(val) + 273150000U;
/* Temperature is stored in 64 * Kelvin */
temp *= 64;
sys_put_le16(DIV_ROUND_CLOSEST(temp, 1000000U), buf);
ret = data->tf->write_data(dev, ENS160_REG_TEMP_IN, buf, 2U);
if (ret < 0) {
LOG_ERR("Failed to write temperature");
return ret;
}
return 0;
}
static int ens160_set_humidity(const struct device *dev, const struct sensor_value *val)
{
struct ens160_data *data = dev->data;
uint8_t buf[2];
uint64_t rh;
int ret;
/* Recommended operation: 20 to 80% RH */
if (!IN_RANGE(val->val1, 20, 80)) {
LOG_ERR("Invalid RH value");
return -EINVAL;
}
rh = sensor_value_to_micro(val);
/* RH value is stored in 512 * %RH */
rh *= 512;
sys_put_le16(DIV_ROUND_CLOSEST(rh, 1000000U), buf);
ret = data->tf->write_data(dev, ENS160_REG_RH_IN, buf, 2U);
if (ret < 0) {
LOG_ERR("Failed to write RH");
return ret;
}
return 0;
}
static bool ens160_new_data(const struct device *dev)
{
struct ens160_data *data = dev->data;
uint8_t status;
int ret;
ret = data->tf->read_reg(dev, ENS160_REG_DEVICE_STATUS, &status);
if (ret < 0) {
return ret;
}
return FIELD_GET(ENS160_STATUS_NEWDAT, status) != 0;
}
static int ens160_sample_fetch(const struct device *dev, enum sensor_channel chan)
{
struct ens160_data *data = dev->data;
uint16_t le16_buffer;
uint8_t buffer;
int ret;
__ASSERT_NO_MSG(chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_CO2 ||
chan == SENSOR_CHAN_VOC ||
chan == (enum sensor_channel)SENSOR_CHAN_ENS160_AQI);
if (!IS_ENABLED(CONFIG_ENS160_TRIGGER)) {
WAIT_FOR(ens160_new_data(dev), ENS160_TIMEOUT_US, k_msleep(10));
}
ret = data->tf->read_data(dev, ENS160_REG_DATA_ECO2, (uint8_t *)&le16_buffer,
sizeof(le16_buffer));
if (ret < 0) {
LOG_ERR("Failed to fetch CO2");
return ret;
}
data->eco2 = sys_le16_to_cpu(le16_buffer);
ret = data->tf->read_data(dev, ENS160_REG_DATA_TVOC, (uint8_t *)&le16_buffer,
sizeof(le16_buffer));
if (ret < 0) {
LOG_ERR("Failed to fetch VOC");
return ret;
}
data->tvoc = sys_le16_to_cpu(le16_buffer);
ret = data->tf->read_reg(dev, ENS160_REG_DATA_AQI, &buffer);
if (ret < 0) {
LOG_ERR("Failed to fetch AQI");
return ret;
}
data->aqi = FIELD_GET(ENS160_DATA_AQI_UBA, buffer);
return 0;
}
static int ens160_channel_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *val)
{
struct ens160_data *data = dev->data;
switch (chan) {
case SENSOR_CHAN_CO2:
val->val1 = data->eco2;
val->val2 = 0;
break;
case SENSOR_CHAN_VOC:
val->val1 = data->tvoc;
val->val2 = 0;
break;
case SENSOR_CHAN_ENS160_AQI:
val->val1 = data->aqi;
val->val2 = 0;
default:
return -ENOTSUP;
}
return 0;
}
static int ens160_attr_set(const struct device *dev, enum sensor_channel chan,
enum sensor_attribute attr, const struct sensor_value *val)
{
int ret = 0;
switch ((uint32_t)attr) {
case SENSOR_ATTR_ENS160_TEMP:
ret = ens160_set_temperature(dev, val);
break;
case SENSOR_ATTR_ENS160_RH:
ret = ens160_set_humidity(dev, val);
break;
default:
return -ENOTSUP;
}
return ret;
}
static const struct sensor_driver_api ens160_driver_api = {
.sample_fetch = ens160_sample_fetch,
.channel_get = ens160_channel_get,
.attr_set = ens160_attr_set,
#ifdef CONFIG_ENS160_TRIGGER
.trigger_set = ens160_trigger_set,
#endif
};
static int ens160_init(const struct device *dev)
{
const struct ens160_config *config = dev->config;
struct ens160_data *data = dev->data;
uint8_t fw_version[3];
uint16_t part_id;
uint8_t status;
int ret;
ret = config->bus_init(dev);
if (ret < 0) {
return ret;
}
ret = data->tf->write_reg(dev, ENS160_REG_OPMODE, ENS160_OPMODE_RESET);
if (ret < 0) {
LOG_ERR("Failed to reset the device");
return ret;
}
k_msleep(ENS160_BOOTING_TIME_MS);
ret = data->tf->read_data(dev, ENS160_REG_PART_ID, (uint8_t *)&part_id, sizeof(part_id));
if (ret < 0) {
LOG_ERR("Failed to read Part ID");
return -EIO;
}
if (sys_le16_to_cpu(part_id) != ENS160_PART_ID) {
LOG_ERR("Part ID is invalid. Expected: 0x%x; read: 0x%x", ENS160_PART_ID, part_id);
return -EIO;
}
ret = data->tf->write_reg(dev, ENS160_REG_OPMODE, ENS160_OPMODE_IDLE);
if (ret < 0) {
LOG_ERR("Failed to set operation mode");
return ret;
}
k_msleep(ENS160_BOOTING_TIME_MS);
ret = data->tf->write_reg(dev, ENS160_REG_COMMAND, ENS160_COMMAND_CLRGPR);
if (ret < 0) {
LOG_ERR("Failed to clear GPR registers");
return ret;
}
ret = data->tf->write_reg(dev, ENS160_REG_COMMAND, ENS160_COMMAND_GET_APPVER);
if (ret < 0) {
LOG_ERR("Failed to write GET_APPVER command");
return ret;
}
k_msleep(ENS160_BOOTING_TIME_MS);
ret = data->tf->read_data(dev, ENS160_REG_GPR_READ4, fw_version, sizeof(fw_version));
if (ret < 0) {
LOG_ERR("Failed to read firmware version");
return ret;
}
LOG_INF("Firmware version: %u.%u.%u", fw_version[2], fw_version[1], fw_version[0]);
#ifdef CONFIG_ENS160_TRIGGER
ret = ens160_init_interrupt(dev);
if (ret < 0) {
LOG_ERR("Failed to initialize interrupt");
return ret;
}
#endif /* CONFIG_ENS160_TRIGGER */
ret = data->tf->write_reg(dev, ENS160_REG_OPMODE, ENS160_OPMODE_STANDARD);
if (ret < 0) {
LOG_ERR("Failed to set operation mode");
return ret;
}
k_msleep(ENS160_BOOTING_TIME_MS);
ret = data->tf->read_reg(dev, ENS160_REG_DEVICE_STATUS, &status);
if (ret < 0) {
LOG_ERR("Failed to read device status");
return ret;
}
if (FIELD_GET(ENS160_STATUS_VALIDITY_FLAG, status) != ENS160_STATUS_NORMAL) {
LOG_ERR("Status 0x%02x is invalid", status);
return -EINVAL;
}
return 0;
}
#ifdef CONFIG_PM_DEVICE
static int ens160_pm_action(const struct device *dev, enum pm_device_action action)
{
struct ens160_data *data = dev->data;
int ret = 0;
switch (action) {
case PM_DEVICE_ACTION_RESUME:
ret = data->tf->write_reg(dev, ENS160_REG_OPMODE, ENS160_OPMODE_IDLE);
k_msleep(ENS160_BOOTING_TIME_MS);
ret = data->tf->write_reg(dev, ENS160_REG_OPMODE, ENS160_OPMODE_STANDARD);
break;
case PM_DEVICE_ACTION_SUSPEND:
ret = data->tf->write_reg(dev, ENS160_REG_OPMODE, ENS160_OPMODE_DEEP_SLEEP);
break;
default:
return -ENOTSUP;
}
k_msleep(ENS160_BOOTING_TIME_MS);
return ret;
}
#endif
#define ENS160_SPI_OPERATION \
(SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | SPI_MODE_CPOL | SPI_MODE_CPHA | SPI_TRANSFER_MSB)
#define ENS160_CONFIG_SPI(inst) \
.bus_init = &ens160_spi_init, \
.spi = SPI_DT_SPEC_INST_GET(inst, ENS160_SPI_OPERATION, 0),
#define ENS160_CONFIG_I2C(inst) \
.bus_init = &ens160_i2c_init, \
.i2c = I2C_DT_SPEC_INST_GET(inst),
#define ENS160_DEFINE(inst) \
static struct ens160_data ens160_data_##inst; \
static const struct ens160_config ens160_config_##inst = { \
IF_ENABLED(CONFIG_ENS160_TRIGGER, \
(.int_gpio = GPIO_DT_SPEC_INST_GET(inst, int_gpios),)) \
COND_CODE_1(DT_INST_ON_BUS(inst, spi), \
(ENS160_CONFIG_SPI(inst)), \
(ENS160_CONFIG_I2C(inst))) \
}; \
\
PM_DEVICE_DT_INST_DEFINE(inst, ens160_pm_action); \
SENSOR_DEVICE_DT_INST_DEFINE(inst, ens160_init, PM_DEVICE_DT_INST_GET(inst), \
&ens160_data_##inst, &ens160_config_##inst, POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, &ens160_driver_api);
DT_INST_FOREACH_STATUS_OKAY(ENS160_DEFINE)