From b423c00b2fae39409565f5cd7a00ddc474c28897 Mon Sep 17 00:00:00 2001 From: Jordan Yates Date: Sun, 27 Apr 2025 16:35:36 +1000 Subject: [PATCH 1/5] lora: shift drivers to dedicated `loramac-node` folder Move the current implementation of the LoRa API using `loramac-node` to a dedicated folder in preparation for the LoRa basics modem implementation. Signed-off-by: Jordan Yates --- drivers/lora/CMakeLists.txt | 22 ++----------------- drivers/lora/Kconfig | 14 +++++++++--- drivers/lora/loramac_node/CMakeLists.txt | 22 +++++++++++++++++++ drivers/lora/{ => loramac_node}/hal_common.c | 0 drivers/lora/{ => loramac_node}/sx126x.c | 0 .../lora/{ => loramac_node}/sx126x_common.h | 0 .../{ => loramac_node}/sx126x_standalone.c | 0 .../lora/{ => loramac_node}/sx126x_stm32wl.c | 0 drivers/lora/{ => loramac_node}/sx127x.c | 0 .../lora/{ => loramac_node}/sx12xx_common.c | 0 .../lora/{ => loramac_node}/sx12xx_common.h | 0 modules/loramac-node/Kconfig | 6 ++--- 12 files changed, 38 insertions(+), 26 deletions(-) create mode 100644 drivers/lora/loramac_node/CMakeLists.txt rename drivers/lora/{ => loramac_node}/hal_common.c (100%) rename drivers/lora/{ => loramac_node}/sx126x.c (100%) rename drivers/lora/{ => loramac_node}/sx126x_common.h (100%) rename drivers/lora/{ => loramac_node}/sx126x_standalone.c (100%) rename drivers/lora/{ => loramac_node}/sx126x_stm32wl.c (100%) rename drivers/lora/{ => loramac_node}/sx127x.c (100%) rename drivers/lora/{ => loramac_node}/sx12xx_common.c (100%) rename drivers/lora/{ => loramac_node}/sx12xx_common.h (100%) diff --git a/drivers/lora/CMakeLists.txt b/drivers/lora/CMakeLists.txt index 85a37f0d55948..20b8c7adbfb05 100644 --- a/drivers/lora/CMakeLists.txt +++ b/drivers/lora/CMakeLists.txt @@ -1,23 +1,5 @@ # SPDX-License-Identifier: Apache-2.0 -# LoRa drivers depend on the include directories exposed by the loramac-node -# library. Hence, if it exists then the source files are added to that otherwise -# a library with same name is created. -if(TARGET loramac-node) - set(ZEPHYR_CURRENT_LIBRARY loramac-node) -else() - zephyr_library_named(loramac-node) -endif() +zephyr_sources_ifdef(CONFIG_LORA_SHELL shell.c) -zephyr_library_sources_ifdef(CONFIG_LORA_SHELL shell.c) -zephyr_library_sources_ifdef(CONFIG_HAS_SEMTECH_RADIO_DRIVERS hal_common.c) -zephyr_library_sources_ifdef(CONFIG_HAS_SEMTECH_RADIO_DRIVERS sx12xx_common.c) -zephyr_library_sources_ifdef(CONFIG_LORA_SX127X sx127x.c) - -if (CONFIG_LORA_SX126X OR CONFIG_LORA_STM32WL_SUBGHZ_RADIO) - zephyr_library_sources(sx126x.c) -endif() - -zephyr_library_sources_ifdef(CONFIG_LORA_SX126X sx126x_standalone.c) -zephyr_library_sources_ifdef(CONFIG_LORA_STM32WL_SUBGHZ_RADIO sx126x_stm32wl.c) -zephyr_library_sources_ifdef(CONFIG_LORA_RYLRXXX rylrxxx.c) +add_subdirectory_ifdef(CONFIG_LORA_MODULE_BACKEND_LORAMAC_NODE loramac_node) diff --git a/drivers/lora/Kconfig b/drivers/lora/Kconfig index 6319ccf6be9e2..f6768dacb649a 100644 --- a/drivers/lora/Kconfig +++ b/drivers/lora/Kconfig @@ -15,6 +15,15 @@ menuconfig LORA if LORA +choice LORA_MODULE_BACKEND + prompt "Low-level LoRa modem integration to use" + +config LORA_MODULE_BACKEND_LORAMAC_NODE + bool "loramac-node backend" + depends on ZEPHYR_LORAMAC_NODE_MODULE + +endchoice + module = LORA module-str = lora source "subsys/logging/Kconfig.template.log_config" @@ -31,8 +40,7 @@ config LORA_INIT_PRIORITY help System initialization priority for LoRa drivers. -source "drivers/lora/Kconfig.sx12xx" - -source "drivers/lora/Kconfig.rylrxxx" +rsource "Kconfig.sx12xx" +rsource "Kconfig.rylrxxx" endif # LORA diff --git a/drivers/lora/loramac_node/CMakeLists.txt b/drivers/lora/loramac_node/CMakeLists.txt new file mode 100644 index 0000000000000..558899dfa2775 --- /dev/null +++ b/drivers/lora/loramac_node/CMakeLists.txt @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: Apache-2.0 + +# LoRa drivers depend on the include directories exposed by the loramac-node +# library. Hence, if it exists then the source files are added to that otherwise +# a library with same name is created. +if(TARGET loramac-node) + set(ZEPHYR_CURRENT_LIBRARY loramac-node) +else() + zephyr_library_named(loramac-node) +endif() + +zephyr_library_sources_ifdef(CONFIG_HAS_SEMTECH_RADIO_DRIVERS hal_common.c) +zephyr_library_sources_ifdef(CONFIG_HAS_SEMTECH_RADIO_DRIVERS sx12xx_common.c) +zephyr_library_sources_ifdef(CONFIG_LORA_SX127X sx127x.c) + +if (CONFIG_LORA_SX126X OR CONFIG_LORA_STM32WL_SUBGHZ_RADIO) + zephyr_library_sources(sx126x.c) +endif() + +zephyr_library_sources_ifdef(CONFIG_LORA_SX126X sx126x_standalone.c) +zephyr_library_sources_ifdef(CONFIG_LORA_STM32WL_SUBGHZ_RADIO sx126x_stm32wl.c) +zephyr_library_sources_ifdef(CONFIG_LORA_RYLRXXX ../rylrxxx.c) diff --git a/drivers/lora/hal_common.c b/drivers/lora/loramac_node/hal_common.c similarity index 100% rename from drivers/lora/hal_common.c rename to drivers/lora/loramac_node/hal_common.c diff --git a/drivers/lora/sx126x.c b/drivers/lora/loramac_node/sx126x.c similarity index 100% rename from drivers/lora/sx126x.c rename to drivers/lora/loramac_node/sx126x.c diff --git a/drivers/lora/sx126x_common.h b/drivers/lora/loramac_node/sx126x_common.h similarity index 100% rename from drivers/lora/sx126x_common.h rename to drivers/lora/loramac_node/sx126x_common.h diff --git a/drivers/lora/sx126x_standalone.c b/drivers/lora/loramac_node/sx126x_standalone.c similarity index 100% rename from drivers/lora/sx126x_standalone.c rename to drivers/lora/loramac_node/sx126x_standalone.c diff --git a/drivers/lora/sx126x_stm32wl.c b/drivers/lora/loramac_node/sx126x_stm32wl.c similarity index 100% rename from drivers/lora/sx126x_stm32wl.c rename to drivers/lora/loramac_node/sx126x_stm32wl.c diff --git a/drivers/lora/sx127x.c b/drivers/lora/loramac_node/sx127x.c similarity index 100% rename from drivers/lora/sx127x.c rename to drivers/lora/loramac_node/sx127x.c diff --git a/drivers/lora/sx12xx_common.c b/drivers/lora/loramac_node/sx12xx_common.c similarity index 100% rename from drivers/lora/sx12xx_common.c rename to drivers/lora/loramac_node/sx12xx_common.c diff --git a/drivers/lora/sx12xx_common.h b/drivers/lora/loramac_node/sx12xx_common.h similarity index 100% rename from drivers/lora/sx12xx_common.h rename to drivers/lora/loramac_node/sx12xx_common.h diff --git a/modules/loramac-node/Kconfig b/modules/loramac-node/Kconfig index cce6c98067790..0cd70c000d4b0 100644 --- a/modules/loramac-node/Kconfig +++ b/modules/loramac-node/Kconfig @@ -14,15 +14,15 @@ config HAS_SEMTECH_RADIO_DRIVERS config HAS_SEMTECH_SX1272 bool - select HAS_SEMTECH_RADIO_DRIVERS + select HAS_SEMTECH_RADIO_DRIVERS if LORA_MODULE_BACKEND_LORAMAC_NODE config HAS_SEMTECH_SX1276 bool - select HAS_SEMTECH_RADIO_DRIVERS + select HAS_SEMTECH_RADIO_DRIVERS if LORA_MODULE_BACKEND_LORAMAC_NODE config HAS_SEMTECH_SX126X bool - select HAS_SEMTECH_RADIO_DRIVERS + select HAS_SEMTECH_RADIO_DRIVERS if LORA_MODULE_BACKEND_LORAMAC_NODE config HAS_SEMTECH_LORAMAC bool "Semtech LoRaMac Stack" From 3010686dd153f3b8a905a14e9c809c929aafb2eb Mon Sep 17 00:00:00 2001 From: Jordan Yates Date: Tue, 29 Apr 2025 14:49:30 +1000 Subject: [PATCH 2/5] manifest: add lora-basics-modem module Import the lora-basics-modem module as an alternate backend for LoRa and LoRaWAN, since loramac-node has been deprecated. Support is currently limited and experimental. Signed-off-by: Jordan Yates --- MAINTAINERS.yml | 9 ++++++++ modules/Kconfig | 3 +++ modules/lora-basics-modem/CMakeLists.txt | 26 ++++++++++++++++++++++ modules/lora-basics-modem/Kconfig | 11 ++++++++++ modules/lora-basics-modem/sx126x.cmake | 24 ++++++++++++++++++++ modules/lora-basics-modem/sx127x.cmake | 28 ++++++++++++++++++++++++ west.yml | 3 +++ 7 files changed, 104 insertions(+) create mode 100644 modules/lora-basics-modem/CMakeLists.txt create mode 100644 modules/lora-basics-modem/Kconfig create mode 100644 modules/lora-basics-modem/sx126x.cmake create mode 100644 modules/lora-basics-modem/sx127x.cmake diff --git a/MAINTAINERS.yml b/MAINTAINERS.yml index e4559ac4ad994..06ec2c96a01a5 100644 --- a/MAINTAINERS.yml +++ b/MAINTAINERS.yml @@ -5364,6 +5364,15 @@ West: labels: - "area: Storage" +"West project: lora-basics-modem": + status: maintained + maintainers: + - JordanYates + files: + - modules/lora-basics-modem/ + labels: + - "area: LoRa" + "West project: loramac-node": status: maintained maintainers: diff --git a/modules/Kconfig b/modules/Kconfig index eda6daa16095e..23fdb825371ef 100644 --- a/modules/Kconfig +++ b/modules/Kconfig @@ -99,6 +99,9 @@ comment "Lz4 module not available." comment "loramac-node module not available." depends on !ZEPHYR_LORAMAC_NODE_MODULE +comment "LoRa Basics Modem module not available." + depends on !ZEPHYR_LORA_BASICS_MODEM_MODULE + comment "CANopenNode module not available." depends on !ZEPHYR_CANOPENNODE_MODULE diff --git a/modules/lora-basics-modem/CMakeLists.txt b/modules/lora-basics-modem/CMakeLists.txt new file mode 100644 index 0000000000000..fe10e4a7a48f5 --- /dev/null +++ b/modules/lora-basics-modem/CMakeLists.txt @@ -0,0 +1,26 @@ +# Copyright (c) 2025 Embeint Inc +# SPDX-License-Identifier: Apache-2.0 + +if(CONFIG_USE_LORA_BASICS_MODEM_DRIVERS) + + set(LORA_BASICS_MODEM_DIR ${ZEPHYR_CURRENT_MODULE_DIR}) + set(LBM_LIB_DIR ${LORA_BASICS_MODEM_DIR}/lbm_lib) + set(LBM_SMTC_MODEM_CORE_DIR ${LBM_LIB_DIR}/smtc_modem_core) + set(LBM_RADIO_DRIVERS_DIR ${LBM_LIB_DIR}/smtc_modem_core/radio_drivers) + + if(TARGET lora_basics_modem) + set(ZEPHYR_CURRENT_LIBRARY lora_basics_modem) + else() + zephyr_library_named(lora_basics_modem) + endif() + + zephyr_library_include_directories(${LBM_SMTC_MODEM_CORE_DIR}/smtc_ral/src) + zephyr_library_include_directories(${LBM_SMTC_MODEM_CORE_DIR}/smtc_ralf/src) + + if(CONFIG_LORA_SX126X) + include(sx126x.cmake) + endif() + if(CONFIG_LORA_SX127X) + include(sx127x.cmake) + endif() +endif() diff --git a/modules/lora-basics-modem/Kconfig b/modules/lora-basics-modem/Kconfig new file mode 100644 index 0000000000000..ce4382c247f70 --- /dev/null +++ b/modules/lora-basics-modem/Kconfig @@ -0,0 +1,11 @@ +# Copyright (c) 2024 Semtech Corporation +# SPDX-License-Identifier: Apache-2.0 + +# Top-level configuration file for LoRa Basics Modem containing LoRa +# transceiver drivers and LBM LoRaWAN stack + +config ZEPHYR_LORA_BASICS_MODEM_MODULE + bool + +config USE_LORA_BASICS_MODEM_DRIVERS + bool diff --git a/modules/lora-basics-modem/sx126x.cmake b/modules/lora-basics-modem/sx126x.cmake new file mode 100644 index 0000000000000..021f047711dc0 --- /dev/null +++ b/modules/lora-basics-modem/sx126x.cmake @@ -0,0 +1,24 @@ +# Copyright (c) 2024 Semtech Corporation +# SPDX-License-Identifier: Apache-2.0 + +# Used by LBM +zephyr_library_compile_definitions(SX126X) +zephyr_library_compile_definitions(SX126X_TRANSCEIVER) + +# Allow modem options +set(ALLOW_CSMA_BUILD true) + +set(LBM_SX126X_LIB_DIR ${LBM_RADIO_DRIVERS_DIR}/sx126x_driver/src) +zephyr_include_directories(${LBM_SX126X_LIB_DIR}) + +#----------------------------------------------------------------------------- +# Radio specific sources +#----------------------------------------------------------------------------- +zephyr_library_sources( + ${LBM_SX126X_LIB_DIR}/lr_fhss_mac.c + ${LBM_SX126X_LIB_DIR}/sx126x.c + ${LBM_SX126X_LIB_DIR}/sx126x_driver_version.c + ${LBM_SX126X_LIB_DIR}/sx126x_lr_fhss.c + ${LBM_SMTC_MODEM_CORE_DIR}/smtc_ral/src/ral_sx126x.c + ${LBM_SMTC_MODEM_CORE_DIR}/smtc_ralf/src/ralf_sx126x.c +) diff --git a/modules/lora-basics-modem/sx127x.cmake b/modules/lora-basics-modem/sx127x.cmake new file mode 100644 index 0000000000000..512114520d209 --- /dev/null +++ b/modules/lora-basics-modem/sx127x.cmake @@ -0,0 +1,28 @@ +# Copyright (c) 2024 Semtech Corporation +# SPDX-License-Identifier: Apache-2.0 + +# Used by LBM +zephyr_library_compile_definitions(SX127X) +zephyr_library_compile_definitions(SX127X_TRANSCEIVER) + +if(CONFIG_DT_HAS_SEMTECH_SX1272_ENABLED) + zephyr_library_compile_definitions(SX1272) +endif() +if(CONFIG_DT_HAS_SEMTECH_SX1276_ENABLED) + zephyr_library_compile_definitions(SX1276) +endif() + +# Allow modem options +set(ALLOW_CSMA_BUILD true) + +set(LBM_SX127X_LIB_DIR ${LBM_RADIO_DRIVERS_DIR}/sx127x_driver/src) +zephyr_include_directories(${LBM_SX127X_LIB_DIR}) + +#----------------------------------------------------------------------------- +# Radio specific sources +#----------------------------------------------------------------------------- +zephyr_library_sources( + ${LBM_SX127X_LIB_DIR}/sx127x.c + ${LBM_SMTC_MODEM_CORE_DIR}/smtc_ral/src/ral_sx127x.c + ${LBM_SMTC_MODEM_CORE_DIR}/smtc_ralf/src/ralf_sx127x.c +) diff --git a/west.yml b/west.yml index 3c57f452369a6..ff609c3cc4fa4 100644 --- a/west.yml +++ b/west.yml @@ -298,6 +298,9 @@ manifest: groups: - fs revision: 8f5ca347843363882619d8f96c00d8dbd88a8e79 + - name: lora-basics-modem + revision: 9a14f6772c1d6e303bacb2d594c8063bb804b6ee + path: modules/lib/lora-basics-modem - name: loramac-node revision: fb00b383072518c918e2258b0916c996f2d4eebe path: modules/lib/loramac-node From 51022a4f0a2f51902bb6e6df8703bfaf7d000bb7 Mon Sep 17 00:00:00 2001 From: Jordan Yates Date: Tue, 29 Apr 2025 15:31:45 +1000 Subject: [PATCH 3/5] lora: LoRa Basics Modem backend for SX12xx chips As a first integration of the LoRa Basics Modem backend, implement the LoRa API for the standard SX126x/SX127x chips. Much of the logic from `lbm_common.c` is taken from the loramac-node `sx12xx_common` implementation, but it should now be agnostic for all LoRa RF transceivers. Signed-off-by: Jordan Yates --- drivers/lora/CMakeLists.txt | 1 + drivers/lora/Kconfig | 9 + drivers/lora/lora_basics_modem/CMakeLists.txt | 15 + drivers/lora/lora_basics_modem/Kconfig | 16 + drivers/lora/lora_basics_modem/lbm_common.c | 547 ++++++++++++++++++ drivers/lora/lora_basics_modem/lbm_common.h | 107 ++++ drivers/lora/lora_basics_modem/lbm_sx126x.c | 462 +++++++++++++++ drivers/lora/lora_basics_modem/lbm_sx127x.c | 459 +++++++++++++++ 8 files changed, 1616 insertions(+) create mode 100644 drivers/lora/lora_basics_modem/CMakeLists.txt create mode 100644 drivers/lora/lora_basics_modem/Kconfig create mode 100644 drivers/lora/lora_basics_modem/lbm_common.c create mode 100644 drivers/lora/lora_basics_modem/lbm_common.h create mode 100644 drivers/lora/lora_basics_modem/lbm_sx126x.c create mode 100644 drivers/lora/lora_basics_modem/lbm_sx127x.c diff --git a/drivers/lora/CMakeLists.txt b/drivers/lora/CMakeLists.txt index 20b8c7adbfb05..f1bbba12c0257 100644 --- a/drivers/lora/CMakeLists.txt +++ b/drivers/lora/CMakeLists.txt @@ -3,3 +3,4 @@ zephyr_sources_ifdef(CONFIG_LORA_SHELL shell.c) add_subdirectory_ifdef(CONFIG_LORA_MODULE_BACKEND_LORAMAC_NODE loramac_node) +add_subdirectory_ifdef(CONFIG_LORA_MODULE_BACKEND_LORA_BASICS_MODEM lora_basics_modem) diff --git a/drivers/lora/Kconfig b/drivers/lora/Kconfig index f6768dacb649a..124f4619d07eb 100644 --- a/drivers/lora/Kconfig +++ b/drivers/lora/Kconfig @@ -22,6 +22,14 @@ config LORA_MODULE_BACKEND_LORAMAC_NODE bool "loramac-node backend" depends on ZEPHYR_LORAMAC_NODE_MODULE +config LORA_MODULE_BACKEND_LORA_BASICS_MODEM + bool "LoRa Basic modem backend [EXPERIMENTAL]" + depends on ZEPHYR_LORA_BASICS_MODEM_MODULE + depends on DT_HAS_SEMTECH_SX1262_ENABLED || DT_HAS_SEMTECH_SX1261_ENABLED || DT_HAS_SEMTECH_SX1272_ENABLED || DT_HAS_SEMTECH_SX1276_ENABLED + select USE_LORA_BASICS_MODEM_DRIVERS + help + Experimental support for implementing the LoRa API using the LoRa Basics Modem module. + endchoice module = LORA @@ -42,5 +50,6 @@ config LORA_INIT_PRIORITY rsource "Kconfig.sx12xx" rsource "Kconfig.rylrxxx" +rsource "lora_basics_modem/Kconfig" endif # LORA diff --git a/drivers/lora/lora_basics_modem/CMakeLists.txt b/drivers/lora/lora_basics_modem/CMakeLists.txt new file mode 100644 index 0000000000000..fa96b05064172 --- /dev/null +++ b/drivers/lora/lora_basics_modem/CMakeLists.txt @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: Apache-2.0 + +# LoRa drivers depend on the include directories exposed by the lora_basics_modem +# library. Hence, if it exists then the source files are added to that otherwise +# a library with same name is created. +if(TARGET lora_basics_modem) + set(ZEPHYR_CURRENT_LIBRARY lora_basics_modem) +else() + zephyr_library_named(lora_basics_modem) +endif() + +zephyr_library_sources(lbm_common.c) +zephyr_library_sources_ifdef(CONFIG_LORA_SX126X lbm_sx126x.c) +zephyr_library_sources_ifdef(CONFIG_LORA_SX127X lbm_sx127x.c) +zephyr_library_sources_ifdef(CONFIG_LORA_RYLRXXX ../rylrxxx.c) diff --git a/drivers/lora/lora_basics_modem/Kconfig b/drivers/lora/lora_basics_modem/Kconfig new file mode 100644 index 0000000000000..2248ff4e15046 --- /dev/null +++ b/drivers/lora/lora_basics_modem/Kconfig @@ -0,0 +1,16 @@ +# +# Copyright (c) 2025 Embeint Inc +# +# SPDX-License-Identifier: Apache-2.0 +# + +if LORA_MODULE_BACKEND_LORA_BASICS_MODEM + +config LORA_BASICS_MODEM_ASYNC_RX_MAX_PAYLOAD + int "Maximum size payload that can be received by async RX" + default 256 + help + A buffer of this size is allocated on the system workqueue stack when + running the async RX handler. + +endif diff --git a/drivers/lora/lora_basics_modem/lbm_common.c b/drivers/lora/lora_basics_modem/lbm_common.c new file mode 100644 index 0000000000000..c060f01a6c199 --- /dev/null +++ b/drivers/lora/lora_basics_modem/lbm_common.c @@ -0,0 +1,547 @@ +/* + * Copyright (c) 2025 Embeint Inc + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "lbm_common.h" + +LOG_MODULE_REGISTER(lbm_driver, CONFIG_LORA_LOG_LEVEL); + +/** + * @brief Attempt to acquire the modem for operations + * + * @param dev modem to acquire + * + * @retval true if modem was acquired + * @retval false otherwise + */ +static inline bool modem_acquire(const struct device *dev) +{ + struct lbm_lora_data_common *data = dev->data; + + return atomic_cas(&data->modem_state, STATE_FREE, STATE_BUSY); +} + +/** + * @brief Safely release the modem from any context + * + * This function can be called from any context and guarantees that the + * release operations will only be run once. + * + * @param dev modem to release + * + * @retval true if modem was released by this function + * @retval false otherwise + */ +static bool modem_release(const struct device *dev) +{ + const struct lbm_lora_config_common *config = dev->config; + struct lbm_lora_data_common *data = dev->data; + + /* Move to cleanup state so both acquire and release will fail */ + if (!atomic_cas(&data->modem_state, STATE_BUSY, STATE_CLEANUP)) { + return false; + } + + /* Configure modem for sleep */ + lbm_driver_antenna_configure(dev, MODE_SLEEP); + data->modem_mode = MODE_SLEEP; + + /* Put radio back into sleep mode */ + (void)ral_set_sleep(&config->ralf.ral, true); + + /* Completely release modem */ + data->operation_done = NULL; + atomic_set(&data->modem_state, STATE_FREE); + return true; +} + +int lbm_lora_config(const struct device *dev, struct lora_modem_config *lora_config) +{ + const struct lbm_lora_config_common *config = dev->config; + struct lbm_lora_data_common *data = dev->data; + ralf_params_lora_t params = { + .mod_params = { + .sf = lora_config->datarate, + .cr = lora_config->coding_rate, + .ldro = 0, + }, + .pkt_params = { + .preamble_len_in_symb = lora_config->preamble_len, + .header_type = RAL_LORA_PKT_EXPLICIT, + .pld_len_in_bytes = UINT8_MAX, + .crc_is_on = true, + .invert_iq_is_on = lora_config->iq_inverted, + }, + .rf_freq_in_hz = lora_config->frequency, + .output_pwr_in_dbm = lora_config->tx_power, + .sync_word = lora_config->public_network ? LBM_LORA_SYNC_WORD_PUBLIC + : LBM_LORA_SYNC_WORD_PRIVATE, + }; + ral_status_t status; + int ret; + + /* Ensure available, decremented after configuration */ + if (!modem_acquire(dev)) { + return -EBUSY; + } + + switch (lora_config->bandwidth) { + case BW_125_KHZ: + params.mod_params.bw = RAL_LORA_BW_125_KHZ; + break; + case BW_250_KHZ: + params.mod_params.bw = RAL_LORA_BW_250_KHZ; + break; + case BW_500_KHZ: + params.mod_params.bw = RAL_LORA_BW_500_KHZ; + break; + default: + ret = -EINVAL; + goto release; + } + + /* Store TX parameters for use in the TX functions */ + data->mod_params = params.mod_params; + data->pkt_params = params.pkt_params; + + status = ralf_setup_lora(&config->ralf, ¶ms); + ret = status == RAL_STATUS_OK ? 0 : -EIO; + +release: + modem_release(dev); + return ret; +} + +int lbm_lora_send_async(const struct device *dev, uint8_t *msg, uint32_t msg_len, + struct k_poll_signal *async) +{ + const struct lbm_lora_config_common *config = dev->config; + struct lbm_lora_data_common *data = dev->data; + ral_status_t status; + int ret = 0; + + /* Ensure available, freed by TX done callback */ + if (!modem_acquire(dev)) { + return -EBUSY; + } + + /* Configure modem for TX */ + lbm_driver_antenna_configure(dev, MODE_TX); + data->modem_mode = MODE_TX; + + /* Validate that we have a TX configuration */ + if (data->mod_params.sf == 0) { + ret = -EINVAL; + goto release; + } + + /* Store signal */ + data->operation_done = async; + + /* Update packet params to override the internal packet length variable. + * This has a huge overhead since it performs many register writes, but is the only + * generic way to update the variable. Why this isn't just done in ral_set_pkt_payload + * is anyones guess. + */ + data->pkt_params.pld_len_in_bytes = msg_len; + status = ral_set_lora_pkt_params(&config->ralf.ral, &data->pkt_params); + if (status != RAL_STATUS_OK) { + ret = -EINVAL; + goto release; + } + + /* Set the packet payload */ + status = ral_set_pkt_payload(&config->ralf.ral, msg, msg_len); + if (status != RAL_STATUS_OK) { + ret = -EINVAL; + goto release; + } + + /* Start the transmission */ + status = ral_set_tx(&config->ralf.ral); + if (status != RAL_STATUS_OK) { + ret = -EINVAL; + goto release; + } + return 0; + +release: + modem_release(dev); + return ret; +} + +int lbm_lora_send(const struct device *dev, uint8_t *msg, uint32_t msg_len) +{ + const struct lbm_lora_config_common *config = dev->config; + struct lbm_lora_data_common *data = dev->data; + struct k_poll_signal done = K_POLL_SIGNAL_INITIALIZER(done); + struct k_poll_event evt = + K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_SIGNAL, K_POLL_MODE_NOTIFY_ONLY, &done); + uint32_t air_time; + int ret; + + /* Trigger the asynchronous send */ + ret = lbm_lora_send_async(dev, msg, msg_len, &done); + if (ret < 0) { + return ret; + } + + /* Calculate expected airtime of the packet */ + air_time = ral_get_lora_time_on_air_in_ms(&config->ralf.ral, &data->pkt_params, + &data->mod_params); + LOG_DBG("Expected airtime: %d ms", air_time); + + /* Wait for the packet to finish transmitting. + * Use twice the tx duration to ensure that we are actually detecting + * a failed transmission, and not some minor timing variation between + * modem and driver. + */ + ret = k_poll(&evt, 1, K_MSEC(2 * air_time)); + if (ret < 0) { + if (modem_release(dev)) { + LOG_ERR("Packet transmission failed!"); + } else { + /* TX done interrupt is currently running */ + k_poll(&evt, 1, K_FOREVER); + } + } + return ret; +} + +int lbm_lora_recv(const struct device *dev, uint8_t *msg, uint8_t msg_len, k_timeout_t timeout, + int16_t *rssi, int8_t *snr) +{ + const struct lbm_lora_config_common *config = dev->config; + struct lbm_lora_data_common *data = dev->data; + struct k_poll_signal done = K_POLL_SIGNAL_INITIALIZER(done); + struct k_poll_event evt = + K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_SIGNAL, K_POLL_MODE_NOTIFY_ONLY, &done); + ral_status_t status; + int ret; + + /* Ensure available, decremented by op_done_work_handler or on timeout */ + if (!modem_acquire(dev)) { + return -EBUSY; + } + + /* Store signal */ + data->operation_done = &done; + data->rx_state.sync.msg = msg; + data->rx_state.sync.msg_len = msg_len; + + /* Configure modem for RX */ + lbm_driver_antenna_configure(dev, MODE_RX); + data->modem_mode = MODE_RX; + + /* Start the reception in continuous mode. + * Receive timeouts are handled by the k_poll timeout. + * In theory we should be able to use the one-shot mode here and transition + * back to IDLE slightly faster, but the SX127x driver does not appear to + * receive packets reliably in the single-shot mode. + */ + status = ral_set_rx(&config->ralf.ral, RAL_RX_TIMEOUT_CONTINUOUS_MODE); + if (status != RAL_STATUS_OK) { + ret = -EINVAL; + goto release; + } + + /* Wait for the packet to be received */ + ret = k_poll(&evt, 1, timeout); + if (ret < 0) { + if (modem_release(dev)) { + LOG_INF("Receive timeout"); + return -EAGAIN; + } + /* Releasing the modem failed, which means that + * the RX callback is currently running. Wait until + * the RX callback finishes and we get our packet. + */ + k_poll(&evt, 1, K_FOREVER); + + /* We did receive a packet, continue processing */ + } + + if (done.result != 0) { + LOG_ERR("Receive error"); + ret = done.result; + goto release; + } + + /* Retrieve cached RSSI and SNR */ + *rssi = data->rx_state.sync.rssi_dbm; + *snr = data->rx_state.sync.snr_db; + ret = data->rx_state.sync.msg_len; + +release: + modem_release(dev); + return ret; +} + +int lbm_lora_recv_async(const struct device *dev, lora_recv_cb cb, void *user_data) +{ + const struct lbm_lora_config_common *config = dev->config; + struct lbm_lora_data_common *data = dev->data; + ral_status_t status; + + /* Cancel ongoing reception */ + if (cb == NULL) { + if (!modem_release(dev)) { + /* Not receiving or already being stopped */ + return -EINVAL; + } + return 0; + } + + /* Ensure available */ + if (!modem_acquire(dev)) { + return -EBUSY; + } + + /* Configure modem for asynchronous RX */ + lbm_driver_antenna_configure(dev, MODE_RX_ASYNC); + data->modem_mode = MODE_RX_ASYNC; + + /* Store user state */ + data->rx_state.async.rx_cb = cb; + data->rx_state.async.user_data = user_data; + + /* Start the reception in continuous mode */ + status = ral_set_rx(&config->ralf.ral, RAL_RX_TIMEOUT_CONTINUOUS_MODE); + if (status != RAL_STATUS_OK) { + modem_release(dev); + return -EIO; + } + return 0; +} + +int lbm_lora_test_cw(const struct device *dev, uint32_t frequency, int8_t tx_power, + uint16_t duration) +{ + const struct lbm_lora_config_common *config = dev->config; + struct lbm_lora_data_common *data = dev->data; + ral_status_t status; + int ret = 0; + + /* Ensure available, freed by op_done_work */ + if (!modem_acquire(dev)) { + return -EBUSY; + } + + /* Configure modem for CW */ + lbm_driver_antenna_configure(dev, MODE_CW); + data->modem_mode = MODE_CW; + + /* Invalidate stored config */ + data->mod_params.sf = 0; + + /* Configure continuous wave */ + status = ral_set_pkt_type(&config->ralf.ral, RAL_PKT_TYPE_LORA); + if (status != RAL_STATUS_OK) { + return status; + } + status = ral_set_rf_freq(&config->ralf.ral, frequency); + if (status != RAL_STATUS_OK) { + return status; + } + status = ral_set_tx_cfg(&config->ralf.ral, tx_power, frequency); + if (status != RAL_STATUS_OK) { + return status; + } + + /* Start the continuous wave transmission */ + status = ral_set_tx_cw(&config->ralf.ral); + if (status != RAL_STATUS_OK) { + ret = -EIO; + goto release; + } + + /* Schedule the end of the transmission */ + k_work_reschedule(&data->op_done_work, K_MSEC(duration)); + return 0; +release: + modem_release(dev); + return ret; +} + +static int op_done_sync_rx(const struct device *dev) +{ + const struct lbm_lora_config_common *config = dev->config; + struct lbm_lora_data_common *data = dev->data; + ral_lora_rx_pkt_status_t pkt_status; + ral_status_t status; + int ret; + + /* Retrieve packet information before putting modem into sleep mode */ + status = ral_get_pkt_payload(&config->ralf.ral, data->rx_state.sync.msg_len, + data->rx_state.sync.msg, &data->rx_state.sync.msg_len); + if (status == RAL_STATUS_OK) { + LOG_HEXDUMP_DBG(data->rx_state.sync.msg, data->rx_state.sync.msg_len, "RX"); + ret = 0; + } else { + LOG_ERR("Failed to retrieve packet payload"); + ret = -EIO; + } + + status = ral_get_lora_rx_pkt_status(&config->ralf.ral, &pkt_status); + if (status == RAL_STATUS_OK) { + data->rx_state.sync.rssi_dbm = pkt_status.rssi_pkt_in_dbm; + data->rx_state.sync.snr_db = pkt_status.snr_pkt_in_db; + } else { + LOG_WRN("Failed to query packet signal stats"); + data->rx_state.sync.rssi_dbm = INT16_MIN; + data->rx_state.sync.snr_db = INT8_MIN; + } + + return ret; +} + +static void op_done_async_rx(const struct device *dev) +{ + const struct lbm_lora_config_common *config = dev->config; + struct lbm_lora_data_common *data = dev->data; + ral_lora_rx_pkt_status_t pkt_status; + uint8_t rx_buffer[CONFIG_LORA_BASICS_MODEM_ASYNC_RX_MAX_PAYLOAD]; + ral_status_t status; + uint16_t size; + + /* Retrieve the packet payload */ + status = ral_get_pkt_payload(&config->ralf.ral, sizeof(rx_buffer), rx_buffer, &size); + if (status == RAL_STATUS_OK) { + LOG_HEXDUMP_DBG(rx_buffer, size, "RX"); + } else { + LOG_ERR("Failed to retrieve packet payload"); + return; + } + + /* Retrieve packet parameters */ + status = ral_get_lora_rx_pkt_status(&config->ralf.ral, &pkt_status); + if (status != RAL_STATUS_OK) { + LOG_WRN("Failed to query packet signal stats"); + } + + /* Run the user callback */ + data->rx_state.async.rx_cb(dev, rx_buffer, size, pkt_status.rssi_pkt_in_dbm, + pkt_status.snr_pkt_in_db, data->rx_state.async.user_data); +} + +static void op_done_work_handler(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct lbm_lora_data_common *data = + CONTAINER_OF(dwork, struct lbm_lora_data_common, op_done_work); + const struct device *dev = data->dev; + const struct lbm_lora_config_common *config = dev->config; + struct k_poll_signal *sig_done; + ral_irq_t irq_state; + ral_status_t status; + bool release = false; + bool error_irq; + int ret = 0; + + LOG_DBG("%s: %d", __func__, data->modem_mode); + + switch (data->modem_mode) { + case MODE_SLEEP: + LOG_WRN("Unexpected modem mode"); + return; + case MODE_TX: + case MODE_CW: + status = ral_handle_tx_done(&config->ralf.ral); + if (status != RAL_STATUS_OK) { + LOG_WRN("RAL handle TX done failed (%d)", status); + } + release = true; + break; + case MODE_RX: + status = ral_handle_rx_done(&config->ralf.ral); + if (status != RAL_STATUS_OK) { + LOG_WRN("RAL handle RX done failed (%d)", status); + } + ret = op_done_sync_rx(dev); + release = true; + break; + case MODE_RX_ASYNC: + status = ral_handle_rx_done(&config->ralf.ral); + if (status != RAL_STATUS_OK) { + LOG_WRN("RAL handle RX done failed (%d)", status); + } + op_done_async_rx(dev); + /* Don't release the modem here, RX continues */ + break; + case MODE_CAD: + LOG_DBG("CAD complete (TBC)"); + break; + } + + /* Get and reset the current IRQ state */ + (void)ral_get_irq_status(&config->ralf.ral, &irq_state); + (void)ral_clear_irq_status(&config->ralf.ral, RAL_IRQ_ALL); + error_irq = irq_state & (RAL_IRQ_RX_TIMEOUT | RAL_IRQ_RX_HDR_ERROR | RAL_IRQ_RX_CRC_ERROR); + + /* Release the modem before running the user callback so that the notified thread can + * immediately start another operation before the work item terminates. This requires + * preserving the operation_done pointer, since modem_release clears it. + */ + sig_done = data->operation_done; + + /* Modem should return to idle */ + if (release) { + /* Return to sleep mode */ + modem_release(dev); + } + + /* Notify user that operation has completed */ + if (sig_done) { + k_poll_signal_raise(sig_done, error_irq ? -EAGAIN : ret); + } +} + +int lbm_lora_common_init(const struct device *dev) +{ + const struct lbm_lora_config_common *config = dev->config; + struct lbm_lora_data_common *data = dev->data; + ral_status_t status; + + data->dev = dev; + k_work_init_delayable(&data->op_done_work, op_done_work_handler); + atomic_clear(&data->modem_state); + + /* Initialise the radio abstraction layer */ + status = ral_init(&config->ralf.ral); + if (status != RAL_STATUS_OK) { + LOG_ERR("RAL init failure (%d)", status); + return -EIO; + } + + /* Enable all relevant interrupts */ + status = ral_set_dio_irq_params(&config->ralf.ral, + RAL_IRQ_TX_DONE | RAL_IRQ_RX_DONE | RAL_IRQ_RX_HDR_ERROR | + RAL_IRQ_RX_CRC_ERROR | RAL_IRQ_CAD_DONE | + RAL_IRQ_CAD_OK); + if (status != RAL_STATUS_OK) { + LOG_ERR("RAL DIO init failure (%d)", status); + return -EIO; + } + + /* Idle in sleep mode */ + status = ral_set_sleep(&config->ralf.ral, true); + if (status != RAL_STATUS_OK) { + LOG_ERR("Sleep failure (%d)", status); + return -EIO; + } + return 0; +} + +DEVICE_API(lora, lbm_lora_api) = { + .config = lbm_lora_config, + .send = lbm_lora_send, + .send_async = lbm_lora_send_async, + .recv = lbm_lora_recv, + .recv_async = lbm_lora_recv_async, + .test_cw = lbm_lora_test_cw, +}; diff --git a/drivers/lora/lora_basics_modem/lbm_common.h b/drivers/lora/lora_basics_modem/lbm_common.h new file mode 100644 index 0000000000000..8ce24e780d5ff --- /dev/null +++ b/drivers/lora/lora_basics_modem/lbm_common.h @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2025 Embeint Inc + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "ralf.h" + +#define STATE_FREE 0 +#define STATE_BUSY 1 +#define STATE_CLEANUP 2 + +/* LoRa sync words, taken from the legacy loramac-node sx1272.h/sx1276.h */ +enum lbm_modem_lora_sync_word { + LBM_LORA_SYNC_WORD_PRIVATE = 0x12, + LBM_LORA_SYNC_WORD_PUBLIC = 0x34, +}; + +/* Current operation mode of the LBM modem */ +enum lbm_modem_mode { + MODE_SLEEP = 0, + MODE_TX = 1, + MODE_CW = 2, + MODE_RX = 3, + MODE_RX_ASYNC = 4, + MODE_CAD = 5, +}; + +/* Common LBM modem configuration, must be first element of device config */ +struct lbm_lora_config_common { + /* LBM radio abstration layer structure */ + ralf_t ralf; +}; + +/* Common LBM modem data, must be first element of device data */ +struct lbm_lora_data_common { + /* Reference back to parent device */ + const struct device *dev; + /* Current LoRa parameters */ + ral_lora_mod_params_t mod_params; + ral_lora_pkt_params_t pkt_params; + /* Operation complete worker */ + struct k_work_delayable op_done_work; + /* RX state storage */ + union { + struct { + /* Sync RX params */ + uint8_t *msg; + uint16_t msg_len; + int16_t rssi_dbm; + int8_t snr_db; + } sync; + struct { + /* Async RX params */ + lora_recv_cb rx_cb; + void *user_data; + } async; + } rx_state; + /* User signal */ + struct k_poll_signal *operation_done; + /* Current modem state */ + atomic_t modem_state; + enum lbm_modem_mode modem_mode; +}; + +/** + * @brief Initialise common LBM data structures + * + * @param dev Modem to initialise + * + * @retval 0 On success + * @retval -errno On failure + */ +int lbm_lora_common_init(const struct device *dev); + +/** + * @brief Configure modem for a given mode + * + * Expected to be implemented by individual drivers + * + * @param dev Modem to configure + * @param mode Mode to configure for + */ +void lbm_driver_antenna_configure(const struct device *dev, enum lbm_modem_mode mode); + +/** + * @brief Control a GPIO pin if it has been configured + * + * @param spec GPIO specification from devicetree + * @param value Value assigned to the pin. + * + * @return a value from gpio_pin_set_dt() + */ +static inline int lbm_optional_gpio_set_dt(const struct gpio_dt_spec *spec, int value) +{ + if (spec->port != NULL) { + return gpio_pin_set_dt(spec, value); + } + return 0; +} + +/* Common LBM implementation of the LoRa API */ +extern const struct lora_driver_api lbm_lora_api; diff --git a/drivers/lora/lora_basics_modem/lbm_sx126x.c b/drivers/lora/lora_basics_modem/lbm_sx126x.c new file mode 100644 index 0000000000000..71287524d5866 --- /dev/null +++ b/drivers/lora/lora_basics_modem/lbm_sx126x.c @@ -0,0 +1,462 @@ +/* + * Copyright (c) 2025 Embeint Inc + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ral.h" + +#include "ralf_sx126x.h" +#include "ral_sx126x_bsp.h" +#include "sx126x_hal.h" +#include "sx126x.h" + +#include "lbm_common.h" + +enum sx126x_variant { + VARIANT_SX1261, + VARIANT_SX1262, +}; + +struct lbm_sx126x_config { + struct lbm_lora_config_common lbm_common; + struct spi_dt_spec spi; + struct gpio_dt_spec reset; + struct gpio_dt_spec busy; + struct gpio_dt_spec dio1; + struct gpio_dt_spec ant_enable; + struct gpio_dt_spec tx_enable; + struct gpio_dt_spec rx_enable; + int dio3_tcxo_startup_delay_ms; + uint8_t dio3_tcxo_voltage; + bool dio2_rf_switch; + enum sx126x_variant variant; +}; + +struct lbm_sx126x_data { + struct lbm_lora_data_common lbm_common; + const struct device *dev; + struct gpio_callback dio1_callback; + bool asleep; +}; + +LOG_MODULE_DECLARE(lbm_driver, CONFIG_LORA_LOG_LEVEL); + +static bool sx126x_is_busy(const struct device *dev) +{ + const struct lbm_sx126x_config *config = dev->config; + + return gpio_pin_get_dt(&config->busy); +} + +static int sx126x_wait_device_ready(const struct device *dev, k_timeout_t timeout) +{ + k_timepoint_t expiry = sys_timepoint_calc(timeout); + + do { + if (!sx126x_is_busy(dev)) { + return 0; + } + k_sleep(K_MSEC(1)); + } while (!sys_timepoint_expired(expiry)); + return -EAGAIN; +} + +static int sx126x_ensure_device_ready(const struct device *dev, k_timeout_t timeout) +{ + const struct lbm_sx126x_config *config = dev->config; + struct lbm_sx126x_data *data = dev->data; + uint8_t get_status_cmd[2] = {0xC0, 0xFF}; + const struct spi_buf tx_bufs[] = { + { + .buf = (void *)get_status_cmd, + .len = sizeof(get_status_cmd), + }, + }; + const struct spi_buf_set tx_buf_set = {tx_bufs, .count = ARRAY_SIZE(tx_bufs)}; + int ret; + + if (data->asleep) { + LOG_DBG("SLEEP -> ACTIVE"); + /* Re-enable the DIO1 interrupt */ + gpio_pin_interrupt_configure_dt(&config->dio1, GPIO_INT_EDGE_TO_ACTIVE); + /* DO NOT USE sx126x_get_status as this will result in recursion */ + ret = spi_write_dt(&config->spi, &tx_buf_set); + if (ret) { + return ret; + } + } + ret = sx126x_wait_device_ready(dev, timeout); + data->asleep = false; + return ret; +} + +sx126x_hal_status_t sx126x_hal_write(const void *context, const uint8_t *command, + const uint16_t command_length, const uint8_t *data, + const uint16_t data_length) +{ + const struct device *dev = context; + const struct lbm_sx126x_config *config = dev->config; + struct lbm_sx126x_data *dev_data = dev->data; + const struct spi_buf tx_bufs[] = { + { + .buf = (void *)command, + .len = command_length, + }, + { + .buf = (void *)data, + .len = data_length, + }, + }; + const struct spi_buf_set tx_buf_set = {tx_bufs, .count = ARRAY_SIZE(tx_bufs)}; + int ret; + + LOG_DBG("CMD[0]=0x%02x CMD_LEN=%d DATA_LEN=%d", command[0], command_length, data_length); + + ret = sx126x_ensure_device_ready(dev, K_SECONDS(1)); + if (ret) { + return SX126X_HAL_STATUS_ERROR; + } + + ret = spi_write_dt(&config->spi, &tx_buf_set); + if (ret) { + return SX126X_HAL_STATUS_ERROR; + } + + /* 0x84 - SX126x_SET_SLEEP opcode */ + if (command[0] == 0x84) { + LOG_DBG("ACTIVE -> SLEEP"); + /* Disable the DIO1 interrupt to save power */ + (void)gpio_pin_interrupt_configure_dt(&config->dio1, GPIO_INT_DISABLE); + dev_data->asleep = true; + /* Wait for sleep to take effect */ + k_sleep(K_MSEC(1)); + } + + return SX126X_HAL_STATUS_OK; +} + +sx126x_hal_status_t sx126x_hal_read(const void *context, const uint8_t *command, + const uint16_t command_length, uint8_t *data, + const uint16_t data_length) +{ + const struct device *dev = context; + const struct lbm_sx126x_config *config = dev->config; + const struct spi_buf tx_bufs[] = { + { + .buf = (uint8_t *)command, + .len = command_length, + }, + { + .buf = NULL, + .len = data_length, + }, + }; + const struct spi_buf rx_bufs[] = { + { + .buf = NULL, + .len = command_length, + }, + { + .buf = data, + .len = data_length, + }, + }; + const struct spi_buf_set tx_buf_set = {.buffers = tx_bufs, .count = ARRAY_SIZE(tx_bufs)}; + const struct spi_buf_set rx_buf_set = {.buffers = rx_bufs, .count = ARRAY_SIZE(rx_bufs)}; + int ret; + + LOG_DBG("CMD[0]=0x%02x DATA_LEN=%d", command[0], data_length); + + ret = sx126x_ensure_device_ready(dev, K_SECONDS(1)); + if (ret) { + return SX126X_HAL_STATUS_ERROR; + } + + ret = spi_transceive_dt(&config->spi, &tx_buf_set, &rx_buf_set); + if (ret) { + return SX126X_HAL_STATUS_ERROR; + } + return SX126X_HAL_STATUS_OK; +} + +sx126x_hal_status_t sx126x_hal_reset(const void *context) +{ + const struct device *dev = context; + const struct lbm_sx126x_config *config = dev->config; + + LOG_DBG(""); + + gpio_pin_set_dt(&config->reset, 1); + k_sleep(K_MSEC(20)); + gpio_pin_set_dt(&config->reset, 0); + k_sleep(K_MSEC(10)); + + return SX126X_HAL_STATUS_OK; +} + +sx126x_hal_status_t sx126x_hal_wakeup(const void *context) +{ + const struct device *dev = context; + int ret; + + LOG_DBG(""); + + ret = sx126x_ensure_device_ready(dev, K_SECONDS(1)); + if (ret) { + return SX126X_HAL_STATUS_ERROR; + } + return SX126X_HAL_STATUS_OK; +} + +void ral_sx126x_bsp_get_reg_mode(const void *context, sx126x_reg_mod_t *reg_mode) +{ + /* Not currently described in devicetree */ + *reg_mode = SX126X_REG_MODE_DCDC; +} + +void ral_sx126x_bsp_get_rf_switch_cfg(const void *context, bool *dio2_is_set_as_rf_switch) +{ + const struct device *dev = context; + const struct lbm_sx126x_config *config = dev->config; + + *dio2_is_set_as_rf_switch = config->dio2_rf_switch; +} + +void ral_sx126x_bsp_get_tx_cfg(const void *context, + const ral_sx126x_bsp_tx_cfg_input_params_t *input_params, + ral_sx126x_bsp_tx_cfg_output_params_t *output_params) + +{ + const struct device *dev = context; + const struct lbm_sx126x_config *config = dev->config; + int16_t power = input_params->system_output_pwr_in_dbm; + + output_params->pa_ramp_time = SX126X_RAMP_40_US; + output_params->pa_cfg.pa_lut = 0x01; + + if (config->variant == VARIANT_SX1261) { + power = MAX(power, -17); + power = MIN(power, 15); + output_params->pa_cfg.device_sel = 0x01; + output_params->chip_output_pwr_in_dbm_configured = power; + output_params->chip_output_pwr_in_dbm_expected = power; + if (power == 15) { + output_params->chip_output_pwr_in_dbm_configured = 14; + output_params->pa_cfg.pa_duty_cycle = 0x06; + } else { + output_params->pa_cfg.pa_duty_cycle = 0x04; + } + } else { + power = MAX(power, -9); + power = MIN(power, 22); + output_params->pa_cfg.device_sel = 0x00; + output_params->pa_cfg.hp_max = 0x07; + output_params->pa_cfg.pa_duty_cycle = 0x04; + output_params->chip_output_pwr_in_dbm_configured = power; + output_params->chip_output_pwr_in_dbm_expected = power; + } +} + +void ral_sx126x_bsp_get_xosc_cfg(const void *context, ral_xosc_cfg_t *xosc_cfg, + sx126x_tcxo_ctrl_voltages_t *supply_voltage, + uint32_t *startup_time_in_tick) +{ + const struct device *dev = context; + const struct lbm_sx126x_config *config = dev->config; + + if (config->dio3_tcxo_voltage == UINT8_MAX) { + *xosc_cfg = RAL_XOSC_CFG_XTAL; + } else { + *xosc_cfg = RAL_XOSC_CFG_TCXO_RADIO_CTRL; + *supply_voltage = config->dio3_tcxo_voltage; + /* From datasheet: 1 tick = 15.625 us = 65536 Hz */ + *startup_time_in_tick = z_tmcvt_32(config->dio3_tcxo_startup_delay_ms, Z_HZ_ms, + 65536, true, true, false); + } +} + +void ral_sx126x_bsp_get_trim_cap(const void *context, uint8_t *trimming_cap_xta, + uint8_t *trimming_cap_xtb) +{ + /* Do nothing, let the driver choose the default values */ +} + +void ral_sx126x_bsp_get_rx_boost_cfg(const void *context, bool *rx_boost_is_activated) +{ + /* Not currently described in devicetree */ + *rx_boost_is_activated = false; +} + +void ral_sx126x_bsp_get_ocp_value(const void *context, uint8_t *ocp_in_step_of_2_5_ma) +{ + /* Do nothing, let the driver choose the default values */ +} + +void ral_sx126x_bsp_get_lora_cad_det_peak(const void *context, ral_lora_sf_t sf, ral_lora_bw_t bw, + ral_lora_cad_symbs_t nb_symbol, + uint8_t *in_out_cad_det_peak) +{ + /* The DetPeak value set in the sx126x Radio Abstraction Layer is too close to the + * sensitivity for BW500 and SF>=9 + */ + if (bw >= RAL_LORA_BW_500_KHZ) { + if (sf >= RAL_LORA_SF9) { + *in_out_cad_det_peak += 11; + } + } +} + +ral_status_t ral_sx126x_bsp_get_instantaneous_tx_power_consumption( + const void *context, const ral_sx126x_bsp_tx_cfg_output_params_t *tx_cfg_output_params, + sx126x_reg_mod_t radio_reg_mode, uint32_t *pwr_consumption_in_ua) +{ + /* Not yet implemented, not relevant for LoRa */ + return RAL_STATUS_UNSUPPORTED_FEATURE; +} + +ral_status_t ral_sx126x_bsp_get_instantaneous_gfsk_rx_power_consumption( + const void *context, sx126x_reg_mod_t radio_reg_mode, bool rx_boosted, + uint32_t *pwr_consumption_in_ua) +{ + /* Not yet implemented, not relevant for LoRa */ + return RAL_STATUS_UNSUPPORTED_FEATURE; +} + +ral_status_t ral_sx126x_bsp_get_instantaneous_lora_rx_power_consumption( + const void *context, sx126x_reg_mod_t radio_reg_mode, bool rx_boosted, + uint32_t *pwr_consumption_in_ua) +{ + /* Not yet implemented, not relevant for LoRa */ + return RAL_STATUS_UNSUPPORTED_FEATURE; +} + +void lbm_driver_antenna_configure(const struct device *dev, enum lbm_modem_mode mode) +{ + const struct lbm_sx126x_config *config = dev->config; + + /* Antenna / RF switch control */ + switch (mode) { + case MODE_SLEEP: + lbm_optional_gpio_set_dt(&config->ant_enable, 0); + lbm_optional_gpio_set_dt(&config->rx_enable, 0); + lbm_optional_gpio_set_dt(&config->tx_enable, 0); + break; + case MODE_TX: + case MODE_CW: + lbm_optional_gpio_set_dt(&config->rx_enable, 0); + lbm_optional_gpio_set_dt(&config->tx_enable, 1); + lbm_optional_gpio_set_dt(&config->ant_enable, 1); + break; + case MODE_RX: + case MODE_RX_ASYNC: + case MODE_CAD: + lbm_optional_gpio_set_dt(&config->tx_enable, 0); + lbm_optional_gpio_set_dt(&config->rx_enable, 1); + lbm_optional_gpio_set_dt(&config->ant_enable, 1); + break; + } +} + +static void sx126x_dio1_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins) +{ + struct lbm_sx126x_data *data = CONTAINER_OF(cb, struct lbm_sx126x_data, dio1_callback); + + LOG_DBG(""); + /* Submit work to process the interrupt immediately */ + k_work_schedule(&data->lbm_common.op_done_work, K_NO_WAIT); +} + +static int sx126x_init(const struct device *dev) +{ + const struct lbm_sx126x_config *config = dev->config; + struct lbm_sx126x_data *data = dev->data; + ral_status_t status; + int ret; + + /* Validate hardware is ready */ + if (!spi_is_ready_dt(&config->spi)) { + LOG_ERR("SPI bus %s not ready", config->spi.bus->name); + return -ENODEV; + } + + /* Setup GPIOs */ + if (gpio_pin_configure_dt(&config->reset, GPIO_OUTPUT_INACTIVE) || + gpio_pin_configure_dt(&config->busy, GPIO_INPUT) || + gpio_pin_configure_dt(&config->dio1, GPIO_INPUT)) { + LOG_ERR("GPIO configuration failed."); + return -EIO; + } + if (config->ant_enable.port) { + gpio_pin_configure_dt(&config->ant_enable, GPIO_OUTPUT_INACTIVE); + } + if (config->tx_enable.port) { + gpio_pin_configure_dt(&config->tx_enable, GPIO_OUTPUT_INACTIVE); + } + if (config->rx_enable.port) { + gpio_pin_configure_dt(&config->rx_enable, GPIO_OUTPUT_INACTIVE); + } + + /* Configure interrupts */ + gpio_init_callback(&data->dio1_callback, sx126x_dio1_callback, BIT(config->dio1.pin)); + if (gpio_add_callback(config->dio1.port, &data->dio1_callback) < 0) { + LOG_ERR("Could not set GPIO callback for DIO1 interrupt."); + return -EIO; + } + + /* Reset chip on boot */ + status = ral_reset(&config->lbm_common.ralf.ral); + if (status != RAL_STATUS_OK) { + LOG_ERR("Reset failure (%d)", status); + return -EIO; + } + + /* Wait for chip to be ready */ + ret = sx126x_ensure_device_ready(dev, K_MSEC(100)); + if (ret) { + LOG_ERR("Failed to return to ready after reset"); + return -EIO; + } + + /* Enable interrupts */ + gpio_pin_interrupt_configure_dt(&config->dio1, GPIO_INT_EDGE_TO_ACTIVE); + + /* Common structure init */ + return lbm_lora_common_init(dev); +} + +#define SX126X_DEFINE(node_id, sx_variant) \ + static const struct lbm_sx126x_config config_##node_id = { \ + .lbm_common.ralf = RALF_SX126X_INSTANTIATE(DEVICE_DT_GET(node_id)), \ + .spi = SPI_DT_SPEC_GET( \ + node_id, SPI_WORD_SET(8) | SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB, 0), \ + .reset = GPIO_DT_SPEC_GET(node_id, reset_gpios), \ + .busy = GPIO_DT_SPEC_GET(node_id, busy_gpios), \ + .dio1 = GPIO_DT_SPEC_GET(node_id, dio1_gpios), \ + .ant_enable = GPIO_DT_SPEC_GET_OR(node_id, antenna_enable_gpios, {0}), \ + .tx_enable = GPIO_DT_SPEC_GET_OR(node_id, tx_enable_gpios, {0}), \ + .rx_enable = GPIO_DT_SPEC_GET_OR(node_id, rx_enable_gpios, {0}), \ + .dio3_tcxo_startup_delay_ms = DT_PROP_OR(node_id, tcxo_power_startup_delay_ms, 0), \ + .dio3_tcxo_voltage = DT_PROP_OR(node_id, dio3_tcxo_voltage, UINT8_MAX), \ + .dio2_rf_switch = DT_PROP(node_id, dio2_tx_enable), \ + .variant = sx_variant, \ + }; \ + static struct lbm_sx126x_data data_##node_id; \ + DEVICE_DT_DEFINE(node_id, sx126x_init, NULL, &data_##node_id, &config_##node_id, \ + POST_KERNEL, CONFIG_LORA_INIT_PRIORITY, &lbm_lora_api) + +#define SX1261_DEFINE(node_id) SX126X_DEFINE(node_id, VARIANT_SX1261) +#define SX1262_DEFINE(node_id) SX126X_DEFINE(node_id, VARIANT_SX1262) + +DT_FOREACH_STATUS_OKAY(semtech_sx1261, SX1261_DEFINE); +DT_FOREACH_STATUS_OKAY(semtech_sx1262, SX1262_DEFINE); diff --git a/drivers/lora/lora_basics_modem/lbm_sx127x.c b/drivers/lora/lora_basics_modem/lbm_sx127x.c new file mode 100644 index 0000000000000..434761502a560 --- /dev/null +++ b/drivers/lora/lora_basics_modem/lbm_sx127x.c @@ -0,0 +1,459 @@ +/* + * Copyright (c) 2025 Embeint Inc + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ral.h" + +#include "ralf_sx127x.h" +#include "ral_sx127x_bsp.h" +#include "sx127x_hal.h" +#include "sx127x.h" + +#include "lbm_common.h" + +struct lbm_sx127x_config { + struct lbm_lora_config_common lbm_common; + struct spi_dt_spec spi; + struct gpio_dt_spec reset; + struct gpio_dt_spec ant_enable; + struct gpio_dt_spec rfi_enable; + struct gpio_dt_spec rfo_enable; + struct gpio_dt_spec pa_boost_enable; + struct gpio_dt_spec tcxo_power; + const struct gpio_dt_spec *dios; + uint8_t num_dios; +}; + +struct lbm_sx127x_dio_package { + uint8_t idx; + struct gpio_callback callback; + struct k_work worker; +}; + +struct lbm_sx127x_data { + struct lbm_lora_data_common lbm_common; + sx127x_t radio; + const struct device *dev; + struct lbm_sx127x_dio_package dio_packages[3]; + struct k_timer timer; + void (*timer_cb)(void *context); + bool asleep; +}; + +LOG_MODULE_DECLARE(lbm_driver, CONFIG_LORA_LOG_LEVEL); + +static int sx127x_transceive(const struct device *dev, uint8_t reg, bool write, void *data, + size_t length) +{ + const struct lbm_sx127x_config *config = dev->config; + const struct spi_buf buf[2] = { + { + .buf = ®, + .len = sizeof(reg), + }, + { + .buf = data, + .len = length, + }, + }; + struct spi_buf_set tx = { + .buffers = buf, + .count = ARRAY_SIZE(buf), + }; + + if (!write) { + const struct spi_buf_set rx = {.buffers = buf, .count = ARRAY_SIZE(buf)}; + + return spi_transceive_dt(&config->spi, &tx, &rx); + } + + return spi_write_dt(&config->spi, &tx); +} + +static int sx127x_write(const struct device *dev, uint8_t reg_addr, uint8_t *data, uint8_t len) +{ + return sx127x_transceive(dev, reg_addr | BIT(7), true, data, len); +} + +static int sx127x_read(const struct device *dev, uint8_t reg_addr, uint8_t *data, uint8_t len) +{ + return sx127x_transceive(dev, reg_addr, false, data, len); +} + +sx127x_radio_id_t sx127x_hal_get_radio_id(const sx127x_t *radio) +{ +#if defined(SX1272) + return SX127X_RADIO_ID_SX1272; +#elif defined(SX1276) + return SX127X_RADIO_ID_SX1276; +#else +#error "Please define the radio to be used" +#endif +} + +sx127x_hal_status_t sx127x_hal_write(const sx127x_t *radio, const uint16_t address, + const uint8_t *data, const uint16_t data_len) +{ + const struct device *dev = radio->hal_context; + int ret; + + LOG_DBG("ADDR=0x%02x DATA_LEN=%d", address, data_len); + + /* Only 7 bit addresses make any sense */ + __ASSERT_NO_MSG(address < BIT(7)); + + ret = sx127x_write(dev, address, (uint8_t *)data, data_len); + return ret == 0 ? SX127X_HAL_STATUS_OK : SX127X_HAL_STATUS_ERROR; +} + +sx127x_hal_status_t sx127x_hal_read(const sx127x_t *radio, const uint16_t address, uint8_t *data, + const uint16_t data_len) +{ + const struct device *dev = radio->hal_context; + int ret; + + LOG_DBG("ADDR=0x%02x DATA_LEN=%d", address, data_len); + + /* Only 7 bit addresses make any sense */ + __ASSERT_NO_MSG(address < BIT(7)); + + ret = sx127x_read(dev, address, data, data_len); + return ret == 0 ? SX127X_HAL_STATUS_OK : SX127X_HAL_STATUS_ERROR; +} + +void sx127x_hal_reset(const sx127x_t *radio) +{ + const struct device *dev = radio->hal_context; + const struct lbm_sx127x_config *config = dev->config; + + LOG_DBG(""); + + /* Assert reset pin for >= 100 us */ + gpio_pin_set_dt(&config->reset, 1); + k_sleep(K_MSEC(1)); + gpio_pin_set_dt(&config->reset, 0); + + /* Wait >= 5ms for modem to be ready again */ + k_sleep(K_MSEC(50)); +} + +uint32_t sx127x_hal_get_dio_1_pin_state(const sx127x_t *radio) +{ + const struct device *dev = radio->hal_context; + const struct lbm_sx127x_config *config = dev->config; + + return gpio_pin_get_dt(&config->dios[0]); +} + +static void sx127x_timer_expiry(struct k_timer *timer) +{ + struct lbm_sx127x_data *data = CONTAINER_OF(timer, struct lbm_sx127x_data, timer); + + LOG_DBG(""); + + /* Run the provided callback */ + data->timer_cb((void *)&data->radio); +} + +sx127x_hal_status_t sx127x_hal_timer_start(const sx127x_t *radio, const uint32_t time_in_ms, + void (*callback)(void *context)) +{ + const struct device *dev = radio->hal_context; + struct lbm_sx127x_data *data = dev->data; + + if (callback == NULL) { + return SX127X_HAL_STATUS_ERROR; + } + + LOG_DBG("Starting %d ms timer", time_in_ms); + + /* Update internal state */ + data->timer_cb = callback; + + /* Start the timer */ + k_timer_start(&data->timer, K_MSEC(time_in_ms), K_FOREVER); + return SX127X_HAL_STATUS_OK; +} + +sx127x_hal_status_t sx127x_hal_timer_stop(const sx127x_t *radio) +{ + const struct device *dev = radio->hal_context; + struct lbm_sx127x_data *data = dev->data; + + LOG_DBG(""); + + k_timer_stop(&data->timer); + return SX127X_HAL_STATUS_OK; +} + +bool sx127x_hal_timer_is_started(const sx127x_t *radio) +{ + const struct device *dev = radio->hal_context; + struct lbm_sx127x_data *data = dev->data; + + return k_timer_remaining_get(&data->timer) > 0; +} + +void ral_sx127x_bsp_get_tx_cfg(const void *context, + const ral_sx127x_bsp_tx_cfg_input_params_t *input_params, + ral_sx127x_bsp_tx_cfg_output_params_t *output_params) + +{ + int16_t power = input_params->system_output_pwr_in_dbm; +#if defined(SX1272) + output_params->pa_cfg.pa_select = SX127X_PA_SELECT_RFO; + output_params->pa_cfg.is_20_dbm_output_on = false; +#elif defined(SX1276) + if (input_params->freq_in_hz > RF_FREQUENCY_MID_BAND_THRESHOLD) { + output_params->pa_cfg.pa_select = SX127X_PA_SELECT_BOOST; + output_params->pa_cfg.is_20_dbm_output_on = true; + } else { + output_params->pa_cfg.pa_select = SX127X_PA_SELECT_RFO; + output_params->pa_cfg.is_20_dbm_output_on = false; + } +#endif + + if (output_params->pa_cfg.pa_select == SX127X_PA_SELECT_BOOST) { + if (output_params->pa_cfg.is_20_dbm_output_on == true) { + power = MAX(power, 5); + power = MIN(power, 20); + } else { + power = MAX(power, 2); + power = MIN(power, 17); + } + } else { +#if defined(SX1272) + power = MAX(power, -1); + power = MIN(power, 14); +#elif defined(SX1276) + power = MAX(power, -4); + power = MIN(power, 15); +#endif + } + + output_params->chip_output_pwr_in_dbm_configured = power; + output_params->chip_output_pwr_in_dbm_expected = power; + output_params->pa_ramp_time = SX127X_RAMP_40_US; +} + +void ral_sx127x_bsp_get_ocp_value(const void *context, uint8_t *ocp_trim_value) +{ + /* Do nothing, let the driver choose the default values */ +} + +ral_status_t ral_sx127x_bsp_get_instantaneous_tx_power_consumption( + const void *context, + const ral_sx127x_bsp_tx_cfg_output_params_t *tx_cfg_output_params_local, + uint32_t *pwr_consumption_in_ua) +{ + /* Not yet implemented, not relevant for LoRa */ + return RAL_STATUS_UNSUPPORTED_FEATURE; +} + +ral_status_t +ral_sx127x_bsp_get_instantaneous_gfsk_rx_power_consumption(const void *context, bool rx_boosted, + uint32_t *pwr_consumption_in_ua) +{ + /* Not yet implemented, not relevant for LoRa */ + return RAL_STATUS_UNSUPPORTED_FEATURE; +} + +ral_status_t +ral_sx127x_bsp_get_instantaneous_lora_rx_power_consumption(const void *context, bool rx_boosted, + uint32_t *pwr_consumption_in_ua) + +{ + /* Not yet implemented, not relevant for LoRa */ + return RAL_STATUS_UNSUPPORTED_FEATURE; +} + +void lbm_driver_antenna_configure(const struct device *dev, enum lbm_modem_mode mode) +{ + const struct lbm_sx127x_config *config = dev->config; + + /* Antenna / RF switch control */ + switch (mode) { + case MODE_SLEEP: + lbm_optional_gpio_set_dt(&config->pa_boost_enable, 0); + lbm_optional_gpio_set_dt(&config->ant_enable, 0); + lbm_optional_gpio_set_dt(&config->rfi_enable, 0); + lbm_optional_gpio_set_dt(&config->rfo_enable, 0); + break; + case MODE_TX: + case MODE_CW: + lbm_optional_gpio_set_dt(&config->rfi_enable, 0); + lbm_optional_gpio_set_dt(&config->pa_boost_enable, 1); + lbm_optional_gpio_set_dt(&config->rfo_enable, 1); + lbm_optional_gpio_set_dt(&config->ant_enable, 1); + break; + case MODE_RX: + case MODE_RX_ASYNC: + case MODE_CAD: + lbm_optional_gpio_set_dt(&config->pa_boost_enable, 0); + lbm_optional_gpio_set_dt(&config->rfo_enable, 0); + lbm_optional_gpio_set_dt(&config->rfi_enable, 1); + lbm_optional_gpio_set_dt(&config->ant_enable, 1); + break; + } +} + +static void sx127x_dio_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins) +{ + /* Get DIO container to find the DIO index */ + struct lbm_sx127x_dio_package *dio_package = + CONTAINER_OF(cb, struct lbm_sx127x_dio_package, callback); + uint8_t dio = dio_package->idx; + /* Get the parent data structure */ + struct lbm_sx127x_data *data = + CONTAINER_OF(dio_package, struct lbm_sx127x_data, dio_packages[dio]); + + LOG_DBG("%d", dio); + + __ASSERT_NO_MSG(dio <= 3); + /* Submit work to process the interrupt immediately */ + k_work_submit(&data->dio_packages[dio].worker); +} + +static void dio_work_function(struct k_work *work) +{ + /* Get DIO container to find the DIO index */ + struct lbm_sx127x_dio_package *dio_package = + CONTAINER_OF(work, struct lbm_sx127x_dio_package, worker); + uint8_t dio = dio_package->idx; + /* Get the parent data structure */ + struct lbm_sx127x_data *data = + CONTAINER_OF(dio_package, struct lbm_sx127x_data, dio_packages[dio]); + + switch (dio) { + case 0: + data->radio.dio_0_irq_handler(&data->radio); + break; + case 1: + data->radio.dio_1_irq_handler(&data->radio); + break; + case 2: + data->radio.dio_2_irq_handler(&data->radio); + break; + default: + LOG_WRN("Unknown DIO %d", dio); + } +} + +void sx127x_hal_dio_irq_attach(const sx127x_t *radio) +{ + /* Nothing to do here */ +} + +static void sx127x_irq_handler(void *irq_context) +{ + const struct device *dev = irq_context; + struct lbm_sx127x_data *data = dev->data; + + /* Finish the current task from the common worker */ + k_work_reschedule(&data->lbm_common.op_done_work, K_NO_WAIT); +} + +static int sx127x_driver_init(const struct device *dev) +{ + const struct lbm_sx127x_config *config = dev->config; + struct lbm_sx127x_data *data = dev->data; + ral_status_t status; + + data->radio.hal_context = dev; + data->radio.irq_handler_context = (void *)dev; + data->radio.irq_handler = sx127x_irq_handler; + k_timer_init(&data->timer, sx127x_timer_expiry, NULL); + + /* Validate hardware is ready */ + if (!spi_is_ready_dt(&config->spi)) { + LOG_ERR("SPI bus %s not ready", config->spi.bus->name); + return -ENODEV; + } + + /* Setup GPIOs */ + if (gpio_pin_configure_dt(&config->reset, GPIO_OUTPUT_INACTIVE)) { + LOG_ERR("GPIO configuration failed."); + return -EIO; + } + if (config->ant_enable.port) { + gpio_pin_configure_dt(&config->ant_enable, GPIO_OUTPUT_INACTIVE); + } + if (config->rfi_enable.port) { + gpio_pin_configure_dt(&config->rfi_enable, GPIO_OUTPUT_INACTIVE); + } + if (config->rfo_enable.port) { + gpio_pin_configure_dt(&config->rfo_enable, GPIO_OUTPUT_INACTIVE); + } + if (config->pa_boost_enable.port) { + gpio_pin_configure_dt(&config->pa_boost_enable, GPIO_OUTPUT_INACTIVE); + } + if (config->tcxo_power.port) { + gpio_pin_configure_dt(&config->tcxo_power, GPIO_OUTPUT_INACTIVE); + } + + /* Configure interrupts */ + for (int i = 0; i < MIN(config->num_dios, 3); i++) { + data->dio_packages[i].idx = i; + k_work_init(&data->dio_packages[i].worker, dio_work_function); + + gpio_pin_configure_dt(&config->dios[i], GPIO_INPUT); + gpio_init_callback(&data->dio_packages[i].callback, sx127x_dio_callback, + BIT(config->dios[i].pin)); + if (gpio_add_callback(config->dios[i].port, &data->dio_packages[i].callback) < 0) { + LOG_ERR("Could not set GPIO callback for DIO%d interrupt.", i); + return -EIO; + } + gpio_pin_interrupt_configure_dt(&config->dios[i], GPIO_INT_EDGE_RISING); + } + + /* Reset chip on boot */ + status = ral_reset(&config->lbm_common.ralf.ral); + if (status != RAL_STATUS_OK) { + LOG_ERR("Reset failure (%d)", status); + return -EIO; + } + + /* Common structure init */ + return lbm_lora_common_init(dev); +} + +#define SX127X_DIO_GPIO_ELEM(idx, node_id) GPIO_DT_SPEC_GET_BY_IDX(node_id, dio_gpios, idx) + +#define SX127X_DIO_GPIO_INIT(node_id) \ + LISTIFY(DT_PROP_LEN(node_id, dio_gpios), SX127X_DIO_GPIO_ELEM, (,), node_id) + +#define SX127X_DEFINE(node_id) \ + static const struct gpio_dt_spec sx127x_dios_##node_id[] = { \ + SX127X_DIO_GPIO_INIT(node_id)}; \ + BUILD_ASSERT(ARRAY_SIZE(sx127x_dios_##node_id) >= 1); \ + static struct lbm_sx127x_data data_##node_id; \ + static const struct lbm_sx127x_config config_##node_id = { \ + .lbm_common.ralf = RALF_SX127X_INSTANTIATE(&data_##node_id.radio), \ + .spi = SPI_DT_SPEC_GET( \ + node_id, SPI_WORD_SET(8) | SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB, 0), \ + .reset = GPIO_DT_SPEC_GET(node_id, reset_gpios), \ + .ant_enable = GPIO_DT_SPEC_GET_OR(node_id, antenna_enable_gpios, {0}), \ + .rfi_enable = GPIO_DT_SPEC_GET_OR(node_id, rfi_enable_gpios, {0}), \ + .rfo_enable = GPIO_DT_SPEC_GET_OR(node_id, rfo_enable_gpios, {0}), \ + .pa_boost_enable = GPIO_DT_SPEC_GET_OR(node_id, pa_boost_enable_gpios, {0}), \ + .tcxo_power = GPIO_DT_SPEC_GET_OR(node_id, tcxo_power_gpios, {0}), \ + .dios = sx127x_dios_##node_id, \ + .num_dios = ARRAY_SIZE(sx127x_dios_##node_id), \ + }; \ + DEVICE_DT_DEFINE(node_id, sx127x_driver_init, NULL, &data_##node_id, &config_##node_id, \ + POST_KERNEL, CONFIG_LORA_INIT_PRIORITY, &lbm_lora_api) + +DT_FOREACH_STATUS_OKAY(semtech_sx1272, SX127X_DEFINE); +DT_FOREACH_STATUS_OKAY(semtech_sx1276, SX127X_DEFINE); From 0d62fbffd1e31198704701c2b194b6631fef57ca Mon Sep 17 00:00:00 2001 From: Jordan Yates Date: Tue, 29 Apr 2025 15:47:31 +1000 Subject: [PATCH 4/5] samples: lora: validate LBM build Add testcases for `CONFIG_LORA_MODULE_BACKEND_LORA_BASICS_MODEM` in the LoRa API samples. Signed-off-by: Jordan Yates --- samples/drivers/lora/receive/sample.yaml | 20 +++++++++++++++----- samples/drivers/lora/send/sample.yaml | 20 +++++++++++++++----- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/samples/drivers/lora/receive/sample.yaml b/samples/drivers/lora/receive/sample.yaml index 93e070db46ca5..c4f115312ab56 100644 --- a/samples/drivers/lora/receive/sample.yaml +++ b/samples/drivers/lora/receive/sample.yaml @@ -1,15 +1,25 @@ common: tags: lora depends_on: lora + harness: console + harness_config: + type: one_line + regex: + - " lora_receive: Synchronous reception" sample: description: Demonstration of LoRa Receive functionality name: LoRa Receive Sample tests: sample.driver.lora.receive: - harness: console - harness_config: - type: one_line - regex: - - " lora_receive: Synchronous reception" integration_platforms: - b_l072z_lrwan1 + sample.driver.lora.receive.lbm: + filter: dt_compat_enabled("semtech,sx1261") or + dt_compat_enabled("semtech,sx1262") or + dt_compat_enabled("semtech,sx1272") or + dt_compat_enabled("semtech,sx1276") + integration_platforms: + - b_l072z_lrwan1 + - rak11720 + extra_configs: + - CONFIG_LORA_MODULE_BACKEND_LORA_BASICS_MODEM=y diff --git a/samples/drivers/lora/send/sample.yaml b/samples/drivers/lora/send/sample.yaml index 871143405bf47..a4af9b7405f14 100644 --- a/samples/drivers/lora/send/sample.yaml +++ b/samples/drivers/lora/send/sample.yaml @@ -1,15 +1,25 @@ common: tags: lora depends_on: lora + harness: console + harness_config: + type: one_line + regex: + - " lora_send: Data sent 0!" sample: description: Demonstration of LoRa Send functionality name: LoRa Send Sample tests: sample.driver.lora.send: - harness: console - harness_config: - type: one_line - regex: - - " lora_send: Data sent 0!" integration_platforms: - b_l072z_lrwan1 + sample.driver.lora.send.lbm: + filter: dt_compat_enabled("semtech,sx1261") or + dt_compat_enabled("semtech,sx1262") or + dt_compat_enabled("semtech,sx1272") or + dt_compat_enabled("semtech,sx1276") + integration_platforms: + - b_l072z_lrwan1 + - rak11720 + extra_configs: + - CONFIG_LORA_MODULE_BACKEND_LORA_BASICS_MODEM=y From f3a8f9436a00ffa4d47e37b875507306de7191ec Mon Sep 17 00:00:00 2001 From: Jordan Yates Date: Tue, 29 Apr 2025 17:51:53 +1000 Subject: [PATCH 5/5] doc: lora_lorawan: document LoRa Basics Modem Document the current status of LoRaMAC-node and LoRa Basics Modem. Signed-off-by: Jordan Yates --- doc/connectivity/lora_lorawan/index.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/doc/connectivity/lora_lorawan/index.rst b/doc/connectivity/lora_lorawan/index.rst index 7098c05b4a662..85ea530833839 100644 --- a/doc/connectivity/lora_lorawan/index.rst +++ b/doc/connectivity/lora_lorawan/index.rst @@ -23,12 +23,25 @@ to the internet through a gateway. The Zephyr implementation is based on Semtech's `LoRaMac-node library`_, which is included as a Zephyr module. +.. note:: + + ``LoRaMac-node`` has been deprecated by Semtech in favor of + `LoRa Basics Modem`_. Porting the Zephyr API's to use + ``LoRa Basics Modem`` as the backend is in progress. + + Currently, only the base LoRa API is supported for the SX1261, SX1262, + SX1272 and SX1276 chipsets through + :kconfig:option:`CONFIG_LORA_MODULE_BACKEND_LORA_BASICS_MODEM`. + + The LoRaWAN specification is published by the `LoRa Alliance`_. .. _`Semtech Corporation`: https://www.semtech.com/ .. _`LoRaMac-node library`: https://github.com/Lora-net/LoRaMac-node +.. _`LoRa Basics Modem`: https://github.com/Lora-net/SWL2001 + .. _`LoRa Alliance`: https://lora-alliance.org/ Configuration Options