1 /*
2 * Copyright (c) 2023 Trackunit Corporation
3 * Copyright (c) 2023 Bjarki Arge Andreasen
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #include <zephyr/drivers/gnss.h>
9 #include <zephyr/drivers/gnss/gnss_publish.h>
10 #include <zephyr/modem/chat.h>
11 #include <zephyr/modem/backend/uart.h>
12 #include <zephyr/kernel.h>
13 #include <zephyr/pm/device.h>
14 #include <zephyr/drivers/gpio.h>
15 #include <zephyr/pm/device_runtime.h>
16 #include <string.h>
17
18 #include "gnss_nmea0183.h"
19 #include "gnss_nmea0183_match.h"
20 #include "gnss_parse.h"
21
22 #include <zephyr/logging/log.h>
23 LOG_MODULE_REGISTER(quectel_lcx6g, CONFIG_GNSS_LOG_LEVEL);
24
25 #define QUECTEL_LCX6G_PM_TIMEOUT_MS 500U
26 #define QUECTEL_LCX6G_SCRIPT_TIMEOUT_S 10U
27
28 #define QUECTEL_LCX6G_PAIR_NAV_MODE_STATIONARY 4
29 #define QUECTEL_LCX6G_PAIR_NAV_MODE_FITNESS 1
30 #define QUECTEL_LCX6G_PAIR_NAV_MODE_NORMAL 0
31 #define QUECTEL_LCX6G_PAIR_NAV_MODE_DRONE 5
32
33 #define QUECTEL_LCX6G_PAIR_PPS_MODE_DISABLED 0
34 #define QUECTEL_LCX6G_PAIR_PPS_MODE_ENABLED 4
35 #define QUECTEL_LCX6G_PAIR_PPS_MODE_ENABLED_AFTER_LOCK 1
36 #define QUECTEL_LCX6G_PAIR_PPS_MODE_ENABLED_WHILE_LOCKED 2
37
38 struct quectel_lcx6g_config {
39 const struct device *uart;
40 const enum gnss_pps_mode pps_mode;
41 const uint16_t pps_pulse_width;
42 };
43
44 struct quectel_lcx6g_data {
45 struct gnss_nmea0183_match_data match_data;
46 #if CONFIG_GNSS_SATELLITES
47 struct gnss_satellite satellites[CONFIG_GNSS_QUECTEL_LCX6G_SAT_ARRAY_SIZE];
48 #endif
49
50 /* UART backend */
51 struct modem_pipe *uart_pipe;
52 struct modem_backend_uart uart_backend;
53 uint8_t uart_backend_receive_buf[CONFIG_GNSS_QUECTEL_LCX6G_UART_RX_BUF_SIZE];
54 uint8_t uart_backend_transmit_buf[CONFIG_GNSS_QUECTEL_LCX6G_UART_TX_BUF_SIZE];
55
56 /* Modem chat */
57 struct modem_chat chat;
58 uint8_t chat_receive_buf[256];
59 uint8_t chat_delimiter[2];
60 uint8_t *chat_argv[32];
61
62 /* Pair chat script */
63 uint8_t pair_request_buf[32];
64 uint8_t pair_match_buf[32];
65 struct modem_chat_match pair_match;
66 struct modem_chat_script_chat pair_script_chat;
67 struct modem_chat_script pair_script;
68
69 /* Allocation for responses from GNSS modem */
70 union {
71 uint16_t fix_rate_response;
72 gnss_systems_t enabled_systems_response;
73 enum gnss_navigation_mode navigation_mode_response;
74 };
75
76 struct k_sem lock;
77 k_timeout_t pm_timeout;
78 };
79
80 #ifdef CONFIG_PM_DEVICE
81 MODEM_CHAT_MATCH_DEFINE(pair003_success_match, "$PAIR001,003,0*38", "", NULL);
82 MODEM_CHAT_SCRIPT_CMDS_DEFINE(
83 suspend_script_cmds,
84 MODEM_CHAT_SCRIPT_CMD_RESP("$PAIR003*39", pair003_success_match)
85 );
86
87 MODEM_CHAT_SCRIPT_NO_ABORT_DEFINE(suspend_script, suspend_script_cmds,
88 NULL, QUECTEL_LCX6G_SCRIPT_TIMEOUT_S);
89 #endif /* CONFIG_PM_DEVICE */
90
91 MODEM_CHAT_MATCH_DEFINE(pair062_ack_match, "$PAIR001,062,0*3F", "", NULL);
92 MODEM_CHAT_SCRIPT_CMDS_DEFINE(
93 resume_script_cmds,
94 MODEM_CHAT_SCRIPT_CMD_RESP("$PAIR002*38", modem_chat_any_match),
95 MODEM_CHAT_SCRIPT_CMD_RESP("$PAIR062,0,1*3F", pair062_ack_match),
96 MODEM_CHAT_SCRIPT_CMD_RESP("$PAIR062,1,0*3F", pair062_ack_match),
97 MODEM_CHAT_SCRIPT_CMD_RESP("$PAIR062,2,0*3C", pair062_ack_match),
98 #if CONFIG_GNSS_SATELLITES
99 MODEM_CHAT_SCRIPT_CMD_RESP("$PAIR062,3,5*38", pair062_ack_match),
100 #else
101 MODEM_CHAT_SCRIPT_CMD_RESP("$PAIR062,3,0*3D", pair062_ack_match),
102 #endif
103 MODEM_CHAT_SCRIPT_CMD_RESP("$PAIR062,4,1*3B", pair062_ack_match),
104 MODEM_CHAT_SCRIPT_CMD_RESP("$PAIR062,5,0*3B", pair062_ack_match),
105 );
106
107 MODEM_CHAT_SCRIPT_NO_ABORT_DEFINE(resume_script, resume_script_cmds,
108 NULL, QUECTEL_LCX6G_SCRIPT_TIMEOUT_S);
109
110 MODEM_CHAT_MATCHES_DEFINE(unsol_matches,
111 MODEM_CHAT_MATCH_WILDCARD("$??GGA,", ",*", gnss_nmea0183_match_gga_callback),
112 MODEM_CHAT_MATCH_WILDCARD("$??RMC,", ",*", gnss_nmea0183_match_rmc_callback),
113 #if CONFIG_GNSS_SATELLITES
114 MODEM_CHAT_MATCH_WILDCARD("$??GSV,", ",*", gnss_nmea0183_match_gsv_callback),
115 #endif
116 );
117
quectel_lcx6g_configure_pps(const struct device * dev)118 static int quectel_lcx6g_configure_pps(const struct device *dev)
119 {
120 const struct quectel_lcx6g_config *config = dev->config;
121 struct quectel_lcx6g_data *data = dev->data;
122 uint8_t pps_mode = 0;
123 int ret;
124
125 switch (config->pps_mode) {
126 case GNSS_PPS_MODE_DISABLED:
127 pps_mode = QUECTEL_LCX6G_PAIR_PPS_MODE_DISABLED;
128 break;
129
130 case GNSS_PPS_MODE_ENABLED:
131 pps_mode = QUECTEL_LCX6G_PAIR_PPS_MODE_ENABLED;
132 break;
133
134 case GNSS_PPS_MODE_ENABLED_AFTER_LOCK:
135 pps_mode = QUECTEL_LCX6G_PAIR_PPS_MODE_ENABLED_AFTER_LOCK;
136 break;
137
138 case GNSS_PPS_MODE_ENABLED_WHILE_LOCKED:
139 pps_mode = QUECTEL_LCX6G_PAIR_PPS_MODE_ENABLED_WHILE_LOCKED;
140 break;
141 }
142
143 ret = gnss_nmea0183_snprintk(data->pair_request_buf, sizeof(data->pair_request_buf),
144 "PAIR752,%u,%u", pps_mode, config->pps_pulse_width);
145 if (ret < 0) {
146 return ret;
147 }
148
149 ret = modem_chat_script_chat_set_request(&data->pair_script_chat, data->pair_request_buf);
150 if (ret < 0) {
151 return ret;
152 }
153
154 ret = gnss_nmea0183_snprintk(data->pair_match_buf, sizeof(data->pair_match_buf),
155 "PAIR001,752,0");
156 if (ret < 0) {
157 return ret;
158 }
159
160 ret = modem_chat_match_set_match(&data->pair_match, data->pair_match_buf);
161 if (ret < 0) {
162 return ret;
163 }
164
165 return modem_chat_run_script(&data->chat, &data->pair_script);
166 }
167
quectel_lcx6g_lock(const struct device * dev)168 static void quectel_lcx6g_lock(const struct device *dev)
169 {
170 struct quectel_lcx6g_data *data = dev->data;
171
172 (void)k_sem_take(&data->lock, K_FOREVER);
173 }
174
quectel_lcx6g_unlock(const struct device * dev)175 static void quectel_lcx6g_unlock(const struct device *dev)
176 {
177 struct quectel_lcx6g_data *data = dev->data;
178
179 k_sem_give(&data->lock);
180 }
181
quectel_lcx6g_pm_changed(const struct device * dev)182 static void quectel_lcx6g_pm_changed(const struct device *dev)
183 {
184 struct quectel_lcx6g_data *data = dev->data;
185 uint32_t pm_ready_at_ms;
186
187 pm_ready_at_ms = k_uptime_get() + QUECTEL_LCX6G_PM_TIMEOUT_MS;
188 data->pm_timeout = K_TIMEOUT_ABS_MS(pm_ready_at_ms);
189 }
190
quectel_lcx6g_await_pm_ready(const struct device * dev)191 static void quectel_lcx6g_await_pm_ready(const struct device *dev)
192 {
193 struct quectel_lcx6g_data *data = dev->data;
194
195 LOG_INF("Waiting until PM ready");
196 k_sleep(data->pm_timeout);
197 }
198
quectel_lcx6g_resume(const struct device * dev)199 static int quectel_lcx6g_resume(const struct device *dev)
200 {
201 struct quectel_lcx6g_data *data = dev->data;
202 int ret;
203
204 LOG_INF("Resuming");
205
206 quectel_lcx6g_await_pm_ready(dev);
207
208 ret = modem_pipe_open(data->uart_pipe, K_SECONDS(10));
209 if (ret < 0) {
210 LOG_ERR("Failed to open pipe");
211 return ret;
212 }
213
214 ret = modem_chat_attach(&data->chat, data->uart_pipe);
215 if (ret < 0) {
216 LOG_ERR("Failed to attach chat");
217 modem_pipe_close(data->uart_pipe, K_SECONDS(10));
218 return ret;
219 }
220
221 ret = modem_chat_run_script(&data->chat, &resume_script);
222 if (ret < 0) {
223 LOG_ERR("Failed to initialize GNSS");
224 modem_pipe_close(data->uart_pipe, K_SECONDS(10));
225 return ret;
226 }
227
228 ret = quectel_lcx6g_configure_pps(dev);
229 if (ret < 0) {
230 LOG_ERR("Failed to configure PPS");
231 modem_pipe_close(data->uart_pipe, K_SECONDS(10));
232 return ret;
233 }
234
235 LOG_INF("Resumed");
236 return ret;
237 }
238
239 #ifdef CONFIG_PM_DEVICE
quectel_lcx6g_suspend(const struct device * dev)240 static int quectel_lcx6g_suspend(const struct device *dev)
241 {
242 struct quectel_lcx6g_data *data = dev->data;
243 int ret;
244
245 LOG_INF("Suspending");
246
247 quectel_lcx6g_await_pm_ready(dev);
248
249 ret = modem_chat_run_script(&data->chat, &suspend_script);
250 if (ret < 0) {
251 LOG_ERR("Failed to suspend GNSS");
252 } else {
253 LOG_INF("Suspended");
254 }
255
256 modem_pipe_close(data->uart_pipe, K_SECONDS(10));
257 return ret;
258 }
259
quectel_lcx6g_turn_on(const struct device * dev)260 static void quectel_lcx6g_turn_on(const struct device *dev)
261 {
262 LOG_INF("Powered on");
263 }
264
quectel_lcx6g_turn_off(const struct device * dev)265 static int quectel_lcx6g_turn_off(const struct device *dev)
266 {
267 struct quectel_lcx6g_data *data = dev->data;
268
269 LOG_INF("Powered off");
270
271 return modem_pipe_close(data->uart_pipe, K_SECONDS(10));
272 }
273
quectel_lcx6g_pm_action(const struct device * dev,enum pm_device_action action)274 static int quectel_lcx6g_pm_action(const struct device *dev, enum pm_device_action action)
275 {
276 int ret = -ENOTSUP;
277
278 quectel_lcx6g_lock(dev);
279
280 switch (action) {
281 case PM_DEVICE_ACTION_SUSPEND:
282 ret = quectel_lcx6g_suspend(dev);
283 break;
284
285 case PM_DEVICE_ACTION_RESUME:
286 ret = quectel_lcx6g_resume(dev);
287 break;
288
289 case PM_DEVICE_ACTION_TURN_ON:
290 quectel_lcx6g_turn_on(dev);
291 ret = 0;
292 break;
293
294 case PM_DEVICE_ACTION_TURN_OFF:
295 ret = quectel_lcx6g_turn_off(dev);
296 break;
297
298 default:
299 break;
300 }
301
302 quectel_lcx6g_pm_changed(dev);
303
304 quectel_lcx6g_unlock(dev);
305 return ret;
306 }
307 #endif /* CONFIG_PM_DEVICE */
308
quectel_lcx6g_set_fix_rate(const struct device * dev,uint32_t fix_interval_ms)309 static int quectel_lcx6g_set_fix_rate(const struct device *dev, uint32_t fix_interval_ms)
310 {
311 struct quectel_lcx6g_data *data = dev->data;
312 int ret;
313
314 if (fix_interval_ms < 100 || fix_interval_ms > 1000) {
315 return -EINVAL;
316 }
317
318 quectel_lcx6g_lock(dev);
319
320 ret = gnss_nmea0183_snprintk(data->pair_request_buf, sizeof(data->pair_request_buf),
321 "PAIR050,%u", fix_interval_ms);
322 if (ret < 0) {
323 goto unlock_return;
324 }
325
326 ret = modem_chat_script_chat_set_request(&data->pair_script_chat, data->pair_request_buf);
327 if (ret < 0) {
328 goto unlock_return;
329 }
330
331 ret = gnss_nmea0183_snprintk(data->pair_match_buf, sizeof(data->pair_match_buf),
332 "PAIR001,050,0");
333 if (ret < 0) {
334 goto unlock_return;
335 }
336
337 ret = modem_chat_match_set_match(&data->pair_match, data->pair_match_buf);
338 if (ret < 0) {
339 goto unlock_return;
340 }
341
342 ret = modem_chat_run_script(&data->chat, &data->pair_script);
343 if (ret < 0) {
344 goto unlock_return;
345 }
346
347 unlock_return:
348 quectel_lcx6g_unlock(dev);
349 return ret;
350 }
351
quectel_lcx6g_get_fix_rate_callback(struct modem_chat * chat,char ** argv,uint16_t argc,void * user_data)352 static void quectel_lcx6g_get_fix_rate_callback(struct modem_chat *chat, char **argv,
353 uint16_t argc, void *user_data)
354 {
355 struct quectel_lcx6g_data *data = user_data;
356 int32_t tmp;
357
358 if (argc != 3) {
359 return;
360 }
361
362 if ((gnss_parse_atoi(argv[1], 10, &tmp) < 0) || (tmp < 0) || (tmp > 1000)) {
363 return;
364 }
365
366 data->fix_rate_response = (uint16_t)tmp;
367 }
368
quectel_lcx6g_get_fix_rate(const struct device * dev,uint32_t * fix_interval_ms)369 static int quectel_lcx6g_get_fix_rate(const struct device *dev, uint32_t *fix_interval_ms)
370 {
371 struct quectel_lcx6g_data *data = dev->data;
372 int ret;
373
374 quectel_lcx6g_lock(dev);
375
376 ret = gnss_nmea0183_snprintk(data->pair_request_buf, sizeof(data->pair_request_buf),
377 "PAIR051");
378 if (ret < 0) {
379 goto unlock_return;
380 }
381
382 ret = modem_chat_script_chat_set_request(&data->pair_script_chat, data->pair_request_buf);
383 if (ret < 0) {
384 goto unlock_return;
385 }
386
387 strncpy(data->pair_match_buf, "$PAIR051,", sizeof(data->pair_match_buf));
388 ret = modem_chat_match_set_match(&data->pair_match, data->pair_match_buf);
389 if (ret < 0) {
390 goto unlock_return;
391 }
392
393 modem_chat_match_set_callback(&data->pair_match, quectel_lcx6g_get_fix_rate_callback);
394 ret = modem_chat_run_script(&data->chat, &data->pair_script);
395 modem_chat_match_set_callback(&data->pair_match, NULL);
396 if (ret < 0) {
397 goto unlock_return;
398 }
399
400 *fix_interval_ms = data->fix_rate_response;
401
402 unlock_return:
403 quectel_lcx6g_unlock(dev);
404 return 0;
405 }
406
quectel_lcx6g_set_navigation_mode(const struct device * dev,enum gnss_navigation_mode mode)407 static int quectel_lcx6g_set_navigation_mode(const struct device *dev,
408 enum gnss_navigation_mode mode)
409 {
410 struct quectel_lcx6g_data *data = dev->data;
411 uint8_t navigation_mode = 0;
412 int ret;
413
414 switch (mode) {
415 case GNSS_NAVIGATION_MODE_ZERO_DYNAMICS:
416 navigation_mode = QUECTEL_LCX6G_PAIR_NAV_MODE_STATIONARY;
417 break;
418
419 case GNSS_NAVIGATION_MODE_LOW_DYNAMICS:
420 navigation_mode = QUECTEL_LCX6G_PAIR_NAV_MODE_FITNESS;
421 break;
422
423 case GNSS_NAVIGATION_MODE_BALANCED_DYNAMICS:
424 navigation_mode = QUECTEL_LCX6G_PAIR_NAV_MODE_NORMAL;
425 break;
426
427 case GNSS_NAVIGATION_MODE_HIGH_DYNAMICS:
428 navigation_mode = QUECTEL_LCX6G_PAIR_NAV_MODE_DRONE;
429 break;
430 }
431
432 quectel_lcx6g_lock(dev);
433
434 ret = gnss_nmea0183_snprintk(data->pair_request_buf, sizeof(data->pair_request_buf),
435 "PAIR080,%u", navigation_mode);
436 if (ret < 0) {
437 goto unlock_return;
438 }
439
440 ret = modem_chat_script_chat_set_request(&data->pair_script_chat, data->pair_request_buf);
441 if (ret < 0) {
442 goto unlock_return;
443 }
444
445 ret = gnss_nmea0183_snprintk(data->pair_match_buf, sizeof(data->pair_match_buf),
446 "PAIR001,080,0");
447 if (ret < 0) {
448 goto unlock_return;
449 }
450
451 ret = modem_chat_match_set_match(&data->pair_match, data->pair_match_buf);
452 if (ret < 0) {
453 goto unlock_return;
454 }
455
456 ret = modem_chat_run_script(&data->chat, &data->pair_script);
457 if (ret < 0) {
458 goto unlock_return;
459 }
460
461 unlock_return:
462 quectel_lcx6g_unlock(dev);
463 return ret;
464 }
465
quectel_lcx6g_get_nav_mode_callback(struct modem_chat * chat,char ** argv,uint16_t argc,void * user_data)466 static void quectel_lcx6g_get_nav_mode_callback(struct modem_chat *chat, char **argv,
467 uint16_t argc, void *user_data)
468 {
469 struct quectel_lcx6g_data *data = user_data;
470 int32_t tmp;
471
472 if (argc != 3) {
473 return;
474 }
475
476 if ((gnss_parse_atoi(argv[1], 10, &tmp) < 0) || (tmp < 0) || (tmp > 7)) {
477 return;
478 }
479
480 switch (tmp) {
481 case QUECTEL_LCX6G_PAIR_NAV_MODE_FITNESS:
482 data->navigation_mode_response = GNSS_NAVIGATION_MODE_LOW_DYNAMICS;
483 break;
484
485 case QUECTEL_LCX6G_PAIR_NAV_MODE_STATIONARY:
486 data->navigation_mode_response = GNSS_NAVIGATION_MODE_ZERO_DYNAMICS;
487 break;
488
489 case QUECTEL_LCX6G_PAIR_NAV_MODE_DRONE:
490 data->navigation_mode_response = GNSS_NAVIGATION_MODE_HIGH_DYNAMICS;
491 break;
492
493 default:
494 data->navigation_mode_response = GNSS_NAVIGATION_MODE_BALANCED_DYNAMICS;
495 break;
496 }
497 }
498
quectel_lcx6g_get_navigation_mode(const struct device * dev,enum gnss_navigation_mode * mode)499 static int quectel_lcx6g_get_navigation_mode(const struct device *dev,
500 enum gnss_navigation_mode *mode)
501 {
502 struct quectel_lcx6g_data *data = dev->data;
503 int ret;
504
505 quectel_lcx6g_lock(dev);
506
507 ret = gnss_nmea0183_snprintk(data->pair_request_buf, sizeof(data->pair_request_buf),
508 "PAIR081");
509 if (ret < 0) {
510 goto unlock_return;
511 }
512
513 ret = modem_chat_script_chat_set_request(&data->pair_script_chat, data->pair_request_buf);
514 if (ret < 0) {
515 goto unlock_return;
516 }
517
518 strncpy(data->pair_match_buf, "$PAIR081,", sizeof(data->pair_match_buf));
519 ret = modem_chat_match_set_match(&data->pair_match, data->pair_match_buf);
520 if (ret < 0) {
521 goto unlock_return;
522 }
523
524 modem_chat_match_set_callback(&data->pair_match, quectel_lcx6g_get_nav_mode_callback);
525 ret = modem_chat_run_script(&data->chat, &data->pair_script);
526 modem_chat_match_set_callback(&data->pair_match, NULL);
527 if (ret < 0) {
528 goto unlock_return;
529 }
530
531 *mode = data->navigation_mode_response;
532
533 unlock_return:
534 quectel_lcx6g_unlock(dev);
535 return ret;
536 }
537
quectel_lcx6g_set_enabled_systems(const struct device * dev,gnss_systems_t systems)538 static int quectel_lcx6g_set_enabled_systems(const struct device *dev, gnss_systems_t systems)
539 {
540 struct quectel_lcx6g_data *data = dev->data;
541 gnss_systems_t supported_systems;
542 int ret;
543
544 supported_systems = (GNSS_SYSTEM_GPS | GNSS_SYSTEM_GLONASS | GNSS_SYSTEM_GALILEO |
545 GNSS_SYSTEM_BEIDOU | GNSS_SYSTEM_QZSS | GNSS_SYSTEM_SBAS);
546
547 if ((~supported_systems) & systems) {
548 return -EINVAL;
549 }
550
551 quectel_lcx6g_lock(dev);
552
553 ret = gnss_nmea0183_snprintk(data->pair_request_buf, sizeof(data->pair_request_buf),
554 "PAIR066,%u,%u,%u,%u,%u,0",
555 (0 < (systems & GNSS_SYSTEM_GPS)),
556 (0 < (systems & GNSS_SYSTEM_GLONASS)),
557 (0 < (systems & GNSS_SYSTEM_GALILEO)),
558 (0 < (systems & GNSS_SYSTEM_BEIDOU)),
559 (0 < (systems & GNSS_SYSTEM_QZSS)));
560 if (ret < 0) {
561 goto unlock_return;
562 }
563
564 ret = modem_chat_script_chat_set_request(&data->pair_script_chat, data->pair_request_buf);
565 if (ret < 0) {
566 goto unlock_return;
567 }
568
569 ret = gnss_nmea0183_snprintk(data->pair_match_buf, sizeof(data->pair_match_buf),
570 "PAIR001,066,0");
571 if (ret < 0) {
572 goto unlock_return;
573 }
574
575 ret = modem_chat_match_set_match(&data->pair_match, data->pair_match_buf);
576 if (ret < 0) {
577 goto unlock_return;
578 }
579
580 ret = modem_chat_run_script(&data->chat, &data->pair_script);
581 if (ret < 0) {
582 goto unlock_return;
583 }
584
585 ret = gnss_nmea0183_snprintk(data->pair_request_buf, sizeof(data->pair_request_buf),
586 "PAIR410,%u", (0 < (systems & GNSS_SYSTEM_SBAS)));
587 if (ret < 0) {
588 goto unlock_return;
589 }
590
591 ret = modem_chat_script_chat_set_request(&data->pair_script_chat, data->pair_request_buf);
592 if (ret < 0) {
593 goto unlock_return;
594 }
595
596 ret = gnss_nmea0183_snprintk(data->pair_match_buf, sizeof(data->pair_match_buf),
597 "PAIR001,410,0");
598 if (ret < 0) {
599 goto unlock_return;
600 }
601
602 ret = modem_chat_match_set_match(&data->pair_match, data->pair_match_buf);
603 if (ret < 0) {
604 goto unlock_return;
605 }
606
607 ret = modem_chat_run_script(&data->chat, &data->pair_script);
608 if (ret < 0) {
609 goto unlock_return;
610 }
611
612 unlock_return:
613 quectel_lcx6g_unlock(dev);
614 return ret;
615 }
616
search_mode_enabled(const char * arg)617 static inline bool search_mode_enabled(const char *arg)
618 {
619 return arg[0] == '1';
620 }
621
quectel_lcx6g_get_search_mode_callback(struct modem_chat * chat,char ** argv,uint16_t argc,void * user_data)622 static void quectel_lcx6g_get_search_mode_callback(struct modem_chat *chat, char **argv,
623 uint16_t argc, void *user_data)
624 {
625 struct quectel_lcx6g_data *data = user_data;
626
627 if (argc != 8) {
628 return;
629 }
630
631 data->enabled_systems_response = search_mode_enabled(argv[1]) ? GNSS_SYSTEM_GPS : 0;
632 data->enabled_systems_response |= search_mode_enabled(argv[2]) ? GNSS_SYSTEM_GLONASS : 0;
633 data->enabled_systems_response |= search_mode_enabled(argv[3]) ? GNSS_SYSTEM_GALILEO : 0;
634 data->enabled_systems_response |= search_mode_enabled(argv[4]) ? GNSS_SYSTEM_BEIDOU : 0;
635 data->enabled_systems_response |= search_mode_enabled(argv[5]) ? GNSS_SYSTEM_QZSS : 0;
636 }
637
quectel_lcx6g_get_sbas_status_callback(struct modem_chat * chat,char ** argv,uint16_t argc,void * user_data)638 static void quectel_lcx6g_get_sbas_status_callback(struct modem_chat *chat, char **argv,
639 uint16_t argc, void *user_data)
640 {
641 struct quectel_lcx6g_data *data = user_data;
642
643 if (argc != 3) {
644 return;
645 }
646
647 data->enabled_systems_response |= ('1' == argv[1][0]) ? GNSS_SYSTEM_SBAS : 0;
648 }
649
650
quectel_lcx6g_get_enabled_systems(const struct device * dev,gnss_systems_t * systems)651 static int quectel_lcx6g_get_enabled_systems(const struct device *dev, gnss_systems_t *systems)
652 {
653 struct quectel_lcx6g_data *data = dev->data;
654 int ret;
655
656 quectel_lcx6g_lock(dev);
657
658 ret = gnss_nmea0183_snprintk(data->pair_request_buf, sizeof(data->pair_request_buf),
659 "PAIR067");
660 if (ret < 0) {
661 goto unlock_return;
662 }
663
664 ret = modem_chat_script_chat_set_request(&data->pair_script_chat, data->pair_request_buf);
665 if (ret < 0) {
666 goto unlock_return;
667 }
668
669 strncpy(data->pair_match_buf, "$PAIR067,", sizeof(data->pair_match_buf));
670 ret = modem_chat_match_set_match(&data->pair_match, data->pair_match_buf);
671 if (ret < 0) {
672 goto unlock_return;
673 }
674
675 modem_chat_match_set_callback(&data->pair_match, quectel_lcx6g_get_search_mode_callback);
676 ret = modem_chat_run_script(&data->chat, &data->pair_script);
677 modem_chat_match_set_callback(&data->pair_match, NULL);
678 if (ret < 0) {
679 goto unlock_return;
680 }
681
682 ret = gnss_nmea0183_snprintk(data->pair_request_buf, sizeof(data->pair_request_buf),
683 "PAIR411");
684 if (ret < 0) {
685 goto unlock_return;
686 }
687
688 ret = modem_chat_script_chat_set_request(&data->pair_script_chat, data->pair_request_buf);
689 if (ret < 0) {
690 goto unlock_return;
691 }
692
693 strncpy(data->pair_match_buf, "$PAIR411,", sizeof(data->pair_match_buf));
694 ret = modem_chat_match_set_match(&data->pair_match, data->pair_match_buf);
695 if (ret < 0) {
696 goto unlock_return;
697 }
698
699 modem_chat_match_set_callback(&data->pair_match, quectel_lcx6g_get_sbas_status_callback);
700 ret = modem_chat_run_script(&data->chat, &data->pair_script);
701 modem_chat_match_set_callback(&data->pair_match, NULL);
702 if (ret < 0) {
703 goto unlock_return;
704 }
705
706 *systems = data->enabled_systems_response;
707
708 unlock_return:
709 quectel_lcx6g_unlock(dev);
710 return ret;
711 }
712
quectel_lcx6g_get_supported_systems(const struct device * dev,gnss_systems_t * systems)713 static int quectel_lcx6g_get_supported_systems(const struct device *dev, gnss_systems_t *systems)
714 {
715 *systems = (GNSS_SYSTEM_GPS | GNSS_SYSTEM_GLONASS | GNSS_SYSTEM_GALILEO |
716 GNSS_SYSTEM_BEIDOU | GNSS_SYSTEM_QZSS | GNSS_SYSTEM_SBAS);
717 return 0;
718 }
719
720 static DEVICE_API(gnss, gnss_api) = {
721 .set_fix_rate = quectel_lcx6g_set_fix_rate,
722 .get_fix_rate = quectel_lcx6g_get_fix_rate,
723 .set_navigation_mode = quectel_lcx6g_set_navigation_mode,
724 .get_navigation_mode = quectel_lcx6g_get_navigation_mode,
725 .set_enabled_systems = quectel_lcx6g_set_enabled_systems,
726 .get_enabled_systems = quectel_lcx6g_get_enabled_systems,
727 .get_supported_systems = quectel_lcx6g_get_supported_systems,
728 };
729
quectel_lcx6g_init_nmea0183_match(const struct device * dev)730 static int quectel_lcx6g_init_nmea0183_match(const struct device *dev)
731 {
732 struct quectel_lcx6g_data *data = dev->data;
733
734 const struct gnss_nmea0183_match_config config = {
735 .gnss = dev,
736 #if CONFIG_GNSS_SATELLITES
737 .satellites = data->satellites,
738 .satellites_size = ARRAY_SIZE(data->satellites),
739 #endif
740 };
741
742 return gnss_nmea0183_match_init(&data->match_data, &config);
743 }
744
quectel_lcx6g_init_pipe(const struct device * dev)745 static void quectel_lcx6g_init_pipe(const struct device *dev)
746 {
747 const struct quectel_lcx6g_config *config = dev->config;
748 struct quectel_lcx6g_data *data = dev->data;
749
750 const struct modem_backend_uart_config uart_backend_config = {
751 .uart = config->uart,
752 .receive_buf = data->uart_backend_receive_buf,
753 .receive_buf_size = ARRAY_SIZE(data->uart_backend_receive_buf),
754 .transmit_buf = data->uart_backend_transmit_buf,
755 .transmit_buf_size = ARRAY_SIZE(data->uart_backend_transmit_buf),
756 };
757
758 data->uart_pipe = modem_backend_uart_init(&data->uart_backend, &uart_backend_config);
759 }
760
quectel_lcx6g_init_chat(const struct device * dev)761 static int quectel_lcx6g_init_chat(const struct device *dev)
762 {
763 struct quectel_lcx6g_data *data = dev->data;
764
765 const struct modem_chat_config chat_config = {
766 .user_data = data,
767 .receive_buf = data->chat_receive_buf,
768 .receive_buf_size = ARRAY_SIZE(data->chat_receive_buf),
769 .delimiter = data->chat_delimiter,
770 .delimiter_size = ARRAY_SIZE(data->chat_delimiter),
771 .filter = NULL,
772 .filter_size = 0,
773 .argv = data->chat_argv,
774 .argv_size = ARRAY_SIZE(data->chat_argv),
775 .unsol_matches = unsol_matches,
776 .unsol_matches_size = ARRAY_SIZE(unsol_matches),
777 };
778
779 return modem_chat_init(&data->chat, &chat_config);
780 }
781
quectel_lcx6g_init_pair_script(const struct device * dev)782 static void quectel_lcx6g_init_pair_script(const struct device *dev)
783 {
784 struct quectel_lcx6g_data *data = dev->data;
785
786 modem_chat_match_init(&data->pair_match);
787 modem_chat_match_set_separators(&data->pair_match, ",*");
788
789 modem_chat_script_chat_init(&data->pair_script_chat);
790 modem_chat_script_chat_set_response_matches(&data->pair_script_chat,
791 &data->pair_match, 1);
792
793 modem_chat_script_init(&data->pair_script);
794 modem_chat_script_set_name(&data->pair_script, "pair");
795 modem_chat_script_set_script_chats(&data->pair_script, &data->pair_script_chat, 1);
796 modem_chat_script_set_abort_matches(&data->pair_script, NULL, 0);
797 modem_chat_script_set_timeout(&data->pair_script, 10);
798 }
799
quectel_lcx6g_init(const struct device * dev)800 static int quectel_lcx6g_init(const struct device *dev)
801 {
802 struct quectel_lcx6g_data *data = dev->data;
803 int ret;
804
805 k_sem_init(&data->lock, 1, 1);
806
807 ret = quectel_lcx6g_init_nmea0183_match(dev);
808 if (ret < 0) {
809 return ret;
810 }
811
812 quectel_lcx6g_init_pipe(dev);
813
814 ret = quectel_lcx6g_init_chat(dev);
815 if (ret < 0) {
816 return ret;
817 }
818
819 quectel_lcx6g_init_pair_script(dev);
820
821 quectel_lcx6g_pm_changed(dev);
822
823 if (pm_device_is_powered(dev)) {
824 ret = quectel_lcx6g_resume(dev);
825 if (ret < 0) {
826 return ret;
827 }
828 quectel_lcx6g_pm_changed(dev);
829 } else {
830 pm_device_init_off(dev);
831 }
832
833 return pm_device_runtime_enable(dev);
834 }
835
836 #define LCX6G_INST_NAME(inst, name) \
837 _CONCAT(_CONCAT(_CONCAT(name, _), DT_DRV_COMPAT), inst)
838
839 #define LCX6G_DEVICE(inst) \
840 static const struct quectel_lcx6g_config LCX6G_INST_NAME(inst, config) = { \
841 .uart = DEVICE_DT_GET(DT_INST_BUS(inst)), \
842 .pps_mode = DT_INST_STRING_UPPER_TOKEN(inst, pps_mode), \
843 .pps_pulse_width = DT_INST_PROP(inst, pps_pulse_width), \
844 }; \
845 \
846 static struct quectel_lcx6g_data LCX6G_INST_NAME(inst, data) = { \
847 .chat_delimiter = {'\r', '\n'}, \
848 }; \
849 \
850 PM_DEVICE_DT_INST_DEFINE(inst, quectel_lcx6g_pm_action); \
851 \
852 DEVICE_DT_INST_DEFINE(inst, quectel_lcx6g_init, PM_DEVICE_DT_INST_GET(inst), \
853 &LCX6G_INST_NAME(inst, data), &LCX6G_INST_NAME(inst, config), \
854 POST_KERNEL, CONFIG_GNSS_INIT_PRIORITY, &gnss_api);
855
856 #define DT_DRV_COMPAT quectel_lc26g
857 DT_INST_FOREACH_STATUS_OKAY(LCX6G_DEVICE)
858 #undef DT_DRV_COMPAT
859
860 #define DT_DRV_COMPAT quectel_lc76g
861 DT_INST_FOREACH_STATUS_OKAY(LCX6G_DEVICE)
862 #undef DT_DRV_COMPAT
863
864 #define DT_DRV_COMPAT quectel_lc86g
865 DT_INST_FOREACH_STATUS_OKAY(LCX6G_DEVICE)
866 #undef DT_DRV_COMPAT
867