1 /*
2  * Copyright (c) 2024, Vitrolife A/S
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  *
6  * Datasheet:
7  * https://sensorsandpower.angst-pfister.com/fileadmin/products/datasheets/272/Manual-FCX-MLD_1620-21914-0033-E-0821.pdf
8  *
9  */
10 
11 #define DT_DRV_COMPAT ap_fcx_mldx5
12 #include <ctype.h>
13 #include <zephyr/kernel.h>
14 #include <zephyr/device.h>
15 #include <zephyr/drivers/sensor.h>
16 #include <zephyr/drivers/sensor/fcx_mldx5.h>
17 #include <zephyr/drivers/uart.h>
18 #include <zephyr/logging/log.h>
19 #include <zephyr/pm/device.h>
20 #include <zephyr/sys/util.h>
21 
22 LOG_MODULE_REGISTER(fcx_mldx5_sensor, CONFIG_SENSOR_LOG_LEVEL);
23 
24 #define FCX_MLDX5_STX 0x2
25 #define FCX_MLDX5_ETX 0x3
26 
27 #define FCX_MLDX5_STX_LEN      1
28 #define FCX_MLDX5_CMD_LEN      2
29 /* Data length depends on command type thus defined in array */
30 #define FCX_MLDX5_CHECKSUM_LEN 2
31 #define FCX_MLDX5_ETX_LEN      1
32 #define FCX_MLDX5_HEADER_LEN                                                                       \
33 	(FCX_MLDX5_STX_LEN + FCX_MLDX5_CMD_LEN + FCX_MLDX5_CHECKSUM_LEN + FCX_MLDX5_ETX_LEN)
34 
35 #define FCX_MLDX5_STX_INDEX                 0
36 #define FCX_MLDX5_CMD_INDEX                 (FCX_MLDX5_STX_INDEX + FCX_MLDX5_STX_LEN)
37 #define FCX_MLDX5_DATA_INDEX                (FCX_MLDX5_CMD_INDEX + FCX_MLDX5_CMD_LEN)
38 #define FCX_MLDX5_CHECKSUM_INDEX(frame_len) ((frame_len)-FCX_MLDX5_CHECKSUM_LEN - FCX_MLDX5_ETX_LEN)
39 #define FCX_MLDX5_ETX_INDEX(frame_len)      ((frame_len)-FCX_MLDX5_ETX_LEN)
40 
41 #define FCX_MLDX5_MAX_FRAME_LEN      11
42 #define FCX_MLDX5_MAX_RESPONSE_DELAY 200 /* Not specified in datasheet */
43 #define FCX_MLDX5_MAX_HEAT_UP_TIME   180000
44 
45 struct fcx_mldx5_data {
46 	struct k_mutex uart_mutex;
47 	struct k_sem uart_rx_sem;
48 	uint32_t o2_ppm;
49 	uint8_t status;
50 	uint8_t frame[FCX_MLDX5_MAX_FRAME_LEN];
51 	uint8_t frame_len;
52 };
53 
54 struct fcx_mldx5_cfg {
55 	const struct device *uart_dev;
56 	uart_irq_callback_user_data_t cb;
57 };
58 
59 enum fcx_mldx5_cmd {
60 	FCX_MLDX5_CMD_READ_STATUS,
61 	FCX_MLDX5_CMD_READ_O2_VALUE,
62 	FCX_MLDX5_CMD_SWITCH_SENSOR_ON_OFF,
63 	FCX_MLDX5_CMD_RESET,
64 	FCX_MLDX5_CMD_ERROR,
65 };
66 
67 enum fcx_mldx5_errors {
68 	FCX_MLDX5_ERROR_CHECKSUM,
69 	FCX_MLDX5_ERROR_UNKNOWN_COMMAND,
70 	FCX_MLDX5_ERROR_PARAMETER,
71 	FCX_MLDX5_ERROR_EEPROM,
72 };
73 
74 static const char *const fcx_mldx5_cmds[] = {
75 	[FCX_MLDX5_CMD_READ_STATUS] = "01",
76 	[FCX_MLDX5_CMD_READ_O2_VALUE] = "02",
77 	[FCX_MLDX5_CMD_SWITCH_SENSOR_ON_OFF] = "04",
78 	[FCX_MLDX5_CMD_RESET] = "11",
79 	[FCX_MLDX5_CMD_ERROR] = "EE",
80 };
81 
82 static const uint8_t fcx_mldx5_cmds_data_len[] = {
83 	[FCX_MLDX5_CMD_READ_STATUS] = 2,
84 	[FCX_MLDX5_CMD_READ_O2_VALUE] = 5,
85 	[FCX_MLDX5_CMD_SWITCH_SENSOR_ON_OFF] = 1,
86 	[FCX_MLDX5_CMD_RESET] = 0,
87 	[FCX_MLDX5_CMD_ERROR] = 2,
88 };
89 
90 static const char *const fcx_mldx5_errors[] = {
91 	[FCX_MLDX5_ERROR_CHECKSUM] = "checksum",
92 	[FCX_MLDX5_ERROR_UNKNOWN_COMMAND] = "command",
93 	[FCX_MLDX5_ERROR_PARAMETER] = "parameter",
94 	[FCX_MLDX5_ERROR_EEPROM] = "eeprom",
95 };
96 
fcx_mldx5_uart_flush(const struct device * uart_dev)97 static void fcx_mldx5_uart_flush(const struct device *uart_dev)
98 {
99 	uint8_t tmp;
100 
101 	while (uart_fifo_read(uart_dev, &tmp, 1) > 0) {
102 		continue;
103 	}
104 }
105 
fcx_mldx5_calculate_checksum(const uint8_t * buf,size_t len)106 static uint8_t fcx_mldx5_calculate_checksum(const uint8_t *buf, size_t len)
107 {
108 	uint8_t checksum;
109 	size_t i;
110 
111 	if (buf == NULL || len == 0) {
112 		return 0;
113 	}
114 
115 	checksum = buf[0];
116 	for (i = 1; i < len; ++i) {
117 		checksum ^= buf[i];
118 	}
119 
120 	return checksum;
121 }
122 
fcx_mldx5_frame_check_error(const struct fcx_mldx5_data * data,const char * command_sent)123 static int fcx_mldx5_frame_check_error(const struct fcx_mldx5_data *data, const char *command_sent)
124 {
125 	const uint8_t len = FCX_MLDX5_HEADER_LEN + fcx_mldx5_cmds_data_len[FCX_MLDX5_CMD_ERROR];
126 	const char *command_error = fcx_mldx5_cmds[FCX_MLDX5_CMD_ERROR];
127 	const char *command_received = &data->frame[FCX_MLDX5_CMD_INDEX];
128 	const char *data_received = &data->frame[FCX_MLDX5_DATA_INDEX];
129 	uint8_t error;
130 
131 	if (data->frame_len != len ||
132 	    strncmp(command_error, command_received, FCX_MLDX5_CMD_LEN) != 0) {
133 		return 0;
134 	}
135 
136 	if (data_received[0] != 'E' || char2hex(data_received[1], &error) != 0 ||
137 	    error >= ARRAY_SIZE(fcx_mldx5_errors)) {
138 		LOG_ERR("Could not parse error value %.*s",
139 			fcx_mldx5_cmds_data_len[FCX_MLDX5_CMD_ERROR], data_received);
140 	} else {
141 		LOG_ERR("Command '%s' received error '%s'", command_sent, fcx_mldx5_errors[error]);
142 	}
143 
144 	return -EIO;
145 }
146 
fcx_mldx5_frame_verify(const struct fcx_mldx5_data * data,enum fcx_mldx5_cmd cmd)147 static int fcx_mldx5_frame_verify(const struct fcx_mldx5_data *data, enum fcx_mldx5_cmd cmd)
148 {
149 	const uint8_t frame_len = FCX_MLDX5_HEADER_LEN + fcx_mldx5_cmds_data_len[cmd];
150 	const char *command = fcx_mldx5_cmds[cmd];
151 	const char *command_received = &data->frame[FCX_MLDX5_CMD_INDEX];
152 	uint8_t checksum;
153 	uint8_t checksum_received;
154 
155 	if (fcx_mldx5_frame_check_error(data, command) != 0) {
156 		return -EIO;
157 	} else if (data->frame_len != frame_len) {
158 		LOG_ERR("Expected command %s frame length %u not %u", command, frame_len,
159 			data->frame_len);
160 		return -EIO;
161 	} else if (data->frame[FCX_MLDX5_STX_INDEX] != FCX_MLDX5_STX) {
162 		LOG_ERR("No STX");
163 		return -EIO;
164 	} else if (strncmp(command, command_received, FCX_MLDX5_CMD_LEN) != 0) {
165 		LOG_ERR("Expected command %s not %.*s", command, FCX_MLDX5_CMD_LEN,
166 			command_received);
167 		return -EIO;
168 	} else if (data->frame[FCX_MLDX5_ETX_INDEX(data->frame_len)] != FCX_MLDX5_ETX) {
169 		LOG_ERR("No ETX");
170 		return -EIO;
171 	}
172 
173 	/* cmd and data bytes are used to calculate checksum */
174 	checksum = fcx_mldx5_calculate_checksum(command_received,
175 						FCX_MLDX5_CMD_LEN + fcx_mldx5_cmds_data_len[cmd]);
176 	checksum_received =
177 		strtol(&data->frame[FCX_MLDX5_CHECKSUM_INDEX(data->frame_len)], NULL, 16);
178 	if (checksum != checksum_received) {
179 		LOG_ERR("Expected checksum 0x%02x not 0x%02x", checksum, checksum_received);
180 		return -EIO;
181 	}
182 
183 	return 0;
184 }
185 
fcx_mldx5_uart_isr(const struct device * uart_dev,void * user_data)186 static void fcx_mldx5_uart_isr(const struct device *uart_dev, void *user_data)
187 {
188 	const struct device *dev = user_data;
189 	struct fcx_mldx5_data *data = dev->data;
190 	int rc, read_len;
191 
192 	if (!device_is_ready(uart_dev)) {
193 		LOG_DBG("UART device is not ready");
194 		return;
195 	}
196 
197 	if (!uart_irq_update(uart_dev)) {
198 		LOG_DBG("Unable to process interrupts");
199 		return;
200 	}
201 
202 	if (!uart_irq_rx_ready(uart_dev)) {
203 		LOG_DBG("No RX data");
204 		return;
205 	}
206 
207 	read_len = FCX_MLDX5_MAX_FRAME_LEN - data->frame_len;
208 	rc = read_len > 0 ? uart_fifo_read(uart_dev, &data->frame[data->frame_len], read_len)
209 			  : -ENOMEM;
210 
211 	if (rc < 0) {
212 		LOG_ERR("UART read failed: %d", rc < 0 ? rc : -ERANGE);
213 		fcx_mldx5_uart_flush(uart_dev);
214 		LOG_HEXDUMP_ERR(data->frame, data->frame_len, "Discarding");
215 	} else {
216 		data->frame_len += rc;
217 		if (data->frame[FCX_MLDX5_ETX_INDEX(data->frame_len)] != FCX_MLDX5_ETX) {
218 			return;
219 		}
220 		LOG_HEXDUMP_DBG(data->frame, data->frame_len, "Frame received");
221 	}
222 
223 	k_sem_give(&data->uart_rx_sem);
224 }
225 
fcx_mldx5_uart_send(const struct device * dev,enum fcx_mldx5_cmd cmd,const char * cmd_data)226 static void fcx_mldx5_uart_send(const struct device *dev, enum fcx_mldx5_cmd cmd,
227 				const char *cmd_data)
228 {
229 	const struct fcx_mldx5_cfg *cfg = dev->config;
230 	size_t cmd_data_len = cmd_data != NULL ? strlen(cmd_data) : 0;
231 	size_t frame_len = FCX_MLDX5_HEADER_LEN + cmd_data_len;
232 	char buf[FCX_MLDX5_MAX_FRAME_LEN];
233 	uint8_t checksum;
234 	size_t i;
235 
236 	buf[FCX_MLDX5_STX_INDEX] = FCX_MLDX5_STX;
237 	memcpy(&buf[FCX_MLDX5_CMD_INDEX], fcx_mldx5_cmds[cmd], FCX_MLDX5_CMD_LEN);
238 	if (cmd_data_len != 0 && cmd_data_len == fcx_mldx5_cmds_data_len[cmd]) {
239 		memcpy(&buf[FCX_MLDX5_DATA_INDEX], cmd_data, strlen(cmd_data));
240 	}
241 
242 	checksum = fcx_mldx5_calculate_checksum(&buf[FCX_MLDX5_CMD_INDEX],
243 						FCX_MLDX5_CMD_LEN + cmd_data_len);
244 	bin2hex(&checksum, 1, &buf[FCX_MLDX5_CHECKSUM_INDEX(frame_len)],
245 		FCX_MLDX5_MAX_FRAME_LEN - FCX_MLDX5_CHECKSUM_INDEX(frame_len));
246 	buf[FCX_MLDX5_ETX_INDEX(frame_len)] = FCX_MLDX5_ETX;
247 
248 	for (i = 0; i < frame_len; ++i) {
249 		uart_poll_out(cfg->uart_dev, buf[i]);
250 	}
251 
252 	LOG_HEXDUMP_DBG(buf, frame_len, "Frame sent");
253 }
254 
fcx_mldx5_await_receive(const struct device * dev)255 static int fcx_mldx5_await_receive(const struct device *dev)
256 {
257 	int rc;
258 	const struct fcx_mldx5_cfg *cfg = dev->config;
259 	struct fcx_mldx5_data *data = dev->data;
260 
261 	uart_irq_rx_enable(cfg->uart_dev);
262 
263 	rc = k_sem_take(&data->uart_rx_sem, K_MSEC(FCX_MLDX5_MAX_RESPONSE_DELAY));
264 
265 	/* Reset semaphore if sensor did not respond within maximum specified response time
266 	 */
267 	if (rc == -EAGAIN) {
268 		k_sem_reset(&data->uart_rx_sem);
269 	}
270 
271 	uart_irq_rx_disable(cfg->uart_dev);
272 
273 	return rc;
274 }
275 
fcx_mldx5_read_status_value(struct fcx_mldx5_data * data,uint8_t data_len)276 static int fcx_mldx5_read_status_value(struct fcx_mldx5_data *data, uint8_t data_len)
277 {
278 	char *cmd_data_received = &data->frame[FCX_MLDX5_DATA_INDEX];
279 	uint8_t value;
280 
281 	if (cmd_data_received[0] != '0' || char2hex(cmd_data_received[1], &value)) {
282 		LOG_ERR("Could not parse status value %.*s", data_len, cmd_data_received);
283 		return -EIO;
284 	}
285 
286 	switch (value) {
287 	case FCX_MLDX5_STATUS_STANDBY:
288 		break;
289 	case FCX_MLDX5_STATUS_RAMP_UP:
290 		break;
291 	case FCX_MLDX5_STATUS_RUN:
292 		break;
293 	case FCX_MLDX5_STATUS_ERROR:
294 		break;
295 	default:
296 		LOG_ERR("Status value %u invalid", value);
297 		return -EIO;
298 	}
299 
300 	data->status = value;
301 	return 0;
302 }
303 
fcx_mldx5_read_o2_value(struct fcx_mldx5_data * data)304 static int fcx_mldx5_read_o2_value(struct fcx_mldx5_data *data)
305 {
306 	const char *o2_data = &data->frame[FCX_MLDX5_DATA_INDEX];
307 	uint8_t o2_data_len = fcx_mldx5_cmds_data_len[FCX_MLDX5_CMD_READ_O2_VALUE];
308 	uint32_t value = 0;
309 	size_t i;
310 
311 	for (i = 0; i < o2_data_len; ++i) {
312 		if (i == 2) {
313 			if (o2_data[i] != '.') {
314 				goto invalid_data;
315 			}
316 		} else if (isdigit((int)o2_data[i]) == 0) {
317 			goto invalid_data;
318 		} else {
319 			value = value * 10 + (o2_data[i] - '0');
320 		}
321 	}
322 
323 	data->o2_ppm = value * 100;
324 	return 0;
325 
326 invalid_data:
327 	LOG_HEXDUMP_ERR(o2_data, o2_data_len, "Invalid O2 data");
328 	return -EIO;
329 }
330 
fcx_mldx5_buffer_process(struct fcx_mldx5_data * data,enum fcx_mldx5_cmd cmd,const char * cmd_data)331 static int fcx_mldx5_buffer_process(struct fcx_mldx5_data *data, enum fcx_mldx5_cmd cmd,
332 				    const char *cmd_data)
333 {
334 	if (fcx_mldx5_frame_verify(data, cmd) != 0) {
335 		return -EIO;
336 	}
337 
338 	switch (cmd) {
339 	case FCX_MLDX5_CMD_READ_STATUS:
340 		return fcx_mldx5_read_status_value(data, fcx_mldx5_cmds_data_len[cmd]);
341 	case FCX_MLDX5_CMD_READ_O2_VALUE:
342 		return fcx_mldx5_read_o2_value(data);
343 	case FCX_MLDX5_CMD_SWITCH_SENSOR_ON_OFF:
344 		return cmd_data != NULL && data->frame[FCX_MLDX5_DATA_INDEX] == cmd_data[0];
345 	case FCX_MLDX5_CMD_RESET:
346 		return 0;
347 	default:
348 		LOG_ERR("Unknown command 0x%02x", cmd);
349 		return -EIO;
350 	}
351 }
352 
fcx_mldx5_uart_transceive(const struct device * dev,enum fcx_mldx5_cmd cmd,const char * cmd_data)353 static int fcx_mldx5_uart_transceive(const struct device *dev, enum fcx_mldx5_cmd cmd,
354 				     const char *cmd_data)
355 {
356 	struct fcx_mldx5_data *data = dev->data;
357 	int rc;
358 
359 	k_mutex_lock(&data->uart_mutex, K_FOREVER);
360 
361 	data->frame_len = 0;
362 	fcx_mldx5_uart_send(dev, cmd, cmd_data);
363 
364 	rc = fcx_mldx5_await_receive(dev);
365 	if (rc != 0) {
366 		LOG_ERR("%s did not receive a response: %d", fcx_mldx5_cmds[cmd], rc);
367 	} else {
368 		rc = fcx_mldx5_buffer_process(data, cmd, cmd_data);
369 	}
370 
371 	k_mutex_unlock(&data->uart_mutex);
372 
373 	return rc;
374 }
375 
fcx_mldx5_attr_get(const struct device * dev,enum sensor_channel chan,enum sensor_attribute attr,struct sensor_value * val)376 static int fcx_mldx5_attr_get(const struct device *dev, enum sensor_channel chan,
377 			      enum sensor_attribute attr, struct sensor_value *val)
378 {
379 	struct fcx_mldx5_data *data = dev->data;
380 	int rc;
381 
382 	if (chan != SENSOR_CHAN_O2) {
383 		return -ENOTSUP;
384 	}
385 
386 	switch (attr) {
387 	case SENSOR_ATTR_FCX_MLDX5_STATUS:
388 		rc = fcx_mldx5_uart_transceive(dev, FCX_MLDX5_CMD_READ_STATUS, NULL);
389 		val->val1 = data->status;
390 		return rc;
391 	default:
392 		return -ENOTSUP;
393 	}
394 }
395 
fcx_mldx5_sample_fetch(const struct device * dev,enum sensor_channel chan)396 static int fcx_mldx5_sample_fetch(const struct device *dev, enum sensor_channel chan)
397 {
398 	if (chan != SENSOR_CHAN_O2 && chan != SENSOR_CHAN_ALL) {
399 		return -ENOTSUP;
400 	}
401 
402 	return fcx_mldx5_uart_transceive(dev, FCX_MLDX5_CMD_READ_O2_VALUE, NULL);
403 }
404 
fcx_mldx5_channel_get(const struct device * dev,enum sensor_channel chan,struct sensor_value * val)405 static int fcx_mldx5_channel_get(const struct device *dev, enum sensor_channel chan,
406 				 struct sensor_value *val)
407 {
408 	struct fcx_mldx5_data *data = dev->data;
409 
410 	if (chan != SENSOR_CHAN_O2) {
411 		return -ENOTSUP;
412 	}
413 
414 	val->val1 = data->o2_ppm;
415 	val->val2 = 0;
416 
417 	return 0;
418 }
419 
420 static DEVICE_API(sensor, fcx_mldx5_api_funcs) = {
421 	.attr_get = fcx_mldx5_attr_get,
422 	.sample_fetch = fcx_mldx5_sample_fetch,
423 	.channel_get = fcx_mldx5_channel_get,
424 };
425 
426 #ifdef CONFIG_PM_DEVICE
pm_action(const struct device * dev,enum pm_device_action action)427 static int pm_action(const struct device *dev, enum pm_device_action action)
428 {
429 	switch (action) {
430 	case PM_DEVICE_ACTION_RESUME:
431 		return fcx_mldx5_uart_transceive(dev, FCX_MLDX5_CMD_SWITCH_SENSOR_ON_OFF, "1");
432 	case PM_DEVICE_ACTION_SUSPEND:
433 		/* Standby with 20 % heating output */
434 		return fcx_mldx5_uart_transceive(dev, FCX_MLDX5_CMD_SWITCH_SENSOR_ON_OFF, "0");
435 	default:
436 		return -ENOTSUP;
437 	}
438 }
439 #endif
440 
fcx_mldx5_init(const struct device * dev)441 static int fcx_mldx5_init(const struct device *dev)
442 {
443 	int rc;
444 	const struct fcx_mldx5_cfg *cfg = dev->config;
445 	struct fcx_mldx5_data *data = dev->data;
446 
447 	LOG_DBG("Initializing %s", dev->name);
448 
449 	if (!device_is_ready(cfg->uart_dev)) {
450 		return -ENODEV;
451 	}
452 
453 	k_mutex_init(&data->uart_mutex);
454 	k_sem_init(&data->uart_rx_sem, 0, 1);
455 
456 	uart_irq_rx_disable(cfg->uart_dev);
457 	uart_irq_tx_disable(cfg->uart_dev);
458 
459 	rc = uart_irq_callback_user_data_set(cfg->uart_dev, cfg->cb, (void *)dev);
460 	if (rc != 0) {
461 		LOG_ERR("UART IRQ setup failed: %d", rc);
462 		return rc;
463 	}
464 
465 	/* Retry in case of garbled tx due to GPIO setup, crash during unfinished send or sensor
466 	 * start up time
467 	 */
468 	if (!WAIT_FOR(fcx_mldx5_uart_transceive(dev, FCX_MLDX5_CMD_READ_STATUS, NULL) == 0,
469 		      1000 * USEC_PER_MSEC, k_msleep(10))) {
470 		LOG_ERR("Read status failed");
471 		return -EIO;
472 	}
473 
474 	LOG_INF("%s status 0x%x", dev->name, data->status);
475 
476 	return 0;
477 }
478 
479 #define FCX_MLDX5_INIT(n)                                                                          \
480                                                                                                    \
481 	static struct fcx_mldx5_data fcx_mldx5_data_##n = {                                        \
482 		.status = FCX_MLDX5_STATUS_UNKNOWN,                                                \
483 	};                                                                                         \
484                                                                                                    \
485 	static const struct fcx_mldx5_cfg fcx_mldx5_cfg_##n = {                                    \
486 		.uart_dev = DEVICE_DT_GET(DT_INST_BUS(n)),                                         \
487 		.cb = fcx_mldx5_uart_isr,                                                          \
488 	};                                                                                         \
489                                                                                                    \
490 	PM_DEVICE_DT_INST_DEFINE(n, pm_action);                                                    \
491                                                                                                    \
492 	SENSOR_DEVICE_DT_INST_DEFINE(n, fcx_mldx5_init, PM_DEVICE_DT_INST_GET(n),                  \
493 				     &fcx_mldx5_data_##n, &fcx_mldx5_cfg_##n, POST_KERNEL,         \
494 				     CONFIG_SENSOR_INIT_PRIORITY, &fcx_mldx5_api_funcs);
495 
496 DT_INST_FOREACH_STATUS_OKAY(FCX_MLDX5_INIT)
497