| /* |
| * Copyright 2024 NXP |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/drivers/gnss.h> |
| #include <zephyr/drivers/gnss/gnss_publish.h> |
| #include <zephyr/modem/chat.h> |
| #include <zephyr/modem/ubx.h> |
| #include <zephyr/modem/backend/uart.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/drivers/gpio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| |
| #include "gnss_nmea0183.h" |
| #include "gnss_nmea0183_match.h" |
| #include "gnss_parse.h" |
| |
| #include "gnss_u_blox_protocol/gnss_u_blox_protocol.h" |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(ubx_m8, CONFIG_GNSS_LOG_LEVEL); |
| |
| #define DT_DRV_COMPAT u_blox_m8 |
| |
| #define UART_RECV_BUF_SZ 128 |
| #define UART_TRNF_BUF_SZ 128 |
| |
| #define CHAT_RECV_BUF_SZ 256 |
| #define CHAT_ARGV_SZ 32 |
| |
| #define UBX_RECV_BUF_SZ UBX_FRM_SZ_MAX |
| #define UBX_TRNS_BUF_SZ UBX_FRM_SZ_MAX |
| #define UBX_WORK_BUF_SZ UBX_FRM_SZ_MAX |
| |
| #define UBX_FRM_BUF_SZ UBX_FRM_SZ_MAX |
| |
| #define MODEM_UBX_SCRIPT_TIMEOUT_MS 1000 |
| #define UBX_M8_SCRIPT_RETRY_DEFAULT 10 |
| |
| #define UBX_M8_GNSS_SYS_CNT 8 |
| #define UBX_M8_GNSS_SUPP_SYS_CNT 6 |
| |
| struct ubx_m8_config { |
| const struct device *uart; |
| const uint32_t uart_baudrate; |
| }; |
| |
| struct ubx_m8_data { |
| struct gnss_nmea0183_match_data match_data; |
| #if CONFIG_GNSS_SATELLITES |
| struct gnss_satellite satellites[CONFIG_GNSS_U_BLOX_M8_SATELLITES_COUNT]; |
| #endif |
| |
| /* UART backend */ |
| struct modem_pipe *uart_pipe; |
| struct modem_backend_uart uart_backend; |
| uint8_t uart_backend_receive_buf[UART_RECV_BUF_SZ]; |
| uint8_t uart_backend_transmit_buf[UART_TRNF_BUF_SZ]; |
| |
| /* Modem chat */ |
| struct modem_chat chat; |
| uint8_t chat_receive_buf[CHAT_RECV_BUF_SZ]; |
| uint8_t *chat_argv[CHAT_ARGV_SZ]; |
| |
| /* Modem ubx */ |
| struct modem_ubx ubx; |
| uint8_t ubx_receive_buf[UBX_RECV_BUF_SZ]; |
| uint8_t ubx_work_buf[UBX_WORK_BUF_SZ]; |
| |
| /* Modem ubx script */ |
| struct modem_ubx_script script; |
| uint8_t request_buf[UBX_FRM_BUF_SZ]; |
| uint8_t response_buf[UBX_FRM_BUF_SZ]; |
| uint8_t match_buf[UBX_FRM_BUF_SZ]; |
| |
| struct k_spinlock lock; |
| }; |
| |
| MODEM_CHAT_MATCHES_DEFINE(unsol_matches, |
| MODEM_CHAT_MATCH_WILDCARD("$??GGA,", ",*", gnss_nmea0183_match_gga_callback), |
| MODEM_CHAT_MATCH_WILDCARD("$??RMC,", ",*", gnss_nmea0183_match_rmc_callback), |
| #if CONFIG_GNSS_SATELLITES |
| MODEM_CHAT_MATCH_WILDCARD("$??GSV,", ",*", gnss_nmea0183_match_gsv_callback), |
| #endif |
| ); |
| |
| static int ubx_m8_resume(const struct device *dev) |
| { |
| struct ubx_m8_data *data = dev->data; |
| int ret; |
| |
| ret = modem_pipe_open(data->uart_pipe, K_SECONDS(10)); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| ret = modem_chat_attach(&data->chat, data->uart_pipe); |
| if (ret < 0) { |
| (void)modem_pipe_close(data->uart_pipe, K_SECONDS(10)); |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| static int ubx_m8_turn_off(const struct device *dev) |
| { |
| struct ubx_m8_data *data = dev->data; |
| |
| return modem_pipe_close(data->uart_pipe, K_SECONDS(10)); |
| } |
| |
| static int ubx_m8_init_nmea0183_match(const struct device *dev) |
| { |
| struct ubx_m8_data *data = dev->data; |
| |
| const struct gnss_nmea0183_match_config match_config = { |
| .gnss = dev, |
| #if CONFIG_GNSS_SATELLITES |
| .satellites = data->satellites, |
| .satellites_size = ARRAY_SIZE(data->satellites), |
| #endif |
| }; |
| |
| return gnss_nmea0183_match_init(&data->match_data, &match_config); |
| } |
| |
| static void ubx_m8_init_pipe(const struct device *dev) |
| { |
| const struct ubx_m8_config *cfg = dev->config; |
| struct ubx_m8_data *data = dev->data; |
| |
| const struct modem_backend_uart_config uart_backend_config = { |
| .uart = cfg->uart, |
| .receive_buf = data->uart_backend_receive_buf, |
| .receive_buf_size = sizeof(data->uart_backend_receive_buf), |
| .transmit_buf = data->uart_backend_transmit_buf, |
| .transmit_buf_size = ARRAY_SIZE(data->uart_backend_transmit_buf), |
| }; |
| |
| data->uart_pipe = modem_backend_uart_init(&data->uart_backend, &uart_backend_config); |
| } |
| |
| static uint8_t ubx_m8_char_delimiter[] = {'\r', '\n'}; |
| |
| static int ubx_m8_init_chat(const struct device *dev) |
| { |
| struct ubx_m8_data *data = dev->data; |
| |
| const struct modem_chat_config chat_config = { |
| .user_data = data, |
| .receive_buf = data->chat_receive_buf, |
| .receive_buf_size = sizeof(data->chat_receive_buf), |
| .delimiter = ubx_m8_char_delimiter, |
| .delimiter_size = ARRAY_SIZE(ubx_m8_char_delimiter), |
| .filter = NULL, |
| .filter_size = 0, |
| .argv = data->chat_argv, |
| .argv_size = ARRAY_SIZE(data->chat_argv), |
| .unsol_matches = unsol_matches, |
| .unsol_matches_size = ARRAY_SIZE(unsol_matches), |
| }; |
| |
| return modem_chat_init(&data->chat, &chat_config); |
| } |
| |
| static int ubx_m8_init_ubx(const struct device *dev) |
| { |
| struct ubx_m8_data *data = dev->data; |
| |
| const struct modem_ubx_config ubx_config = { |
| .user_data = data, |
| .receive_buf = data->ubx_receive_buf, |
| .receive_buf_size = sizeof(data->ubx_receive_buf), |
| .work_buf = data->ubx_work_buf, |
| .work_buf_size = sizeof(data->ubx_work_buf), |
| }; |
| |
| return modem_ubx_init(&data->ubx, &ubx_config); |
| } |
| |
| /** |
| * @brief Changes modem module (chat or ubx) attached to the uart pipe. |
| * @param dev Dev instance |
| * @param change_from_to 0 for changing from "chat" to "ubx", 1 for changing from "ubx" to "chat" |
| * @returns 0 if successful |
| * @returns negative errno code if failure |
| */ |
| static int ubx_m8_modem_module_change(const struct device *dev, bool change_from_to) |
| { |
| struct ubx_m8_data *data = dev->data; |
| int ret; |
| |
| if (change_from_to == 0) { |
| modem_chat_release(&data->chat); |
| ret = modem_ubx_attach(&data->ubx, data->uart_pipe); |
| } else { /* change_from_to == 1 */ |
| modem_ubx_release(&data->ubx); |
| ret = modem_chat_attach(&data->chat, data->uart_pipe); |
| } |
| |
| if (ret < 0) { |
| (void)modem_pipe_close(data->uart_pipe, K_SECONDS(10)); |
| } |
| |
| return ret; |
| } |
| |
| static int ubx_m8_modem_ubx_run_script(const struct device *dev, |
| struct modem_ubx_script *modem_ubx_script_tx) |
| { |
| struct ubx_m8_data *data = dev->data; |
| int ret; |
| |
| ret = ubx_m8_modem_module_change(dev, 0); |
| if (ret < 0) { |
| goto reset_modem_module; |
| } |
| |
| ret = modem_ubx_run_script(&data->ubx, modem_ubx_script_tx); |
| |
| reset_modem_module: |
| ret |= ubx_m8_modem_module_change(dev, 1); |
| |
| return ret; |
| } |
| |
| static void ubx_m8_modem_ubx_script_fill(const struct device *dev) |
| { |
| struct ubx_m8_data *data = dev->data; |
| |
| data->script.request = (struct ubx_frame *)data->request_buf; |
| data->script.response = (struct ubx_frame *)data->response_buf; |
| data->script.match = (struct ubx_frame *)data->match_buf; |
| data->script.retry_count = UBX_M8_SCRIPT_RETRY_DEFAULT; |
| data->script.timeout = K_MSEC(MODEM_UBX_SCRIPT_TIMEOUT_MS); |
| } |
| |
| static int ubx_m8_modem_ubx_script_init(const struct device *dev, void *payload, uint16_t payld_sz, |
| enum ubx_msg_class msg_cls, enum ubx_config_message msg_id) |
| { |
| int ret; |
| struct ubx_m8_data *data = dev->data; |
| struct ubx_cfg_ack_payload match_payload = { |
| .message_class = msg_cls, |
| .message_id = msg_id, |
| }; |
| |
| ubx_m8_modem_ubx_script_fill(dev); |
| |
| ret = ubx_create_and_validate_frame(data->match_buf, sizeof(data->match_buf), UBX_CLASS_ACK, |
| UBX_ACK_ACK, &match_payload, UBX_CFG_ACK_PAYLOAD_SZ); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| ret = ubx_create_and_validate_frame(data->request_buf, sizeof(data->request_buf), msg_cls, |
| msg_id, payload, payld_sz); |
| |
| return ret; |
| } |
| |
| static int ubx_m8_ubx_cfg_rate(const struct device *dev) |
| { |
| int ret; |
| k_spinlock_key_t key; |
| struct ubx_m8_data *data = dev->data; |
| struct ubx_cfg_rate_payload payload; |
| |
| key = k_spin_lock(&data->lock); |
| |
| ubx_cfg_rate_payload_default(&payload); |
| |
| ret = ubx_m8_modem_ubx_script_init(dev, &payload, UBX_CFG_RATE_PAYLOAD_SZ, UBX_CLASS_CFG, |
| UBX_CFG_RATE); |
| if (ret < 0) { |
| goto unlock; |
| } |
| |
| ret = ubx_m8_modem_ubx_run_script(dev, &(data->script)); |
| |
| unlock: |
| k_spin_unlock(&data->lock, key); |
| |
| return ret; |
| } |
| |
| static int ubx_m8_ubx_cfg_prt_set(const struct device *dev, uint32_t target_baudrate, |
| uint8_t retry) |
| { |
| int ret; |
| k_spinlock_key_t key; |
| struct ubx_m8_data *data = dev->data; |
| struct ubx_cfg_prt_set_payload payload; |
| |
| key = k_spin_lock(&data->lock); |
| |
| ubx_cfg_prt_set_payload_default(&payload); |
| payload.baudrate = target_baudrate; |
| |
| ret = ubx_m8_modem_ubx_script_init(dev, &payload, UBX_CFG_PRT_SET_PAYLOAD_SZ, |
| UBX_CLASS_CFG, UBX_CFG_PRT); |
| if (ret < 0) { |
| goto unlock; |
| } |
| |
| data->script.retry_count = retry; |
| /* Returns failure if "target_baudrate" is different than device's currently set baudrate, |
| * because the device will change its baudrate and respond with UBX-ACK with new baudrate, |
| * which we will miss. Hence, we need to change uart's baudrate after sending the frame |
| * (in order to receive response as well), which we are not doing right now. |
| */ |
| ret = ubx_m8_modem_ubx_run_script(dev, &(data->script)); |
| |
| unlock: |
| k_spin_unlock(&data->lock, key); |
| |
| return ret; |
| } |
| |
| static int ubx_m8_ubx_cfg_rst(const struct device *dev, uint8_t reset_mode) |
| { |
| int ret; |
| k_spinlock_key_t key; |
| struct ubx_m8_data *data = dev->data; |
| struct ubx_cfg_rst_payload payload; |
| |
| key = k_spin_lock(&data->lock); |
| |
| ubx_cfg_rst_payload_default(&payload); |
| |
| payload.nav_bbr_mask = UBX_CFG_RST_NAV_BBR_MASK_HOT_START; |
| payload.reset_mode = reset_mode; |
| |
| ret = ubx_m8_modem_ubx_script_init(dev, &payload, UBX_CFG_RST_PAYLOAD_SZ, UBX_CLASS_CFG, |
| UBX_CFG_RST); |
| if (ret < 0) { |
| goto unlock; |
| } |
| |
| data->script.match = NULL; |
| ret = ubx_m8_modem_ubx_run_script(dev, &(data->script)); |
| if (ret < 0) { |
| goto unlock; |
| } |
| |
| if (reset_mode == UBX_CFG_RST_RESET_MODE_CONTROLLED_GNSS_STOP) { |
| k_sleep(K_MSEC(UBX_CFG_RST_WAIT_MS)); |
| } |
| |
| unlock: |
| k_spin_unlock(&data->lock, key); |
| |
| return ret; |
| } |
| |
| static int ubx_m8_set_uart_baudrate(const struct device *dev, uint32_t baudrate) |
| { |
| int ret; |
| k_spinlock_key_t key; |
| struct ubx_m8_data *data = dev->data; |
| const struct ubx_m8_config *config = dev->config; |
| struct uart_config uart_cfg; |
| |
| key = k_spin_lock(&data->lock); |
| |
| ret = ubx_m8_turn_off(dev); |
| if (ret < 0) { |
| goto reset_and_unlock; |
| } |
| |
| ret = uart_config_get(config->uart, &uart_cfg); |
| if (ret < 0) { |
| goto reset_and_unlock; |
| } |
| uart_cfg.baudrate = baudrate; |
| |
| ret = uart_configure(config->uart, &uart_cfg); |
| |
| reset_and_unlock: |
| ret |= ubx_m8_resume(dev); |
| |
| k_spin_unlock(&data->lock, key); |
| |
| return ret; |
| } |
| |
| static bool ubx_m8_validate_baudrate(const struct device *dev, uint32_t baudrate) |
| { |
| for (int i = 0; i < UBX_BAUDRATE_COUNT; ++i) { |
| if (baudrate == ubx_baudrate[i]) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /* This function will return failure if "target_baudrate" != device's current baudrate. |
| * Refer the function description of ubx_m8_ubx_cfg_prt_set for a detailed explanation. |
| */ |
| static int ubx_m8_configure_gnss_device_baudrate_prerequisite(const struct device *dev) |
| { |
| /* Retry = 1 should be enough, but setting 2 just to be safe. */ |
| int ret, retry = 2; |
| const struct ubx_m8_config *config = dev->config; |
| uint32_t target_baudrate = config->uart_baudrate; |
| |
| ret = ubx_m8_validate_baudrate(dev, target_baudrate); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| /* Try communication with device with all possible baudrates, because initially we don't |
| * know the currently set baudrate of the device. We will match the baudrate in one of the |
| * following attempts and the device will thus change its baudrate to "target_baudrate". |
| */ |
| for (int i = 0; i < UBX_BAUDRATE_COUNT; ++i) { |
| /* Set baudrate of UART pipe as ubx_baudrate[i]. */ |
| ret = ubx_m8_set_uart_baudrate(dev, ubx_baudrate[i]); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| /* Try setting baudrate of device as target_baudrate. */ |
| ret = ubx_m8_ubx_cfg_prt_set(dev, target_baudrate, retry); |
| if (ret == 0) { |
| break; |
| } |
| } |
| |
| /* Reset baudrate of UART pipe as target_baudrate. */ |
| ret = ubx_m8_set_uart_baudrate(dev, target_baudrate); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int ubx_m8_configure_gnss_device_baudrate(const struct device *dev) |
| { |
| int ret; |
| const struct ubx_m8_config *config = dev->config; |
| uint32_t target_baudrate = config->uart_baudrate; |
| |
| ret = ubx_m8_validate_baudrate(dev, target_baudrate); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| ret = ubx_m8_ubx_cfg_prt_set(dev, target_baudrate, UBX_M8_SCRIPT_RETRY_DEFAULT); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int ubx_m8_configure_messages(const struct device *dev) |
| { |
| int ret = 0; |
| k_spinlock_key_t key; |
| struct ubx_m8_data *data = dev->data; |
| struct ubx_cfg_msg_payload payload; |
| |
| key = k_spin_lock(&data->lock); |
| |
| ubx_cfg_msg_payload_default(&payload); |
| |
| /* Enabling GGA, RMC and GSV messages. */ |
| payload.rate = 1; |
| uint8_t message_enable[] = {UBX_NMEA_GGA, UBX_NMEA_RMC, UBX_NMEA_GSV}; |
| |
| for (int i = 0; i < sizeof(message_enable); ++i) { |
| payload.message_id = message_enable[i]; |
| ret = ubx_m8_modem_ubx_script_init(dev, &payload, UBX_CFG_MSG_PAYLOAD_SZ, |
| UBX_CLASS_CFG, UBX_CFG_MSG); |
| if (ret < 0) { |
| goto unlock; |
| } |
| |
| ret = ubx_m8_modem_ubx_run_script(dev, &(data->script)); |
| if (ret < 0) { |
| goto unlock; |
| } |
| } |
| |
| /* Disabling DTM, GBS, GLL, GNS, GRS, GSA, GST, VLW, VTG and ZDA messages. */ |
| payload.rate = 0; |
| uint8_t message_disable[] = {UBX_NMEA_DTM, UBX_NMEA_GBS, UBX_NMEA_GLL, UBX_NMEA_GNS, |
| UBX_NMEA_GRS, UBX_NMEA_GSA, UBX_NMEA_GST, UBX_NMEA_VLW, |
| UBX_NMEA_VTG, UBX_NMEA_ZDA}; |
| |
| for (int i = 0; i < sizeof(message_disable); ++i) { |
| payload.message_id = message_disable[i]; |
| ret = ubx_m8_modem_ubx_script_init(dev, &payload, UBX_CFG_MSG_PAYLOAD_SZ, |
| UBX_CLASS_CFG, UBX_CFG_MSG); |
| if (ret < 0) { |
| goto unlock; |
| } |
| |
| ret = ubx_m8_modem_ubx_run_script(dev, &(data->script)); |
| if (ret < 0) { |
| goto unlock; |
| } |
| } |
| |
| unlock: |
| k_spin_unlock(&data->lock, key); |
| |
| return ret; |
| } |
| |
| static int ubx_m8_navigation_mode_to_ubx_dynamic_model(const struct device *dev, |
| enum gnss_navigation_mode mode) |
| { |
| switch (mode) { |
| case GNSS_NAVIGATION_MODE_ZERO_DYNAMICS: |
| return UBX_DYN_MODEL_STATIONARY; |
| case GNSS_NAVIGATION_MODE_LOW_DYNAMICS: |
| return UBX_DYN_MODEL_PORTABLE; |
| case GNSS_NAVIGATION_MODE_BALANCED_DYNAMICS: |
| return UBX_DYN_MODEL_AIRBORNE1G; |
| case GNSS_NAVIGATION_MODE_HIGH_DYNAMICS: |
| return UBX_DYN_MODEL_AIRBORNE4G; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int ubx_m8_ubx_dynamic_model_to_navigation_mode(const struct device *dev, |
| enum ubx_dynamic_model dynamic_model) |
| { |
| switch (dynamic_model) { |
| case UBX_DYN_MODEL_PORTABLE: |
| return GNSS_NAVIGATION_MODE_LOW_DYNAMICS; |
| case UBX_DYN_MODEL_STATIONARY: |
| return GNSS_NAVIGATION_MODE_ZERO_DYNAMICS; |
| case UBX_DYN_MODEL_PEDESTRIAN: |
| return GNSS_NAVIGATION_MODE_LOW_DYNAMICS; |
| case UBX_DYN_MODEL_AUTOMOTIVE: |
| return GNSS_NAVIGATION_MODE_LOW_DYNAMICS; |
| case UBX_DYN_MODEL_SEA: |
| return GNSS_NAVIGATION_MODE_BALANCED_DYNAMICS; |
| case UBX_DYN_MODEL_AIRBORNE1G: |
| return GNSS_NAVIGATION_MODE_BALANCED_DYNAMICS; |
| case UBX_DYN_MODEL_AIRBORNE2G: |
| return GNSS_NAVIGATION_MODE_BALANCED_DYNAMICS; |
| case UBX_DYN_MODEL_AIRBORNE4G: |
| return GNSS_NAVIGATION_MODE_HIGH_DYNAMICS; |
| case UBX_DYN_MODEL_WRIST: |
| return GNSS_NAVIGATION_MODE_BALANCED_DYNAMICS; |
| case UBX_DYN_MODEL_BIKE: |
| return GNSS_NAVIGATION_MODE_HIGH_DYNAMICS; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int ubx_m8_set_navigation_mode(const struct device *dev, enum gnss_navigation_mode mode) |
| { |
| int ret; |
| k_spinlock_key_t key; |
| struct ubx_m8_data *data = dev->data; |
| struct ubx_cfg_nav5_payload payload; |
| |
| key = k_spin_lock(&data->lock); |
| |
| ubx_cfg_nav5_payload_default(&payload); |
| |
| ret = ubx_m8_navigation_mode_to_ubx_dynamic_model(dev, mode); |
| if (ret < 0) { |
| goto unlock; |
| } |
| |
| payload.dyn_model = ret; |
| |
| ret = ubx_m8_modem_ubx_script_init(dev, &payload, UBX_CFG_NAV5_PAYLOAD_SZ, UBX_CLASS_CFG, |
| UBX_CFG_NAV5); |
| if (ret < 0) { |
| goto unlock; |
| } |
| |
| ret = ubx_m8_modem_ubx_run_script(dev, &(data->script)); |
| if (ret < 0) { |
| goto unlock; |
| } |
| |
| k_sleep(K_MSEC(UBX_CFG_NAV5_WAIT_MS)); |
| |
| unlock: |
| k_spin_unlock(&data->lock, key); |
| |
| return ret; |
| } |
| |
| static int ubx_m8_get_navigation_mode(const struct device *dev, enum gnss_navigation_mode *mode) |
| { |
| int ret; |
| k_spinlock_key_t key; |
| struct ubx_m8_data *data = dev->data; |
| enum ubx_dynamic_model dynamic_model; |
| |
| key = k_spin_lock(&data->lock); |
| |
| ret = ubx_m8_modem_ubx_script_init(dev, NULL, UBX_FRM_GET_PAYLOAD_SZ, UBX_CLASS_CFG, |
| UBX_CFG_NAV5); |
| if (ret < 0) { |
| goto unlock; |
| } |
| |
| ret = ubx_m8_modem_ubx_run_script(dev, &(data->script)); |
| if (ret < 0) { |
| goto unlock; |
| } |
| |
| struct ubx_frame *response = data->script.response; |
| |
| dynamic_model = ((struct ubx_cfg_nav5_payload *)response->payload_and_checksum)->dyn_model; |
| ret = ubx_m8_ubx_dynamic_model_to_navigation_mode(dev, dynamic_model); |
| if (ret < 0) { |
| goto unlock; |
| } |
| |
| *mode = ret; |
| |
| unlock: |
| k_spin_unlock(&data->lock, key); |
| |
| return ret; |
| } |
| |
| static int ubx_m8_get_supported_systems(const struct device *dev, gnss_systems_t *systems) |
| { |
| *systems = (GNSS_SYSTEM_GPS | GNSS_SYSTEM_GLONASS | GNSS_SYSTEM_GALILEO | |
| GNSS_SYSTEM_BEIDOU | GNSS_SYSTEM_SBAS | GNSS_SYSTEM_QZSS); |
| |
| return 0; |
| } |
| |
| static int ubx_m8_ubx_gnss_id_to_gnss_system(const struct device *dev, enum ubx_gnss_id gnss_id) |
| { |
| switch (gnss_id) { |
| case UBX_GNSS_ID_GPS: |
| return GNSS_SYSTEM_GPS; |
| case UBX_GNSS_ID_SBAS: |
| return GNSS_SYSTEM_SBAS; |
| case UBX_GNSS_ID_GALILEO: |
| return GNSS_SYSTEM_GALILEO; |
| case UBX_GNSS_ID_BEIDOU: |
| return GNSS_SYSTEM_BEIDOU; |
| case UBX_GNSS_ID_QZSS: |
| return GNSS_SYSTEM_QZSS; |
| case UBX_GNSS_ID_GLONASS: |
| return GNSS_SYSTEM_GLONASS; |
| default: |
| return -EINVAL; |
| }; |
| } |
| |
| static int ubx_m8_config_block_fill(const struct device *dev, gnss_systems_t gnss_system, |
| struct ubx_cfg_gnss_payload *payload, uint8_t index, |
| uint32_t enable) |
| { |
| uint32_t signal_config; |
| |
| switch (gnss_system) { |
| case GNSS_SYSTEM_GPS: |
| payload->config_blocks[index].gnss_id = UBX_GNSS_ID_GPS; |
| signal_config = UBX_CFG_GNSS_FLAG_SGN_CNF_GPS_L1C_A; |
| break; |
| case GNSS_SYSTEM_GLONASS: |
| payload->config_blocks[index].gnss_id = UBX_GNSS_ID_GLONASS; |
| signal_config = UBX_CFG_GNSS_FLAG_SGN_CNF_GLONASS_L1; |
| break; |
| case GNSS_SYSTEM_GALILEO: |
| payload->config_blocks[index].gnss_id = UBX_GNSS_ID_GALILEO; |
| signal_config = UBX_CFG_GNSS_FLAG_SGN_CNF_GALILEO_E1; |
| break; |
| case GNSS_SYSTEM_BEIDOU: |
| payload->config_blocks[index].gnss_id = UBX_GNSS_ID_BEIDOU; |
| signal_config = UBX_CFG_GNSS_FLAG_SGN_CNF_BEIDOU_B1I; |
| break; |
| case GNSS_SYSTEM_QZSS: |
| payload->config_blocks[index].gnss_id = UBX_GNSS_ID_QZSS; |
| signal_config = UBX_CFG_GNSS_FLAG_SGN_CNF_QZSS_L1C_A; |
| break; |
| case GNSS_SYSTEM_SBAS: |
| payload->config_blocks[index].gnss_id = UBX_GNSS_ID_SBAS; |
| signal_config = UBX_CFG_GNSS_FLAG_SGN_CNF_SBAS_L1C_A; |
| break; |
| default: |
| return -EINVAL; |
| }; |
| |
| payload->config_blocks[index].flags = enable | signal_config; |
| |
| return 0; |
| } |
| |
| static int ubx_m8_set_enabled_systems(const struct device *dev, gnss_systems_t systems) |
| { |
| int ret; |
| k_spinlock_key_t key; |
| struct ubx_m8_data *data = dev->data; |
| |
| key = k_spin_lock(&data->lock); |
| |
| struct ubx_cfg_gnss_payload *payload; |
| |
| /* Get number of tracking channels for each supported gnss system by sending CFG-GNSS. */ |
| ret = ubx_m8_modem_ubx_script_init(dev, NULL, UBX_FRM_GET_PAYLOAD_SZ, UBX_CLASS_CFG, |
| UBX_CFG_GNSS); |
| if (ret < 0) { |
| goto unlock; |
| } |
| |
| ret = ubx_m8_modem_ubx_run_script(dev, &(data->script)); |
| if (ret < 0) { |
| goto unlock; |
| } |
| |
| struct ubx_frame *response = data->script.response; |
| uint16_t res_trk_ch_sum = 0, max_trk_ch_sum = 0; |
| |
| /* Calculate sum of reserved and maximum tracking channels for each supported gnss system, |
| * and assert that the sum is not greater than the number of tracking channels in use. |
| */ |
| payload = (struct ubx_cfg_gnss_payload *) response->payload_and_checksum; |
| for (int i = 0; i < payload->num_config_blocks; ++i) { |
| ret = ubx_m8_ubx_gnss_id_to_gnss_system(dev, payload->config_blocks[i].gnss_id); |
| if (ret < 0) { |
| goto unlock; |
| } |
| |
| if (ret & systems) { |
| res_trk_ch_sum += payload->config_blocks[i].num_res_trk_ch; |
| max_trk_ch_sum += payload->config_blocks[i].max_num_trk_ch; |
| } |
| |
| if (res_trk_ch_sum > payload->num_trk_ch_use || |
| max_trk_ch_sum > payload->num_trk_ch_use) { |
| ret = -EINVAL; |
| goto unlock; |
| } |
| } |
| |
| /* Prepare payload (payload) for sending CFG-GNSS for enabling the gnss systems. */ |
| payload = malloc(sizeof(*payload) + |
| sizeof(struct ubx_cfg_gnss_payload_config_block) * UBX_M8_GNSS_SUPP_SYS_CNT); |
| if (!payload) { |
| ret = -ENOMEM; |
| goto unlock; |
| } |
| |
| payload->num_config_blocks = UBX_M8_GNSS_SUPP_SYS_CNT; |
| |
| ubx_cfg_gnss_payload_default(payload); |
| |
| uint8_t filled_blocks = 0; |
| gnss_systems_t supported_systems; |
| |
| ret = ubx_m8_get_supported_systems(dev, &supported_systems); |
| if (ret < 0) { |
| goto free_and_unlock; |
| } |
| |
| for (int i = 0; i < UBX_M8_GNSS_SYS_CNT; ++i) { |
| gnss_systems_t gnss_system = 1 << i; |
| |
| if (gnss_system & supported_systems) { |
| uint32_t enable = (systems & gnss_system) ? |
| UBX_CFG_GNSS_FLAG_ENABLE : UBX_CFG_GNSS_FLAG_DISABLE; |
| |
| ret = ubx_m8_config_block_fill(dev, gnss_system, payload, filled_blocks, |
| enable); |
| if (ret < 0) { |
| goto free_and_unlock; |
| } |
| |
| ++filled_blocks; |
| } |
| } |
| |
| ret = ubx_m8_modem_ubx_script_init(dev, payload, |
| UBX_CFG_GNSS_PAYLOAD_SZ(UBX_M8_GNSS_SUPP_SYS_CNT), |
| UBX_CLASS_CFG, UBX_CFG_GNSS); |
| if (ret < 0) { |
| goto free_and_unlock; |
| } |
| |
| ret = ubx_m8_modem_ubx_run_script(dev, &(data->script)); |
| if (ret < 0) { |
| goto free_and_unlock; |
| } |
| |
| k_sleep(K_MSEC(UBX_CFG_GNSS_WAIT_MS)); |
| |
| free_and_unlock: |
| free(payload); |
| |
| unlock: |
| k_spin_unlock(&data->lock, key); |
| |
| return ret; |
| } |
| |
| static int ubx_m8_get_enabled_systems(const struct device *dev, gnss_systems_t *systems) |
| { |
| int ret; |
| k_spinlock_key_t key; |
| struct ubx_m8_data *data = dev->data; |
| |
| key = k_spin_lock(&data->lock); |
| |
| ret = ubx_m8_modem_ubx_script_init(dev, NULL, UBX_FRM_GET_PAYLOAD_SZ, UBX_CLASS_CFG, |
| UBX_CFG_GNSS); |
| if (ret < 0) { |
| goto unlock; |
| } |
| |
| ret = ubx_m8_modem_ubx_run_script(dev, &(data->script)); |
| if (ret < 0) { |
| goto unlock; |
| } |
| |
| struct ubx_frame *response = data->script.response; |
| struct ubx_cfg_gnss_payload *payload = |
| (struct ubx_cfg_gnss_payload *) response->payload_and_checksum; |
| |
| *systems = 0; |
| for (int i = 0; i < payload->num_config_blocks; ++i) { |
| if (payload->config_blocks[i].flags & UBX_CFG_GNSS_FLAG_ENABLE) { |
| enum ubx_gnss_id gnss_id = payload->config_blocks[i].gnss_id; |
| |
| ret = ubx_m8_ubx_gnss_id_to_gnss_system(dev, gnss_id); |
| if (ret < 0) { |
| goto unlock; |
| } |
| |
| *systems |= ret; |
| } |
| } |
| |
| unlock: |
| k_spin_unlock(&data->lock, key); |
| |
| return ret; |
| } |
| |
| static int ubx_m8_set_fix_rate(const struct device *dev, uint32_t fix_interval_ms) |
| { |
| int ret; |
| k_spinlock_key_t key; |
| struct ubx_m8_data *data = dev->data; |
| struct ubx_cfg_rate_payload payload; |
| |
| if (fix_interval_ms < 50) { |
| return -1; |
| } |
| |
| key = k_spin_lock(&data->lock); |
| |
| ubx_cfg_rate_payload_default(&payload); |
| payload.meas_rate_ms = fix_interval_ms; |
| |
| ret = ubx_m8_modem_ubx_script_init(dev, &payload, UBX_CFG_RATE_PAYLOAD_SZ, UBX_CLASS_CFG, |
| UBX_CFG_RATE); |
| if (ret < 0) { |
| goto unlock; |
| } |
| |
| ret = ubx_m8_modem_ubx_run_script(dev, &(data->script)); |
| |
| unlock: |
| k_spin_unlock(&data->lock, key); |
| |
| return ret; |
| } |
| |
| static int ubx_m8_get_fix_rate(const struct device *dev, uint32_t *fix_interval_ms) |
| { |
| int ret; |
| k_spinlock_key_t key; |
| struct ubx_m8_data *data = dev->data; |
| struct ubx_cfg_rate_payload *payload; |
| |
| key = k_spin_lock(&data->lock); |
| |
| ret = ubx_m8_modem_ubx_script_init(dev, NULL, UBX_FRM_GET_PAYLOAD_SZ, UBX_CLASS_CFG, |
| UBX_CFG_RATE); |
| if (ret < 0) { |
| goto unlock; |
| } |
| |
| ret = ubx_m8_modem_ubx_run_script(dev, &(data->script)); |
| if (ret < 0) { |
| goto unlock; |
| } |
| |
| struct ubx_frame *response = data->script.response; |
| |
| payload = (struct ubx_cfg_rate_payload *) response->payload_and_checksum; |
| *fix_interval_ms = payload->meas_rate_ms; |
| |
| unlock: |
| k_spin_unlock(&data->lock, key); |
| |
| return ret; |
| } |
| |
| static const struct gnss_driver_api gnss_api = { |
| .set_fix_rate = ubx_m8_set_fix_rate, |
| .get_fix_rate = ubx_m8_get_fix_rate, |
| .set_navigation_mode = ubx_m8_set_navigation_mode, |
| .get_navigation_mode = ubx_m8_get_navigation_mode, |
| .set_enabled_systems = ubx_m8_set_enabled_systems, |
| .get_enabled_systems = ubx_m8_get_enabled_systems, |
| .get_supported_systems = ubx_m8_get_supported_systems, |
| }; |
| |
| static int ubx_m8_configure(const struct device *dev) |
| { |
| int ret; |
| |
| /* The return value could be ignored. See function description for more details. */ |
| (void)ubx_m8_configure_gnss_device_baudrate_prerequisite(dev); |
| |
| /* Stopping GNSS messages for clearer communication while configuring the device. */ |
| ret = ubx_m8_ubx_cfg_rst(dev, UBX_CFG_RST_RESET_MODE_CONTROLLED_GNSS_STOP); |
| if (ret < 0) { |
| goto reset; |
| } |
| |
| ret = ubx_m8_ubx_cfg_rate(dev); |
| if (ret < 0) { |
| LOG_ERR("Configuring rate failed. Returned %d.", ret); |
| goto reset; |
| } |
| |
| ret = ubx_m8_configure_gnss_device_baudrate(dev); |
| if (ret < 0) { |
| LOG_ERR("Configuring baudrate failed. Returned %d.", ret); |
| goto reset; |
| } |
| |
| ret = ubx_m8_configure_messages(dev); |
| if (ret < 0) { |
| LOG_ERR("Configuring messages failed. Returned %d.", ret); |
| } |
| |
| reset: |
| ret = ubx_m8_ubx_cfg_rst(dev, UBX_CFG_RST_RESET_MODE_CONTROLLED_GNSS_START); |
| |
| return ret; |
| } |
| |
| static int ubx_m8_init(const struct device *dev) |
| { |
| int ret; |
| |
| ret = ubx_m8_init_nmea0183_match(dev); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| ubx_m8_init_pipe(dev); |
| |
| ret = ubx_m8_init_chat(dev); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| ret = ubx_m8_init_ubx(dev); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| ret = ubx_m8_resume(dev); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| ret = ubx_m8_configure(dev); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| #define UBX_M8(inst) \ |
| static const struct ubx_m8_config ubx_m8_cfg_##inst = { \ |
| .uart = DEVICE_DT_GET(DT_INST_BUS(inst)), \ |
| .uart_baudrate = DT_PROP(DT_DRV_INST(inst), uart_baudrate), \ |
| }; \ |
| \ |
| static struct ubx_m8_data ubx_m8_data_##inst = { \ |
| .script.request = (struct ubx_frame *)ubx_m8_data_##inst.request_buf, \ |
| .script.response = (struct ubx_frame *)ubx_m8_data_##inst.response_buf, \ |
| .script.match = (struct ubx_frame *)ubx_m8_data_##inst.match_buf, \ |
| .script.retry_count = UBX_M8_SCRIPT_RETRY_DEFAULT, \ |
| .script.timeout = K_MSEC(MODEM_UBX_SCRIPT_TIMEOUT_MS), \ |
| }; \ |
| \ |
| DEVICE_DT_INST_DEFINE(inst, \ |
| ubx_m8_init, \ |
| NULL, \ |
| &ubx_m8_data_##inst, \ |
| &ubx_m8_cfg_##inst, \ |
| POST_KERNEL, \ |
| CONFIG_GNSS_INIT_PRIORITY, \ |
| &gnss_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(UBX_M8) |