1 /*
2  * Copyright (c) 2021 G-Technologies Sdn. Bhd.
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  *
6  * Datasheet:
7  * https://www.winsen-sensor.com/sensors/co2-sensor/mh-z19b.html
8  */
9 
10 #define DT_DRV_COMPAT winsen_mhz19b
11 
12 #include <zephyr/logging/log.h>
13 #include <zephyr/sys/byteorder.h>
14 #include <zephyr/drivers/sensor.h>
15 
16 #include <zephyr/drivers/sensor/mhz19b.h>
17 #include "mhz19b.h"
18 
19 LOG_MODULE_REGISTER(mhz19b, CONFIG_SENSOR_LOG_LEVEL);
20 
21 /* Table of supported MH-Z19B commands with precomputed checksum */
22 static const uint8_t mhz19b_cmds[MHZ19B_CMD_IDX_MAX][MHZ19B_BUF_LEN] = {
23 	[MHZ19B_CMD_IDX_GET_CO2] = {
24 		MHZ19B_HEADER, MHZ19B_RESERVED, MHZ19B_CMD_GET_CO2, MHZ19B_NULL_COUNT(5), 0x79
25 	},
26 	[MHZ19B_CMD_IDX_GET_RANGE] = {
27 		MHZ19B_HEADER, MHZ19B_RESERVED, MHZ19B_CMD_GET_RANGE, MHZ19B_NULL_COUNT(5), 0x64
28 	},
29 	[MHZ19B_CMD_IDX_GET_ABC] = {
30 		MHZ19B_HEADER, MHZ19B_RESERVED, MHZ19B_CMD_GET_ABC, MHZ19B_NULL_COUNT(5), 0x82
31 	},
32 	[MHZ19B_CMD_IDX_SET_ABC_ON] = {
33 		MHZ19B_HEADER, MHZ19B_RESERVED, MHZ19B_CMD_SET_ABC, MHZ19B_ABC_ON,
34 		MHZ19B_NULL_COUNT(4), 0xE6
35 	},
36 	[MHZ19B_CMD_IDX_SET_ABC_OFF] = {
37 		MHZ19B_HEADER, MHZ19B_RESERVED, MHZ19B_CMD_SET_ABC, MHZ19B_ABC_OFF,
38 		MHZ19B_NULL_COUNT(4), 0x86
39 	},
40 	[MHZ19B_CMD_IDX_SET_RANGE_2000] = {
41 		MHZ19B_HEADER, MHZ19B_RESERVED, MHZ19B_CMD_SET_RANGE, MHZ19B_NULL_COUNT(3),
42 		MHZ19B_RANGE_2000, 0x8F
43 	},
44 	[MHZ19B_CMD_IDX_SET_RANGE_5000] = {
45 		MHZ19B_HEADER, MHZ19B_RESERVED, MHZ19B_CMD_SET_RANGE, MHZ19B_NULL_COUNT(3),
46 		MHZ19B_RANGE_5000, 0xCB
47 	},
48 	[MHZ19B_CMD_IDX_SET_RANGE_10000] = {
49 		MHZ19B_HEADER, MHZ19B_RESERVED, MHZ19B_CMD_SET_RANGE, MHZ19B_NULL_COUNT(3),
50 		MHZ19B_RANGE_10000, 0x2F
51 	},
52 };
53 
mhz19b_uart_flush(const struct device * uart_dev)54 static void mhz19b_uart_flush(const struct device *uart_dev)
55 {
56 	uint8_t c;
57 
58 	while (uart_fifo_read(uart_dev, &c, 1) > 0) {
59 		continue;
60 	}
61 }
62 
mhz19b_checksum(const uint8_t * data)63 static uint8_t mhz19b_checksum(const uint8_t *data)
64 {
65 	uint8_t cs = 0;
66 
67 	for (uint8_t i = 1; i < MHZ19B_BUF_LEN - 1; i++) {
68 		cs += data[i];
69 	}
70 
71 	return 0xff - cs + 1;
72 }
73 
mhz19b_send_cmd(const struct device * dev,enum mhz19b_cmd_idx cmd_idx,bool has_rsp)74 static int mhz19b_send_cmd(const struct device *dev, enum mhz19b_cmd_idx cmd_idx, bool has_rsp)
75 {
76 	struct mhz19b_data *data = dev->data;
77 	const struct mhz19b_cfg *cfg = dev->config;
78 	int ret;
79 
80 	/* Make sure last command has been transferred */
81 	ret = k_sem_take(&data->tx_sem, MHZ19B_WAIT);
82 	if (ret) {
83 		return ret;
84 	}
85 
86 	data->cmd_idx = cmd_idx;
87 	data->has_rsp = has_rsp;
88 	k_sem_reset(&data->rx_sem);
89 
90 	uart_irq_tx_enable(cfg->uart_dev);
91 
92 	if (has_rsp) {
93 		uart_irq_rx_enable(cfg->uart_dev);
94 		ret = k_sem_take(&data->rx_sem, MHZ19B_WAIT);
95 	}
96 
97 	return ret;
98 }
99 
mhz19b_send_config(const struct device * dev,enum mhz19b_cmd_idx cmd_idx)100 static inline int mhz19b_send_config(const struct device *dev, enum mhz19b_cmd_idx cmd_idx)
101 {
102 	struct mhz19b_data *data = dev->data;
103 	int ret;
104 
105 	ret = mhz19b_send_cmd(dev, cmd_idx, true);
106 	if (ret < 0) {
107 		return ret;
108 	}
109 
110 	if (data->rd_data[MHZ19B_RX_CMD_IDX] != mhz19b_cmds[data->cmd_idx][MHZ19B_TX_CMD_IDX]) {
111 		return -EINVAL;
112 	}
113 
114 	return 0;
115 }
116 
mhz19b_poll_data(const struct device * dev,enum mhz19b_cmd_idx cmd_idx)117 static inline int mhz19b_poll_data(const struct device *dev, enum mhz19b_cmd_idx cmd_idx)
118 {
119 	struct mhz19b_data *data = dev->data;
120 	uint8_t checksum;
121 	int ret;
122 
123 	ret = mhz19b_send_cmd(dev, cmd_idx, true);
124 	if (ret < 0) {
125 		return ret;
126 	}
127 
128 	checksum = mhz19b_checksum(data->rd_data);
129 	if (checksum != data->rd_data[MHZ19B_CHECKSUM_IDX]) {
130 		LOG_DBG("Checksum mismatch: 0x%x != 0x%x", checksum,
131 			data->rd_data[MHZ19B_CHECKSUM_IDX]);
132 		return -EBADMSG;
133 	}
134 
135 	switch (cmd_idx) {
136 	case MHZ19B_CMD_IDX_GET_CO2:
137 		data->data = sys_get_be16(&data->rd_data[2]);
138 		break;
139 	case MHZ19B_CMD_IDX_GET_RANGE:
140 		data->data = sys_get_be16(&data->rd_data[4]);
141 		break;
142 	case MHZ19B_CMD_IDX_GET_ABC:
143 		data->data = data->rd_data[7];
144 		break;
145 	default:
146 		return -EINVAL;
147 	}
148 
149 	return 0;
150 }
151 
mhz19b_channel_get(const struct device * dev,enum sensor_channel chan,struct sensor_value * val)152 static int mhz19b_channel_get(const struct device *dev, enum sensor_channel chan,
153 			      struct sensor_value *val)
154 {
155 	struct mhz19b_data *data = dev->data;
156 
157 	if (chan != SENSOR_CHAN_CO2) {
158 		return -ENOTSUP;
159 	}
160 
161 	val->val1 = (int32_t)data->data;
162 	val->val2 = 0;
163 
164 	return 0;
165 }
166 
mhz19b_attr_full_scale_cfg(const struct device * dev,int range)167 static int mhz19b_attr_full_scale_cfg(const struct device *dev, int range)
168 {
169 	switch (range) {
170 	case 2000:
171 		LOG_DBG("Configure range to %d", range);
172 		return mhz19b_send_config(dev, MHZ19B_CMD_IDX_SET_RANGE_2000);
173 	case 5000:
174 		LOG_DBG("Configure range to %d", range);
175 		return mhz19b_send_config(dev, MHZ19B_CMD_IDX_SET_RANGE_5000);
176 	case 10000:
177 		LOG_DBG("Configure range to %d", range);
178 		return mhz19b_send_config(dev, MHZ19B_CMD_IDX_SET_RANGE_10000);
179 	default:
180 		return -ENOTSUP;
181 	}
182 }
183 
mhz19b_attr_abc_cfg(const struct device * dev,bool on)184 static int mhz19b_attr_abc_cfg(const struct device *dev, bool on)
185 {
186 	if (on) {
187 		LOG_DBG("%s ABC", "Enable");
188 		return mhz19b_send_config(dev, MHZ19B_CMD_IDX_SET_ABC_ON);
189 	}
190 
191 	LOG_DBG("%s ABC", "Disable");
192 	return mhz19b_send_config(dev, MHZ19B_CMD_IDX_SET_ABC_OFF);
193 }
194 
mhz19b_attr_set(const struct device * dev,enum sensor_channel chan,enum sensor_attribute attr,const struct sensor_value * val)195 static int mhz19b_attr_set(const struct device *dev, enum sensor_channel chan,
196 			   enum sensor_attribute attr, const struct sensor_value *val)
197 {
198 	if (chan != SENSOR_CHAN_CO2) {
199 		return -ENOTSUP;
200 	}
201 
202 	switch (attr) {
203 	case SENSOR_ATTR_FULL_SCALE:
204 		return mhz19b_attr_full_scale_cfg(dev, val->val1);
205 
206 	case SENSOR_ATTR_MHZ19B_ABC:
207 		return mhz19b_attr_abc_cfg(dev, val->val1);
208 
209 	default:
210 		return -ENOTSUP;
211 	}
212 }
213 
mhz19b_attr_get(const struct device * dev,enum sensor_channel chan,enum sensor_attribute attr,struct sensor_value * val)214 static int mhz19b_attr_get(const struct device *dev, enum sensor_channel chan,
215 			   enum sensor_attribute attr, struct sensor_value *val)
216 {
217 	struct mhz19b_data *data = dev->data;
218 	int ret;
219 
220 	if (chan != SENSOR_CHAN_CO2) {
221 		return -ENOTSUP;
222 	}
223 
224 	switch (attr) {
225 	case SENSOR_ATTR_FULL_SCALE:
226 		ret = mhz19b_poll_data(dev, MHZ19B_CMD_IDX_GET_RANGE);
227 		break;
228 	case SENSOR_ATTR_MHZ19B_ABC:
229 		ret = mhz19b_poll_data(dev, MHZ19B_CMD_IDX_GET_ABC);
230 		break;
231 	default:
232 		return -ENOTSUP;
233 	}
234 
235 	val->val1 = (int32_t)data->data;
236 	val->val2 = 0;
237 
238 	return ret;
239 }
240 
mhz19b_sample_fetch(const struct device * dev,enum sensor_channel chan)241 static int mhz19b_sample_fetch(const struct device *dev, enum sensor_channel chan)
242 {
243 	if (chan == SENSOR_CHAN_CO2 || chan == SENSOR_CHAN_ALL) {
244 		return mhz19b_poll_data(dev, MHZ19B_CMD_IDX_GET_CO2);
245 	}
246 
247 	return -ENOTSUP;
248 }
249 
250 static const struct sensor_driver_api mhz19b_api_funcs = {
251 	.attr_set = mhz19b_attr_set,
252 	.attr_get = mhz19b_attr_get,
253 	.sample_fetch = mhz19b_sample_fetch,
254 	.channel_get = mhz19b_channel_get,
255 };
256 
mhz19b_uart_isr(const struct device * uart_dev,void * user_data)257 static void mhz19b_uart_isr(const struct device *uart_dev, void *user_data)
258 {
259 	const struct device *dev = user_data;
260 	struct mhz19b_data *data = dev->data;
261 
262 	ARG_UNUSED(user_data);
263 
264 	if (uart_dev == NULL) {
265 		return;
266 	}
267 
268 	if (!uart_irq_update(uart_dev)) {
269 		return;
270 	}
271 
272 	if (uart_irq_rx_ready(uart_dev)) {
273 		data->xfer_bytes += uart_fifo_read(uart_dev, &data->rd_data[data->xfer_bytes],
274 						   MHZ19B_BUF_LEN - data->xfer_bytes);
275 
276 		if (data->xfer_bytes == MHZ19B_BUF_LEN) {
277 			data->xfer_bytes = 0;
278 			uart_irq_rx_disable(uart_dev);
279 			k_sem_give(&data->rx_sem);
280 			if (data->has_rsp) {
281 				k_sem_give(&data->tx_sem);
282 			}
283 		}
284 	}
285 
286 	if (uart_irq_tx_ready(uart_dev)) {
287 		data->xfer_bytes +=
288 			uart_fifo_fill(uart_dev, &mhz19b_cmds[data->cmd_idx][data->xfer_bytes],
289 				       MHZ19B_BUF_LEN - data->xfer_bytes);
290 
291 		if (data->xfer_bytes == MHZ19B_BUF_LEN) {
292 			data->xfer_bytes = 0;
293 			uart_irq_tx_disable(uart_dev);
294 			if (!data->has_rsp) {
295 				k_sem_give(&data->tx_sem);
296 			}
297 		}
298 	}
299 }
300 
mhz19b_init(const struct device * dev)301 static int mhz19b_init(const struct device *dev)
302 {
303 	struct mhz19b_data *data = dev->data;
304 	const struct mhz19b_cfg *cfg = dev->config;
305 	int ret;
306 
307 	uart_irq_rx_disable(cfg->uart_dev);
308 	uart_irq_tx_disable(cfg->uart_dev);
309 
310 	mhz19b_uart_flush(cfg->uart_dev);
311 
312 	uart_irq_callback_user_data_set(cfg->uart_dev, cfg->cb, (void *)dev);
313 
314 	k_sem_init(&data->rx_sem, 0, 1);
315 	k_sem_init(&data->tx_sem, 1, 1);
316 
317 	/* Configure default detection range */
318 	ret = mhz19b_attr_full_scale_cfg(dev, cfg->range);
319 	if (ret != 0) {
320 		LOG_ERR("Error setting default range %d", cfg->range);
321 		return ret;
322 	}
323 
324 	/* Configure ABC logic */
325 	ret = mhz19b_attr_abc_cfg(dev, cfg->abc_on);
326 	if (ret != 0) {
327 		LOG_ERR("Error setting default ABC %s", cfg->abc_on ? "on" : "off");
328 	}
329 
330 	return ret;
331 }
332 
333 #define MHZ19B_INIT(inst)									\
334 												\
335 	static struct mhz19b_data mhz19b_data_##inst;						\
336 												\
337 	static const struct mhz19b_cfg mhz19b_cfg_##inst = {					\
338 		.uart_dev = DEVICE_DT_GET(DT_INST_BUS(inst)),					\
339 		.range = DT_INST_PROP(inst, maximum_range),					\
340 		.abc_on = DT_INST_PROP(inst, abc_on),						\
341 		.cb = mhz19b_uart_isr,								\
342 	};											\
343 												\
344 	SENSOR_DEVICE_DT_INST_DEFINE(inst, mhz19b_init, NULL,					\
345 			      &mhz19b_data_##inst, &mhz19b_cfg_##inst,				\
346 			      POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &mhz19b_api_funcs);
347 
348 DT_INST_FOREACH_STATUS_OKAY(MHZ19B_INIT)
349