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