| /* |
| * 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; |
| break; |
| 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) |