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