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