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