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