330 lines
8.7 KiB
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)
|