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