/* * Copyright (c) 2021 Leonard Pollak * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT sensirion_sgp40 #include #include #include #include #include #include #include #include #include #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)