1 /*
2 * Copyright (c) 2021 Leonard Pollak
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT sensirion_sgp40
8
9 #include <zephyr/device.h>
10 #include <zephyr/drivers/i2c.h>
11 #include <zephyr/kernel.h>
12 #include <zephyr/drivers/sensor.h>
13 #include <zephyr/logging/log.h>
14 #include <zephyr/pm/device.h>
15 #include <zephyr/sys/byteorder.h>
16 #include <zephyr/sys/crc.h>
17
18 #include <zephyr/drivers/sensor/sgp40.h>
19 #include "sgp40.h"
20
21 LOG_MODULE_REGISTER(SGP40, CONFIG_SENSOR_LOG_LEVEL);
22
sgp40_compute_crc(uint16_t value)23 static uint8_t sgp40_compute_crc(uint16_t value)
24 {
25 uint8_t buf[2];
26
27 sys_put_be16(value, buf);
28
29 return crc8(buf, 2, SGP40_CRC_POLY, SGP40_CRC_INIT, false);
30 }
31
sgp40_write_command(const struct device * dev,uint16_t cmd)32 static int sgp40_write_command(const struct device *dev, uint16_t cmd)
33 {
34
35 const struct sgp40_config *cfg = dev->config;
36 uint8_t tx_buf[2];
37
38 sys_put_be16(cmd, tx_buf);
39
40 return i2c_write_dt(&cfg->bus, tx_buf, sizeof(tx_buf));
41 }
42
sgp40_start_measurement(const struct device * dev)43 static int sgp40_start_measurement(const struct device *dev)
44 {
45 const struct sgp40_config *cfg = dev->config;
46 struct sgp40_data *data = dev->data;
47 uint8_t tx_buf[8];
48
49 sys_put_be16(SGP40_CMD_MEASURE_RAW, tx_buf);
50 sys_put_be24(sys_get_be24(data->rh_param), &tx_buf[2]);
51 sys_put_be24(sys_get_be24(data->t_param), &tx_buf[5]);
52
53 return i2c_write_dt(&cfg->bus, tx_buf, sizeof(tx_buf));
54 }
55
sgp40_attr_set(const struct device * dev,enum sensor_channel chan,enum sensor_attribute attr,const struct sensor_value * val)56 static int sgp40_attr_set(const struct device *dev,
57 enum sensor_channel chan,
58 enum sensor_attribute attr,
59 const struct sensor_value *val)
60 {
61 struct sgp40_data *data = dev->data;
62
63 /*
64 * Temperature and RH conversion to ticks as explained in datasheet
65 * in section "I2C commands"
66 */
67
68 switch ((enum sensor_attribute_sgp40)attr) {
69 case SENSOR_ATTR_SGP40_TEMPERATURE:
70 {
71 uint16_t t_ticks;
72 int16_t tmp;
73
74 tmp = (int16_t)CLAMP(val->val1, SGP40_COMP_MIN_T, SGP40_COMP_MAX_T);
75 /* adding +87 to avoid most rounding errors through truncation */
76 t_ticks = (uint16_t)((((tmp + 45) * 65535) + 87) / 175);
77 sys_put_be16(t_ticks, data->t_param);
78 data->t_param[2] = sgp40_compute_crc(t_ticks);
79 }
80 break;
81 case SENSOR_ATTR_SGP40_HUMIDITY:
82 {
83 uint16_t rh_ticks;
84 uint8_t tmp;
85
86 tmp = (uint8_t)CLAMP(val->val1, SGP40_COMP_MIN_RH, SGP40_COMP_MAX_RH);
87 /* adding +50 to eliminate rounding errors through truncation */
88 rh_ticks = (uint16_t)(((tmp * 65535U) + 50U) / 100U);
89 sys_put_be16(rh_ticks, data->rh_param);
90 data->rh_param[2] = sgp40_compute_crc(rh_ticks);
91 }
92 break;
93 default:
94 return -ENOTSUP;
95 }
96 return 0;
97 }
98
sgp40_selftest(const struct device * dev)99 static int sgp40_selftest(const struct device *dev)
100 {
101 const struct sgp40_config *cfg = dev->config;
102 uint8_t rx_buf[3];
103 uint16_t raw_sample;
104 int rc;
105
106 rc = sgp40_write_command(dev, SGP40_CMD_MEASURE_TEST);
107 if (rc < 0) {
108 LOG_ERR("Failed to start selftest!");
109 return rc;
110 }
111
112 k_sleep(K_MSEC(SGP40_TEST_WAIT_MS));
113
114 rc = i2c_read_dt(&cfg->bus, rx_buf, sizeof(rx_buf));
115 if (rc < 0) {
116 LOG_ERR("Failed to read data sample.");
117 return rc;
118 }
119
120 raw_sample = sys_get_be16(rx_buf);
121 if (sgp40_compute_crc(raw_sample) != rx_buf[2]) {
122 LOG_ERR("Received invalid CRC from selftest.");
123 return -EIO;
124 }
125
126 if (raw_sample != SGP40_TEST_OK) {
127 LOG_ERR("Selftest failed.");
128 return -EIO;
129 }
130
131 return 0;
132 }
133
sgp40_sample_fetch(const struct device * dev,enum sensor_channel chan)134 static int sgp40_sample_fetch(const struct device *dev,
135 enum sensor_channel chan)
136 {
137 struct sgp40_data *data = dev->data;
138 const struct sgp40_config *cfg = dev->config;
139 uint8_t rx_buf[3];
140 uint16_t raw_sample;
141 int rc;
142
143 if (chan != SENSOR_CHAN_GAS_RES && chan != SENSOR_CHAN_ALL) {
144 return -ENOTSUP;
145 }
146
147 rc = sgp40_start_measurement(dev);
148 if (rc < 0) {
149 LOG_ERR("Failed to start measurement.");
150 return rc;
151 }
152
153 k_sleep(K_MSEC(SGP40_MEASURE_WAIT_MS));
154
155 rc = i2c_read_dt(&cfg->bus, rx_buf, sizeof(rx_buf));
156 if (rc < 0) {
157 LOG_ERR("Failed to read data sample.");
158 return rc;
159 }
160
161 raw_sample = sys_get_be16(rx_buf);
162 if (sgp40_compute_crc(raw_sample) != rx_buf[2]) {
163 LOG_ERR("Invalid CRC8 for data sample.");
164 return -EIO;
165 }
166
167 data->raw_sample = raw_sample;
168
169 return 0;
170 }
171
sgp40_channel_get(const struct device * dev,enum sensor_channel chan,struct sensor_value * val)172 static int sgp40_channel_get(const struct device *dev,
173 enum sensor_channel chan,
174 struct sensor_value *val)
175 {
176 const struct sgp40_data *data = dev->data;
177
178 if (chan != SENSOR_CHAN_GAS_RES) {
179 return -ENOTSUP;
180 }
181
182 val->val1 = data->raw_sample;
183 val->val2 = 0;
184
185 return 0;
186 }
187
188
189 #ifdef CONFIG_PM_DEVICE
sgp40_pm_action(const struct device * dev,enum pm_device_action action)190 static int sgp40_pm_action(const struct device *dev,
191 enum pm_device_action action)
192 {
193 uint16_t cmd;
194
195 switch (action) {
196 case PM_DEVICE_ACTION_RESUME:
197 /* activate the hotplate by sending a measure command */
198 cmd = SGP40_CMD_MEASURE_RAW;
199 break;
200 case PM_DEVICE_ACTION_SUSPEND:
201 cmd = SGP40_CMD_HEATER_OFF;
202 break;
203 default:
204 return -ENOTSUP;
205 }
206
207 return sgp40_write_command(dev, cmd);
208 }
209 #endif /* CONFIG_PM_DEVICE */
210
sgp40_init(const struct device * dev)211 static int sgp40_init(const struct device *dev)
212 {
213 const struct sgp40_config *cfg = dev->config;
214 struct sensor_value comp_data;
215
216 if (!device_is_ready(cfg->bus.bus)) {
217 LOG_ERR("Device not ready.");
218 return -ENODEV;
219 }
220
221 if (cfg->selftest) {
222 int rc = sgp40_selftest(dev);
223
224 if (rc < 0) {
225 LOG_ERR("Selftest failed!");
226 return rc;
227 }
228 LOG_DBG("Selftest succeeded!");
229 }
230
231 comp_data.val1 = SGP40_COMP_DEFAULT_T;
232 sensor_attr_set(dev,
233 SENSOR_CHAN_GAS_RES,
234 (enum sensor_attribute) SENSOR_ATTR_SGP40_TEMPERATURE,
235 &comp_data);
236 comp_data.val1 = SGP40_COMP_DEFAULT_RH;
237 sensor_attr_set(dev,
238 SENSOR_CHAN_GAS_RES,
239 (enum sensor_attribute) SENSOR_ATTR_SGP40_HUMIDITY,
240 &comp_data);
241
242 return 0;
243 }
244
245 static const struct sensor_driver_api sgp40_api = {
246 .sample_fetch = sgp40_sample_fetch,
247 .channel_get = sgp40_channel_get,
248 .attr_set = sgp40_attr_set,
249 };
250
251 #define SGP40_INIT(n) \
252 static struct sgp40_data sgp40_data_##n; \
253 \
254 static const struct sgp40_config sgp40_config_##n = { \
255 .bus = I2C_DT_SPEC_INST_GET(n), \
256 .selftest = DT_INST_PROP(n, enable_selftest), \
257 }; \
258 \
259 PM_DEVICE_DT_INST_DEFINE(n, sgp40_pm_action); \
260 \
261 SENSOR_DEVICE_DT_INST_DEFINE(n, \
262 sgp40_init, \
263 PM_DEVICE_DT_INST_GET(n), \
264 &sgp40_data_##n, \
265 &sgp40_config_##n, \
266 POST_KERNEL, \
267 CONFIG_SENSOR_INIT_PRIORITY, \
268 &sgp40_api);
269
270 DT_INST_FOREACH_STATUS_OKAY(SGP40_INIT)
271