1 /*
2  * Copyright (c) 2024 Jerónimo Agulló
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/drivers/gnss.h>
8 #include <zephyr/drivers/gnss/gnss_publish.h>
9 #include <zephyr/modem/chat.h>
10 #include <zephyr/modem/backend/uart.h>
11 #include <zephyr/kernel.h>
12 #include <zephyr/pm/device.h>
13 #include <zephyr/drivers/gpio.h>
14 #include <string.h>
15 
16 #include "gnss_nmea0183.h"
17 #include "gnss_nmea0183_match.h"
18 #include "gnss_parse.h"
19 
20 #include <zephyr/logging/log.h>
21 LOG_MODULE_REGISTER(luatos_air530z, CONFIG_GNSS_LOG_LEVEL);
22 
23 #define DT_DRV_COMPAT luatos_air530z
24 
25 #define UART_RECV_BUF_SZ 128
26 #define UART_TRANS_BUF_SZ 64
27 
28 #define CHAT_RECV_BUF_SZ 256
29 #define CHAT_ARGV_SZ 32
30 
31 MODEM_CHAT_SCRIPT_CMDS_DEFINE(init_script_cmds,
32 #if CONFIG_GNSS_SATELLITES
33 	/* receive only GGA, RMC and GSV NMEA messages */
34 	MODEM_CHAT_SCRIPT_CMD_RESP_NONE("$PCAS03,1,0,0,1,1,0,0,0,0,0,0,0,0*1F", 10),
35 #else
36 	/* receive only GGA and RMC NMEA messages */
37 	MODEM_CHAT_SCRIPT_CMD_RESP_NONE("$PCAS03,1,0,0,0,1,0,0,0,0,0,0,0,0*1E", 10),
38 #endif
39 );
40 
41 MODEM_CHAT_SCRIPT_NO_ABORT_DEFINE(init_script, init_script_cmds, NULL, 5);
42 
43 struct gnss_luatos_air530z_config {
44 	const struct device *uart;
45 	const struct gpio_dt_spec on_off_gpio;
46 	const int uart_baudrate;
47 };
48 
49 struct gnss_luatos_air530z_data {
50 	struct gnss_nmea0183_match_data match_data;
51 #if CONFIG_GNSS_SATELLITES
52 	struct gnss_satellite satellites[CONFIG_GNSS_LUATOS_AIR530Z_SATELLITES_COUNT];
53 #endif
54 
55 	/* UART backend */
56 	struct modem_pipe *uart_pipe;
57 	struct modem_backend_uart uart_backend;
58 	uint8_t uart_backend_receive_buf[UART_RECV_BUF_SZ];
59 	uint8_t uart_backend_transmit_buf[UART_TRANS_BUF_SZ];
60 
61 	/* Modem chat */
62 	struct modem_chat chat;
63 	uint8_t chat_receive_buf[CHAT_RECV_BUF_SZ];
64 	uint8_t chat_delimiter[2];
65 	uint8_t *chat_argv[CHAT_ARGV_SZ];
66 
67 	/* Dynamic chat script */
68 	uint8_t dynamic_separators_buf[2];
69 	uint8_t dynamic_request_buf[32];
70 	struct modem_chat_script_chat dynamic_script_chat;
71 	struct modem_chat_script dynamic_script;
72 
73 	struct k_sem lock;
74 };
75 
76 MODEM_CHAT_MATCHES_DEFINE(unsol_matches,
77 	MODEM_CHAT_MATCH_WILDCARD("$??GGA,", ",*", gnss_nmea0183_match_gga_callback),
78 	MODEM_CHAT_MATCH_WILDCARD("$??RMC,", ",*", gnss_nmea0183_match_rmc_callback),
79 #if CONFIG_GNSS_SATELLITES
80 	MODEM_CHAT_MATCH_WILDCARD("$??GSV,", ",*", gnss_nmea0183_match_gsv_callback),
81 #endif
82 );
83 
luatos_air530z_lock(const struct device * dev)84 static void luatos_air530z_lock(const struct device *dev)
85 {
86 	struct gnss_luatos_air530z_data *data = dev->data;
87 
88 	(void)k_sem_take(&data->lock, K_FOREVER);
89 }
90 
luatos_air530z_unlock(const struct device * dev)91 static void luatos_air530z_unlock(const struct device *dev)
92 {
93 	struct gnss_luatos_air530z_data *data = dev->data;
94 
95 	k_sem_give(&data->lock);
96 }
97 
gnss_luatos_air530z_init_nmea0183_match(const struct device * dev)98 static int gnss_luatos_air530z_init_nmea0183_match(const struct device *dev)
99 {
100 	struct gnss_luatos_air530z_data *data = dev->data;
101 
102 	const struct gnss_nmea0183_match_config match_config = {
103 		.gnss = dev,
104 #if CONFIG_GNSS_SATELLITES
105 		.satellites = data->satellites,
106 		.satellites_size = ARRAY_SIZE(data->satellites),
107 #endif
108 	};
109 
110 	return gnss_nmea0183_match_init(&data->match_data, &match_config);
111 }
112 
gnss_luatos_air530z_init_pipe(const struct device * dev)113 static void gnss_luatos_air530z_init_pipe(const struct device *dev)
114 {
115 	const struct gnss_luatos_air530z_config *config = dev->config;
116 	struct gnss_luatos_air530z_data *data = dev->data;
117 
118 	const struct modem_backend_uart_config uart_backend_config = {
119 		.uart = config->uart,
120 		.receive_buf = data->uart_backend_receive_buf,
121 		.receive_buf_size = sizeof(data->uart_backend_receive_buf),
122 		.transmit_buf = data->uart_backend_transmit_buf,
123 		.transmit_buf_size = ARRAY_SIZE(data->uart_backend_transmit_buf),
124 	};
125 
126 	data->uart_pipe = modem_backend_uart_init(&data->uart_backend, &uart_backend_config);
127 }
128 
gnss_luatos_air530z_init_chat(const struct device * dev)129 static int gnss_luatos_air530z_init_chat(const struct device *dev)
130 {
131 	struct gnss_luatos_air530z_data *data = dev->data;
132 
133 	const struct modem_chat_config chat_config = {
134 		.user_data = data,
135 		.receive_buf = data->chat_receive_buf,
136 		.receive_buf_size = sizeof(data->chat_receive_buf),
137 		.delimiter = data->chat_delimiter,
138 		.delimiter_size = ARRAY_SIZE(data->chat_delimiter),
139 		.filter = NULL,
140 		.filter_size = 0,
141 		.argv = data->chat_argv,
142 		.argv_size = ARRAY_SIZE(data->chat_argv),
143 		.unsol_matches = unsol_matches,
144 		.unsol_matches_size = ARRAY_SIZE(unsol_matches),
145 	};
146 
147 	return modem_chat_init(&data->chat, &chat_config);
148 }
149 
luatos_air530z_init_dynamic_script(const struct device * dev)150 static void luatos_air530z_init_dynamic_script(const struct device *dev)
151 {
152 	struct gnss_luatos_air530z_data *data = dev->data;
153 
154 	/* Air530z doesn't respond to commands. Thus, response_matches_size = 0; */
155 	data->dynamic_script_chat.request = data->dynamic_request_buf;
156 	data->dynamic_script_chat.response_matches = NULL;
157 	data->dynamic_script_chat.response_matches_size = 0;
158 	data->dynamic_script_chat.timeout = 0;
159 
160 	data->dynamic_script.name = "PCAS";
161 	data->dynamic_script.script_chats = &data->dynamic_script_chat;
162 	data->dynamic_script.script_chats_size = 1;
163 	data->dynamic_script.abort_matches = NULL;
164 	data->dynamic_script.abort_matches_size = 0;
165 	data->dynamic_script.callback = NULL;
166 	data->dynamic_script.timeout = 5;
167 }
168 
gnss_luatos_air530z_init(const struct device * dev)169 static int gnss_luatos_air530z_init(const struct device *dev)
170 {
171 	struct gnss_luatos_air530z_data *data = dev->data;
172 	const struct gnss_luatos_air530z_config *config = dev->config;
173 	int ret;
174 
175 	k_sem_init(&data->lock, 1, 1);
176 
177 	ret = gnss_luatos_air530z_init_nmea0183_match(dev);
178 	if (ret < 0) {
179 		return ret;
180 	}
181 
182 	gnss_luatos_air530z_init_pipe(dev);
183 
184 	ret = gnss_luatos_air530z_init_chat(dev);
185 	if (ret < 0) {
186 		return ret;
187 	}
188 
189 	luatos_air530z_init_dynamic_script(dev);
190 
191 	ret = modem_pipe_open(data->uart_pipe, K_SECONDS(10));
192 	if (ret < 0) {
193 		return ret;
194 	}
195 
196 	ret = modem_chat_attach(&data->chat, data->uart_pipe);
197 	if (ret < 0) {
198 		modem_pipe_close(data->uart_pipe, K_SECONDS(10));
199 		return ret;
200 	}
201 
202 	ret = modem_chat_run_script(&data->chat, &init_script);
203 	if (ret < 0) {
204 		LOG_ERR("Failed to run init_script");
205 		modem_pipe_close(data->uart_pipe, K_SECONDS(10));
206 		return ret;
207 	}
208 
209 	/* setup on-off gpio for power management */
210 	if (!gpio_is_ready_dt(&config->on_off_gpio)) {
211 		LOG_ERR("on-off GPIO device not ready");
212 		return -ENODEV;
213 	}
214 
215 	gpio_pin_configure_dt(&config->on_off_gpio, GPIO_OUTPUT_HIGH);
216 
217 	return 0;
218 }
219 
luatos_air530z_pm_resume(const struct device * dev)220 static int luatos_air530z_pm_resume(const struct device *dev)
221 {
222 	struct gnss_luatos_air530z_data *data = dev->data;
223 	int ret;
224 
225 	ret = modem_pipe_open(data->uart_pipe, K_SECONDS(10));
226 	if (ret < 0) {
227 		return ret;
228 	}
229 
230 	ret = modem_chat_attach(&data->chat, data->uart_pipe);
231 	if (ret < 0) {
232 		modem_pipe_close(data->uart_pipe, K_SECONDS(10));
233 		return ret;
234 	}
235 
236 	ret = modem_chat_run_script(&data->chat, &init_script);
237 	if (ret < 0) {
238 		modem_pipe_close(data->uart_pipe, K_SECONDS(10));
239 		return ret;
240 	}
241 
242 	return 0;
243 }
244 
luatos_air530z_pm_action(const struct device * dev,enum pm_device_action action)245 static int luatos_air530z_pm_action(const struct device *dev, enum pm_device_action action)
246 {
247 	struct gnss_luatos_air530z_data *data = dev->data;
248 	const struct gnss_luatos_air530z_config *config = dev->config;
249 	int ret = -ENOTSUP;
250 
251 	switch (action) {
252 	case PM_DEVICE_ACTION_SUSPEND:
253 		gpio_pin_set_dt(&config->on_off_gpio, 0);
254 		ret = modem_pipe_close(data->uart_pipe, K_SECONDS(10));
255 		break;
256 
257 	case PM_DEVICE_ACTION_RESUME:
258 		gpio_pin_set_dt(&config->on_off_gpio, 1);
259 		ret = luatos_air530z_pm_resume(dev);
260 		break;
261 
262 	default:
263 		break;
264 	}
265 
266 	return ret;
267 }
268 
luatos_air530z_set_fix_rate(const struct device * dev,uint32_t fix_interval_ms)269 static int luatos_air530z_set_fix_rate(const struct device *dev, uint32_t fix_interval_ms)
270 {
271 	struct gnss_luatos_air530z_data *data = dev->data;
272 	int ret;
273 
274 	if (fix_interval_ms < 100 || fix_interval_ms > 1000) {
275 		return -EINVAL;
276 	}
277 
278 	luatos_air530z_lock(dev);
279 
280 	ret = gnss_nmea0183_snprintk(data->dynamic_request_buf, sizeof(data->dynamic_request_buf),
281 				    "PCAS02,%u", fix_interval_ms);
282 
283 	data->dynamic_script_chat.request_size = ret;
284 
285 	ret = modem_chat_run_script(&data->chat, &data->dynamic_script);
286 	if (ret < 0) {
287 		goto unlock_return;
288 	}
289 
290 unlock_return:
291 	luatos_air530z_unlock(dev);
292 	return ret;
293 }
294 
luatos_air530z_set_enabled_systems(const struct device * dev,gnss_systems_t systems)295 static int luatos_air530z_set_enabled_systems(const struct device *dev, gnss_systems_t systems)
296 {
297 	struct gnss_luatos_air530z_data *data = dev->data;
298 	gnss_systems_t supported_systems;
299 	uint8_t encoded_systems = 0;
300 	int ret;
301 
302 	supported_systems = (GNSS_SYSTEM_GPS | GNSS_SYSTEM_GLONASS | GNSS_SYSTEM_BEIDOU);
303 
304 	if ((~supported_systems) & systems) {
305 		return -EINVAL;
306 	}
307 
308 	luatos_air530z_lock(dev);
309 
310 	WRITE_BIT(encoded_systems, 0, systems & GNSS_SYSTEM_GPS);
311 	WRITE_BIT(encoded_systems, 1, systems & GNSS_SYSTEM_GLONASS);
312 	WRITE_BIT(encoded_systems, 2, systems & GNSS_SYSTEM_BEIDOU);
313 
314 	ret = gnss_nmea0183_snprintk(data->dynamic_request_buf, sizeof(data->dynamic_request_buf),
315 					"PCAS04,%u", encoded_systems);
316 	if (ret < 0) {
317 		goto unlock_return;
318 	}
319 
320 	data->dynamic_script_chat.request_size = ret;
321 
322 	ret = modem_chat_run_script(&data->chat, &data->dynamic_script);
323 	if (ret < 0) {
324 		goto unlock_return;
325 	}
326 
327 unlock_return:
328 	luatos_air530z_unlock(dev);
329 	return ret;
330 
331 }
332 
luatos_air530z_get_supported_systems(const struct device * dev,gnss_systems_t * systems)333 static int luatos_air530z_get_supported_systems(const struct device *dev, gnss_systems_t *systems)
334 {
335 	*systems = (GNSS_SYSTEM_GPS | GNSS_SYSTEM_GLONASS | GNSS_SYSTEM_BEIDOU);
336 	return 0;
337 }
338 
339 static DEVICE_API(gnss, gnss_api) = {
340 	.set_fix_rate = luatos_air530z_set_fix_rate,
341 	.set_enabled_systems = luatos_air530z_set_enabled_systems,
342 	.get_supported_systems = luatos_air530z_get_supported_systems,
343 };
344 
345 #define LUATOS_AIR530Z(inst)									\
346 	static const struct gnss_luatos_air530z_config gnss_luatos_air530z_cfg_##inst = {	\
347 		.uart = DEVICE_DT_GET(DT_INST_BUS(inst)),					\
348 		.on_off_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, on_off_gpios, { 0 }),		\
349 	};											\
350 												\
351 	static struct gnss_luatos_air530z_data gnss_luatos_air530z_data_##inst = {		\
352 		.chat_delimiter = {'\r', '\n'},							\
353 		.dynamic_separators_buf = {',', '*'},						\
354 	};											\
355 												\
356 	PM_DEVICE_DT_INST_DEFINE(inst, luatos_air530z_pm_action);				\
357 												\
358 	DEVICE_DT_INST_DEFINE(inst, gnss_luatos_air530z_init,					\
359 		PM_DEVICE_DT_INST_GET(inst),							\
360 		&gnss_luatos_air530z_data_##inst,						\
361 		&gnss_luatos_air530z_cfg_##inst,						\
362 		POST_KERNEL, CONFIG_GNSS_INIT_PRIORITY, &gnss_api);
363 
364 DT_INST_FOREACH_STATUS_OKAY(LUATOS_AIR530Z)
365