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