From edcc40a884a2951506c4ec070b0ac7820731f6a7 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Sat, 19 Sep 2026 16:46:14 +0200 Subject: [PATCH] track zephyr main 3e8f38faf93: rebase patches, lora 0.9 api, nrf clock split, s3 heap, lr11xx/lr20xx dc preamble guard, usb kick loop fix --- .gitignore | 1 + zephcore/CMakeLists.txt | 10 +- zephcore/Kconfig | 19 + zephcore/Kconfig.psram | 11 +- zephcore/adapters/board/ZephyrBoard.cpp | 10 + zephcore/adapters/radio/LR1110Radio.cpp | 2 +- zephcore/adapters/radio/LR2021Radio.cpp | 2 +- zephcore/adapters/radio/LoRaRadioBase.cpp | 4 +- zephcore/adapters/radio/LoRaRadioBase.h | 2 +- zephcore/adapters/radio/SX126xRadio.cpp | 2 +- zephcore/adapters/radio/SX127xRadio.cpp | 2 +- .../transport/SerialCompanionTransport.c | 14 +- zephcore/adapters/usb/ZephyrCompanionUSB.cpp | 14 +- zephcore/app/main_observer.cpp | 9 +- zephcore/boards/common/esp32_common.conf | 3 +- .../boards/common/esp32_cpu_80mhz.overlay | 12 +- zephcore/boards/common/esp32s3_common.conf | 28 + zephcore/boards/common/nrf52_common.conf | 8 + zephcore/boards/common/zephcore_common.conf | 4 + .../heltec_wifi_lora32_v3/partitions.overlay | 5 +- .../Kconfig.heltec_wifi_lora32_v4 | 2 +- .../heltec_wifi_lora32_v4_procpu.dts | 13 +- .../heltec_wifi_lora32_v4/partitions.overlay | 5 +- .../Kconfig.heltec_wifi_lora32_v43 | 2 +- .../heltec_wifi_lora32_v43_procpu.dts | 13 +- .../heltec_wifi_lora32_v43/partitions.overlay | 5 +- .../partitions.overlay | 5 +- .../Kconfig.heltec_wireless_tracker_v2 | 2 +- .../heltec_wireless_tracker_v2_procpu.dts | 9 +- .../partitions.overlay | 5 +- .../esp32/lilygo_t3s3/Kconfig.lilygo_t3s3 | 2 +- .../esp32/lilygo_t3s3/lilygo_t3s3_procpu.dts | 13 +- .../esp32/lilygo_t3s3/partitions.overlay | 5 +- .../lilygo_tlora_c6/Kconfig.lilygo_tlora_c6 | 2 +- .../lilygo_tlora_c6_hpcore.dts | 9 +- .../esp32/lilygo_tlora_c6/partitions.overlay | 5 +- .../meshnology_w12/Kconfig.meshnology_w12 | 2 +- .../meshnology_w12/meshnology_w12_procpu.dts | 13 +- .../esp32/meshnology_w12/partitions.overlay | 5 +- .../esp32/station_g2/Kconfig.station_g2 | 2 +- .../esp32/station_g2/partitions.overlay | 5 +- .../esp32/station_g2/station_g2_procpu.dts | 13 +- .../esp32/thinknode_m9/Kconfig.thinknode_m9 | 2 +- .../boards/esp32/thinknode_m9/board.overlay | 3 +- .../esp32/thinknode_m9/partitions.overlay | 5 +- .../thinknode_m9_esp32s3_procpu.dts | 18 +- zephcore/boards/esp32/ttgo_lora32/board.conf | 2 +- .../boards/esp32/ttgo_lora32/board.overlay | 3 +- .../boards/esp32/ttgo_tbeam/board.overlay | 3 +- .../esp32/xiao_esp32c3/partitions.overlay | 5 +- .../esp32/xiao_esp32c6/partitions.overlay | 5 +- .../esp32/xiao_esp32s3/partitions.overlay | 5 +- zephcore/boards/example_board/README.md | 2 +- zephcore/boards/mg24/xiao_mg24/board.overlay | 5 + .../heltec_t114/heltec_t114_nrf52840.dts | 3 +- .../nrf52840/promicro_sx1262/board.conf | 18 +- .../promicro_sx1262/promicro_sx1262.dts | 16 +- .../wio_tracker_l1/Kconfig.wio_tracker_l1 | 7 - .../nrf52840/wio_tracker_l1/board.cmake | 14 - .../nrf52840/wio_tracker_l1/board.overlay | 333 +++++- .../boards/nrf52840/wio_tracker_l1/board.yml | 10 - .../wio_tracker_l1_nrf52840-pinctrl.dtsi | 125 -- .../wio_tracker_l1_nrf52840.dts | 354 ------ .../wio_tracker_l1_nrf52840.yaml | 20 - .../wio_tracker_l1_nrf52840_defconfig | 21 - zephcore/boards/nrf54l/me25ls02/board.conf | 4 +- .../me25ls02/me25ls02_nrf54l15_cpuapp.dts | 25 +- .../seeed_lr2021_evk_nrf54l15_cpuapp.dts | 12 + .../seeed_lr2021_evk/support/openocd.cfg | 3 +- .../boards/nrf54l/xiao_nrf54l15/board.overlay | 13 + .../boards/stm32wl/lora_e5_mini/board.overlay | 2 + zephcore/helpers/CommonCLI.cpp | 60 + zephcore/helpers/ui/buzzer.c | 4 +- .../0001-ble-enc-funcs-reset.patch | 22 - .../0002-kconfig-cpu-freq-80mhz.patch | 38 - .../drivers/lora/lr11xx/lr11xx_lora.c | 83 +- .../drivers/lora/lr20xx/lr20xx_lora.c | 79 +- .../0001-lora-lr11xx-lr20xx-build.patch | 9 +- .../zephyr/0003-lora-sx126x-native.patch | 1040 +++++++++-------- .../zephyr/0007-spi-gpio-native-linux.patch | 82 +- ...rd-xiao-nrf54l15-openocd-rram-commit.patch | 33 - zephcore/prj.conf | 1 - zephcore/src/main_companion.cpp | 16 +- zephcore/src/main_repeater.cpp | 7 +- zephcore/src/main_room_server.cpp | 7 +- zephcore/sysbuild.conf | 11 + zephcore/west.yml | 2 +- 87 files changed, 1582 insertions(+), 1249 deletions(-) create mode 100644 zephcore/boards/common/esp32s3_common.conf delete mode 100644 zephcore/boards/nrf52840/wio_tracker_l1/Kconfig.wio_tracker_l1 delete mode 100644 zephcore/boards/nrf52840/wio_tracker_l1/board.cmake delete mode 100644 zephcore/boards/nrf52840/wio_tracker_l1/board.yml delete mode 100644 zephcore/boards/nrf52840/wio_tracker_l1/wio_tracker_l1_nrf52840-pinctrl.dtsi delete mode 100644 zephcore/boards/nrf52840/wio_tracker_l1/wio_tracker_l1_nrf52840.dts delete mode 100644 zephcore/boards/nrf52840/wio_tracker_l1/wio_tracker_l1_nrf52840.yaml delete mode 100644 zephcore/boards/nrf52840/wio_tracker_l1/wio_tracker_l1_nrf52840_defconfig delete mode 100644 zephcore/patches/modules/hal-espressif/0001-ble-enc-funcs-reset.patch delete mode 100644 zephcore/patches/modules/hal-espressif/0002-kconfig-cpu-freq-80mhz.patch delete mode 100644 zephcore/patches/zephyr/0016-board-xiao-nrf54l15-openocd-rram-commit.patch diff --git a/.gitignore b/.gitignore index 3115484..298f325 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ build-*/ # Zephyr SDK and toolchain (large, downloaded separately) /bootloader/ +/doc/ /modules/ /tools/ /zephyr/ diff --git a/zephcore/CMakeLists.txt b/zephcore/CMakeLists.txt index 90b2aa7..1a6e7a6 100644 --- a/zephcore/CMakeLists.txt +++ b/zephcore/CMakeLists.txt @@ -417,6 +417,11 @@ if(ZEPHCORE_PLATFORM_CONF AND EXISTS ${ZEPHCORE_PLATFORM_CONF}) list(APPEND ZEPHCORE_CONF_FILES ${ZEPHCORE_PLATFORM_CONF}) zephcore_auto_pair_overlay("${ZEPHCORE_PLATFORM_CONF}") endif() +# ESP32-S3 only: overrides esp32_common.conf, stays below board.conf. Every S3 +# board target carries the /esp32s3/ qualifier (see esp32s3_common.conf). +if(BOARD MATCHES "/esp32s3/") + list(APPEND ZEPHCORE_CONF_FILES "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/esp32s3_common.conf") +endif() if(ZEPHCORE_BOARD_CONF AND EXISTS ${ZEPHCORE_BOARD_CONF}) list(APPEND ZEPHCORE_CONF_FILES ${ZEPHCORE_BOARD_CONF}) zephcore_auto_pair_overlay("${ZEPHCORE_BOARD_CONF}") @@ -440,7 +445,8 @@ endif() # PLL/peripherals/RAM powered), so CPU frequency is a first-order term in a # battery node's draw — unlike nRF52, which already idles at microamps. Pull # every ESP32 build down to 80 MHz, matching what Arduino MeshCore ships. -# Needs patches/modules/hal-espressif/0002 (upstream omits 80 from its DT map). +# hal_espressif derives ESP_DEFAULT_CPU_FREQ_MHZ from cpu@0 directly, so 80 +# needs no module patch (our 0002 carried that until the Zephyr-main bump). # # Observers are the exception and keep the SoC default (240 MHz on S3/classic, # 160 MHz on C3/C6): they run a full WiFi + TLS + MQTT stack and are mains- or @@ -748,7 +754,7 @@ else() endif() # ========== I2C keypad (boards whose only input is a keyboard) ========== -if(CONFIG_INPUT) +if(CONFIG_INPUT AND CONFIG_DT_HAS_ZEPHCORE_STC8H_KEYPAD_ENABLED) target_sources_ifdef(CONFIG_ZEPHCORE_UI_DISPLAY app PRIVATE helpers/input/stc8h_keypad.c) endif() diff --git a/zephcore/Kconfig b/zephcore/Kconfig index 45c6494..d466c21 100644 --- a/zephcore/Kconfig +++ b/zephcore/Kconfig @@ -765,6 +765,23 @@ config ZEPHCORE_LORA_DC_MARGIN_PCT the loss is signal-strength-independent, so RSSI will look fine and only a PDR comparison against rxduty 0 will show it. +config ZEPHCORE_MEM_STATS + bool "CLI: report thread stack and heap high-water marks" + select INIT_STACKS + select THREAD_STACK_INFO + select THREAD_MONITOR + select THREAD_NAME + select SYS_HEAP_RUNTIME_STATS + help + Adds `get mem ` to the CLI (all roles). Each call returns one line: + n = 0.. walks the threads (name, stack size, peak bytes used), then the + k_heaps (address, size, current and peak allocation), then "end". + + For sizing stacks and heaps against measured use, e.g. after a Zephyr + bump. Stack watermarks need INIT_STACKS (stacks painted at thread + creation), which costs boot time, so leave this off in releases. + Resolve heap addresses to symbols with nm on zephyr.elf. + config ZEPHCORE_LORA_RETENTION_DEBUG bool "SX126x: verify the warm-start retention list on air" depends on ZEPHCORE_RADIO_NATIVE @@ -1114,6 +1131,8 @@ config ZEPHCORE_EASTER_EGG_DOOM config ZEPHCORE_UI_JOYSTICK bool + # In-tree Zephyr board (no board Kconfig of ours to select from) + default y if BOARD_WIO_TRACKER_L1 help Set by board-level Kconfig when the hardware has a 5-way joystick or D-pad. Not user-selectable; set by the board configuration. diff --git a/zephcore/Kconfig.psram b/zephcore/Kconfig.psram index 29cf4b9..6c15e6d 100644 --- a/zephcore/Kconfig.psram +++ b/zephcore/Kconfig.psram @@ -2,13 +2,14 @@ # # DT-driven PSRAM auto-enable for ESP32 boards. # -# Upstream Zephyr's esp32*_wroom_n*r*.dtsi files override psram0 size when -# the chip variant has PSRAM stuffed (R-suffix in the Espressif part number). -# We auto-enable PSRAM whenever the DTS size is non-zero — the upstream +# Each ESP32 board DTS sets &psram0 { size = ...; } when its module has PSRAM +# stuffed (R-suffix in the Espressif part number); since Zephyr 4.5 the board +# declares it, the per-module esp32*_wroom_n*r*.dtsi files are gone. We +# auto-enable PSRAM whenever the DTS size is non-zero — the upstream # Kconfig.spiram then auto-reads ESP_SPIRAM_SIZE from the same DT property. # -# Boards without PSRAM include a non-R-suffix dtsi (e.g. esp32s3_wroom_n8), -# which leaves psram0 at size = 0 — auto-detect leaves PSRAM off for them. +# Boards without PSRAM leave psram0 at the SoC dtsi's size = 0 — auto-detect +# leaves PSRAM off for them. # ESP32-C3 and ESP32-C6 are outside Kconfig.spiram's SOC gate entirely, so # this symbol doesn't exist on those builds — no effect. # diff --git a/zephcore/adapters/board/ZephyrBoard.cpp b/zephcore/adapters/board/ZephyrBoard.cpp index 3b871be..3fab470 100644 --- a/zephcore/adapters/board/ZephyrBoard.cpp +++ b/zephcore/adapters/board/ZephyrBoard.cpp @@ -459,6 +459,16 @@ void ZephyrBoard::rebootToBootloader() /* Clean USB disconnect before the soft reset — same reason as reboot(). */ usb_serial_jtag_ll_phy_enable_pad(false); k_msleep(100); +#ifdef ESP32_FORCE_DOWNLOAD_BOOT + /* ...but put the pad back before resetting into the ROM. The pad-enable + * bit survives a software reset, and unlike the app the ROM download mode + * never re-enables it, so leaving it off keeps USB-Serial-JTAG off the bus: + * the host sees the port die and only a power cycle brings it back. That + * was `start dfu` on every S3 build whose console is USJ (repeater, + * observer, room server, debug). The 100 ms low above still gives the + * host its disconnect; the ROM then enumerates as 303a:1001. */ + usb_serial_jtag_ll_phy_enable_pad(true); +#endif #endif #ifdef ESP32_FORCE_DOWNLOAD_BOOT /* Hand the internal USB PHY back to USB-Serial-JTAG, so the ROM download diff --git a/zephcore/adapters/radio/LR1110Radio.cpp b/zephcore/adapters/radio/LR1110Radio.cpp index 52a5628..02238d1 100644 --- a/zephcore/adapters/radio/LR1110Radio.cpp +++ b/zephcore/adapters/radio/LR1110Radio.cpp @@ -45,7 +45,7 @@ void LR1110Radio::resetDutyCycleTimeoutRestarts() bool LR1110Radio::hwConfigure(const struct lora_modem_config &cfg) { - int ret = lora_config(_dev, const_cast(&cfg)); + int ret = lora_config(_dev, &cfg); if (ret < 0) { LOG_ERR("lora_config failed: %d", ret); return false; diff --git a/zephcore/adapters/radio/LR2021Radio.cpp b/zephcore/adapters/radio/LR2021Radio.cpp index 865e6ed..70aef2b 100644 --- a/zephcore/adapters/radio/LR2021Radio.cpp +++ b/zephcore/adapters/radio/LR2021Radio.cpp @@ -84,7 +84,7 @@ int LR2021Radio::formatFreqErrorStatus(char *buf, int cap) bool LR2021Radio::hwConfigure(const struct lora_modem_config &cfg) { - int ret = lora_config(_dev, const_cast(&cfg)); + int ret = lora_config(_dev, &cfg); if (ret < 0) { LOG_ERR("lora_config failed: %d", ret); return false; diff --git a/zephcore/adapters/radio/LoRaRadioBase.cpp b/zephcore/adapters/radio/LoRaRadioBase.cpp index 40528bd..bb1f226 100644 --- a/zephcore/adapters/radio/LoRaRadioBase.cpp +++ b/zephcore/adapters/radio/LoRaRadioBase.cpp @@ -699,7 +699,7 @@ void LoRaRadioBase::startReceive() (rx_us + sleep_us + trans_us))); } - ret = lora_recv_duty_cycle(_dev, + ret = lora_recv_duty_cycle_async(_dev, K_USEC(rx_us), K_USEC(sleep_us), rxCallbackStatic, this); @@ -722,7 +722,7 @@ void LoRaRadioBase::startReceive() * way and report it. */ LOG_DBG("rxduty: busy (TX in progress) — continuous RX"); } else if (ret != -ENOSYS) { - LOG_ERR("lora_recv_duty_cycle failed: %d", ret); + LOG_ERR("lora_recv_duty_cycle_async failed: %d", ret); } /* Fall through to continuous RX */ } else if (_dc_last_rx_us != UINT32_MAX) { diff --git a/zephcore/adapters/radio/LoRaRadioBase.h b/zephcore/adapters/radio/LoRaRadioBase.h index fa83cda..c49e241 100644 --- a/zephcore/adapters/radio/LoRaRadioBase.h +++ b/zephcore/adapters/radio/LoRaRadioBase.h @@ -511,7 +511,7 @@ protected: uint8_t _override_sf; uint8_t _override_cr; - /* ISR RX callback — passed to lora_recv_async() / lora_recv_duty_cycle() */ + /* ISR RX callback — passed to lora_recv_async() / lora_recv_duty_cycle_async() */ static void rxCallbackStatic(const struct device *dev, uint8_t *data, uint16_t size, int16_t rssi, int8_t snr, void *user_data); diff --git a/zephcore/adapters/radio/SX126xRadio.cpp b/zephcore/adapters/radio/SX126xRadio.cpp index 65d4c3e..646a459 100644 --- a/zephcore/adapters/radio/SX126xRadio.cpp +++ b/zephcore/adapters/radio/SX126xRadio.cpp @@ -42,7 +42,7 @@ void SX126xRadio::begin() bool SX126xRadio::hwConfigure(const struct lora_modem_config &cfg) { - int ret = lora_config(_dev, const_cast(&cfg)); + int ret = lora_config(_dev, &cfg); if (ret < 0) { LOG_ERR("lora_config failed: %d", ret); return false; diff --git a/zephcore/adapters/radio/SX127xRadio.cpp b/zephcore/adapters/radio/SX127xRadio.cpp index a3b15fd..c96539c 100644 --- a/zephcore/adapters/radio/SX127xRadio.cpp +++ b/zephcore/adapters/radio/SX127xRadio.cpp @@ -48,7 +48,7 @@ void SX127xRadio::begin() bool SX127xRadio::hwConfigure(const struct lora_modem_config &cfg) { - int ret = lora_config(_dev, const_cast(&cfg)); + int ret = lora_config(_dev, &cfg); if (ret < 0) { LOG_ERR("lora_config failed: %d", ret); return false; diff --git a/zephcore/adapters/transport/SerialCompanionTransport.c b/zephcore/adapters/transport/SerialCompanionTransport.c index a660360..b7e7b7f 100644 --- a/zephcore/adapters/transport/SerialCompanionTransport.c +++ b/zephcore/adapters/transport/SerialCompanionTransport.c @@ -140,7 +140,12 @@ static void uart_isr(const struct device *dev, void *user_data) { ARG_UNUSED(user_data); - while (uart_irq_update(dev) && uart_irq_is_pending(dev)) { + for (;;) { + uart_irq_update(dev); + if (uart_irq_is_pending(dev) <= 0) { + break; + } + if (uart_irq_rx_ready(dev)) { uint8_t buf[64]; int n = uart_fifo_read(dev, buf, sizeof(buf)); @@ -200,11 +205,14 @@ static void uart_isr(const struct device *dev, void *user_data) uint8_t *out; bool empty; k_spinlock_key_t key = k_spin_lock(&tx_lock); - uint32_t claimed = ring_buf_get_claim(&tx_ring, &out, 64); + uint32_t claimed = MIN(ring_buf_get_ptr(&tx_ring, &out, 0), 64U); if (claimed > 0) { int sent = uart_fifo_fill(dev, out, claimed); - ring_buf_get_finish(&tx_ring, sent > 0 ? sent : 0); + + if (sent > 0) { + ring_buf_consume(&tx_ring, sent); + } } empty = ring_buf_is_empty(&tx_ring); if (empty) { diff --git a/zephcore/adapters/usb/ZephyrCompanionUSB.cpp b/zephcore/adapters/usb/ZephyrCompanionUSB.cpp index 1df5dd0..baebb2f 100644 --- a/zephcore/adapters/usb/ZephyrCompanionUSB.cpp +++ b/zephcore/adapters/usb/ZephyrCompanionUSB.cpp @@ -144,7 +144,12 @@ static void usb_uart_isr(const struct device *dev, void *user_data) { ARG_UNUSED(user_data); - while (uart_irq_update(dev) && uart_irq_is_pending(dev)) { + for (;;) { + uart_irq_update(dev); + if (uart_irq_is_pending(dev) <= 0) { + break; + } + if (uart_irq_rx_ready(dev)) { uint8_t buf[64]; int recv_len = uart_fifo_read(dev, buf, sizeof(buf)); @@ -162,10 +167,13 @@ static void usb_uart_isr(const struct device *dev, void *user_data) uint8_t *out; bool empty; k_spinlock_key_t key = k_spin_lock(&usb_tx_lock); - uint32_t claimed = ring_buf_get_claim(&usb_tx_ring_buf, &out, 64); + uint32_t claimed = MIN(ring_buf_get_ptr(&usb_tx_ring_buf, &out, 0), 64U); if (claimed > 0) { int sent = uart_fifo_fill(dev, out, claimed); - ring_buf_get_finish(&usb_tx_ring_buf, sent > 0 ? sent : 0); + + if (sent > 0) { + ring_buf_consume(&usb_tx_ring_buf, sent); + } } empty = ring_buf_is_empty(&usb_tx_ring_buf); if (empty) { diff --git a/zephcore/app/main_observer.cpp b/zephcore/app/main_observer.cpp index bf78338..d8ac4f3 100644 --- a/zephcore/app/main_observer.cpp +++ b/zephcore/app/main_observer.cpp @@ -86,7 +86,12 @@ static void cli_println(const char *s) static void cli_uart_isr(const struct device *dev, void *user_data) { ARG_UNUSED(user_data); - while (uart_irq_update(dev) && uart_irq_is_pending(dev)) { + for (;;) { + uart_irq_update(dev); + if (uart_irq_is_pending(dev) <= 0) { + break; + } + if (uart_irq_rx_ready(dev)) { uint8_t buf[64]; int n = uart_fifo_read(dev, buf, sizeof(buf)); @@ -276,6 +281,8 @@ static NodePrefs s_radio_prefs; #if IS_ENABLED(CONFIG_ZEPHCORE_RADIO_LR1110) static mesh::LR1110Radio lora_radio(lora_dev, s_board, &s_radio_prefs); +#elif IS_ENABLED(CONFIG_ZEPHCORE_RADIO_LR2021) +static mesh::LR2021Radio lora_radio(lora_dev, s_board, &s_radio_prefs); #elif IS_ENABLED(CONFIG_ZEPHCORE_RADIO_SX127X) static mesh::SX127xRadio lora_radio(lora_dev, s_board, &s_radio_prefs); #else diff --git a/zephcore/boards/common/esp32_common.conf b/zephcore/boards/common/esp32_common.conf index 6e157ff..c97ec50 100644 --- a/zephcore/boards/common/esp32_common.conf +++ b/zephcore/boards/common/esp32_common.conf @@ -82,7 +82,8 @@ CONFIG_STACK_SENTINEL=y # EVT_RX must strictly exceed ACL_TX (enforced by BUILD_ASSERT in buf.h). CONFIG_BT_BUF_ACL_TX_COUNT=12 CONFIG_BT_L2CAP_TX_BUF_COUNT=12 -CONFIG_BT_CONN_TX_MAX=12 +# (BT_CONN_TX_MAX=12 was removed in Zephyr 4.5; pending TX with a callback now +# always follows BT_BUF_ACL_TX_COUNT, which is 12 above -- same limit.) CONFIG_BT_BUF_EVT_RX_COUNT=14 # BLE controller link-layer encryption — Zephyr 4.4.1 (commit 7f01467, "align diff --git a/zephcore/boards/common/esp32_cpu_80mhz.overlay b/zephcore/boards/common/esp32_cpu_80mhz.overlay index 5c87ae0..cf1bf75 100644 --- a/zephcore/boards/common/esp32_cpu_80mhz.overlay +++ b/zephcore/boards/common/esp32_cpu_80mhz.overlay @@ -9,12 +9,12 @@ * 80 MHz for the same reason (ESP32_CPU_FREQ=80 in variants/heltec_v3, * heltec_v4, heltec_tracker, rak3112, xiao_c3, ...). * - * 80 requires patches/modules/hal-espressif/0002: upstream's DT-to-Kconfig map - * lists only the per-SoC defaults (96/120/160/240) and omits 80, so without it - * the build fails with "'CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ' undeclared". Do NOT - * reach for 96 to dodge that -- it is an ESP32-H2 frequency, rejected by - * rtc_clk_cpu_freq_mhz_to_config() on every SoC we build, and it fails at BOOT - * rather than at build time. + * hal_espressif computes CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ as this property / 1e6 + * straight from /cpus/cpu@0, so 80 needs no module patch. (Older pins used a + * fixed 96/120/160/240 map that omitted 80; we carried hal-espressif 0002 for + * it until the Zephyr-main bump.) Do NOT use 96 -- it is an ESP32-H2 + * frequency, rejected by rtc_clk_cpu_freq_mhz_to_config() on every SoC we + * build, and it fails at BOOT rather than at build time. * * The mesh workload is event-driven and nowhere near CPU-bound: wake on a * packet, run some AES and LittleFS, go back to idle. Airtime dominates every diff --git a/zephcore/boards/common/esp32s3_common.conf b/zephcore/boards/common/esp32s3_common.conf new file mode 100644 index 0000000..87136d4 --- /dev/null +++ b/zephcore/boards/common/esp32s3_common.conf @@ -0,0 +1,28 @@ +# ESP32-S3 only. Loaded after esp32_common.conf and before board.conf +# (CMakeLists.txt, matched on the /esp32s3/ board qualifier). +# +# ========== System heap ========== +# esp32_common.conf sizes the heap at 64 KB for the Espressif BLE controller, +# which allocates through k_malloc (CONFIG_ESP_BT_HEAP_SYSTEM=y). On Zephyr 4.5 +# that no longer fits the S3's shared SRAM: the new hal_espressif controller +# blob (libbtdm_app.a) places ~15 KB of link-layer patch code (r_lld_*_hack / +# *_eco) in IRAM, and IRAM and DRAM share the same physical SRAM, so dram0 +# grew by ~21 KB and Heltec V4/V43, Wireless Tracker V2 and Meshnology W12 +# overflowed in the sysbuild (MCUboot) layout. +# +# Measured on a XIAO ESP32-S3 companion (2026-09-19, Zephyr main 3e8f38faf93, +# CONFIG_SYS_HEAP_RUNTIME_STATS): 26.8 KB allocated after BLE init, peak +# 28.8 KB across boot, pairing, contact/channel sync and a live session. +# 44 KB leaves ~16 KB above that for boards that allocate more before +# bt_enable() -- the ThinkNode M9 (display, keypad, LR1110) is the reason the +# pool was once raised from 32 KB (see esp32_common.conf). +# +# C3/C6 keep 64 KB: different controller blobs, not measured, and they fit. +CONFIG_HEAP_MEM_POOL_SIZE=45056 + +# ========== System workqueue stack ========== +# 8192 in zephcore_common.conf. Measured on the XIAO ESP32-S3 (same session as +# the heap figure above, plus a repeater build with flood forwarding and the +# WiFi-OTA AP/HTTP server started and stopped): peak 1092 B (companion). +# 4096 = 3.75x. +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 diff --git a/zephcore/boards/common/nrf52_common.conf b/zephcore/boards/common/nrf52_common.conf index 4a4ea51..3feab66 100644 --- a/zephcore/boards/common/nrf52_common.conf +++ b/zephcore/boards/common/nrf52_common.conf @@ -64,3 +64,11 @@ CONFIG_NORDIC_QSPI_NOR_FLASH_LAYOUT_PAGE_SIZE=4096 # BT_CTLR_ASSERT_HANDLER is harmless when CONFIG_ASSERT=n (no RAM/flash cost, # the handler is simply never invoked) so it stays on for both prod and debug. CONFIG_BT_CTLR_ASSERT_HANDLER=y + +# System workqueue stack: 8192 in zephcore_common.conf is ~10x what it uses. +# Measured 2026-09-19 (CONFIG_ZEPHCORE_MEM_STATS, Zephyr main 3e8f38faf93), +# Wio L1 (SX1262) + T1000-E (LR1110), companion and repeater, under a phone +# BLE session with contact sync, radio TX/RX + flood forwarding, GPS on/off and +# prefs writes: peak 808 B. 4096 = 5x, and the floor healthcheck.conf already +# names for DBG-level logging from ISR context. +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 diff --git a/zephcore/boards/common/zephcore_common.conf b/zephcore/boards/common/zephcore_common.conf index 419b6d2..3fdf4ea 100644 --- a/zephcore/boards/common/zephcore_common.conf +++ b/zephcore/boards/common/zephcore_common.conf @@ -19,6 +19,10 @@ CONFIG_CBPRINTF_FP_SUPPORT=y # ========== Crypto (PSA) ========== # Required for mesh encryption (AES-ECB, SHA256, HMAC) +# PSA_CRYPTO explicitly: since Zephyr 4.5 (Mbed TLS 4 / TF-PSA-Crypto) MBEDTLS +# *depends on* PSA_CRYPTO. Companions still got it via the BT stack selecting +# it; repeaters (BT off) silently lost MBEDTLS and failed on psa/crypto.h. +CONFIG_PSA_CRYPTO=y CONFIG_MBEDTLS=y CONFIG_MBEDTLS_PSA_CRYPTO_C=y CONFIG_PSA_WANT_ALG_SHA_256=y diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v3/partitions.overlay b/zephcore/boards/esp32/heltec_wifi_lora32_v3/partitions.overlay index 7ca3a47..dfe978b 100644 --- a/zephcore/boards/esp32/heltec_wifi_lora32_v3/partitions.overlay +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v3/partitions.overlay @@ -20,7 +20,6 @@ /delete-node/ &slot0_lpcore_partition; /delete-node/ &slot1_lpcore_partition; /delete-node/ &storage_partition; -/delete-node/ &scratch_partition; /delete-node/ &coredump_partition; /delete-node/ &slot0_partition; /delete-node/ &slot1_partition; @@ -35,22 +34,26 @@ partitions { /* App slots — equal size (swap-mode compatible), slot0 at 0x10000. */ slot0_partition: partition@10000 { + compatible = "zephyr,mapped-partition"; label = "image-0"; reg = <0x10000 0x3C6000>; }; slot1_partition: partition@3d6000 { + compatible = "zephyr,mapped-partition"; label = "image-1"; reg = <0x3D6000 0x3C6000>; }; /* BLE bond store — 16KB NVS, isolated from /lfs (see esp32_common.conf). * Offset unchanged so existing bonds survive the reflash. */ storage_partition: partition@79c000 { + compatible = "zephyr,mapped-partition"; label = "storage"; reg = <0x79C000 0x4000>; }; /* LittleFS 384KB for DataStore (identity, prefs, contacts). * Offset unchanged so existing identity/prefs/contacts survive. */ lfs_partition: partition@7a0000 { + compatible = "zephyr,mapped-partition"; label = "lfs"; reg = <0x7A0000 0x60000>; }; diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v4/Kconfig.heltec_wifi_lora32_v4 b/zephcore/boards/esp32/heltec_wifi_lora32_v4/Kconfig.heltec_wifi_lora32_v4 index ea7548a..bb45d7f 100644 --- a/zephcore/boards/esp32/heltec_wifi_lora32_v4/Kconfig.heltec_wifi_lora32_v4 +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v4/Kconfig.heltec_wifi_lora32_v4 @@ -1,3 +1,3 @@ config BOARD_HELTEC_WIFI_LORA32_V4 - select SOC_ESP32S3_WROOM_N16R2 + select SOC_ESP32S3 select SOC_ESP32S3_PROCPU if BOARD_HELTEC_WIFI_LORA32_V4_ESP32S3_PROCPU diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v4/heltec_wifi_lora32_v4_procpu.dts b/zephcore/boards/esp32/heltec_wifi_lora32_v4/heltec_wifi_lora32_v4_procpu.dts index 13bc0c9..f770bc8 100644 --- a/zephcore/boards/esp32/heltec_wifi_lora32_v4/heltec_wifi_lora32_v4_procpu.dts +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v4/heltec_wifi_lora32_v4_procpu.dts @@ -31,7 +31,7 @@ /dts-v1/; -#include +#include #include "heltec_wifi_lora32_v4-pinctrl.dtsi" #include #include @@ -260,3 +260,14 @@ &esp32_bt_hci { status = "okay"; }; &wifi { status = "okay"; }; + +/* Module flash/PSRAM, declared by the board since Zephyr 4.5 removed the + * per-module dtsi (esp32s3_wroom_n16r2.dtsi). */ +&flash0 { + reg = <0x0 DT_SIZE_M(16)>; + ranges = <0x0 0x0 DT_SIZE_M(16)>; +}; + +&psram0 { + size = ; +}; diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v4/partitions.overlay b/zephcore/boards/esp32/heltec_wifi_lora32_v4/partitions.overlay index 5a300ab..ba6a2e3 100644 --- a/zephcore/boards/esp32/heltec_wifi_lora32_v4/partitions.overlay +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v4/partitions.overlay @@ -17,7 +17,6 @@ /delete-node/ &slot0_lpcore_partition; /delete-node/ &slot1_lpcore_partition; /delete-node/ &storage_partition; -/delete-node/ &scratch_partition; /delete-node/ &coredump_partition; /delete-node/ &slot0_partition; /delete-node/ &slot1_partition; @@ -32,20 +31,24 @@ partitions { /* App slots — equal size (swap-mode compatible), slot0 at 0x10000. */ slot0_partition: partition@10000 { + compatible = "zephyr,mapped-partition"; label = "image-0"; reg = <0x10000 0x7C6000>; }; slot1_partition: partition@7D6000 { + compatible = "zephyr,mapped-partition"; label = "image-1"; reg = <0x7D6000 0x7C6000>; }; /* BLE bond NVS — offset unchanged so bonds survive the reflash. */ storage_partition: partition@F9C000 { + compatible = "zephyr,mapped-partition"; label = "storage"; reg = <0xF9C000 0x4000>; }; /* LittleFS (identity/prefs/contacts) — offset unchanged. */ lfs_partition: partition@FA0000 { + compatible = "zephyr,mapped-partition"; label = "lfs"; reg = <0xFA0000 0x60000>; }; diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v43/Kconfig.heltec_wifi_lora32_v43 b/zephcore/boards/esp32/heltec_wifi_lora32_v43/Kconfig.heltec_wifi_lora32_v43 index 74d98b5..9e8d791 100644 --- a/zephcore/boards/esp32/heltec_wifi_lora32_v43/Kconfig.heltec_wifi_lora32_v43 +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v43/Kconfig.heltec_wifi_lora32_v43 @@ -1,3 +1,3 @@ config BOARD_HELTEC_WIFI_LORA32_V43 - select SOC_ESP32S3_WROOM_N16R2 + select SOC_ESP32S3 select SOC_ESP32S3_PROCPU if BOARD_HELTEC_WIFI_LORA32_V43_ESP32S3_PROCPU diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v43/heltec_wifi_lora32_v43_procpu.dts b/zephcore/boards/esp32/heltec_wifi_lora32_v43/heltec_wifi_lora32_v43_procpu.dts index 8539360..183ac3d 100644 --- a/zephcore/boards/esp32/heltec_wifi_lora32_v43/heltec_wifi_lora32_v43_procpu.dts +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v43/heltec_wifi_lora32_v43_procpu.dts @@ -30,7 +30,7 @@ /dts-v1/; -#include +#include #include "heltec_wifi_lora32_v43-pinctrl.dtsi" #include #include @@ -278,3 +278,14 @@ &esp32_bt_hci { status = "okay"; }; &wifi { status = "okay"; }; + +/* Module flash/PSRAM, declared by the board since Zephyr 4.5 removed the + * per-module dtsi (esp32s3_wroom_n16r2.dtsi). */ +&flash0 { + reg = <0x0 DT_SIZE_M(16)>; + ranges = <0x0 0x0 DT_SIZE_M(16)>; +}; + +&psram0 { + size = ; +}; diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v43/partitions.overlay b/zephcore/boards/esp32/heltec_wifi_lora32_v43/partitions.overlay index 5a300ab..ba6a2e3 100644 --- a/zephcore/boards/esp32/heltec_wifi_lora32_v43/partitions.overlay +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v43/partitions.overlay @@ -17,7 +17,6 @@ /delete-node/ &slot0_lpcore_partition; /delete-node/ &slot1_lpcore_partition; /delete-node/ &storage_partition; -/delete-node/ &scratch_partition; /delete-node/ &coredump_partition; /delete-node/ &slot0_partition; /delete-node/ &slot1_partition; @@ -32,20 +31,24 @@ partitions { /* App slots — equal size (swap-mode compatible), slot0 at 0x10000. */ slot0_partition: partition@10000 { + compatible = "zephyr,mapped-partition"; label = "image-0"; reg = <0x10000 0x7C6000>; }; slot1_partition: partition@7D6000 { + compatible = "zephyr,mapped-partition"; label = "image-1"; reg = <0x7D6000 0x7C6000>; }; /* BLE bond NVS — offset unchanged so bonds survive the reflash. */ storage_partition: partition@F9C000 { + compatible = "zephyr,mapped-partition"; label = "storage"; reg = <0xF9C000 0x4000>; }; /* LittleFS (identity/prefs/contacts) — offset unchanged. */ lfs_partition: partition@FA0000 { + compatible = "zephyr,mapped-partition"; label = "lfs"; reg = <0xFA0000 0x60000>; }; diff --git a/zephcore/boards/esp32/heltec_wireless_tracker/partitions.overlay b/zephcore/boards/esp32/heltec_wireless_tracker/partitions.overlay index 7af2f00..cbc575f 100644 --- a/zephcore/boards/esp32/heltec_wireless_tracker/partitions.overlay +++ b/zephcore/boards/esp32/heltec_wireless_tracker/partitions.overlay @@ -17,7 +17,6 @@ /delete-node/ &slot0_lpcore_partition; /delete-node/ &slot1_lpcore_partition; /delete-node/ &storage_partition; -/delete-node/ &scratch_partition; /delete-node/ &coredump_partition; /delete-node/ &slot0_partition; /delete-node/ &slot1_partition; @@ -32,20 +31,24 @@ partitions { /* App slots — equal size (swap-mode compatible), slot0 at 0x10000. */ slot0_partition: partition@10000 { + compatible = "zephyr,mapped-partition"; label = "image-0"; reg = <0x10000 0x3C6000>; }; slot1_partition: partition@3D6000 { + compatible = "zephyr,mapped-partition"; label = "image-1"; reg = <0x3D6000 0x3C6000>; }; /* BLE bond NVS — offset unchanged so bonds survive the reflash. */ storage_partition: partition@79C000 { + compatible = "zephyr,mapped-partition"; label = "storage"; reg = <0x79C000 0x4000>; }; /* LittleFS (identity/prefs/contacts) — offset unchanged. */ lfs_partition: partition@7A0000 { + compatible = "zephyr,mapped-partition"; label = "lfs"; reg = <0x7A0000 0x60000>; }; diff --git a/zephcore/boards/esp32/heltec_wireless_tracker_v2/Kconfig.heltec_wireless_tracker_v2 b/zephcore/boards/esp32/heltec_wireless_tracker_v2/Kconfig.heltec_wireless_tracker_v2 index a70d3df..e875d2f 100644 --- a/zephcore/boards/esp32/heltec_wireless_tracker_v2/Kconfig.heltec_wireless_tracker_v2 +++ b/zephcore/boards/esp32/heltec_wireless_tracker_v2/Kconfig.heltec_wireless_tracker_v2 @@ -1,3 +1,3 @@ config BOARD_HELTEC_WIRELESS_TRACKER_V2 - select SOC_ESP32S3_FN8 + select SOC_ESP32S3 select SOC_ESP32S3_PROCPU if BOARD_HELTEC_WIRELESS_TRACKER_V2_ESP32S3_PROCPU diff --git a/zephcore/boards/esp32/heltec_wireless_tracker_v2/heltec_wireless_tracker_v2_procpu.dts b/zephcore/boards/esp32/heltec_wireless_tracker_v2/heltec_wireless_tracker_v2_procpu.dts index 3a02f53..ae89fa8 100644 --- a/zephcore/boards/esp32/heltec_wireless_tracker_v2/heltec_wireless_tracker_v2_procpu.dts +++ b/zephcore/boards/esp32/heltec_wireless_tracker_v2/heltec_wireless_tracker_v2_procpu.dts @@ -24,7 +24,7 @@ /dts-v1/; -#include +#include #include #include #include @@ -310,3 +310,10 @@ &wifi { status = "okay"; }; &usb_serial { status = "okay"; }; &st7735r_160x80 { status = "okay"; }; + +/* Module flash/PSRAM, declared by the board since Zephyr 4.5 removed the + * per-module dtsi (esp32s3_fn8.dtsi). */ +&flash0 { + reg = <0x0 DT_SIZE_M(8)>; + ranges = <0x0 0x0 DT_SIZE_M(8)>; +}; diff --git a/zephcore/boards/esp32/heltec_wireless_tracker_v2/partitions.overlay b/zephcore/boards/esp32/heltec_wireless_tracker_v2/partitions.overlay index 7af2f00..cbc575f 100644 --- a/zephcore/boards/esp32/heltec_wireless_tracker_v2/partitions.overlay +++ b/zephcore/boards/esp32/heltec_wireless_tracker_v2/partitions.overlay @@ -17,7 +17,6 @@ /delete-node/ &slot0_lpcore_partition; /delete-node/ &slot1_lpcore_partition; /delete-node/ &storage_partition; -/delete-node/ &scratch_partition; /delete-node/ &coredump_partition; /delete-node/ &slot0_partition; /delete-node/ &slot1_partition; @@ -32,20 +31,24 @@ partitions { /* App slots — equal size (swap-mode compatible), slot0 at 0x10000. */ slot0_partition: partition@10000 { + compatible = "zephyr,mapped-partition"; label = "image-0"; reg = <0x10000 0x3C6000>; }; slot1_partition: partition@3D6000 { + compatible = "zephyr,mapped-partition"; label = "image-1"; reg = <0x3D6000 0x3C6000>; }; /* BLE bond NVS — offset unchanged so bonds survive the reflash. */ storage_partition: partition@79C000 { + compatible = "zephyr,mapped-partition"; label = "storage"; reg = <0x79C000 0x4000>; }; /* LittleFS (identity/prefs/contacts) — offset unchanged. */ lfs_partition: partition@7A0000 { + compatible = "zephyr,mapped-partition"; label = "lfs"; reg = <0x7A0000 0x60000>; }; diff --git a/zephcore/boards/esp32/lilygo_t3s3/Kconfig.lilygo_t3s3 b/zephcore/boards/esp32/lilygo_t3s3/Kconfig.lilygo_t3s3 index 3e016b1..d550a7c 100644 --- a/zephcore/boards/esp32/lilygo_t3s3/Kconfig.lilygo_t3s3 +++ b/zephcore/boards/esp32/lilygo_t3s3/Kconfig.lilygo_t3s3 @@ -3,5 +3,5 @@ # SPDX-License-Identifier: MIT config BOARD_LILYGO_T3S3 - select SOC_ESP32S3_WROOM_N4R2 + select SOC_ESP32S3 select SOC_ESP32S3_PROCPU if BOARD_LILYGO_T3S3_ESP32S3_PROCPU diff --git a/zephcore/boards/esp32/lilygo_t3s3/lilygo_t3s3_procpu.dts b/zephcore/boards/esp32/lilygo_t3s3/lilygo_t3s3_procpu.dts index 7f32e72..ed2f2fe 100644 --- a/zephcore/boards/esp32/lilygo_t3s3/lilygo_t3s3_procpu.dts +++ b/zephcore/boards/esp32/lilygo_t3s3/lilygo_t3s3_procpu.dts @@ -18,7 +18,7 @@ /dts-v1/; -#include +#include #include "lilygo_t3s3-pinctrl.dtsi" /* * Base flash map: the *default* 4M table, not the _amp_ variant. @@ -88,3 +88,14 @@ &wifi { status = "okay"; }; + +/* Module flash/PSRAM, declared by the board since Zephyr 4.5 removed the + * per-module dtsi (esp32s3_wroom_n4r2.dtsi). */ +&flash0 { + reg = <0x0 DT_SIZE_M(4)>; + ranges = <0x0 0x0 DT_SIZE_M(4)>; +}; + +&psram0 { + size = ; +}; diff --git a/zephcore/boards/esp32/lilygo_t3s3/partitions.overlay b/zephcore/boards/esp32/lilygo_t3s3/partitions.overlay index daa2bf7..0a19076 100644 --- a/zephcore/boards/esp32/lilygo_t3s3/partitions.overlay +++ b/zephcore/boards/esp32/lilygo_t3s3/partitions.overlay @@ -22,7 +22,6 @@ /delete-node/ &slot0_lpcore_partition; /delete-node/ &slot1_lpcore_partition; /delete-node/ &storage_partition; -/delete-node/ &scratch_partition; /delete-node/ &coredump_partition; /delete-node/ &slot0_partition; /delete-node/ &slot1_partition; @@ -38,20 +37,24 @@ /* App slots — slot0 at 0x10000. sysbuild.conf selects MCUboot * OVERWRITE_ONLY, but keeping them equal size leaves swap available. */ slot0_partition: partition@10000 { + compatible = "zephyr,mapped-partition"; label = "image-0"; reg = <0x10000 0x1C6000>; }; slot1_partition: partition@1D6000 { + compatible = "zephyr,mapped-partition"; label = "image-1"; reg = <0x1D6000 0x1C6000>; }; /* BLE bond NVS — above slot1, so bonds survive a -merged reflash. */ storage_partition: partition@39C000 { + compatible = "zephyr,mapped-partition"; label = "storage"; reg = <0x39C000 0x4000>; }; /* LittleFS (identity/prefs/contacts) — above slot1, same reason. */ lfs_partition: partition@3A0000 { + compatible = "zephyr,mapped-partition"; label = "lfs"; reg = <0x3A0000 0x60000>; }; diff --git a/zephcore/boards/esp32/lilygo_tlora_c6/Kconfig.lilygo_tlora_c6 b/zephcore/boards/esp32/lilygo_tlora_c6/Kconfig.lilygo_tlora_c6 index 3f24138..e8089a5 100644 --- a/zephcore/boards/esp32/lilygo_tlora_c6/Kconfig.lilygo_tlora_c6 +++ b/zephcore/boards/esp32/lilygo_tlora_c6/Kconfig.lilygo_tlora_c6 @@ -3,5 +3,5 @@ # SPDX-License-Identifier: MIT config BOARD_LILYGO_TLORA_C6 - select SOC_ESP32_C6_WROOM_1U_N4 + select SOC_ESP32C6 select SOC_ESP32C6_HPCORE if BOARD_LILYGO_TLORA_C6_ESP32C6_HPCORE diff --git a/zephcore/boards/esp32/lilygo_tlora_c6/lilygo_tlora_c6_hpcore.dts b/zephcore/boards/esp32/lilygo_tlora_c6/lilygo_tlora_c6_hpcore.dts index 6835cad..6374a7f 100644 --- a/zephcore/boards/esp32/lilygo_tlora_c6/lilygo_tlora_c6_hpcore.dts +++ b/zephcore/boards/esp32/lilygo_tlora_c6/lilygo_tlora_c6_hpcore.dts @@ -12,7 +12,7 @@ /dts-v1/; -#include +#include #include "lilygo_tlora_c6-pinctrl.dtsi" #include #include @@ -77,3 +77,10 @@ &esp32_bt_hci { status = "okay"; }; + +/* Module flash/PSRAM, declared by the board since Zephyr 4.5 removed the + * per-module dtsi (esp32c6_wroom_n4.dtsi). */ +&flash0 { + reg = <0x0 DT_SIZE_M(4)>; + ranges = <0x0 0x0 DT_SIZE_M(4)>; +}; diff --git a/zephcore/boards/esp32/lilygo_tlora_c6/partitions.overlay b/zephcore/boards/esp32/lilygo_tlora_c6/partitions.overlay index b3f2b7c..af21a71 100644 --- a/zephcore/boards/esp32/lilygo_tlora_c6/partitions.overlay +++ b/zephcore/boards/esp32/lilygo_tlora_c6/partitions.overlay @@ -17,7 +17,6 @@ /delete-node/ &slot0_partition; /delete-node/ &slot1_partition; /delete-node/ &storage_partition; -/delete-node/ &scratch_partition; / { chosen { @@ -30,20 +29,24 @@ /* App slots — equal size (swap-mode compatible), slot0 at 0x10000, * slots fill up to the kept LP-core slots at 0x3A0000. */ slot0_partition: partition@10000 { + compatible = "zephyr,mapped-partition"; label = "image-0"; reg = <0x10000 0x1C8000>; }; slot1_partition: partition@1d8000 { + compatible = "zephyr,mapped-partition"; label = "image-1"; reg = <0x1D8000 0x1C8000>; }; /* LittleFS 192KB (identity/prefs/contacts) — offset unchanged. */ lfs_partition: partition@3b0000 { + compatible = "zephyr,mapped-partition"; label = "lfs"; reg = <0x3B0000 0x30000>; }; /* BLE bond NVS — offset unchanged so bonds survive the reflash. */ storage_partition: partition@3e0000 { + compatible = "zephyr,mapped-partition"; label = "storage"; reg = <0x3E0000 0x4000>; }; diff --git a/zephcore/boards/esp32/meshnology_w12/Kconfig.meshnology_w12 b/zephcore/boards/esp32/meshnology_w12/Kconfig.meshnology_w12 index a817907..b6a9e13 100644 --- a/zephcore/boards/esp32/meshnology_w12/Kconfig.meshnology_w12 +++ b/zephcore/boards/esp32/meshnology_w12/Kconfig.meshnology_w12 @@ -1,3 +1,3 @@ config BOARD_MESHNOLOGY_W12 - select SOC_ESP32S3_WROOM_N16R8 + select SOC_ESP32S3 select SOC_ESP32S3_PROCPU if BOARD_MESHNOLOGY_W12_ESP32S3_PROCPU diff --git a/zephcore/boards/esp32/meshnology_w12/meshnology_w12_procpu.dts b/zephcore/boards/esp32/meshnology_w12/meshnology_w12_procpu.dts index add0366..7e25fdd 100644 --- a/zephcore/boards/esp32/meshnology_w12/meshnology_w12_procpu.dts +++ b/zephcore/boards/esp32/meshnology_w12/meshnology_w12_procpu.dts @@ -27,7 +27,7 @@ /dts-v1/; -#include +#include #include "meshnology_w12-pinctrl.dtsi" #include #include @@ -339,3 +339,14 @@ &esp32_bt_hci { status = "okay"; }; &wifi { status = "okay"; }; + +/* Module flash/PSRAM, declared by the board since Zephyr 4.5 removed the + * per-module dtsi (esp32s3_wroom_n16r8.dtsi). */ +&flash0 { + reg = <0x0 DT_SIZE_M(16)>; + ranges = <0x0 0x0 DT_SIZE_M(16)>; +}; + +&psram0 { + size = ; +}; diff --git a/zephcore/boards/esp32/meshnology_w12/partitions.overlay b/zephcore/boards/esp32/meshnology_w12/partitions.overlay index 5a300ab..ba6a2e3 100644 --- a/zephcore/boards/esp32/meshnology_w12/partitions.overlay +++ b/zephcore/boards/esp32/meshnology_w12/partitions.overlay @@ -17,7 +17,6 @@ /delete-node/ &slot0_lpcore_partition; /delete-node/ &slot1_lpcore_partition; /delete-node/ &storage_partition; -/delete-node/ &scratch_partition; /delete-node/ &coredump_partition; /delete-node/ &slot0_partition; /delete-node/ &slot1_partition; @@ -32,20 +31,24 @@ partitions { /* App slots — equal size (swap-mode compatible), slot0 at 0x10000. */ slot0_partition: partition@10000 { + compatible = "zephyr,mapped-partition"; label = "image-0"; reg = <0x10000 0x7C6000>; }; slot1_partition: partition@7D6000 { + compatible = "zephyr,mapped-partition"; label = "image-1"; reg = <0x7D6000 0x7C6000>; }; /* BLE bond NVS — offset unchanged so bonds survive the reflash. */ storage_partition: partition@F9C000 { + compatible = "zephyr,mapped-partition"; label = "storage"; reg = <0xF9C000 0x4000>; }; /* LittleFS (identity/prefs/contacts) — offset unchanged. */ lfs_partition: partition@FA0000 { + compatible = "zephyr,mapped-partition"; label = "lfs"; reg = <0xFA0000 0x60000>; }; diff --git a/zephcore/boards/esp32/station_g2/Kconfig.station_g2 b/zephcore/boards/esp32/station_g2/Kconfig.station_g2 index 257b71b..9f1596c 100644 --- a/zephcore/boards/esp32/station_g2/Kconfig.station_g2 +++ b/zephcore/boards/esp32/station_g2/Kconfig.station_g2 @@ -1,3 +1,3 @@ config BOARD_STATION_G2 - select SOC_ESP32S3_WROOM_N16R8 + select SOC_ESP32S3 select SOC_ESP32S3_PROCPU if BOARD_STATION_G2_ESP32S3_PROCPU diff --git a/zephcore/boards/esp32/station_g2/partitions.overlay b/zephcore/boards/esp32/station_g2/partitions.overlay index 5a300ab..ba6a2e3 100644 --- a/zephcore/boards/esp32/station_g2/partitions.overlay +++ b/zephcore/boards/esp32/station_g2/partitions.overlay @@ -17,7 +17,6 @@ /delete-node/ &slot0_lpcore_partition; /delete-node/ &slot1_lpcore_partition; /delete-node/ &storage_partition; -/delete-node/ &scratch_partition; /delete-node/ &coredump_partition; /delete-node/ &slot0_partition; /delete-node/ &slot1_partition; @@ -32,20 +31,24 @@ partitions { /* App slots — equal size (swap-mode compatible), slot0 at 0x10000. */ slot0_partition: partition@10000 { + compatible = "zephyr,mapped-partition"; label = "image-0"; reg = <0x10000 0x7C6000>; }; slot1_partition: partition@7D6000 { + compatible = "zephyr,mapped-partition"; label = "image-1"; reg = <0x7D6000 0x7C6000>; }; /* BLE bond NVS — offset unchanged so bonds survive the reflash. */ storage_partition: partition@F9C000 { + compatible = "zephyr,mapped-partition"; label = "storage"; reg = <0xF9C000 0x4000>; }; /* LittleFS (identity/prefs/contacts) — offset unchanged. */ lfs_partition: partition@FA0000 { + compatible = "zephyr,mapped-partition"; label = "lfs"; reg = <0xFA0000 0x60000>; }; diff --git a/zephcore/boards/esp32/station_g2/station_g2_procpu.dts b/zephcore/boards/esp32/station_g2/station_g2_procpu.dts index 095a209..6c35378 100644 --- a/zephcore/boards/esp32/station_g2/station_g2_procpu.dts +++ b/zephcore/boards/esp32/station_g2/station_g2_procpu.dts @@ -15,7 +15,7 @@ /dts-v1/; -#include +#include #include "station_g2-pinctrl.dtsi" #include #include @@ -79,3 +79,14 @@ &wifi { status = "okay"; }; + +/* Module flash/PSRAM, declared by the board since Zephyr 4.5 removed the + * per-module dtsi (esp32s3_wroom_n16r8.dtsi). */ +&flash0 { + reg = <0x0 DT_SIZE_M(16)>; + ranges = <0x0 0x0 DT_SIZE_M(16)>; +}; + +&psram0 { + size = ; +}; diff --git a/zephcore/boards/esp32/thinknode_m9/Kconfig.thinknode_m9 b/zephcore/boards/esp32/thinknode_m9/Kconfig.thinknode_m9 index daf1777..5c64945 100644 --- a/zephcore/boards/esp32/thinknode_m9/Kconfig.thinknode_m9 +++ b/zephcore/boards/esp32/thinknode_m9/Kconfig.thinknode_m9 @@ -1,5 +1,5 @@ config BOARD_THINKNODE_M9 - select SOC_ESP32S3_WROOM_N16R8 + select SOC_ESP32S3 select SOC_ESP32S3_PROCPU if BOARD_THINKNODE_M9_ESP32S3_PROCPU # STC8H keypad on I2C1 @0x6C is this board's ONLY input — there is no # user button in hardware. Selects the full companion UI, whose key diff --git a/zephcore/boards/esp32/thinknode_m9/board.overlay b/zephcore/boards/esp32/thinknode_m9/board.overlay index 8b2769d..fd3dea2 100644 --- a/zephcore/boards/esp32/thinknode_m9/board.overlay +++ b/zephcore/boards/esp32/thinknode_m9/board.overlay @@ -21,6 +21,7 @@ * sensor bus. */ +#include #include / { @@ -107,7 +108,7 @@ * st7789v driver defaults to inversion ON (no `inversion-off` * prop here) — matched. */ mdac = <0xA0>; - colmod = <0x55>; + pixel-format = ; vcom = <0x20>; gctrl = <0x35>; vrhs = <0x0b>; diff --git a/zephcore/boards/esp32/thinknode_m9/partitions.overlay b/zephcore/boards/esp32/thinknode_m9/partitions.overlay index 5a300ab..ba6a2e3 100644 --- a/zephcore/boards/esp32/thinknode_m9/partitions.overlay +++ b/zephcore/boards/esp32/thinknode_m9/partitions.overlay @@ -17,7 +17,6 @@ /delete-node/ &slot0_lpcore_partition; /delete-node/ &slot1_lpcore_partition; /delete-node/ &storage_partition; -/delete-node/ &scratch_partition; /delete-node/ &coredump_partition; /delete-node/ &slot0_partition; /delete-node/ &slot1_partition; @@ -32,20 +31,24 @@ partitions { /* App slots — equal size (swap-mode compatible), slot0 at 0x10000. */ slot0_partition: partition@10000 { + compatible = "zephyr,mapped-partition"; label = "image-0"; reg = <0x10000 0x7C6000>; }; slot1_partition: partition@7D6000 { + compatible = "zephyr,mapped-partition"; label = "image-1"; reg = <0x7D6000 0x7C6000>; }; /* BLE bond NVS — offset unchanged so bonds survive the reflash. */ storage_partition: partition@F9C000 { + compatible = "zephyr,mapped-partition"; label = "storage"; reg = <0xF9C000 0x4000>; }; /* LittleFS (identity/prefs/contacts) — offset unchanged. */ lfs_partition: partition@FA0000 { + compatible = "zephyr,mapped-partition"; label = "lfs"; reg = <0xFA0000 0x60000>; }; diff --git a/zephcore/boards/esp32/thinknode_m9/thinknode_m9_esp32s3_procpu.dts b/zephcore/boards/esp32/thinknode_m9/thinknode_m9_esp32s3_procpu.dts index 3d843d5..a3a6ac4 100644 --- a/zephcore/boards/esp32/thinknode_m9/thinknode_m9_esp32s3_procpu.dts +++ b/zephcore/boards/esp32/thinknode_m9/thinknode_m9_esp32s3_procpu.dts @@ -22,9 +22,14 @@ /dts-v1/; -#include +#include #include "thinknode_m9-pinctrl.dtsi" #include +/* In the base DTS (not only board.overlay) so the sysbuild MCUboot image, which + * sees this file but not board.overlay, also gets a zephyr,console: MCUboot is + * built with UART_CONSOLE=y (sysbuild/mcuboot.conf) and Zephyr 4.5's + * uart_console.c no longer builds without the chosen node. */ +#include "../../common/esp32s3_console_uart0.dtsi" / { model = "Elecrow ThinkNode M9"; @@ -173,3 +178,14 @@ &wifi { status = "okay"; }; + +/* Module flash/PSRAM, declared by the board since Zephyr 4.5 removed the + * per-module dtsi (esp32s3_wroom_n16r8.dtsi). */ +&flash0 { + reg = <0x0 DT_SIZE_M(16)>; + ranges = <0x0 0x0 DT_SIZE_M(16)>; +}; + +&psram0 { + size = ; +}; diff --git a/zephcore/boards/esp32/ttgo_lora32/board.conf b/zephcore/boards/esp32/ttgo_lora32/board.conf index 6847b01..d302578 100644 --- a/zephcore/boards/esp32/ttgo_lora32/board.conf +++ b/zephcore/boards/esp32/ttgo_lora32/board.conf @@ -28,7 +28,7 @@ CONFIG_ZEPHCORE_RADIO_SX127X=y CONFIG_ZEPHCORE_DEFAULT_TX_POWER_DBM=17 # RX duty cycle is not supported by the loramac-node SX127x driver -# (lora_recv_duty_cycle returns -ENOSYS and gracefully falls back to +# (lora_recv_duty_cycle_async returns -ENOSYS and gracefully falls back to # lora_recv_async, but disable it to avoid the spurious attempt) CONFIG_ZEPHCORE_LORA_RX_DUTY_CYCLE=n diff --git a/zephcore/boards/esp32/ttgo_lora32/board.overlay b/zephcore/boards/esp32/ttgo_lora32/board.overlay index 7ea4be3..e0bf98b 100644 --- a/zephcore/boards/esp32/ttgo_lora32/board.overlay +++ b/zephcore/boards/esp32/ttgo_lora32/board.overlay @@ -30,7 +30,6 @@ * but we leave them to avoid changing the partition map. */ /delete-node/ &storage_partition; -/delete-node/ &scratch_partition; / { chosen { @@ -42,12 +41,14 @@ partitions { /* LittleFS 192KB for DataStore (identity, prefs, creds) */ lfs_partition: partition@3b0000 { + compatible = "zephyr,mapped-partition"; label = "lfs"; reg = <0x3B0000 0x30000>; }; /* BLE bond store — 16KB NVS, isolated from /lfs (see esp32_common.conf). * Placed in the unused scratch region just above lfs (lfs unchanged). */ storage_partition: partition@3e0000 { + compatible = "zephyr,mapped-partition"; label = "storage"; reg = <0x3E0000 0x4000>; }; diff --git a/zephcore/boards/esp32/ttgo_tbeam/board.overlay b/zephcore/boards/esp32/ttgo_tbeam/board.overlay index 679b59c..73cf529 100644 --- a/zephcore/boards/esp32/ttgo_tbeam/board.overlay +++ b/zephcore/boards/esp32/ttgo_tbeam/board.overlay @@ -152,7 +152,6 @@ * LittleFS, same as ttgo_lora32. The 4MB AMP layout keeps its MCUboot slots. */ /delete-node/ &storage_partition; -/delete-node/ &scratch_partition; / { chosen { @@ -164,12 +163,14 @@ partitions { /* LittleFS 192KB for DataStore (identity, prefs, creds) */ lfs_partition: partition@3b0000 { + compatible = "zephyr,mapped-partition"; label = "lfs"; reg = <0x3B0000 0x30000>; }; /* BLE bond store — 16KB NVS, isolated from /lfs (see esp32_common.conf). * Placed in the unused scratch region just above lfs (lfs unchanged). */ storage_partition: partition@3e0000 { + compatible = "zephyr,mapped-partition"; label = "storage"; reg = <0x3E0000 0x4000>; }; diff --git a/zephcore/boards/esp32/xiao_esp32c3/partitions.overlay b/zephcore/boards/esp32/xiao_esp32c3/partitions.overlay index b3f2b7c..af21a71 100644 --- a/zephcore/boards/esp32/xiao_esp32c3/partitions.overlay +++ b/zephcore/boards/esp32/xiao_esp32c3/partitions.overlay @@ -17,7 +17,6 @@ /delete-node/ &slot0_partition; /delete-node/ &slot1_partition; /delete-node/ &storage_partition; -/delete-node/ &scratch_partition; / { chosen { @@ -30,20 +29,24 @@ /* App slots — equal size (swap-mode compatible), slot0 at 0x10000, * slots fill up to the kept LP-core slots at 0x3A0000. */ slot0_partition: partition@10000 { + compatible = "zephyr,mapped-partition"; label = "image-0"; reg = <0x10000 0x1C8000>; }; slot1_partition: partition@1d8000 { + compatible = "zephyr,mapped-partition"; label = "image-1"; reg = <0x1D8000 0x1C8000>; }; /* LittleFS 192KB (identity/prefs/contacts) — offset unchanged. */ lfs_partition: partition@3b0000 { + compatible = "zephyr,mapped-partition"; label = "lfs"; reg = <0x3B0000 0x30000>; }; /* BLE bond NVS — offset unchanged so bonds survive the reflash. */ storage_partition: partition@3e0000 { + compatible = "zephyr,mapped-partition"; label = "storage"; reg = <0x3E0000 0x4000>; }; diff --git a/zephcore/boards/esp32/xiao_esp32c6/partitions.overlay b/zephcore/boards/esp32/xiao_esp32c6/partitions.overlay index b3f2b7c..af21a71 100644 --- a/zephcore/boards/esp32/xiao_esp32c6/partitions.overlay +++ b/zephcore/boards/esp32/xiao_esp32c6/partitions.overlay @@ -17,7 +17,6 @@ /delete-node/ &slot0_partition; /delete-node/ &slot1_partition; /delete-node/ &storage_partition; -/delete-node/ &scratch_partition; / { chosen { @@ -30,20 +29,24 @@ /* App slots — equal size (swap-mode compatible), slot0 at 0x10000, * slots fill up to the kept LP-core slots at 0x3A0000. */ slot0_partition: partition@10000 { + compatible = "zephyr,mapped-partition"; label = "image-0"; reg = <0x10000 0x1C8000>; }; slot1_partition: partition@1d8000 { + compatible = "zephyr,mapped-partition"; label = "image-1"; reg = <0x1D8000 0x1C8000>; }; /* LittleFS 192KB (identity/prefs/contacts) — offset unchanged. */ lfs_partition: partition@3b0000 { + compatible = "zephyr,mapped-partition"; label = "lfs"; reg = <0x3B0000 0x30000>; }; /* BLE bond NVS — offset unchanged so bonds survive the reflash. */ storage_partition: partition@3e0000 { + compatible = "zephyr,mapped-partition"; label = "storage"; reg = <0x3E0000 0x4000>; }; diff --git a/zephcore/boards/esp32/xiao_esp32s3/partitions.overlay b/zephcore/boards/esp32/xiao_esp32s3/partitions.overlay index 0221010..c18b125 100644 --- a/zephcore/boards/esp32/xiao_esp32s3/partitions.overlay +++ b/zephcore/boards/esp32/xiao_esp32s3/partitions.overlay @@ -17,7 +17,6 @@ /delete-node/ &slot0_lpcore_partition; /delete-node/ &slot1_lpcore_partition; /delete-node/ &storage_partition; -/delete-node/ &scratch_partition; /delete-node/ &coredump_partition; /delete-node/ &slot0_partition; /delete-node/ &slot1_partition; @@ -32,20 +31,24 @@ partitions { /* App slots — equal size (swap-mode compatible), slot0 at 0x10000. */ slot0_partition: partition@10000 { + compatible = "zephyr,mapped-partition"; label = "image-0"; reg = <0x10000 0x1C6000>; }; slot1_partition: partition@1D6000 { + compatible = "zephyr,mapped-partition"; label = "image-1"; reg = <0x1D6000 0x1C6000>; }; /* BLE bond NVS — offset unchanged so bonds survive the reflash. */ storage_partition: partition@39C000 { + compatible = "zephyr,mapped-partition"; label = "storage"; reg = <0x39C000 0x4000>; }; /* LittleFS (identity/prefs/contacts) — offset unchanged. */ lfs_partition: partition@3A0000 { + compatible = "zephyr,mapped-partition"; label = "lfs"; reg = <0x3A0000 0x60000>; }; diff --git a/zephcore/boards/example_board/README.md b/zephcore/boards/example_board/README.md index eafd8b9..0c68a21 100644 --- a/zephcore/boards/example_board/README.md +++ b/zephcore/boards/example_board/README.md @@ -122,7 +122,7 @@ SX127x boards require these `board.conf` overrides (the `zephcore_common.conf` d CONFIG_LORA_MODULE_BACKEND_NATIVE=n CONFIG_LORA_MODULE_BACKEND_LORAMAC_NODE=y CONFIG_ZEPHCORE_RADIO_SX127X=y -CONFIG_ZEPHCORE_LORA_RX_DUTY_CYCLE=n # lora_recv_duty_cycle not implemented for SX127x +CONFIG_ZEPHCORE_LORA_RX_DUTY_CYCLE=n # lora_recv_duty_cycle_async not implemented for SX127x CONFIG_ZEPHCORE_DEFAULT_TX_POWER_DBM=17 # PA_BOOST max without external PA ``` diff --git a/zephcore/boards/mg24/xiao_mg24/board.overlay b/zephcore/boards/mg24/xiao_mg24/board.overlay index 9b3ec06..e9c7e1d 100644 --- a/zephcore/boards/mg24/xiao_mg24/board.overlay +++ b/zephcore/boards/mg24/xiao_mg24/board.overlay @@ -81,13 +81,18 @@ &flash0 { partitions { /* Application — expanded to use slot1 space */ + /* zephyr,mapped-partition: the upstream partitions node no longer + * has a fixed-partitions compatible, so each child carries its own + * (Zephyr 4.5), or no PARTITION_ID is generated for it. */ slot0_partition: partition@c000 { + compatible = "zephyr,mapped-partition"; label = "image-0"; reg = <0x0C000 0x150000>; /* 1344KB */ }; /* LittleFS 144KB for DataStore + file-based BLE settings */ lfs_partition: partition@15c000 { + compatible = "zephyr,mapped-partition"; label = "lfs"; reg = <0x15C000 0x24000>; }; diff --git a/zephcore/boards/nrf52840/heltec_t114/heltec_t114_nrf52840.dts b/zephcore/boards/nrf52840/heltec_t114/heltec_t114_nrf52840.dts index 7a2c4b9..878078a 100644 --- a/zephcore/boards/nrf52840/heltec_t114/heltec_t114_nrf52840.dts +++ b/zephcore/boards/nrf52840/heltec_t114/heltec_t114_nrf52840.dts @@ -7,6 +7,7 @@ */ /dts-v1/; +#include #include #include #include @@ -157,7 +158,7 @@ y-offset = <53>; /* MADCTL=0x60: MX|MV, RGB order (landscape, derived from MeshCore landscapeScreen()) */ mdac = <0x60>; - colmod = <0x55>; + pixel-format = ; vcom = <0x20>; gctrl = <0x35>; vrhs = <0x0b>; diff --git a/zephcore/boards/nrf52840/promicro_sx1262/board.conf b/zephcore/boards/nrf52840/promicro_sx1262/board.conf index 5af5c5a..061cfb4 100644 --- a/zephcore/boards/nrf52840/promicro_sx1262/board.conf +++ b/zephcore/boards/nrf52840/promicro_sx1262/board.conf @@ -35,17 +35,17 @@ CONFIG_ZEPHCORE_DEFAULT_TX_POWER_DBM=10 # 50 ppm accurate: the central's receive windows stop landing on our anchor # points and the link drops every few seconds, and every k_timer — including the # LoRa RX duty-cycle windows and the post-TX RX restore — skews with it. -CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC=y +# +# The source and the accuracy claim live in devicetree (k32src / +# k32src-accuracy-ppm on &lfclk in promicro_sx1262.dts), NOT here. Zephyr main's +# split nRF clock drivers (4.5) read them from the lfclk node; the old +# CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC / _250PPM choices only exist for the legacy +# CONFIG_CLOCK_CONTROL_NRF driver and are silently dropped (a Kconfig warning, +# no build error) — which put this board back on the floating LFXO described +# above until 2026-09-19. Calibration stays a Kconfig option and depends on the +# DT source being "rc". CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC_CALIBRATION=y -# Accuracy claimed to the BLE peer. Zephyr would pick 500 ppm here (its default -# for RC on nRF52); the Adafruit core that stock MeshCore builds on declares -# NRF_CLOCK_LF_ACCURACY_250_PPM for the same calibrated RC on the same silicon -# (libraries/Bluefruit52Lib/src/bluefruit.cpp, alongside rc_ctiv=16 / -# rc_temp_ctiv=2 — what RC_CALIBRATION above provides). Matched to stock so the -# central sizes its receive windows the same way on both firmwares. -CONFIG_CLOCK_CONTROL_NRF_K32SRC_250PPM=y - # Compile in the UI display stack; the OLED panel is optional at runtime. # SSD1306 on i2c0 (SDA P1.04 / SCL P0.11) — the same bus and pins official # MeshCore drives via PIN_BOARD_SDA=8 / PIN_BOARD_SCL=7, and it builds every diff --git a/zephcore/boards/nrf52840/promicro_sx1262/promicro_sx1262.dts b/zephcore/boards/nrf52840/promicro_sx1262/promicro_sx1262.dts index eeb57f3..b86d424 100644 --- a/zephcore/boards/nrf52840/promicro_sx1262/promicro_sx1262.dts +++ b/zephcore/boards/nrf52840/promicro_sx1262/promicro_sx1262.dts @@ -12,7 +12,8 @@ * - Battery ADC on AIN7 (P0.31), 150K+150K divider (2:1) * - User button on P1.00, LED on P0.15 * - Optional SSD1306 128x64 OLED on I2C0 @ 0x3c (SDA P1.04, SCL P0.11) - * - No 32.768 kHz crystal fitted — see the K32SRC_RC note in board.conf + * - No 32.768 kHz crystal fitted — LFCLK runs the calibrated RC (&lfclk below; + * rationale in board.conf) * - 3V3 enable on P0.13 (must be HIGH for SX1262 to function) */ @@ -249,3 +250,16 @@ zephyr_udc0: &usbd { /* ---- Flash partitions (SoftDevice v6, app@0x26000) ---- */ #include "../../common/nrf52_partitions_sdv6.dtsi" #include "../../common/nrf52_wakeup.dtsi" + +/* + * 32.768 kHz source: calibrated RC — no LFXO is fitted (see board.conf for the + * failure mode without this). 250 ppm is the accuracy the Adafruit core that + * stock MeshCore builds on declares for the same calibrated RC on the same + * silicon (NRF_CLOCK_LF_ACCURACY_250_PPM in Bluefruit52Lib), so the BLE central + * sizes its receive windows identically on both firmwares; Zephyr's own RC + * default on nRF52 would be 500. + */ +&lfclk { + k32src = "rc"; + k32src-accuracy-ppm = <250>; +}; diff --git a/zephcore/boards/nrf52840/wio_tracker_l1/Kconfig.wio_tracker_l1 b/zephcore/boards/nrf52840/wio_tracker_l1/Kconfig.wio_tracker_l1 deleted file mode 100644 index 8673151..0000000 --- a/zephcore/boards/nrf52840/wio_tracker_l1/Kconfig.wio_tracker_l1 +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) 2025 MeshCore -# -# SPDX-License-Identifier: MIT - -config BOARD_WIO_TRACKER_L1 - select SOC_NRF52840_QIAA - select ZEPHCORE_UI_JOYSTICK diff --git a/zephcore/boards/nrf52840/wio_tracker_l1/board.cmake b/zephcore/boards/nrf52840/wio_tracker_l1/board.cmake deleted file mode 100644 index a22c84a..0000000 --- a/zephcore/boards/nrf52840/wio_tracker_l1/board.cmake +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2025 ZephCore -# -# SPDX-License-Identifier: MIT - -board_runner_args(jlink "--device=nRF52840_xxAA" "--speed=4000") -board_runner_args(pyocd "--target=nrf52840" "--frequency=4000000") - -set(OPENOCD_NRF5_SUBFAMILY "nrf52") - -include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake) -include(${ZEPHYR_BASE}/boards/common/pyocd.board.cmake) -include(${ZEPHYR_BASE}/boards/common/openocd-nrf5.board.cmake) -include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake) -include(${ZEPHYR_BASE}/boards/common/nrfutil.board.cmake) diff --git a/zephcore/boards/nrf52840/wio_tracker_l1/board.overlay b/zephcore/boards/nrf52840/wio_tracker_l1/board.overlay index 3134fd8..8e4cded 100644 --- a/zephcore/boards/nrf52840/wio_tracker_l1/board.overlay +++ b/zephcore/boards/nrf52840/wio_tracker_l1/board.overlay @@ -1,11 +1,149 @@ /* - * Wio Tracker L1 overlay for ZephCore. - * SoftDevice + UF2 bootloader layout. - * Console: USB CDC ACM. - * Battery: AIN7 = P0.31, voltage divider 2:1. + * Wio Tracker L1 overlay for ZephCore, on top of upstream Zephyr's + * boards/seeed/wio_tracker_l1 (in-tree since Zephyr 4.5; we carried a full + * custom board of the same name before that, and the two cannot coexist). + * + * Everything below reproduces the devicetree of that custom board, so an + * update does not move anything a deployed node depends on. The ones that + * are not cosmetic: + * + * - Flash layout: our SoftDevice v7 map (nrf52_partitions_sdv7.dtsi). + * Upstream's uf2_sdv7 map puts storage at 0xEC000; our /lfs volume + * (identity, prefs, contacts) lives at 0xD4000 and must not move. + * - LoRa on SPIM2, not SPIM3: see the &spi2 note. + * - Grove I2C (&i2c1): SDA=P1.12, SCL=P1.11. Upstream has them swapped. + * - QSPI P25Q16H in single-bit mode at 16 MHz with a 50 ms DPD exit, + * matching Arduino. Upstream runs it quad at 104 MHz. + * - OLED bus needs zephyr,concat-buf-size or the SH1106 ACKs init and never + * renders (nordic,nrf-twim). + * - GPS on the luatos,air530z driver (PCAS/CASIC command set) with P1.09 + * as its standby pin, not gnss-nmea-generic. + * - Battery: our on-demand regulator on P0.04 + zephyr,user io-channels. + * Upstream's voltage-divider node also drives P0.04, so it must go. + * - UICR: nfct-pins-as-gpios, and no gpio-as-nreset (the UF2 bootloader + * owns PSELRESET). + * + * Console: USB CDC ACM (upstream's board_cdc_acm_uart). */ -/* GPS standby control — P1.09 = the L76K's WAKEUP pin (schematic net +#include + +/* ---- Nodes replaced wholesale ---------------------------------------- */ + +/delete-node/ &{/buttons}; +/delete-node/ &{/trackball}; +/delete-node/ &vbatt; +/delete-node/ &buzzer; +/delete-node/ &gnss; +/delete-node/ &lora; +/delete-node/ &p25q16h; + +&flash0 { + /delete-node/ partitions; +}; + +/ { + chosen { + zephyr,display = &sh1106; + }; + + buttons: buttons { + compatible = "gpio-keys"; + + user_button: button_0 { + gpios = <&gpio0 8 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + zephyr,code = ; + label = "User Button"; + }; + + joystick_up: button_1 { + gpios = <&gpio1 4 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + zephyr,code = ; + label = "Joystick Up"; + }; + + joystick_down: button_2 { + gpios = <&gpio0 12 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + zephyr,code = ; + label = "Joystick Down"; + }; + + joystick_left: button_3 { + gpios = <&gpio0 11 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + zephyr,code = ; + label = "Joystick Left"; + }; + + joystick_right: button_4 { + gpios = <&gpio1 3 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + zephyr,code = ; + label = "Joystick Right"; + }; + + joystick_press: button_5 { + gpios = <&gpio1 5 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + zephyr,code = ; + label = "Joystick Press"; + }; + }; + + /* Input filter: user button long-press detection (1000ms) */ + user_btn_longpress { + compatible = "zephyr,input-longpress"; + input = <&buttons>; + input-codes = ; + short-codes = ; + long-codes = ; + long-delay-ms = <1000>; + }; + + /* Input filter: user button multi-tap detection + * 1 tap (400ms wait) -> KEY_1 = page next on OLED + * 2 taps (400ms wait) -> KEY_B = LED heartbeat toggle + * 3 taps (400ms wait) -> KEY_D = buzzer mute toggle + * 4 taps (400ms wait) -> KEY_C = GPS on/off + * 5 taps (immediate) -> KEY_E = flood advert */ + user_btn_multitap { + compatible = "zephcore,input-multi-tap"; + /* No 'input' phandle -- listen to ALL devices. + * KEY_A is emitted by the longpress pseudo-device, + * not by &buttons, so we must not filter by device. */ + input-codes = ; + tap-codes = ; + tap-delay-ms = <400>; + }; + + /* Battery voltage enable and ADC - on-demand only to save power */ + vbat_enable: vbat-enable { + compatible = "regulator-fixed"; + regulator-name = "vbat-enable"; + enable-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>; + /* NOT regulator-boot-on - enabled on-demand in ZephyrBoard.cpp */ + }; + + /* Battery ADC channel reference for ZephyrBoard */ + zephyr,user { + io-channels = <&adc 7>; + vbat-mv-multiplier = <7236>; /* 2:1 divider, 3.6V ref; +0.5% for nRF SAADC gain error */ + }; + + /* Buzzer on PWM0 channel 0 (P1.00) - no enable pin needed */ + pwmbuzzer { + compatible = "pwm-leds"; + buzzer: buzzer { + pwms = <&pwm0 0 PWM_MSEC(20) PWM_POLARITY_NORMAL>; + }; + }; + + aliases { + /delete-property/ buzzer0; + /delete-property/ gnss; + sw0 = &user_button; + buzzer = &buzzer; + }; +}; + +/* GPS standby control -- P1.09 = the L76K's WAKEUP pin (schematic net * GNSS_WAKEUP). This is NOT a supply switch: per the L76K hardware design the * pin only enters/exits Standby, where the RF is off but the core and I/O * domain stay powered. VCC is never removed, so ephemeris/almanac/RTC are @@ -29,6 +167,191 @@ }; }; +/* ---- Pin control ------------------------------------------------------ */ + +&pinctrl { + /* SPI2 (SPIM2) for LoRa SX1262: SCK=P0.30, MOSI=P0.28, MISO=P0.03 */ + spi2_default: spi2_default { + group1 { + psels = , + , + ; + }; + }; + + spi2_sleep: spi2_sleep { + group1 { + psels = , + , + ; + low-power-enable; + }; + }; +}; + +/* Grove I2C: SDA=P1.12 (D18), SCL=P1.11 (D17), the physical Grove pinout + * (Arduino Wire1 / Meshtastic). Upstream has the two swapped. */ +&i2c1_default { + group1 { + psels = , + ; + }; +}; + +&i2c1_sleep { + group1 { + psels = , + ; + }; +}; + +/* GPS UART: pull-up on both lines, as before. */ +&uart0_default { + /delete-node/ group2; + + group1 { + psels = , + ; + bias-pull-up; + }; +}; + +/* QSPI sleep: CSN in the same group as the data lines, no pull-up. */ +&qspi_sleep { + /delete-node/ group2; + + group1 { + psels = , + , + , + , + , + ; + }; +}; + +/* ---- Peripherals ------------------------------------------------------ */ + +&uicr { + /delete-property/ gpio-as-nreset; + nfct-pins-as-gpios; +}; + +&adc { + /* Battery voltage on P0.31 (AIN7) */ + channel@7 { + /delete-property/ zephyr,vref-mv; + zephyr,acquisition-time = ; + }; +}; + +/* Quectel L76KB GNSS on UART0 - 9600 baud (PCAS/CASIC protocol) + * Uses Zephyr luatos,air530z driver (same PCAS command set for + * constellation config via PCAS04, fix rate via PCAS02, etc.) + * on-off-gpios = P1.09, the module's WAKEUP/standby pin (see the + * gps-enable note above) -- driver drives it high for Continuous, low for + * Standby. */ +&uart0 { + gnss: gnss { + compatible = "luatos,air530z"; + on-off-gpios = <&gpio1 9 GPIO_ACTIVE_HIGH>; + }; +}; + +/* I2C0 for onboard OLED display only. + * External environment/power sensors live on the Grove bus (&i2c1). */ +&i2c0 { + clock-frequency = ; + /* SH1106 sends 1 cmd + 128 data = 129 bytes per page write. + * nRF TWIM needs an internal buffer >= this size. */ + zephyr,concat-buf-size = <256>; + zephyr,flash-buf-max-size = <256>; +}; + +sh1106: &oled { +}; + +/* I2C1 for Grove connector -- external environment/power sensors. + * Auto-detected at runtime; absent sensors fail device_is_ready() and + * are skipped harmlessly by ZephyrEnvSensors. */ +&i2c1 { + clock-frequency = ; + + /* All supported environment & power sensors -- auto-detected at runtime */ + #include "../../common/sensors-i2c.dtsi" +}; + +/* SPIM3 is where upstream puts the radio; it is unused here. */ +&spi3 { + status = "disabled"; +}; + +/* SPI2 (SPIM2) for LoRa SX1262. + * Must NOT use SPIM0 (shares instance 0 with I2C0/OLED) or SPIM1 (shares + * instance 1 with TWIM1, which drives the Grove I2C bus &i2c1). SPIM2 is a + * dedicated SPI-only instance, so the radio and both I2C buses coexist. */ +&spi2 { + compatible = "nordic,nrf-spim"; + status = "okay"; + cs-gpios = <&gpio1 14 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&spi2_default>; + pinctrl-1 = <&spi2_sleep>; + pinctrl-names = "default", "sleep"; + + lora: lora@0 { + compatible = "semtech,sx1262"; + reg = <0>; + reset-gpios = <&gpio1 7 GPIO_ACTIVE_LOW>; + busy-gpios = <&gpio1 10 GPIO_ACTIVE_HIGH>; + dio1-gpios = <&gpio0 7 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>; + /* RXEN on P1.08, DIO2 as RF switch for TX */ + rx-enable-gpios = <&gpio1 8 GPIO_ACTIVE_HIGH>; + dio2-tx-enable; + dio3-tcxo-voltage = ; + tcxo-power-startup-delay-ms = <5>; + spi-max-frequency = <8000000>; + rx-boosted; + }; +}; + +/* QSPI Flash - P25Q16H 2MB - single-bit mode at 16MHz (matches Arduino) */ +&qspi { + qspi_flash: qspi_flash@0 { + compatible = "nordic,qspi-nor"; + reg = <0>; + writeoc = "pp"; /* Single-bit page program (universal) */ + readoc = "fastread"; /* Single-bit fast read (universal) */ + sck-frequency = <16000000>; /* 16 MHz (matches Arduino) */ + jedec-id = [85 60 15]; /* P25Q16H */ + sfdp-bfp = [ + e5 20 f1 ff ff ff ff 01 44 eb 08 6b 08 3b 04 bb + ee ff ff ff ff ff 00 ff ff ff 00 ff 0c 20 0f 52 + 10 d8 00 ff + ]; + size = <16777216>; /* 2MB = 16Mbit */ + has-dpd; + t-enter-dpd = <3000>; + t-exit-dpd = <50000>; /* 50ms for reliable wakeup */ + /* quad-enable not needed for single-bit mode */ + + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + /* Full 2MB for LittleFS storage */ + qspi_storage_partition: partition@0 { + label = "qspi_storage"; + reg = <0x00000000 0x00200000>; + }; + }; + }; +}; + +/* Arduino MeshCore compatible partition layout (SoftDevice v7) */ +#include "../../common/nrf52_partitions_sdv7.dtsi" +#include "../../common/nrf52_wakeup.dtsi" + /* Internal LFS mounted manually by ZephyrDataStore (block_size=128, custom erase) */ /* External QSPI /ext auto-mounted via DTS (standard 4KB blocks) */ #include "../../common/qspi-ext.dtsi" diff --git a/zephcore/boards/nrf52840/wio_tracker_l1/board.yml b/zephcore/boards/nrf52840/wio_tracker_l1/board.yml deleted file mode 100644 index 8f9dea3..0000000 --- a/zephcore/boards/nrf52840/wio_tracker_l1/board.yml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2025 MeshCore -# -# SPDX-License-Identifier: MIT - -board: - name: wio_tracker_l1 - full_name: Seeed Wio Tracker L1 - vendor: seeed - socs: - - name: nrf52840 diff --git a/zephcore/boards/nrf52840/wio_tracker_l1/wio_tracker_l1_nrf52840-pinctrl.dtsi b/zephcore/boards/nrf52840/wio_tracker_l1/wio_tracker_l1_nrf52840-pinctrl.dtsi deleted file mode 100644 index d97d742..0000000 --- a/zephcore/boards/nrf52840/wio_tracker_l1/wio_tracker_l1_nrf52840-pinctrl.dtsi +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Seeed Wio Tracker L1 pin control definitions - * Copyright (c) 2025 ZephCore - * - * SPDX-License-Identifier: MIT - * - * Pin mappings from Arduino variant.cpp: - * - SPI2 (LoRa): SCK=P0.30, MISO=P0.03, MOSI=P0.28, CS=P1.14 - * - I2C0 (OLED): SDA=P0.06, SCL=P0.05 - * - I2C1 (Grove): SDA=P1.12 (D18), SCL=P1.11 (D17) - * - UART (GPS): TX=P0.27, RX=P0.26 - * - QSPI: SCK=P0.21, CS=P0.25, IO0=P0.20, IO1=P0.24, IO2=P0.22, IO3=P0.23 - */ - -&pinctrl { - /* SPI2 (SPIM2) for LoRa SX1262 — dedicated SPI-only instance so it - * doesn't collide with I2C0 (instance 0) or the Grove I2C1 (instance 1). */ - spi2_default: spi2_default { - group1 { - psels = , - , - ; - }; - }; - - spi2_sleep: spi2_sleep { - group1 { - psels = , - , - ; - low-power-enable; - }; - }; - - /* I2C0 for OLED display */ - i2c0_default: i2c0_default { - group1 { - psels = , - ; - }; - }; - - i2c0_sleep: i2c0_sleep { - group1 { - psels = , - ; - low-power-enable; - }; - }; - - /* I2C1 for Grove connector — SDA=P1.12 (D18), SCL=P1.11 (D17) - * to match the physical Grove pinout (Arduino Wire1 / Meshtastic). */ - i2c1_default: i2c1_default { - group1 { - psels = , - ; - }; - }; - - i2c1_sleep: i2c1_sleep { - group1 { - psels = , - ; - low-power-enable; - }; - }; - - /* UART0 for GPS L76KB at 9600 baud - * P0.27 = GNSS_TX (GPS sends to MCU) - * P0.26 = GNSS_RX (MCU sends to GPS) - */ - uart0_default: uart0_default { - group1 { - psels = , - ; - bias-pull-up; - }; - }; - - uart0_sleep: uart0_sleep { - group1 { - psels = , - ; - low-power-enable; - }; - }; - - /* PWM0 for buzzer on P1.00 */ - pwm0_default: pwm0_default { - group1 { - psels = ; - }; - }; - - pwm0_sleep: pwm0_sleep { - group1 { - psels = ; - low-power-enable; - }; - }; - - /* QSPI for external flash */ - qspi_default: qspi_default { - group1 { - psels = , - , - , - , - , - ; - }; - }; - - qspi_sleep: qspi_sleep { - group1 { - psels = , - , - , - , - , - ; - low-power-enable; - }; - }; -}; diff --git a/zephcore/boards/nrf52840/wio_tracker_l1/wio_tracker_l1_nrf52840.dts b/zephcore/boards/nrf52840/wio_tracker_l1/wio_tracker_l1_nrf52840.dts deleted file mode 100644 index 227d848..0000000 --- a/zephcore/boards/nrf52840/wio_tracker_l1/wio_tracker_l1_nrf52840.dts +++ /dev/null @@ -1,354 +0,0 @@ -/* - * Seeed Wio Tracker L1 - nRF52840 + SX1262 - * Copyright (c) 2025 ZephCore - * - * SPDX-License-Identifier: MIT - * - * Hardware: - * - nRF52840 SoC with BLE - * - SX1262 LoRa radio (DIO2 as RF switch, TCXO 1.8V) - * - P25Q16H 2MB QSPI flash - * - SH1106 OLED display (I2C 0x3D) - * - L76KB GPS module - * - Joystick + user button - * - Battery voltage ADC - */ - -/dts-v1/; -#include -#include -#include -#include -#include -#include -#include "wio_tracker_l1_nrf52840-pinctrl.dtsi" - -/* Delete default partitions from nrf52840_partition.dtsi */ -/delete-node/ &boot_partition; -/delete-node/ &slot0_partition; -/delete-node/ &slot1_partition; -/delete-node/ &storage_partition; - -/ { - model = "Seeed Wio Tracker L1"; - compatible = "seeed,wio-tracker-l1"; - - chosen { - zephyr,code-partition = &code_partition; - zephyr,console = &cdc_acm_uart; - zephyr,shell-uart = &cdc_acm_uart; - zephyr,display = &sh1106; - }; - - leds { - compatible = "gpio-leds"; - - user_led: led_0 { - gpios = <&gpio1 1 GPIO_ACTIVE_HIGH>; - label = "User LED"; - }; - }; - - buttons: buttons { - compatible = "gpio-keys"; - - user_button: button_0 { - gpios = <&gpio0 8 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; - zephyr,code = ; - label = "User Button"; - }; - - joystick_up: button_1 { - gpios = <&gpio1 4 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; - zephyr,code = ; - label = "Joystick Up"; - }; - - joystick_down: button_2 { - gpios = <&gpio0 12 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; - zephyr,code = ; - label = "Joystick Down"; - }; - - joystick_left: button_3 { - gpios = <&gpio0 11 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; - zephyr,code = ; - label = "Joystick Left"; - }; - - joystick_right: button_4 { - gpios = <&gpio1 3 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; - zephyr,code = ; - label = "Joystick Right"; - }; - - joystick_press: button_5 { - gpios = <&gpio1 5 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; - zephyr,code = ; - label = "Joystick Press"; - }; - }; - - /* Input filter: user button long-press detection (1000ms) */ - user_btn_longpress { - compatible = "zephyr,input-longpress"; - input = <&buttons>; - input-codes = ; - short-codes = ; - long-codes = ; - long-delay-ms = <1000>; - }; - - /* Input filter: user button multi-tap detection - * 1 tap (400ms wait) → KEY_1 = page next on OLED - * 2 taps (400ms wait) → KEY_B = LED heartbeat toggle - * 3 taps (400ms wait) → KEY_D = buzzer mute toggle - * 4 taps (400ms wait) → KEY_C = GPS on/off - * 5 taps (immediate) → KEY_E = flood advert */ - user_btn_multitap { - compatible = "zephcore,input-multi-tap"; - /* No 'input' phandle — listen to ALL devices. - * KEY_A is emitted by the longpress pseudo-device, - * not by &buttons, so we must not filter by device. */ - input-codes = ; - tap-codes = ; - tap-delay-ms = <400>; - }; - - /* Battery voltage enable and ADC - on-demand only to save power */ - vbat_enable: vbat-enable { - compatible = "regulator-fixed"; - regulator-name = "vbat-enable"; - enable-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>; - /* NOT regulator-boot-on - enabled on-demand in ZephyrBoard.cpp */ - }; - - /* Battery ADC channel reference for ZephyrBoard */ - zephyr,user { - io-channels = <&adc 7>; - vbat-mv-multiplier = <7236>; /* 2:1 divider, 3.6V ref; +0.5% for nRF SAADC gain error */ - }; - - /* Buzzer on PWM0 channel 0 (P1.00) - no enable pin needed */ - pwmbuzzer { - compatible = "pwm-leds"; - buzzer: buzzer { - pwms = <&pwm0 0 PWM_MSEC(20) PWM_POLARITY_NORMAL>; - }; - }; - - aliases { - led0 = &user_led; - sw0 = &user_button; - lora0 = &lora; - watchdog0 = &wdt0; - buzzer = &buzzer; - }; -}; - -®0 { - status = "okay"; -}; - -®1 { - regulator-initial-mode = ; -}; - -&adc { - status = "okay"; - #address-cells = <1>; - #size-cells = <0>; - - /* Battery voltage on P0.31 (AIN7) */ - channel@7 { - reg = <7>; - zephyr,gain = "ADC_GAIN_1_6"; - zephyr,reference = "ADC_REF_INTERNAL"; - zephyr,acquisition-time = ; - zephyr,input-positive = ; - zephyr,resolution = <12>; - }; -}; - -&uicr { - nfct-pins-as-gpios; -}; - -&gpiote { - status = "okay"; -}; - -&gpio0 { - status = "okay"; -}; - -&gpio1 { - status = "okay"; - - /* NOTE: buzzer-off gpio-hog REMOVED - buzzer pin is now managed by PWM - * driver. When PWM is not driving, the pin is low (no current draw). */ - - /* NOTE: GPS enable hog REMOVED — GPS works fine without it, and the - * reason is now confirmed rather than guessed: P1.09 is the L76K's - * WAKEUP pin, which the L76K hardware design specifies as active low - * "with pull-up internally". Left unconfigured it floats high and the - * module stays in Continuous mode. */ -}; - -/* PWM0 for buzzer */ -&pwm0 { - status = "okay"; - pinctrl-0 = <&pwm0_default>; - pinctrl-1 = <&pwm0_sleep>; - pinctrl-names = "default", "sleep"; -}; - -/* GPS L76KB module - NOTE: Feb 6 working version had no GPIO control nodes. - * The GPS just worked without any power control! Keeping it simple. */ - -/* Quectel L76KB GNSS on UART0 - 9600 baud (PCAS/CASIC protocol) - * Uses Zephyr luatos,air530z driver (same PCAS command set for - * constellation config via PCAS04, fix rate via PCAS02, etc.) - * on-off-gpios = P1.09, the module's WAKEUP/standby pin (see &gpio1 note and - * board.overlay) — driver drives it high for Continuous, low for Standby. */ -&uart0 { - compatible = "nordic,nrf-uarte"; - status = "okay"; - current-speed = <9600>; - pinctrl-0 = <&uart0_default>; - pinctrl-1 = <&uart0_sleep>; - pinctrl-names = "default", "sleep"; - /* No hw-flow-control - GPS doesn't use RTS/CTS */ - - gnss: gnss { - compatible = "luatos,air530z"; - on-off-gpios = <&gpio1 9 GPIO_ACTIVE_HIGH>; - }; -}; - -/* I2C0 for onboard OLED display only. - * External environment/power sensors live on the Grove bus (&i2c1). */ -&i2c0 { - compatible = "nordic,nrf-twim"; - status = "okay"; - clock-frequency = ; - pinctrl-0 = <&i2c0_default>; - pinctrl-1 = <&i2c0_sleep>; - pinctrl-names = "default", "sleep"; - /* SH1106 sends 1 cmd + 128 data = 129 bytes per page write. - * nRF TWIM needs an internal buffer >= this size. */ - zephyr,concat-buf-size = <256>; - zephyr,flash-buf-max-size = <256>; - - sh1106: sh1106@3d { - compatible = "sinowealth,sh1106"; - reg = <0x3d>; - width = <128>; - height = <64>; - segment-offset = <2>; /* SH1106 has 2-column offset */ - page-offset = <0>; - display-offset = <0>; - multiplex-ratio = <63>; - segment-remap; - com-invdir; - prechargep = <0x22>; - }; -}; - -/* I2C1 for Grove connector — external environment/power sensors. - * Auto-detected at runtime; absent sensors fail device_is_ready() and - * are skipped harmlessly by ZephyrEnvSensors. */ -&i2c1 { - compatible = "nordic,nrf-twim"; - status = "okay"; - clock-frequency = ; - pinctrl-0 = <&i2c1_default>; - pinctrl-1 = <&i2c1_sleep>; - pinctrl-names = "default", "sleep"; - - /* All supported environment & power sensors — auto-detected at runtime */ - #include "../../common/sensors-i2c.dtsi" -}; - -/* SPI2 (SPIM2) for LoRa SX1262. - * Must NOT use SPIM0 (shares instance 0 with I2C0/OLED) or SPIM1 (shares - * instance 1 with TWIM1, which drives the Grove I2C bus &i2c1). SPIM2 is a - * dedicated SPI-only instance, so the radio and both I2C buses coexist. */ -&spi2 { - compatible = "nordic,nrf-spim"; - status = "okay"; - cs-gpios = <&gpio1 14 GPIO_ACTIVE_LOW>; - pinctrl-0 = <&spi2_default>; - pinctrl-1 = <&spi2_sleep>; - pinctrl-names = "default", "sleep"; - - lora: lora@0 { - compatible = "semtech,sx1262"; - reg = <0>; - reset-gpios = <&gpio1 7 GPIO_ACTIVE_LOW>; - busy-gpios = <&gpio1 10 GPIO_ACTIVE_HIGH>; - dio1-gpios = <&gpio0 7 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>; - /* RXEN on P1.08, DIO2 as RF switch for TX */ - rx-enable-gpios = <&gpio1 8 GPIO_ACTIVE_HIGH>; - dio2-tx-enable; - dio3-tcxo-voltage = ; - tcxo-power-startup-delay-ms = <5>; - spi-max-frequency = <8000000>; - rx-boosted; - }; -}; - -/* QSPI Flash - P25Q16H 2MB - single-bit mode at 16MHz (matches Arduino) */ -&qspi { - status = "okay"; - pinctrl-0 = <&qspi_default>; - pinctrl-1 = <&qspi_sleep>; - pinctrl-names = "default", "sleep"; - - qspi_flash: qspi_flash@0 { - compatible = "nordic,qspi-nor"; - reg = <0>; - writeoc = "pp"; /* Single-bit page program (universal) */ - readoc = "fastread"; /* Single-bit fast read (universal) */ - sck-frequency = <16000000>; /* 16 MHz (matches Arduino) */ - jedec-id = [85 60 15]; /* P25Q16H */ - sfdp-bfp = [ - e5 20 f1 ff ff ff ff 01 44 eb 08 6b 08 3b 04 bb - ee ff ff ff ff ff 00 ff ff ff 00 ff 0c 20 0f 52 - 10 d8 00 ff - ]; - size = <16777216>; /* 2MB = 16Mbit */ - has-dpd; - t-enter-dpd = <3000>; - t-exit-dpd = <50000>; /* 50ms for reliable wakeup */ - /* quad-enable not needed for single-bit mode */ - }; -}; - -zephyr_udc0: &usbd { - compatible = "nordic,nrf-usbd"; - status = "okay"; - - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; -}; - -/* Arduino MeshCore compatible partition layout (SoftDevice v7) */ -#include "../../common/nrf52_partitions_sdv7.dtsi" - -/* External QSPI flash partitions */ -&qspi_flash { - partitions { - compatible = "fixed-partitions"; - #address-cells = <1>; - #size-cells = <1>; - - /* Full 2MB for LittleFS storage */ - qspi_storage_partition: partition@0 { - label = "qspi_storage"; - reg = <0x00000000 0x00200000>; - }; - }; -}; -#include "../../common/nrf52_wakeup.dtsi" diff --git a/zephcore/boards/nrf52840/wio_tracker_l1/wio_tracker_l1_nrf52840.yaml b/zephcore/boards/nrf52840/wio_tracker_l1/wio_tracker_l1_nrf52840.yaml deleted file mode 100644 index 4f86e39..0000000 --- a/zephcore/boards/nrf52840/wio_tracker_l1/wio_tracker_l1_nrf52840.yaml +++ /dev/null @@ -1,20 +0,0 @@ -identifier: wio_tracker_l1/nrf52840 -name: Seeed Wio Tracker L1 -type: mcu -arch: arm -toolchain: - - zephyr - - gnuarmemb - - xtools -ram: 256 -flash: 1024 -supported: - - adc - - ble - - gpio - - i2c - - spi - - usb_device - - qspi - - lora -vendor: seeed diff --git a/zephcore/boards/nrf52840/wio_tracker_l1/wio_tracker_l1_nrf52840_defconfig b/zephcore/boards/nrf52840/wio_tracker_l1/wio_tracker_l1_nrf52840_defconfig deleted file mode 100644 index 76a1501..0000000 --- a/zephcore/boards/nrf52840/wio_tracker_l1/wio_tracker_l1_nrf52840_defconfig +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) 2025 MeshCore -# SPDX-License-Identifier: MIT - -# Enable MPU -CONFIG_ARM_MPU=y - -# Enable hardware stack protection -CONFIG_HW_STACK_PROTECTION=y - -# Enable GPIO -CONFIG_GPIO=y - -# Enable UART driver -CONFIG_SERIAL=y - -# Enable console -CONFIG_CONSOLE=y - -# Build UF2 by default -CONFIG_BUILD_OUTPUT_UF2=y -CONFIG_USE_DT_CODE_PARTITION=y diff --git a/zephcore/boards/nrf54l/me25ls02/board.conf b/zephcore/boards/nrf54l/me25ls02/board.conf index 4c79947..b07e9f7 100644 --- a/zephcore/boards/nrf54l/me25ls02/board.conf +++ b/zephcore/boards/nrf54l/me25ls02/board.conf @@ -12,7 +12,9 @@ CONFIG_BT_DIS_MODEL_NUMBER_STR="ME25LS02" # and the carrier's Y1 sits behind solder bridges), so selecting the crystal # would hang at boot on any board that lacks one. See the LFXO comment block in # me25ls02.dts before changing this. -CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC=y +# The source itself is k32src = "rc" on &lfclk in the DTS: Zephyr main's split +# nRF clock drivers read it from devicetree, and CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC +# (the legacy-driver choice this file used until 2026-09-19) is silently dropped. CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC_CALIBRATION=y # Same SoC and RAM budget as the XIAO nRF54L15 — 450 contacts is the ceiling diff --git a/zephcore/boards/nrf54l/me25ls02/me25ls02_nrf54l15_cpuapp.dts b/zephcore/boards/nrf54l/me25ls02/me25ls02_nrf54l15_cpuapp.dts index ee0486d..0a6105f 100644 --- a/zephcore/boards/nrf54l/me25ls02/me25ls02_nrf54l15_cpuapp.dts +++ b/zephcore/boards/nrf54l/me25ls02/me25ls02_nrf54l15_cpuapp.dts @@ -101,6 +101,22 @@ status = "okay"; }; +/* Zephyr main (4.5) split the nRF clock driver per clock; each one the + * application uses must be enabled here or it is not built (the BT controller + * fails to compile without lfclk). Same set as upstream's nrf54l15dk. */ +&xo { + status = "okay"; +}; + +/* Calibrated RC, not a crystal: see the LFXO comment block below. 250 ppm is + * the nRF54L15 calibrated-RC figure (DS v1.0 §11.2.2) and Zephyr's default for + * RC outside nRF52. */ +&lfclk { + status = "okay"; + k32src = "rc"; + k32src-accuracy-ppm = <250>; +}; + &uart20 { status = "okay"; current-speed = <115200>; @@ -202,16 +218,17 @@ * nothing there at all. Configuring LFXO with no crystal present means LFCLK * never becomes ready and the node hangs at boot instead of failing visibly. * - * So the board runs the calibrated LFRC instead (K32SRC_RC in board.conf), + * So the board runs the calibrated LFRC instead (k32src = "rc" on &lfclk above), * which is what MinewSemi's own shipped firmware does. Cost is clock accuracy: * ±250 ppm calibrated versus ±50 ppm for a crystal (nRF54L15 DS v1.0 §11.2.2). * That is fine for BLE and for mesh timing, but it is the reason a node here * drifts faster than a crystal-equipped board — relevant if MeshTimeSync is * ever tuned against this hardware. * - * To use the crystal on a carrier that definitely has one, delete - * CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC / add K32SRC_XTAL in board.conf and - * enable &lfxo here with the right load capacitance. + * To use the crystal on a carrier that definitely has one, set k32src = "xtal" + * (and drop the accuracy override) on &lfclk above, remove + * K32SRC_RC_CALIBRATION from board.conf, and enable &lfxo here with the right + * load capacitance. */ &radio { diff --git a/zephcore/boards/nrf54l/seeed_lr2021_evk/seeed_lr2021_evk_nrf54l15_cpuapp.dts b/zephcore/boards/nrf54l/seeed_lr2021_evk/seeed_lr2021_evk_nrf54l15_cpuapp.dts index 287f0af..98609a8 100644 --- a/zephcore/boards/nrf54l/seeed_lr2021_evk/seeed_lr2021_evk_nrf54l15_cpuapp.dts +++ b/zephcore/boards/nrf54l/seeed_lr2021_evk/seeed_lr2021_evk_nrf54l15_cpuapp.dts @@ -231,6 +231,18 @@ status = "okay"; }; +/* Zephyr main (4.5) split the nRF clock driver per clock; each one the + * application uses must be enabled or it is not built (the BT controller fails + * to compile without lfclk). Same set as upstream's nrf54l15dk. LFCLK keeps the + * binding's default source, the crystal (&lfxo below). */ +&xo { + status = "okay"; +}; + +&lfclk { + status = "okay"; +}; + /* Console: P1.09 TX / P1.08 RX -> SAMD11 CMSIS-DAP bridge -> USB CDC */ &uart20 { status = "okay"; diff --git a/zephcore/boards/nrf54l/seeed_lr2021_evk/support/openocd.cfg b/zephcore/boards/nrf54l/seeed_lr2021_evk/support/openocd.cfg index cc90c5f..6563161 100644 --- a/zephcore/boards/nrf54l/seeed_lr2021_evk/support/openocd.cfg +++ b/zephcore/boards/nrf54l/seeed_lr2021_evk/support/openocd.cfg @@ -68,7 +68,8 @@ proc nrf54l-load {file} { load_image $file # Commit the final partial 128-bit RRAM write line left by load_image. # RRAMC.TASKS_COMMITWRITEBUF @ 0x5004b008. Mirrors upstream 1079b21dcda7 - # (see patches/zephyr/0016); without it the image's last line keeps the + # (in our Zephyr pin since the 2026-09-19 move to main; we carried it as + # patch 0016 before that); without it the image's last line keeps the # previous firmware's bytes. mww 0x5004b008 1 } diff --git a/zephcore/boards/nrf54l/xiao_nrf54l15/board.overlay b/zephcore/boards/nrf54l/xiao_nrf54l15/board.overlay index 9573b7c..f479175 100644 --- a/zephcore/boards/nrf54l/xiao_nrf54l15/board.overlay +++ b/zephcore/boards/nrf54l/xiao_nrf54l15/board.overlay @@ -50,6 +50,19 @@ }; }; +/* Zephyr main (4.5) split the nRF clock driver per clock; each one the + * application uses must be enabled or it is not built (the BT controller fails + * to compile without lfclk). Upstream's xiao_nrf54l15 DTS enables only &clock, + * so we add the same pair as upstream's nrf54l15dk. LFCLK keeps the binding's + * default source, the XIAO's own crystal (see "Clocks" above). */ +&xo { + status = "okay"; +}; + +&lfclk { + status = "okay"; +}; + /* Disable I2C22 — default pins P1.10/P1.11 (D4/D5) conflict with NSS/RXEN */ &i2c22 { status = "disabled"; diff --git a/zephcore/boards/stm32wl/lora_e5_mini/board.overlay b/zephcore/boards/stm32wl/lora_e5_mini/board.overlay index b6d1102..f18e615 100644 --- a/zephcore/boards/stm32wl/lora_e5_mini/board.overlay +++ b/zephcore/boards/stm32wl/lora_e5_mini/board.overlay @@ -51,11 +51,13 @@ &flash0 { partitions { slot0_partition: partition@0 { + compatible = "zephyr,mapped-partition"; label = "image-0"; reg = <0x00000000 0x00038000>; }; lfs_partition: partition@38000 { + compatible = "zephyr,mapped-partition"; label = "lfs"; reg = <0x00038000 0x00008000>; }; diff --git a/zephcore/helpers/CommonCLI.cpp b/zephcore/helpers/CommonCLI.cpp index 4c81d2e..c61a0fc 100644 --- a/zephcore/helpers/CommonCLI.cpp +++ b/zephcore/helpers/CommonCLI.cpp @@ -167,6 +167,62 @@ static int cliOnOff(const char* arg, int def_val) { return -1; } +#if IS_ENABLED(CONFIG_ZEPHCORE_MEM_STATS) +#include + +struct mem_stats_walk { + int want; + int idx; + char *reply; +}; + +static void memStatsThread(const struct k_thread *t, void *u) +{ + struct mem_stats_walk *w = (struct mem_stats_walk *)u; + size_t unused = 0; + + if (w->idx++ != w->want) { + return; + } + const char *name = k_thread_name_get((k_tid_t)t); + size_t size = t->stack_info.size; + + if (k_thread_stack_space_get(t, &unused) != 0) { + unused = 0; + } + snprintf(w->reply, CLI_REPLY_SIZE, "t %d %s prio=%d size=%u peak=%u free=%u", w->want, + (name && name[0]) ? name : "?", t->base.prio, (unsigned)size, + (unsigned)(size - unused), (unsigned)unused); +} + +/* `get mem `: one line per call -- threads first, then every k_heap. */ +static void memStatsLine(int n, char *reply) +{ + struct mem_stats_walk w = { n, 0, reply }; + + reply[0] = '\0'; + k_thread_foreach(memStatsThread, &w); + if (reply[0] != '\0') { + return; + } + int h = w.idx; + + STRUCT_SECTION_FOREACH(k_heap, heap) { + if (h++ != n) { + continue; + } + struct sys_memory_stats st; + + sys_heap_runtime_stats_get(&heap->heap, &st); + snprintf(reply, CLI_REPLY_SIZE, "h %d %p size=%u alloc=%u peak=%u", n, (void *)heap, + (unsigned)(st.allocated_bytes + st.free_bytes), + (unsigned)st.allocated_bytes, (unsigned)st.max_allocated_bytes); + return; + } + snprintf(reply, CLI_REPLY_SIZE, "end"); +} +#endif /* CONFIG_ZEPHCORE_MEM_STATS */ + static bool isValidName(const char* n) { while (*n) { if (*n == '[' || *n == ']' || *n == '\\' || *n == ':' || @@ -634,6 +690,10 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch uint32_t s = gps_get_poll_interval_sec(); // now-effective value if (s == 0) strcpy(reply, "> always on (0)"); else snprintf(reply, CLI_REPLY_SIZE, "> %u", (unsigned)s); +#if IS_ENABLED(CONFIG_ZEPHCORE_MEM_STATS) + } else if (memcmp(config, "mem", 3) == 0) { + memStatsLine(atoi(&config[3]), reply); +#endif } else if (memcmp(config, "dc.restarts", 11) == 0) { snprintf(reply, CLI_REPLY_SIZE, "> %u", (uint32_t)_callbacks->getDutyCycleTimeoutRestarts()); diff --git a/zephcore/helpers/ui/buzzer.c b/zephcore/helpers/ui/buzzer.c index 74ff11f..29503c0 100644 --- a/zephcore/helpers/ui/buzzer.c +++ b/zephcore/helpers/ui/buzzer.c @@ -404,8 +404,8 @@ int buzzer_init(void) k_work_queue_init(&buzzer_wq); k_work_queue_start(&buzzer_wq, buzzer_wq_stack, K_THREAD_STACK_SIZEOF(buzzer_wq_stack), - BUZZER_WQ_PRIORITY, NULL); - k_thread_name_set(&buzzer_wq.thread, "buzzer_wq"); + BUZZER_WQ_PRIORITY, + &(const struct k_work_queue_config){ .name = "buzzer_wq" }); k_work_init_delayable(&ctx.note_work, note_work_handler); k_work_init_delayable(&ctx.safety_work, safety_work_handler); diff --git a/zephcore/patches/modules/hal-espressif/0001-ble-enc-funcs-reset.patch b/zephcore/patches/modules/hal-espressif/0001-ble-enc-funcs-reset.patch deleted file mode 100644 index fc5d6d8..0000000 --- a/zephcore/patches/modules/hal-espressif/0001-ble-enc-funcs-reset.patch +++ /dev/null @@ -1,22 +0,0 @@ -diff --git a/components/bt/controller/esp32c3/bt.c b/components/bt/controller/esp32c3/bt.c -index 7f47e23f4e..ab872f5654 100644 ---- a/components/bt/controller/esp32c3/bt.c -+++ b/components/bt/controller/esp32c3/bt.c -@@ -1415,9 +1415,15 @@ static void btdm_funcs_table_ready_wrapper(void) - ble_cca_funcs_reset(); - #endif // (BT_BLE_CCA_MODE != 0) - --#if CONFIG_BT_CTRL_BLE_SECURITY_ENABLE -+/* ZephCore patch: guard was CONFIG_BT_CTRL_BLE_SECURITY_ENABLE (an IDF sdkconfig -+ * symbol never defined in Zephyr builds), so ble_enc_funcs_reset() was never -+ * called -> the controller's LE Secure Connections encryption table stayed -+ * uninitialised -> SC pairing produced a wrong session key -> 0x3D MIC failure -+ * (e.g. Pixel 7). BLE_SECURITY_ENABLE maps to CONFIG_ESP32_BT_CTLR_LE_SECURITY_ENABLE -+ * (the Zephyr knob, =y) and is already used for .enc_en in esp_bt.h. */ -+#if BLE_SECURITY_ENABLE - ble_enc_funcs_reset(); --#endif // CONFIG_BT_CTRL_BLE_SECURITY_ENABLE -+#endif // BLE_SECURITY_ENABLE - - #if CONFIG_BT_CTRL_BLE_MASTER - ble_init_funcs_reset(); diff --git a/zephcore/patches/modules/hal-espressif/0002-kconfig-cpu-freq-80mhz.patch b/zephcore/patches/modules/hal-espressif/0002-kconfig-cpu-freq-80mhz.patch deleted file mode 100644 index e9849b5..0000000 --- a/zephcore/patches/modules/hal-espressif/0002-kconfig-cpu-freq-80mhz.patch +++ /dev/null @@ -1,38 +0,0 @@ -Add 80 MHz to the DT-derived CPU frequency mapping. - -zephyr/Kconfig derives ESP_DEFAULT_CPU_FREQ_MHZ from the devicetree cpu0 -clock-frequency via a lookup table listing 96/120/160/240. Those four values -are exactly the per-SoC DEFAULTS in Zephyr's own espressif dtsi files (H2 96, -C2 120, C3/C6 160, esp32/C5/S2/S3 240) -- the table enumerates defaults, not -capabilities, so a board that deliberately downclocks falls through it and the -symbol resolves to nothing: - - clk.c:120: error: 'CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ' undeclared - sleep_modes.c:235: error: 'CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ' undeclared - -80 MHz is valid on every part we build -- rtc_clk.c accepts 80/160/240 on -esp32 and esp32s3, 80/160 on C3, 80/120/160 on C6 -- and Espressif's own IDF -Kconfig.cpu offers ESP_DEFAULT_CPU_FREQ_MHZ_80. This is a gap in the Zephyr -port glue, not a limitation of the silicon or of IDF. - -ZephCore runs every non-observer ESP32 build at 80 MHz for battery life -(boards/common/esp32_cpu_80mhz.overlay). - -Do NOT 'fix' this by selecting 96 instead: 96 is in the table but is an -ESP32-H2 frequency, rejected by rtc_clk_cpu_freq_mhz_to_config() on all of our -SoCs, and it fails at BOOT rather than at build time. - -Upstream candidate: the omission looks accidental. ---- -diff --git a/zephyr/Kconfig b/zephyr/Kconfig -index e9fb2b90a8..f8793f5d59 100644 ---- a/zephyr/Kconfig -+++ b/zephyr/Kconfig -@@ -105,6 +105,7 @@ config ESP_SYSTEM_RTC_EXT_XTAL_BOOTSTRAP_CYCLES - # CPU frequency in MHz derived from devicetree clock-frequency property - config ESP_DEFAULT_CPU_FREQ_MHZ - int -+ default 80 if ESP_CLK_FREQ_HZ = 80000000 - default 96 if ESP_CLK_FREQ_HZ = 96000000 - default 120 if ESP_CLK_FREQ_HZ = 120000000 - default 160 if ESP_CLK_FREQ_HZ = 160000000 diff --git a/zephcore/patches/zephyr-new/drivers/lora/lr11xx/lr11xx_lora.c b/zephcore/patches/zephyr-new/drivers/lora/lr11xx/lr11xx_lora.c index fb43d10..a9efcfa 100644 --- a/zephcore/patches/zephyr-new/drivers/lora/lr11xx/lr11xx_lora.c +++ b/zephcore/patches/zephyr-new/drivers/lora/lr11xx/lr11xx_lora.c @@ -1040,7 +1040,7 @@ static int lr11xx_hw_init(struct lr11xx_data *data, /* ── Driver API: config ─────────────────────────────────────────────── */ static int lr11xx_lora_config(const struct device *dev, - struct lora_modem_config *config) + const struct lora_modem_config *config) { struct lr11xx_data *data = dev->data; const struct lr11xx_config *cfg = dev->config; @@ -1429,6 +1429,56 @@ static void lr11xx_dc_resume(struct lr11xx_data *data, bool was_armed) data->in_rx_mode = true; } +static uint32_t lr11xx_preamble_grace_ms(struct lr11xx_data *data); + +/* Is a duty-cycled reception already under way? Ask before lr11xx_dc_suspend(). + * + * dc_suspend() stands the chip down unconditionally, which ends any reception + * in progress, and with the cycle armed lr11xx_is_receiving() cannot see the + * preamble phase at all: it answers from the header latch only, because a bus + * access in the sleep phase kills the cycle. So every discretionary stand-down + * -- the noise-floor sampler above all -- could land between preamble detect + * and header, and lost that packet without a trace (T1000-E bench, 2026-09-19: + * each missed packet raised no IRQ at all, and coincided with a sampler burst + * whose RSSI read the packet itself, ~30 dB over the floor). + * + * Reading the IRQ register here is safe in both phases. In the sleep phase + * the NSS edge wakes the chip, but the caller is about to terminate and re-arm + * the cycle anyway -- the same sanctioned termination dc_suspend() documents. + * With a preamble latched the chip is in Rx (StopTimerOnPreamble holds it + * there), where bus access is routine. PREAMBLE_DETECTED is not DIO1-routed but + * does latch in the register. A preamble older than the SF-aware grace with no + * header is treated as stale (foreign sync word / false detect), exactly as the + * continuous-RX poll in lr11xx_is_receiving() does, so it cannot starve the + * callers: they proceed, and dc_resume() clears it. + * + * Caller holds data->spi_mutex. */ +static bool lr11xx_dc_rx_in_flight(struct lr11xx_data *data) +{ + lr11xx_system_irq_mask_t irq = 0; + + if (!data->rx_duty_cycle_enabled || !data->in_rx_mode) { + return false; + } + if (lr11xx_system_get_irq_status(&data->hal_ctx, &irq) != LR11XX_STATUS_OK) { + return false; + } + if (irq & LR11XX_SYSTEM_IRQ_SYNC_WORD_HEADER_VALID) { + return true; + } + if (irq & LR11XX_SYSTEM_IRQ_PREAMBLE_DETECTED) { + uint32_t now = k_uptime_get_32(); + uint32_t seen = data->preamble_seen_at_ms; + + if (seen == 0) { + data->preamble_seen_at_ms = (now == 0) ? 1U : now; + return true; + } + return (now - seen) < lr11xx_preamble_grace_ms(data); + } + return false; +} + /* Front-end settle to wait after entering Rx before the first GetRssiInst. * * DS Table 13-82 puts the RSSI averaging window at ~936 us*kHz / BW and the @@ -1480,6 +1530,10 @@ int16_t lr11xx_get_rssi_inst(const struct device *dev) if (k_mutex_lock(&data->spi_mutex, K_NO_WAIT) != 0) { return -128; } + if (lr11xx_dc_rx_in_flight(data)) { + k_mutex_unlock(&data->spi_mutex); + return -128; + } bool armed = lr11xx_dc_suspend(data); @@ -1621,6 +1675,12 @@ int lr11xx_get_rssi_burst(const struct device *dev, int16_t *out, int n, if (k_mutex_lock(&data->spi_mutex, K_NO_WAIT) != 0) { return 0; } + /* A packet already on its way in: same verdict as a preamble landing + * inside the window -- the caller abandons the burst and retries. */ + if (lr11xx_dc_rx_in_flight(data)) { + k_mutex_unlock(&data->spi_mutex); + return -EBUSY; + } bool armed = lr11xx_dc_suspend(data); @@ -1967,6 +2027,11 @@ void lr11xx_recalibrate(const struct device *dev) LOG_DBG("recalibrate: mutex busy, deferring"); return; } + if (lr11xx_dc_rx_in_flight(data)) { + LOG_DBG("recalibrate: reception in flight, deferring"); + k_mutex_unlock(&data->spi_mutex); + return; + } /* Own the cycle across the whole sequence. This opens with SetSleep, * which is fatal to a chip already in its own sleep phase, and the @@ -1996,6 +2061,10 @@ int16_t lr11xx_get_chip_temp_c(const struct device *dev) if (k_mutex_lock(&data->spi_mutex, K_NO_WAIT) != 0) { return INT16_MIN; } + if (lr11xx_dc_rx_in_flight(data)) { + k_mutex_unlock(&data->spi_mutex); + return INT16_MIN; + } /* Bracketed rather than skipped: GetTemp into a chip parked in the * duty-cycle sleep phase both wedges it BUSY-high and, per UM §7.2.6, @@ -2584,16 +2653,16 @@ static int lr11xx_lora_init(const struct device *dev) /* Start dedicated DIO1 work queue at high priority */ k_work_queue_start(&data->dio1_wq, lr11xx_dio1_wq_stack, K_THREAD_STACK_SIZEOF(lr11xx_dio1_wq_stack), - K_PRIO_COOP(7), NULL); - k_thread_name_set(&data->dio1_wq.thread, "lr11xx_dio1"); + K_PRIO_COOP(7), + &(const struct k_work_queue_config){ .name = "lr11xx_dio1" }); /* Wedge-recovery watchdog on its own queue (see handler). Same priority * as DIO1 so it never preempts RX; its confirm poll yields every 2 ms. */ k_work_init_delayable(&data->wedge_work, lr11xx_wedge_watchdog_handler); k_work_queue_start(&data->wedge_wq, lr11xx_wedge_wq_stack, K_THREAD_STACK_SIZEOF(lr11xx_wedge_wq_stack), - K_PRIO_COOP(7), NULL); - k_thread_name_set(&data->wedge_wq.thread, "lr11xx_wedge"); + K_PRIO_COOP(7), + &(const struct k_work_queue_config){ .name = "lr11xx_wedge" }); data->last_dio1_ms = k_uptime_get_32(); k_work_schedule_for_queue(&data->wedge_wq, &data->wedge_work, K_MSEC(LR11XX_WEDGE_CHECK_MS)); @@ -2657,13 +2726,13 @@ static DEVICE_API(lora, lr11xx_lora_api) = { * (over-sleep + no header budget), not a chip defect — now sized by * the shared adapter math. Default-off via prefs; HW-verify on a * live LR1110 before trusting in production. */ - .recv_duty_cycle = lr11xx_lora_recv_duty_cycle, + .recv_duty_cycle_async = lr11xx_lora_recv_duty_cycle, }; #define LR11XX_INIT(n) \ static const struct lr11xx_config lr11xx_config_##n = { \ .bus = SPI_DT_SPEC_INST_GET(n, \ - SPI_WORD_SET(8) | SPI_OP_MODE_MASTER | \ + SPI_WORD_SET(8) | SPI_OP_MODE_CONTROLLER | \ SPI_TRANSFER_MSB), \ .reset = GPIO_DT_SPEC_INST_GET(n, reset_gpios), \ .busy = GPIO_DT_SPEC_INST_GET(n, busy_gpios), \ diff --git a/zephcore/patches/zephyr-new/drivers/lora/lr20xx/lr20xx_lora.c b/zephcore/patches/zephyr-new/drivers/lora/lr20xx/lr20xx_lora.c index 7d496d7..d4a0848 100644 --- a/zephcore/patches/zephyr-new/drivers/lora/lr20xx/lr20xx_lora.c +++ b/zephcore/patches/zephyr-new/drivers/lora/lr20xx/lr20xx_lora.c @@ -643,6 +643,7 @@ static void lr20xx_get_pa_cfg_for_power(int8_t power_dbm, uint32_t freq_hz, static lr20xx_status_t lr20xx_calibrate_front_end(void *ctx, uint32_t freq_hz); static bool lr20xx_dc_suspend(struct lr20xx_data *data); +static bool lr20xx_dc_rx_in_flight(struct lr20xx_data *data); static void lr20xx_dc_resume(struct lr20xx_data *data, bool was_armed); /* ── Firmware Patch RAM (PRAM) ──────────────────────────────────────── */ @@ -1255,6 +1256,12 @@ void lr20xx_recalibrate(const struct device *dev) return; } + if (lr20xx_dc_rx_in_flight(data)) { + LOG_DBG("recalibrate: reception in flight, deferring"); + k_mutex_unlock(&data->spi_mutex); + return; + } + /* Own the cycle across the sequence: it opens with SetSleep, which is * fatal to a chip already in its own sleep phase, and the recalibration * leaves the radio in standby regardless. */ @@ -1283,6 +1290,11 @@ int16_t lr20xx_get_chip_temp_c(const struct device *dev) return INT16_MIN; } + if (lr20xx_dc_rx_in_flight(data)) { + k_mutex_unlock(&data->spi_mutex); + return INT16_MIN; + } + /* Bracketed rather than skipped — GetTemp into the sleep phase both * risks the chip and, per DS §6.3.8, silently ends the cycle. */ bool armed = lr20xx_dc_suspend(data); @@ -1423,6 +1435,53 @@ static void lr20xx_dc_resume(struct lr20xx_data *data, bool was_armed) data->in_rx_mode = true; } +static uint32_t lr20xx_preamble_grace_ms(struct lr20xx_data *data); + +/* Is a duty-cycled reception already under way? Ask before lr20xx_dc_suspend(). + * + * Port of lr11xx_dc_rx_in_flight() -- same fault, same fix. dc_suspend() + * stands the chip down unconditionally, which ends a reception in progress, + * and with the cycle armed lr20xx_is_receiving() answers from the header latch + * only, so the preamble phase is invisible to every discretionary stand-down + * (noise-floor sampler, RSSI read, recalibration, temperature read). On the + * LR1110 that lost ~1 packet in 40 at an 800 ms cadence (T1000-E bench, + * 2026-09-19); each miss raised no IRQ at all and coincided with a sampler + * burst that read the packet itself. + * + * Non-destructive status read (get_status, not get_and_clear). In the sleep + * phase the access wakes the chip through the HAL exactly as the caller's + * SetStandby is about to, and the caller then terminates and re-arms the cycle + * as before. With a preamble latched the chip is in Rx, where bus access is + * routine. A preamble older than the SF-aware grace with no header is stale + * and does not block the caller; dc_resume() clears it. + * + * Caller holds data->spi_mutex. */ +static bool lr20xx_dc_rx_in_flight(struct lr20xx_data *data) +{ + lr20xx_system_irq_mask_t irq = 0; + + if (!data->rx_duty_cycle_enabled || !data->in_rx_mode) { + return false; + } + if (lr20xx_system_get_status(&data->hal_ctx, NULL, NULL, &irq) != LR20XX_STATUS_OK) { + return false; + } + if (irq & LR20XX_SYSTEM_IRQ_SYNC_WORD_HEADER_VALID) { + return true; + } + if (irq & LR20XX_SYSTEM_IRQ_PREAMBLE_DETECTED) { + uint32_t now = k_uptime_get_32(); + uint32_t seen = data->preamble_seen_at_ms; + + if (seen == 0) { + data->preamble_seen_at_ms = (now == 0) ? 1U : now; + return true; + } + return (now - seen) < lr20xx_preamble_grace_ms(data); + } + return false; +} + /* GPIO-only "can the host talk to this chip right now" check, no SPI. * * Gated on an armed duty cycle: unlike the SX126x, the LR2021 holds BUSY high @@ -2154,7 +2213,7 @@ static int lr20xx_hw_init(struct lr20xx_data *data, /* ── Driver API: config ─────────────────────────────────────────────── */ static int lr20xx_lora_config(const struct device *dev, - struct lora_modem_config *config) + const struct lora_modem_config *config) { struct lr20xx_data *data = dev->data; @@ -2717,6 +2776,10 @@ int16_t lr20xx_get_rssi_inst(const struct device *dev) if (k_mutex_lock(&data->spi_mutex, K_NO_WAIT) != 0) { return -128; } + if (lr20xx_dc_rx_in_flight(data)) { + k_mutex_unlock(&data->spi_mutex); + return -128; + } bool armed = lr20xx_dc_suspend(data); @@ -2827,6 +2890,12 @@ int lr20xx_get_rssi_burst(const struct device *dev, int16_t *out, int n, if (k_mutex_lock(&data->spi_mutex, K_NO_WAIT) != 0) { return 0; } + /* A packet already on its way in: same verdict as a preamble landing + * inside the window -- the caller abandons the burst and retries. */ + if (lr20xx_dc_rx_in_flight(data)) { + k_mutex_unlock(&data->spi_mutex); + return -EBUSY; + } bool armed = lr20xx_dc_suspend(data); @@ -3800,8 +3869,8 @@ static int lr20xx_lora_init(const struct device *dev) k_work_queue_start(&data->dio1_wq, lr20xx_dio1_wq_stack, K_THREAD_STACK_SIZEOF(lr20xx_dio1_wq_stack), - K_PRIO_COOP(7), NULL); - k_thread_name_set(&data->dio1_wq.thread, "lr20xx_dio1"); + K_PRIO_COOP(7), + &(const struct k_work_queue_config){ .name = "lr20xx_dio1" }); if (!spi_is_ready_dt(&cfg->bus)) { LOG_ERR("SPI bus not ready"); @@ -3846,13 +3915,13 @@ static DEVICE_API(lora, lr20xx_lora_api) = { .recv_async = lr20xx_lora_recv_async, .cad = lr20xx_lora_cad, .cad_async = lr20xx_lora_cad_async, - .recv_duty_cycle = lr20xx_lora_recv_duty_cycle, + .recv_duty_cycle_async = lr20xx_lora_recv_duty_cycle, }; #define LR20XX_INIT(n) \ static const struct lr20xx_config lr20xx_config_##n = { \ .bus = SPI_DT_SPEC_INST_GET(n, \ - SPI_WORD_SET(8) | SPI_OP_MODE_MASTER | \ + SPI_WORD_SET(8) | SPI_OP_MODE_CONTROLLER | \ SPI_TRANSFER_MSB), \ .reset = GPIO_DT_SPEC_INST_GET(n, reset_gpios), \ .busy = GPIO_DT_SPEC_INST_GET(n, busy_gpios), \ diff --git a/zephcore/patches/zephyr/0001-lora-lr11xx-lr20xx-build.patch b/zephcore/patches/zephyr/0001-lora-lr11xx-lr20xx-build.patch index af1e216..3d5065e 100644 --- a/zephcore/patches/zephyr/0001-lora-lr11xx-lr20xx-build.patch +++ b/zephcore/patches/zephyr/0001-lora-lr11xx-lr20xx-build.patch @@ -15,13 +15,14 @@ zephcore_apply_patches() reset these two files, and 0003 re-supplied the lines patches apply first time. diff --git a/drivers/lora/CMakeLists.txt b/drivers/lora/CMakeLists.txt -index 9cd035fcf2b..18d04ec099a 100644 +index fecfe8ea8bb..0fbeadcc404 100644 --- a/drivers/lora/CMakeLists.txt +++ b/drivers/lora/CMakeLists.txt -@@ -1,7 +1,11 @@ +@@ -1,8 +1,12 @@ # SPDX-License-Identifier: Apache-2.0 +# ZephCore patch: adds lr11xx driver subdirectory. + zephyr_sources(lora_common.c) zephyr_sources_ifdef(CONFIG_LORA_SHELL shell.c) +add_subdirectory_ifdef(CONFIG_LORA_LR11XX lr11xx) @@ -31,7 +32,7 @@ index 9cd035fcf2b..18d04ec099a 100644 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 657bdb9ce28..f8513b37044 100644 +index e31c08c1371..d53979fabd1 100644 --- a/drivers/lora/Kconfig +++ b/drivers/lora/Kconfig @@ -5,6 +5,7 @@ @@ -42,7 +43,7 @@ index 657bdb9ce28..f8513b37044 100644 menuconfig LORA bool "LoRa drivers" -@@ -60,6 +61,8 @@ config LORA_INIT_PRIORITY +@@ -79,6 +80,8 @@ config LORA_INIT_PRIORITY rsource "Kconfig.sx12xx" rsource "Kconfig.rylrxxx" diff --git a/zephcore/patches/zephyr/0003-lora-sx126x-native.patch b/zephcore/patches/zephyr/0003-lora-sx126x-native.patch index 99298a9..6f1c0d8 100644 --- a/zephcore/patches/zephyr/0003-lora-sx126x-native.patch +++ b/zephcore/patches/zephyr/0003-lora-sx126x-native.patch @@ -11,13 +11,16 @@ patches/zephyr-new/. == Base driver == -Upstream's sx126x is a minimal blocking LoRa driver. ZephCore needs an -event-driven one carrying the primitives the mesh dispatcher is built on. On -top of upstream: +This driver was forked from upstream v4.4's sx126x, which was then a minimal +blocking LoRa driver, because ZephCore needs an event-driven one carrying the +primitives the mesh dispatcher is built on. Upstream main has since grown its +own async RX, duty cycle and CAD; how the two now relate is in "Rebased onto +Zephyr main" below. On top of upstream: * DIO1 handling moved off the ISR onto a work queue. - * RX duty cycle -- sx126x_lora_recv_duty_cycle(), with Rx-gain retention + * RX duty cycle -- sx126x_lora_recv_duty_cycle_async(), filling the + .recv_duty_cycle_async slot of the lora.h 0.9.0 API, with Rx-gain retention across warm sleep (sx126x_retain_rx_gain(), DS Sec 9.6), StopTimerOnPreamble, and a two-strike parked-RX watchdog (sx126x_dc_watchdog_*) that re-arms the cycle after a false preamble detect @@ -535,6 +538,50 @@ the exported sx126x_cad_base_peak() and sx126x_cad_probe() cannot drift: the base is what `get cad` prints and what the staircase offsets from, so a base disagreeing with the peak actually programmed would make every rung a lie. +== Rebased onto Zephyr main (2026-09-19) == + +The pin moved from v4.4-branch (1d9ce23e364) to main (3e8f38faf93), 16,052 +commits. Main had reworked this driver on its own, and this patch was rebased +with git apply --3way (15 conflicting hunks in sx126x.c, 1 in sx126x.h). The +rebase is deliberately behaviour-preserving ("step A"): + + * Our state machine wins wherever the two overlap. Main added a parallel + duty-cycle implementation -- STATE_RX_DUTY_CYCLE, set_stop_timer_on_preamble, + set_rx_duty_cycle, rx_restart, duty_cycle_enter/start/stop, a blocking + recv_duty_cycle and its own recv_duty_cycle_async. All of it is removed + here. Ours (STATE_RX plus rx_duty_cycle_enabled, the parked-RX watchdog, + the busy gate) keeps running unchanged, renamed into the API slot above. + The blocking .recv_duty_cycle slot is left NULL; nothing in ZephCore calls + it. handle_irq_rx_done and handle_irq_timeout are our v4.4 versions + verbatim. + + * Upstream changes that do not overlap ours were kept as upstream wrote them: + the SF5/SF6 preamble clamp, validate_config, config->sync_word, the CAS in + the CW-test path, the STM32WL IRQ and PA fixes, .rssi (sx126x_lora_rssi), + and the .cad / .cad_async slots. + + * Several sections below were written against the v4.4 base and describe + aligning with "upstream main". After this rebase that code simply is + upstream's, and the patch no longer carries it: the SetRx timeout constants, + the bandwidth_to_reg()/bandwidth_to_hz() error-returning signatures, and + the body of sx126x_apply_tx_modulation_workaround(). Our second Sec 15.1 + call, inside sx126x_lora_config(), stays next to upstream's in + send_async. + + * sx126x_add_reg_to_retention() is upstream's function plus our count-byte + bounds guard. Upstream still loops "i < n" on the raw count byte, so a + blank or corrupt 0xFF indexes past its 9-byte buffer. That is an upstream + bug worth reporting. + + * The DIO1 work queue is named through k_work_queue_config.name. Main + deprecates writing k_work_q.thread directly. + +Step B, still open: shrink this patch toward upstream's own duty-cycle +structure one piece at a time, with a bench PDR run per piece, until what +remains is only what upstream does not do. Validated so far, on a Wio Tracker +L1 and a T1000-E: 50/50 packet parity with the v4.4 build in both directions, +with RX duty cycle off (2 s gap) and on (2 s and 800 ms gaps). + == Why this is one patch == Five of the sections above shipped as separate files (0011 band-rssi-cal, 0012 @@ -577,10 +624,10 @@ Regenerate with: appended to this preamble. diff --git a/drivers/lora/native/sx126x/sx126x.c b/drivers/lora/native/sx126x/sx126x.c -index 30243ba5dc7..d512fb59afc 100644 +index c8e682ee84c..399b84e8086 100644 --- a/drivers/lora/native/sx126x/sx126x.c +++ b/drivers/lora/native/sx126x/sx126x.c -@@ -6,72 +6,236 @@ +@@ -6,14 +6,52 @@ #include #include #include @@ -633,113 +680,10 @@ index 30243ba5dc7..d512fb59afc 100644 #define SX126X_REST_STATE \ (IS_ENABLED(CONFIG_LORA_SX126X_NATIVE_SLEEP) \ ? SX126X_STATE_SLEEP : SX126X_STATE_IDLE) - --static uint8_t bandwidth_to_reg(enum lora_signal_bandwidth bw) -+/* Both bandwidth mappers report an unsupported enum through the return code -+ * rather than silently substituting 125 kHz, matching upstream's signatures so -+ * these two functions stop being a rebase conflict. ZephCore never reaches the -+ * error path -- bw_khz_to_enum() in the C++ adapter normalises to a valid enum -+ * before lora_config() is called -- so callers that need a value keep their -+ * pre-existing safe default. */ -+static int bandwidth_to_reg(enum lora_signal_bandwidth bw, uint8_t *reg) - { - switch (bw) { - case BW_7_KHZ: -- return SX126X_LORA_BW_7_8; -+ *reg = SX126X_LORA_BW_7_8; -+ return 0; - case BW_10_KHZ: -- return SX126X_LORA_BW_10_4; -+ *reg = SX126X_LORA_BW_10_4; -+ return 0; - case BW_15_KHZ: -- return SX126X_LORA_BW_15_6; -+ *reg = SX126X_LORA_BW_15_6; -+ return 0; - case BW_20_KHZ: -- return SX126X_LORA_BW_20_8; -+ *reg = SX126X_LORA_BW_20_8; -+ return 0; - case BW_31_KHZ: -- return SX126X_LORA_BW_31_25; -+ *reg = SX126X_LORA_BW_31_25; -+ return 0; - case BW_41_KHZ: -- return SX126X_LORA_BW_41_7; -+ *reg = SX126X_LORA_BW_41_7; -+ return 0; - case BW_62_KHZ: -- return SX126X_LORA_BW_62_5; -+ *reg = SX126X_LORA_BW_62_5; -+ return 0; - case BW_125_KHZ: -- return SX126X_LORA_BW_125; -+ *reg = SX126X_LORA_BW_125; -+ return 0; - case BW_250_KHZ: -- return SX126X_LORA_BW_250; -+ *reg = SX126X_LORA_BW_250; -+ return 0; - case BW_500_KHZ: -- return SX126X_LORA_BW_500; -+ *reg = SX126X_LORA_BW_500; -+ return 0; - default: -- return SX126X_LORA_BW_125; -+ return -EINVAL; +@@ -96,6 +134,106 @@ static int bandwidth_to_hz(enum lora_signal_bandwidth bw, uint32_t *bw_hz) } } --static uint32_t bandwidth_to_hz(enum lora_signal_bandwidth bw) -+static int bandwidth_to_hz(enum lora_signal_bandwidth bw, uint32_t *bw_hz) - { - switch (bw) { - case BW_7_KHZ: -- return 7810; -+ *bw_hz = 7810; -+ return 0; - case BW_10_KHZ: -- return 10420; -+ *bw_hz = 10420; -+ return 0; - case BW_15_KHZ: -- return 15630; -+ *bw_hz = 15630; -+ return 0; - case BW_20_KHZ: -- return 20830; -+ *bw_hz = 20830; -+ return 0; - case BW_31_KHZ: -- return 31250; -+ *bw_hz = 31250; -+ return 0; - case BW_41_KHZ: -- return 41670; -+ *bw_hz = 41670; -+ return 0; - case BW_62_KHZ: -- return 62500; -+ *bw_hz = 62500; -+ return 0; - case BW_125_KHZ: -- return 125000; -+ *bw_hz = 125000; -+ return 0; - case BW_250_KHZ: -- return 250000; -+ *bw_hz = 250000; -+ return 0; - case BW_500_KHZ: -- return 500000; -+ *bw_hz = 500000; -+ return 0; - default: -- return 125000; -+ return -EINVAL; -+ } -+} -+ +/* Reset all software state that indicates "we are currently receiving": + * the rx_packet_active latch (and its deadline timestamp), the + * preamble-grace timestamp and the raw-HEADER_VALID deadline. Paired write @@ -830,7 +774,7 @@ index 30243ba5dc7..d512fb59afc 100644 + + if (numer > 4U * (uint32_t)sf) { + numer -= 4U * (uint32_t)sf; - } ++ } + uint32_t n_sym = 8U + ((numer + denom - 1U) / denom) * 8U; + + /* n_sym * 2^sf * 1000000 / bw_hz -> us, then +25% and +100 ms margin. */ @@ -838,104 +782,18 @@ index 30243ba5dc7..d512fb59afc 100644 + uint32_t ms = (uint32_t)((us + 999U) / 1000U); + + return ms + (ms / 4U) + 100U; - } - - static bool should_enable_ldro(enum lora_datarate sf, enum lora_signal_bandwidth bw, -@@ -81,7 +245,11 @@ static bool should_enable_ldro(enum lora_datarate sf, enum lora_signal_bandwidth - return true; - } - -- uint32_t bw_hz = bandwidth_to_hz(bw); -+ uint32_t bw_hz; -+ -+ if (bandwidth_to_hz(bw, &bw_hz) < 0) { -+ bw_hz = 125000; /* unreachable via the adapter; keep the old default */ -+ } - /* Symbol time = 2^SF / BW (in seconds) */ - /* 16.38 ms = 16380 us */ - /* 2^SF / BW > 0.01638 => 2^SF * 1000000 / BW > 16380 */ -@@ -216,12 +384,43 @@ static int sx126x_set_modulation_params(const struct device *dev, - return sx126x_hal_write_cmd(dev, SX126X_CMD_SET_MODULATION_PARAMS, buf, 4); - } - -+/* Sec 15.1 workaround -- modulation quality with 500 kHz LoRa bandwidth. -+ * -+ * The datasheet is explicit that this "must be called before each packet -+ * transmission", which is why it runs on the TX path and not only from -+ * sx126x_lora_config(). Registers do not survive a warm-start wake unless -+ * they are in the retention list, and sx126x_reset_agc() performs exactly -+ * such a sleep; 0x0889 is in the list now (see -+ * sx126x_install_retention_list()), but the per-TX write is what the -+ * datasheet asks for and costs two SPI operations on a path that is already -+ * writing PA config. */ -+static int sx126x_apply_tx_modulation_workaround(const struct device *dev, -+ enum lora_signal_bandwidth bw) -+{ -+ uint8_t reg_val; -+ int ret; -+ -+ ret = sx126x_hal_read_regs(dev, SX126X_REG_TX_MODULATION, ®_val, 1); -+ if (ret < 0) { -+ return ret; -+ } -+ -+ if (bw == BW_500_KHZ) { -+ reg_val &= ~BIT(2); -+ } else { -+ reg_val |= BIT(2); -+ } -+ -+ return sx126x_hal_write_regs(dev, SX126X_REG_TX_MODULATION, ®_val, 1); +} + - static int sx126x_set_packet_params(const struct device *dev, - uint16_t preamble_len, uint8_t header_type, - uint8_t payload_len, uint8_t crc_mode, - uint8_t invert_iq) + static bool should_enable_ldro(enum lora_datarate sf, enum lora_signal_bandwidth bw, + const struct sx126x_hal_config *config) { - uint8_t buf[6]; -+ int ret; - - sys_put_be16(preamble_len, &buf[0]); - buf[2] = header_type; -@@ -229,7 +428,31 @@ static int sx126x_set_packet_params(const struct device *dev, - buf[4] = crc_mode; - buf[5] = invert_iq; - -- return sx126x_hal_write_cmd(dev, SX126X_CMD_SET_PACKET_PARAMS, buf, 6); -+ ret = sx126x_hal_write_cmd(dev, SX126X_CMD_SET_PACKET_PARAMS, buf, 6); -+ if (ret < 0) { -+ return ret; -+ } -+ -+ /* Sec 15.4 IQ Polarity workaround (datasheet errata). -+ * After SetPacketParams, register 0x0736 bit 2 must be: -+ * SET for standard IQ (non-inverted) -+ * CLEAR for inverted IQ -+ * Without this fix, inverted-IQ packets are not received. -+ * -+ * Must read-modify-write -- SetPacketParams actively writes to -+ * this register and other bits may vary. Cannot cache. */ -+ uint8_t iq_val; -+ -+ ret = sx126x_hal_read_regs(dev, SX126X_REG_IQ_POLARITY, &iq_val, 1); -+ if (ret < 0) { -+ return ret; -+ } -+ if (invert_iq == SX126X_LORA_IQ_INVERTED) { -+ iq_val &= ~BIT(2); -+ } else { -+ iq_val |= BIT(2); -+ } -+ return sx126x_hal_write_regs(dev, SX126X_REG_IQ_POLARITY, &iq_val, 1); - } - - static int sx126x_set_sync_word(const struct device *dev, bool public_network) -@@ -243,10 +466,128 @@ static int sx126x_set_sync_word(const struct device *dev, bool public_network) +@@ -336,46 +474,138 @@ static int sx126x_set_sync_word(const struct device *dev, const struct lora_mode return sx126x_hal_write_regs(dev, SX126X_REG_LORA_SYNC_WORD_MSB, buf, 2); } -static int sx126x_set_rx_gain(const struct device *dev, bool boosted) +-{ +- uint8_t val = boosted ? SX126X_RX_GAIN_BOOSTED : SX126X_RX_GAIN_POWER_SAVING; +/* RSSI / AGC band calibration (DS Sec 6.1.6, Table 6-4). + * + * Semtech: "The power seen by the SX1261/2 analog front-end is affected by @@ -965,7 +823,9 @@ index 30243ba5dc7..d512fb59afc 100644 + uint8_t sensi_adjust; /* 0x08AC, bits 7:2 */ + uint8_t gain_tune[SX126X_AGC_GAIN_TUNE_LEN]; /* 0x08F5 .. 0x08FB */ +}; -+ + +- return sx126x_hal_write_regs(dev, SX126X_REG_RX_GAIN, &val, 1); +-} +static const struct sx126x_agc_band_cal sx126x_agc_cal_high = { + .rssi_meas_cal_h = 0x01, + .rssi_meas_cal_l = 0x53, @@ -973,7 +833,11 @@ index 30243ba5dc7..d512fb59afc 100644 + .sensi_adjust = SX126X_AGC_SENSI_ADJUST_HIGH_BAND, + .gain_tune = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, +}; -+ + +-static int sx126x_set_tx(const struct device *dev, uint32_t timeout_ms) +-{ +- uint32_t timeout = SX126X_MS_TO_TIMEOUT(timeout_ms); +- uint8_t buf[3]; +static const struct sx126x_agc_band_cal sx126x_agc_cal_low = { + .rssi_meas_cal_h = 0x01, + .rssi_meas_cal_l = 0x27, @@ -982,30 +846,44 @@ index 30243ba5dc7..d512fb59afc 100644 + /* GainTune_1_2, _3_4, _5_6, _7_8, _9_10, _11_12, _13 */ + .gain_tune = { 0xDE, 0xE2, 0x32, 0x44, 0x33, 0x34, 0x04 }, +}; -+ + +- sys_put_be24(timeout, buf); +- return sx126x_hal_write_cmd(dev, SX126X_CMD_SET_TX, buf, 3); +-} +/* Band split. The datasheet only characterises 470-490 and 868-915, so any + * threshold between them is a judgement call; 600 MHz sits clear of both the + * 433/470 group and the 779/868/915 group that sx126x_calibrate_image() + * already distinguishes. */ +#define SX126X_AGC_LOW_BAND_MAX_HZ 600000000 -+ + +-static int sx126x_set_stop_timer_on_preamble(const struct device *dev, +- bool enable) +static const struct sx126x_agc_band_cal *sx126x_agc_cal_for(uint32_t freq) -+{ + { +- uint8_t val = enable; +- +- return sx126x_hal_write_cmd(dev, SX126X_CMD_STOP_TIMER_ON_PREAMBLE, +- &val, 1); + return (freq < SX126X_AGC_LOW_BAND_MAX_HZ) ? &sx126x_agc_cal_low + : &sx126x_agc_cal_high; -+} -+ + } + +-static int sx126x_set_rx_duty_cycle(const struct device *dev, +- uint32_t rx_period, uint32_t sleep_period) +/* Apply the band's RSSI/AGC calibration. Caches AgcSensiAdjust for the RX + * gain writes, which share register 0x08AC, but does NOT touch 0x08AC itself + * -- sx126x_set_rx_gain() owns that register and runs later in + * sx126x_config(). */ +static int sx126x_apply_agc_band_cal(const struct device *dev, uint32_t freq) -+{ + { +- uint8_t buf[6]; + struct sx126x_data *data = dev->data; + const struct sx126x_agc_band_cal *cal = sx126x_agc_cal_for(freq); + uint8_t val; -+ int ret; -+ + int ret; + +- sys_put_be24(rx_period, &buf[0]); +- sys_put_be24(sleep_period, &buf[3]); + data->agc_sensi_adjust = cal->sensi_adjust; + + /* Bits 4:0 only -- preserve the rest of 0x089C. */ @@ -1024,13 +902,15 @@ index 30243ba5dc7..d512fb59afc 100644 + if (ret < 0) { + return ret; + } -+ + +- ret = sx126x_set_stop_timer_on_preamble(dev, true); + val = cal->gforst_powthr; + ret = sx126x_hal_write_regs(dev, SX126X_REG_AGC_GFORST_POWTHR, &val, 1); -+ if (ret < 0) { -+ return ret; -+ } -+ + if (ret < 0) { + return ret; + } + +- return sx126x_hal_write_cmd(dev, SX126X_CMD_SET_RX_DUTY_CYCLE, buf, 6); + return sx126x_hal_write_regs(dev, SX126X_REG_AGC_GAIN_TUNE_1_2, + cal->gain_tune, + SX126X_AGC_GAIN_TUNE_LEN); @@ -1038,13 +918,12 @@ index 30243ba5dc7..d512fb59afc 100644 + +/* Byte to write to 0x08AC: band AgcSensiAdjust in bits 7:2, gain mode in 1:0. */ +static uint8_t sx126x_rx_gain_byte(const struct device *dev, bool boosted) - { -- uint8_t val = boosted ? SX126X_RX_GAIN_BOOSTED : SX126X_RX_GAIN_POWER_SAVING; ++{ + struct sx126x_data *data = dev->data; + + return SX126X_RX_GAIN_VAL(data->agc_sensi_adjust, boosted); +} - ++ +static int sx126x_set_rx_gain(const struct device *dev, bool boosted) +{ + uint8_t val = sx126x_rx_gain_byte(dev, boosted); @@ -1059,10 +938,20 @@ index 30243ba5dc7..d512fb59afc 100644 + * wakes are chip-internal warm starts the host never sees, which is + * why 0x08AC is in the retention list installed by + * sx126x_install_retention_list() at chip init. */ - return sx126x_hal_write_regs(dev, SX126X_REG_RX_GAIN, &val, 1); ++ return sx126x_hal_write_regs(dev, SX126X_REG_RX_GAIN, &val, 1); ++} ++ ++static int sx126x_set_tx(const struct device *dev, uint32_t timeout_ms) ++{ ++ uint32_t timeout = SX126X_MS_TO_TIMEOUT(timeout_ms); ++ uint8_t buf[3]; ++ ++ sys_put_be24(timeout, buf); ++ return sx126x_hal_write_cmd(dev, SX126X_CMD_SET_TX, buf, 3); } -@@ -296,7 +637,7 @@ static int sx126x_get_packet_status(const struct device *dev, + static int sx126x_set_rx(const struct device *dev, uint32_t timeout_ms) +@@ -415,7 +645,7 @@ static int sx126x_get_packet_status(const struct device *dev, uint8_t buf[3]; int ret; @@ -1071,58 +960,23 @@ index 30243ba5dc7..d512fb59afc 100644 if (ret == 0) { /* RSSI is -value/2 dBm */ *rssi = -((int16_t)buf[0] >> 1); -@@ -307,6 +648,140 @@ static int sx126x_get_packet_status(const struct device *dev, - return ret; - } +@@ -443,6 +673,12 @@ static int sx126x_add_reg_to_retention(const struct device *dev, uint16_t addr) + + n = buf[0]; -+/* Add one register to the chip's warm-start retention list (DS sec 9.6). -+ * -+ * Read-modify-write, so a second call cannot clobber what an earlier one -+ * installed and the four-entry capacity is enforced rather than assumed. -+ * The previous implementation blind-wrote a one-entry list, which was -+ * correct only for as long as 0x08AC stayed the sole entry. -+ * -+ * Called from sx126x_chip_init() and again after any Calibrate(ALL) -- never -+ * from the packet path. The list lives in retention memory and survives -+ * every RxDutyCycle sleep->RX wake by construction, so re-writing it per -+ * received packet only added SPI to the duty-cycle re-arm window, which is -+ * the one place on a duty-cycled node where deaf time is already tight. */ -+static int sx126x_add_reg_to_retention(const struct device *dev, uint16_t addr) -+{ -+ uint8_t buf[1 + 2 * SX126X_MAX_RETENTION_REGS]; -+ uint8_t n; -+ int ret; -+ -+ ret = sx126x_hal_read_regs(dev, SX126X_REG_RETENTION_LIST, buf, sizeof(buf)); -+ if (ret < 0) { -+ return ret; -+ } -+ -+ n = buf[0]; -+ + /* A blank or corrupt count byte would otherwise be taken as a live + * entry count and index past the end of buf[]. */ + if (n > SX126X_MAX_RETENTION_REGS) { + n = 0; + } + -+ for (uint8_t i = 0; i < n; i++) { -+ if (sys_get_be16(&buf[1 + 2 * i]) == addr) { -+ return 0; -+ } -+ } -+ -+ if (n >= SX126X_MAX_RETENTION_REGS) { -+ LOG_ERR("Retention list full"); -+ return -ENOSPC; -+ } -+ -+ buf[0] = n + 1; -+ sys_put_be16(addr, &buf[1 + 2 * n]); -+ -+ return sx126x_hal_write_regs(dev, SX126X_REG_RETENTION_LIST, buf, sizeof(buf)); -+} -+ + for (uint8_t i = 0; i < n; i++) { + if (sys_get_be16(&buf[1 + 2 * i]) == addr) { + return 0; +@@ -460,6 +696,92 @@ static int sx126x_add_reg_to_retention(const struct device *dev, uint16_t addr) + return sx126x_hal_write_regs(dev, SX126X_REG_RETENTION_LIST, buf, sizeof(buf)); + } + +/* The registers that must survive a warm-start wake. + * + * 0x08AC (Rx gain): DS sec 9.6 calls this mandatory when using @@ -1212,7 +1066,7 @@ index 30243ba5dc7..d512fb59afc 100644 static int sx126x_chip_init(const struct device *dev) { const struct sx126x_hal_config *config = dev->config; -@@ -367,6 +842,26 @@ static int sx126x_chip_init(const struct device *dev) +@@ -520,6 +842,26 @@ static int sx126x_chip_init(const struct device *dev) return ret; } @@ -1239,7 +1093,7 @@ index 30243ba5dc7..d512fb59afc 100644 /* Set packet type to LoRa */ ret = sx126x_set_packet_type(dev, SX126X_PACKET_TYPE_LORA); if (ret < 0) { -@@ -374,10 +869,23 @@ static int sx126x_chip_init(const struct device *dev) +@@ -527,10 +869,23 @@ static int sx126x_chip_init(const struct device *dev) return ret; } @@ -1266,18 +1120,32 @@ index 30243ba5dc7..d512fb59afc 100644 if (ret < 0) { LOG_ERR("Set IRQ params failed: %d", ret); return ret; -@@ -390,6 +898,10 @@ static int sx126x_chip_init(const struct device *dev) +@@ -543,21 +898,9 @@ static int sx126x_chip_init(const struct device *dev) return ret; } +- /* +- * Preserve RX gain and TX modulation settings across warm-start +- * sleep cycles by adding them to the chip's retention list. +- */ +- ret = sx126x_add_reg_to_retention(dev, SX126X_REG_RX_GAIN); +- if (ret < 0) { +- LOG_ERR("Add RX_GAIN to retention failed: %d", ret); +- return ret; +- } +- +- ret = sx126x_add_reg_to_retention(dev, SX126X_REG_TX_MODULATION); +- if (ret < 0) { +- LOG_ERR("Add TX_MODULATION to retention failed: %d", ret); +- return ret; +- } + /* Install the warm-start retention list once, here, rather than on + * every duty-cycle re-arm. */ + sx126x_install_retention_list(dev); -+ + LOG_INF("SX126x initialized"); return 0; - } -@@ -398,15 +910,44 @@ static void sx126x_dio1_callback(const struct device *dev) +@@ -567,15 +910,44 @@ static void sx126x_dio1_callback(const struct device *dev) { struct sx126x_data *data = dev->data; @@ -1324,7 +1192,7 @@ index 30243ba5dc7..d512fb59afc 100644 sx126x_hal_set_rf_switch(dev, enable, tx); } } -@@ -426,6 +967,7 @@ static void sx126x_disconnect_rf_gpios(const struct device *dev) +@@ -595,6 +967,7 @@ static void sx126x_disconnect_rf_gpios(const struct device *dev) sx126x_disconnect_gpio(&config->antenna_enable); sx126x_disconnect_gpio(&config->tx_enable); sx126x_disconnect_gpio(&config->rx_enable); @@ -1332,7 +1200,7 @@ index 30243ba5dc7..d512fb59afc 100644 } static int sx126x_reconnect_rf_gpios(const struct device *dev) -@@ -451,10 +993,238 @@ static int sx126x_reconnect_rf_gpios(const struct device *dev) +@@ -620,10 +993,238 @@ static int sx126x_reconnect_rf_gpios(const struct device *dev) return ret; } @@ -1571,7 +1439,33 @@ index 30243ba5dc7..d512fb59afc 100644 static int sx126x_set_sleep(const struct device *dev) { struct sx126x_data *data = dev->data; -@@ -533,8 +1303,16 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta +@@ -697,94 +1298,214 @@ static void sx126x_handle_irq_tx_done(const struct device *dev) + k_msgq_put(&data->tx_msgq, &result, K_NO_WAIT); + } + +-static void sx126x_rx_restart(const struct device *dev) +-{ +- struct sx126x_data *data = dev->data; +- +- if (atomic_get(&data->state) == SX126X_STATE_RX_DUTY_CYCLE) { +- int ret; +- +- ret = sx126x_set_rx_duty_cycle(dev, +- data->duty_cycle.rx_period, +- data->duty_cycle.sleep_period); +- if (ret < 0) { +- data->rx_cb = NULL; +- data->rx_cb_user_data = NULL; +- atomic_set(&data->state, SX126X_REST_STATE); +- sx126x_set_sleep(dev); +- } +- } else { +- sx126x_set_rx(dev, 0); +- } +-} +- + static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_status) + { struct sx126x_data *data = dev->data; struct sx126x_rx_result result = { 0 }; uint8_t payload_len = 0, offset = 0; @@ -1588,13 +1482,13 @@ index 30243ba5dc7..d512fb59afc 100644 /* Get received packet info */ ret = sx126x_get_rx_buffer_status(dev, &payload_len, &offset); if (ret < 0) { -@@ -544,9 +1322,26 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta - /* Get signal quality */ - sx126x_get_packet_status(dev, &result.rssi, &result.snr); - -- /* Check for CRC error */ -- if (irq_status & SX126X_IRQ_CRC_ERR) { -- LOG_WRN("CRC error"); + LOG_ERR("Failed to get RX buffer status"); + result.status = ret; +- goto out; ++ } else { ++ /* Get signal quality */ ++ sx126x_get_packet_status(dev, &result.rssi, &result.snr); ++ + /* Reject CRC errors AND header errors. A header error with no + * valid header means no payload was written to the FIFO, so + * GetRxBufferStatus still points at the PREVIOUS packet -- reading @@ -1615,29 +1509,44 @@ index 30243ba5dc7..d512fb59afc 100644 + * counted; otherwise result.status stays 0 and falls + * through both delivery arms below (silent drop). */ + LOG_DBG("RX done: zero-length payload"); - result.status = -EIO; - } else { - /* Read payload into shared buffer */ -@@ -564,23 +1359,55 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta - } ++ result.status = -EIO; ++ } else { ++ /* Read payload into shared buffer */ ++ result.len = MIN(payload_len, sizeof(data->rx_buf)); ++ ret = sx126x_hal_read_buffer(dev, offset, ++ data->rx_buf, result.len); ++ if (ret < 0) { ++ LOG_ERR("Failed to read RX buffer"); ++ result.status = ret; ++ } else { ++ result.status = result.len; ++ LOG_DBG("RX done: %d bytes, RSSI=%d, SNR=%d", ++ result.len, result.rssi, result.snr); ++ } ++ } } +- sx126x_get_packet_status(dev, &result.rssi, &result.snr); +- +- if (irq_status & SX126X_IRQ_CRC_ERR) { +- LOG_WRN("CRC error"); +- result.status = -EIO; +- goto out; +- } + k_mutex_lock(&data->lock, K_FOREVER); + rx_cb = data->rx_cb; + rx_cb_user_data = data->rx_cb_user_data; + rx_cb_gen = atomic_get(&data->rx_cb_gen); + k_mutex_unlock(&data->lock); -+ - /* Handle async callback or signal sync receiver */ -- if (data->rx_cb != NULL) { -- /* -- * Async mode: only report valid packets. -- * CRC/read failures are dropped and RX is restarted. -- */ -- if (result.status > 0) { -- data->rx_cb(dev, data->rx_buf, result.len, -- result.rssi, result.snr, -- data->rx_cb_user_data); + +- result.len = MIN(payload_len, sizeof(data->rx_buf)); +- ret = sx126x_hal_read_buffer(dev, offset, data->rx_buf, result.len); +- if (ret < 0) { +- LOG_ERR("Failed to read RX buffer"); +- result.status = ret; +- goto out; +- } ++ /* Handle async callback or signal sync receiver */ + if (rx_cb != NULL) { + /* Restart RX FIRST -- minimise the deaf window. + * The callback (mesh processing) can take 100s of us; @@ -1650,27 +1559,28 @@ index 30243ba5dc7..d512fb59afc 100644 + * sx126x_retention_debug_check(). Compiles to nothing + * without CONFIG_ZEPHCORE_LORA_RETENTION_DEBUG. */ + sx126x_retention_debug_check(dev, "dc-rx", true); - } -- /* Restart RX unless the callback stopped reception */ -- if (data->rx_cb != NULL) { -- sx126x_set_rx(dev, 0); ++ } + sx126x_restart_rx(dev, data); -+ + +- result.status = result.len; +- LOG_DBG("RX done: %d bytes, RSSI=%d, SNR=%d", +- result.len, result.rssi, result.snr); + k_mutex_lock(&data->lock, K_FOREVER); + if ((data->rx_cb != rx_cb) || + (atomic_get(&data->rx_cb_gen) != rx_cb_gen)) { + rx_cb = NULL; + } + k_mutex_unlock(&data->lock); -+ + +-out: +- if (data->rx_cb == NULL) { + if (rx_cb != NULL && result.status < 0) { + rx_cb(dev, NULL, 0, result.rssi, result.snr, rx_cb_user_data); + } else if (rx_cb != NULL && result.status > 0) { + rx_cb(dev, data->rx_buf, result.len, result.rssi, result.snr, + rx_cb_user_data); - } - } else { -- /* Sync mode */ ++ } ++ } else { + /* Sync mode. + * + * SetStandby first: DS sec 13.1.1 states SetSleep "can be sent @@ -1686,8 +1596,22 @@ index 30243ba5dc7..d512fb59afc 100644 + sx126x_set_standby(dev, SX126X_STANDBY_RC); sx126x_set_sleep(dev); k_msgq_put(&data->rx_msgq, &result, K_NO_WAIT); +- return; +- } +- +- /* Async mode: deliver valid packets, drop errors silently */ +- if (result.status > 0) { +- data->rx_cb(dev, data->rx_buf, result.len, +- result.rssi, result.snr, +- data->rx_cb_user_data); +- } +- +- /* Restart reception unless the callback stopped it */ +- if (data->rx_cb != NULL) { +- sx126x_rx_restart(dev); } -@@ -589,8 +1416,99 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta + } + static void sx126x_handle_irq_timeout(const struct device *dev) { struct sx126x_data *data = dev->data; @@ -1695,7 +1619,13 @@ index 30243ba5dc7..d512fb59afc 100644 + bool was_cad_rx; LOG_DBG("Timeout"); -+ + +- if (atomic_get(&data->state) == SX126X_STATE_RX_DUTY_CYCLE) { +- /* +- * Preamble was detected but full packet reception failed. +- * The radio fell back to STDBY_RC — restart the duty cycle. +- */ +- sx126x_rx_restart(dev); + /* cadTimeout expired with nothing decoded: the CAD_RX detection had no + * packet behind it. Resolved first, before anything below re-arms the + * node's normal Rx mode, and the verdict is kept because the duty-cycle @@ -1781,13 +1711,10 @@ index 30243ba5dc7..d512fb59afc 100644 + LOG_WRN("Non-DC timeout re-arm failed (%d)", ret); + (void)sx126x_set_sleep(dev); + } -+ return; -+ } -+ - sx126x_set_sleep(dev); + return; + } - if (data->tx_async_signal != NULL) { -@@ -630,13 +1548,97 @@ static void sx126x_irq_work_handler(struct k_work *work) +@@ -827,13 +1548,97 @@ static void sx126x_irq_work_handler(struct k_work *work) } if (irq_status & SX126X_IRQ_RX_DONE) { @@ -1885,22 +1812,10 @@ index 30243ba5dc7..d512fb59afc 100644 /* Re-enable the DIO1 interrupt for the next event (unless sleeping) */ if (atomic_get(&data->state) != SX126X_REST_STATE) { sx126x_hal_dio1_irq_enable(dev); -@@ -648,6 +1650,7 @@ static int sx126x_lora_config(const struct device *dev, - { - struct sx126x_data *data = dev->data; - const struct sx126x_hal_config *hal_config = dev->config; -+ uint8_t bw_reg; - bool ldro; - int ret; - -@@ -679,25 +1682,45 @@ static int sx126x_lora_config(const struct device *dev, +@@ -894,10 +1699,19 @@ static int sx126x_lora_config(const struct device *dev, goto out; } -- /* Configure PA and TX power based on chip variant and frequency */ -- ret = sx126x_hal_configure_tx_params(dev, config->tx_power, -- config->frequency, -- SX126X_RAMP_200_US); + /* Install the band's RSSI/AGC calibration (DS Sec 6.1.6) before the + * RX gain write below, which consumes the AgcSensiAdjust this caches. + * Re-run on every config so a runtime frequency change switches bands @@ -1910,41 +1825,38 @@ index 30243ba5dc7..d512fb59afc 100644 + goto out; + } + -+ /* Configure PA and TX power based on chip variant and frequency */ -+ ret = sx126x_hal_configure_tx_params(dev, config->tx_power, -+ config->frequency, + /* Configure PA and TX power based on chip variant and frequency */ + ret = sx126x_hal_configure_tx_params(dev, config->tx_power, + config->frequency, +- SX126X_RAMP_200_US); + SX126X_TX_RAMP_TIME); if (ret < 0) { goto out; } +@@ -915,13 +1729,19 @@ static int sx126x_lora_config(const struct device *dev, + goto out; + } - /* Set modulation parameters */ -+ ret = bandwidth_to_reg(config->bandwidth, &bw_reg); -+ if (ret < 0) { -+ LOG_ERR("Unsupported bandwidth: %d", config->bandwidth); -+ goto out; -+ } - ldro = should_enable_ldro(config->datarate, config->bandwidth, hal_config); - ret = sx126x_set_modulation_params(dev, - config->datarate, -- bandwidth_to_reg(config->bandwidth), -+ bw_reg, - config->coding_rate, - ldro); +- /* Set sync word */ +- ret = sx126x_set_sync_word(dev, config); ++ /* Sec 15.1 TX Modulation workaround (datasheet errata). */ ++ ret = sx126x_apply_tx_modulation_workaround(dev, config->bandwidth); if (ret < 0) { goto out; } -+ /* Sec 15.1 TX Modulation workaround (datasheet errata). */ -+ ret = sx126x_apply_tx_modulation_workaround(dev, config->bandwidth); +- /* Set RX gain */ ++ /* Set sync word */ ++ ret = sx126x_set_sync_word(dev, config); + if (ret < 0) { + goto out; + } + - /* Set sync word */ - ret = sx126x_set_sync_word(dev, config->public_network); ++ /* Set RX gain */ + ret = sx126x_set_rx_gain(dev, hal_config->rx_boosted); if (ret < 0) { -@@ -721,6 +1744,10 @@ out: + goto out; +@@ -938,6 +1758,10 @@ out: return ret; } @@ -1955,7 +1867,7 @@ index 30243ba5dc7..d512fb59afc 100644 static int sx126x_lora_send_async(const struct device *dev, uint8_t *data_buf, uint32_t data_len, struct k_poll_signal *async) -@@ -738,11 +1765,27 @@ static int sx126x_lora_send_async(const struct device *dev, +@@ -955,11 +1779,27 @@ static int sx126x_lora_send_async(const struct device *dev, return -EINVAL; } @@ -1985,7 +1897,7 @@ index 30243ba5dc7..d512fb59afc 100644 k_mutex_lock(&data->lock, K_FOREVER); ret = sx126x_ensure_ready(dev); -@@ -752,6 +1795,59 @@ static int sx126x_lora_send_async(const struct device *dev, +@@ -969,6 +1809,59 @@ static int sx126x_lora_send_async(const struct device *dev, return ret; } @@ -2045,7 +1957,7 @@ index 30243ba5dc7..d512fb59afc 100644 data->tx_async_signal = async; k_msgq_purge(&data->tx_msgq); -@@ -777,8 +1873,99 @@ static int sx126x_lora_send_async(const struct device *dev, +@@ -999,8 +1892,90 @@ static int sx126x_lora_send_async(const struct device *dev, /* Enable antenna and set TX path */ sx126x_set_rf_path(dev, true, true); @@ -2082,15 +1994,6 @@ index 30243ba5dc7..d512fb59afc 100644 + goto out_error; + } + -+ /* Sec 15.1 workaround, re-asserted per the datasheet's "before each -+ * packet transmission". Same reasoning as the PA config above: the -+ * adapter's config cache means lora_config() may not have run for many -+ * transmits, and sx126x_reset_agc()'s warm sleep can have intervened. */ -+ ret = sx126x_apply_tx_modulation_workaround(dev, data->config.bandwidth); -+ if (ret < 0) { -+ goto out_error; -+ } -+ +#ifdef CONFIG_LORA_SX126X_NATIVE_STANDALONE /* STM32WL HAL clamps in its own configure_tx_params; its config has no is_sx1261 */ + /* Sec 15.2 TX Clamp workaround (datasheet errata) -- SX1262 only. + * Set bits [4:1] of register 0x08D8 before SetTx to prevent @@ -2147,7 +2050,7 @@ index 30243ba5dc7..d512fb59afc 100644 if (ret < 0) { goto out_error; } -@@ -847,6 +2034,8 @@ static int sx126x_lora_recv(const struct device *dev, uint8_t *data_buf, +@@ -1069,6 +2044,8 @@ static int sx126x_lora_recv(const struct device *dev, uint8_t *data_buf, } data->rx_cb = NULL; @@ -2156,7 +2059,7 @@ index 30243ba5dc7..d512fb59afc 100644 k_msgq_purge(&data->rx_msgq); /* Set packet parameters for variable length reception */ -@@ -918,6 +2107,8 @@ static int sx126x_lora_recv_async(const struct device *dev, +@@ -1140,6 +2117,8 @@ static int sx126x_lora_recv_async(const struct device *dev, /* Stop async reception */ data->rx_cb = NULL; data->rx_cb_user_data = NULL; @@ -2165,7 +2068,7 @@ index 30243ba5dc7..d512fb59afc 100644 if (atomic_cas(&data->state, SX126X_STATE_RX, SX126X_STATE_IDLE)) { sx126x_set_standby(dev, SX126X_STANDBY_RC); sx126x_set_sleep(dev); -@@ -932,6 +2123,19 @@ static int sx126x_lora_recv_async(const struct device *dev, +@@ -1154,6 +2133,19 @@ static int sx126x_lora_recv_async(const struct device *dev, return -EINVAL; } @@ -2185,7 +2088,7 @@ index 30243ba5dc7..d512fb59afc 100644 if (!atomic_cas(&data->state, SX126X_REST_STATE, SX126X_STATE_RX)) { LOG_ERR("Busy"); k_mutex_unlock(&data->lock); -@@ -945,8 +2149,18 @@ static int sx126x_lora_recv_async(const struct device *dev, +@@ -1167,8 +2159,18 @@ static int sx126x_lora_recv_async(const struct device *dev, return ret; } @@ -2204,7 +2107,7 @@ index 30243ba5dc7..d512fb59afc 100644 /* Set packet parameters */ ret = sx126x_set_packet_params(dev, -@@ -959,6 +2173,7 @@ static int sx126x_lora_recv_async(const struct device *dev, +@@ -1181,6 +2183,7 @@ static int sx126x_lora_recv_async(const struct device *dev, SX126X_LORA_IQ_INVERTED : SX126X_LORA_IQ_STANDARD); if (ret < 0) { data->rx_cb = NULL; @@ -2212,7 +2115,7 @@ index 30243ba5dc7..d512fb59afc 100644 sx126x_set_sleep(dev); k_mutex_unlock(&data->lock); return ret; -@@ -971,11 +2186,21 @@ static int sx126x_lora_recv_async(const struct device *dev, +@@ -1193,221 +2196,25 @@ static int sx126x_lora_recv_async(const struct device *dev, ret = sx126x_set_rx(dev, 0); if (ret < 0) { data->rx_cb = NULL; @@ -2223,54 +2126,287 @@ index 30243ba5dc7..d512fb59afc 100644 return ret; } +- k_mutex_unlock(&data->lock); +- return 0; +-} +- +-/* +- * Read this if you have trouble making the duty cycle working reliably. +- * I have wasted many hours on this so I'm passing this along. +- * +- * The driver sets StopTimerOnPreamble = true before entering duty +- * cycle mode. This makes the radio stay in RX once a preamble is +- * detected, instead of going back to sleep when the RX window +- * expires. The radio then searches for the sync word within a +- * window sized by the receiver's preamble_len. This value must +- * match the sender's: a mismatch causes the demodulator to stop +- * searching before the sync word arrives. +- * +- * Matching preamble_len — OK (works even if RX joins mid-preamble) +- * +- * TX |#########< 100 preamble symbols >###########|SW|HDR| payload | +- * ^ ^ +- * RX ..sleep..|==RX==|<<< stays in RX >>>>>>>>>>>>| +- * ^ | +- * preamble detected | +- * timer stops (StopTimerOnPreamble)| +- * search window = 100 symbols | +- * sync word found -----------------+ +- * --> full packet received, RX_DONE +- * +- * Mismatched preamble_len (RX = 8, TX = 100) — FAIL +- * +- * TX |#########< 100 preamble symbols >###########|SW|HDR| payload | +- * ^ ^ +- * RX ..sleep..|==RX==|<<< stays in RX | +- * ^ but gives up here | +- * | |<-8->| | +- * preamble detected | +- * search window = only 8 symbols | +- * sync word NOT found within window +- * --> demodulator fails, packet lost +- * +- * See samples/drivers/lora/duty_cycle/ +- */ +-static void sx126x_duty_cycle_stop(const struct device *dev) +-{ +- /* +- * During duty cycle the BUSY pin stays asserted even in the +- * sleep phase. Wake the radio via a raw NSS edge (bypasses +- * BUSY check) so the standby command can go through +- * immediately. +- */ +- if (sx126x_hal_is_busy(dev)) { +- sx126x_hal_wakeup(dev); +- } +- sx126x_set_standby(dev, SX126X_STANDBY_RC); +- sx126x_set_sleep(dev); +-} +- +-static int sx126x_duty_cycle_start(const struct device *dev, +- k_timeout_t rx_period, +- k_timeout_t sleep_period) +-{ +- struct sx126x_data *data = dev->data; +- int ret; +- +- data->duty_cycle.rx_period = SX126X_MS_TO_TIMEOUT( +- k_ticks_to_ms_ceil32(rx_period.ticks)); +- data->duty_cycle.sleep_period = SX126X_MS_TO_TIMEOUT( +- k_ticks_to_ms_ceil32(sleep_period.ticks)); +- +- ret = sx126x_set_packet_params(dev, +- data->config.preamble_len, +- SX126X_LORA_HEADER_EXPLICIT, +- SX126X_MAX_PAYLOAD_LEN, +- data->config.packet_crc_disable ? +- SX126X_LORA_CRC_OFF : SX126X_LORA_CRC_ON, +- data->config.iq_inverted ? +- SX126X_LORA_IQ_INVERTED : +- SX126X_LORA_IQ_STANDARD); +- if (ret < 0) { +- goto out_error; +- } +- +- sx126x_set_rf_path(dev, true, false); +- +- ret = sx126x_set_rx_duty_cycle(dev, data->duty_cycle.rx_period, +- data->duty_cycle.sleep_period); +- if (ret < 0) { +- goto out_error; +- } + /* Re-apply RX gain after SetRx -- register 0x08AC is reset on every + * SetRx without retention, and lora_config sets gain before going to + * sleep, so without this the first packet would be received in + * power-saving gain regardless of DTS rx-boosted. */ + uint8_t gain = sx126x_rx_gain_byte(dev, data->rx_boost_enabled); -+ + +- k_mutex_unlock(&data->lock); +- return 0; + sx126x_hal_write_regs(dev, SX126X_REG_RX_GAIN, &gain, 1); -+ + +-out_error: +- data->rx_cb = NULL; +- atomic_set(&data->state, SX126X_REST_STATE); +- sx126x_set_sleep(dev); k_mutex_unlock(&data->lock); +- return ret; +-} +- +-static int sx126x_duty_cycle_enter(const struct device *dev, +- lora_recv_cb cb, void *user_data) +-{ +- struct sx126x_data *data = dev->data; +- int ret; +- +- if (!data->config_valid) { +- LOG_ERR("Not configured"); +- return -EINVAL; +- } +- +- if (!atomic_cas(&data->state, SX126X_REST_STATE, +- SX126X_STATE_RX_DUTY_CYCLE)) { +- LOG_ERR("Busy"); +- return -EBUSY; +- } +- +- k_mutex_lock(&data->lock, K_FOREVER); +- +- ret = sx126x_ensure_ready(dev); +- if (ret < 0) { +- k_mutex_unlock(&data->lock); +- atomic_set(&data->state, SX126X_REST_STATE); +- return ret; +- } +- +- data->rx_cb = cb; +- data->rx_cb_user_data = user_data; +- return 0; } -@@ -993,7 +2218,9 @@ static uint32_t sx126x_lora_airtime(const struct device *dev, uint32_t data_len) - } - /* Calculate symbol time in microseconds */ -- bw_hz = bandwidth_to_hz(data->config.bandwidth); -+ if (bandwidth_to_hz(data->config.bandwidth, &bw_hz) < 0) { -+ return 0; -+ } - sf = data->config.datarate; - - /* Symbol time = 2^SF / BW (seconds) */ -@@ -1051,7 +2278,7 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency, +-static int sx126x_lora_recv_duty_cycle(const struct device *dev, +- k_timeout_t rx_period, +- k_timeout_t sleep_period, +- uint8_t *data_buf, uint8_t size, +- k_timeout_t timeout, +- int16_t *rssi, int8_t *snr) +-{ +- struct sx126x_data *data = dev->data; +- struct sx126x_rx_result result; +- int ret; +- +- ret = sx126x_duty_cycle_enter(dev, NULL, NULL); +- if (ret < 0) { +- return ret; +- } +- +- k_msgq_purge(&data->rx_msgq); +- +- ret = sx126x_duty_cycle_start(dev, rx_period, sleep_period); +- if (ret < 0) { +- return ret; +- } +- +- /* Block until a packet arrives or timeout */ +- ret = k_msgq_get(&data->rx_msgq, &result, timeout); +- if (ret < 0) { +- LOG_DBG("RX duty cycle timeout"); +- sx126x_duty_cycle_stop(dev); +- return -EAGAIN; +- } +- +- sx126x_duty_cycle_stop(dev); +- +- if (result.status > 0) { +- int copy_len = MIN(result.status, size); +- +- memcpy(data_buf, data->rx_buf, copy_len); +- if (rssi != NULL) { +- *rssi = result.rssi; +- } +- if (snr != NULL) { +- *snr = result.snr; +- } +- return copy_len; +- } +- +- return result.status; +-} +- +-static int sx126x_lora_recv_duty_cycle_async(const struct device *dev, +- k_timeout_t rx_period, +- k_timeout_t sleep_period, +- lora_recv_cb cb, void *user_data) +-{ +- struct sx126x_data *data = dev->data; +- int ret; +- +- if (cb == NULL) { +- k_mutex_lock(&data->lock, K_FOREVER); +- data->rx_cb = NULL; +- data->rx_cb_user_data = NULL; +- if (atomic_cas(&data->state, SX126X_STATE_RX_DUTY_CYCLE, +- SX126X_STATE_IDLE)) { +- sx126x_duty_cycle_stop(dev); +- } +- k_mutex_unlock(&data->lock); +- return 0; +- } +- +- ret = sx126x_duty_cycle_enter(dev, cb, user_data); +- if (ret < 0) { +- return ret; +- } +- +- return sx126x_duty_cycle_start(dev, rx_period, sleep_period); +-} +- + static uint32_t sx126x_lora_airtime(const struct device *dev, uint32_t data_len) + { + struct sx126x_data *data = dev->data; +@@ -1491,7 +2298,7 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency, /* Set PA config and TX power */ ret = sx126x_hal_configure_tx_params(dev, tx_power, frequency, - SX126X_RAMP_200_US); + SX126X_TX_RAMP_TIME); if (ret < 0) { - sx126x_set_sleep(dev); - k_mutex_unlock(&data->lock); -@@ -1083,14 +2310,1076 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency, - return 0; + goto out_sleep; + } +@@ -1523,50 +2330,1135 @@ out_unlock: + return ret; } +-static DEVICE_API(lora, sx126x_lora_api) = { +- .config = sx126x_lora_config, +- .send = sx126x_lora_send, +- .send_async = sx126x_lora_send_async, +- .recv = sx126x_lora_recv, +- .recv_async = sx126x_lora_recv_async, +- .recv_duty_cycle = sx126x_lora_recv_duty_cycle, +- .recv_duty_cycle_async = sx126x_lora_recv_duty_cycle_async, +- .airtime = sx126x_lora_airtime, +- .test_cw = sx126x_lora_test_cw, +- .rssi = sx126x_lora_rssi, +-}; +/* -- Extension API (sx126x_ext.h) -- */ -+ + +-#ifdef CONFIG_PM_DEVICE +-static int sx126x_pm_action(const struct device *dev, +- enum pm_device_action action) +bool sx126x_is_chip_busy(const struct device *dev) -+{ + { +- switch (action) { +- case PM_DEVICE_ACTION_SUSPEND: +- sx126x_disconnect_rf_gpios(dev); +- return 0; +- case PM_DEVICE_ACTION_RESUME: +- return sx126x_reconnect_rf_gpios(dev); +- default: +- return -ENOTSUP; +- } + return sx126x_hal_is_busy(dev); -+} -+ + } +-#endif + +-static int sx126x_init(const struct device *dev) +int16_t sx126x_get_rssi_inst(const struct device *dev) -+{ -+ struct sx126x_data *data = dev->data; + { + struct sx126x_data *data = dev->data; + uint8_t buf[1]; -+ int ret; -+ + int ret; + +- /* Initialize data structures */ +- k_mutex_init(&data->lock); +- k_msgq_init(&data->tx_msgq, (char *)&data->tx_result, +- sizeof(struct sx126x_tx_result), 1); +- k_msgq_init(&data->rx_msgq, (char *)&data->rx_result, +- sizeof(struct sx126x_rx_result), 1); +- k_work_init(&data->irq_work, sx126x_irq_work_handler); +- data->dev = dev; +- atomic_set(&data->state, SX126X_STATE_IDLE); +- data->config_valid = false; + /* GPIO read -- bail before stalling wait_busy on the duty-cycle + * sleep phase. wait_busy's recovery would force-wake the chip and + * break duty cycle. */ @@ -3079,10 +3215,10 @@ index 30243ba5dc7..d512fb59afc 100644 + +/* -- Driver API: recv_duty_cycle -- */ + -+static int sx126x_lora_recv_duty_cycle(const struct device *dev, -+ k_timeout_t rx_period, -+ k_timeout_t sleep_period, -+ lora_recv_cb cb, void *user_data) ++static int sx126x_lora_recv_duty_cycle_async(const struct device *dev, ++ k_timeout_t rx_period, ++ k_timeout_t sleep_period, ++ lora_recv_cb cb, void *user_data) +{ + struct sx126x_data *data = dev->data; + int ret; @@ -3317,14 +3453,7 @@ index 30243ba5dc7..d512fb59afc 100644 + return 0; +} + - static DEVICE_API(lora, sx126x_lora_api) = { -- .config = sx126x_lora_config, -- .send = sx126x_lora_send, -- .send_async = sx126x_lora_send_async, -- .recv = sx126x_lora_recv, -- .recv_async = sx126x_lora_recv_async, -- .airtime = sx126x_lora_airtime, -- .test_cw = sx126x_lora_test_cw, ++static DEVICE_API(lora, sx126x_lora_api) = { + .config = sx126x_lora_config, + .send = sx126x_lora_send, + .send_async = sx126x_lora_send_async, @@ -3333,27 +3462,44 @@ index 30243ba5dc7..d512fb59afc 100644 + .airtime = sx126x_lora_airtime, + .cad = sx126x_lora_cad, + .cad_async = sx126x_lora_cad_async, -+ .recv_duty_cycle = sx126x_lora_recv_duty_cycle, ++ .recv_duty_cycle_async = sx126x_lora_recv_duty_cycle_async, ++ .rssi = sx126x_lora_rssi, + .test_cw = sx126x_lora_test_cw, - }; - - #ifdef CONFIG_PM_DEVICE -@@ -1112,6 +3401,7 @@ static int sx126x_pm_action(const struct device *dev, - static int sx126x_init(const struct device *dev) - { - struct sx126x_data *data = dev->data; ++}; ++ ++#ifdef CONFIG_PM_DEVICE ++static int sx126x_pm_action(const struct device *dev, ++ enum pm_device_action action) ++{ ++ switch (action) { ++ case PM_DEVICE_ACTION_SUSPEND: ++ sx126x_disconnect_rf_gpios(dev); ++ return 0; ++ case PM_DEVICE_ACTION_RESUME: ++ return sx126x_reconnect_rf_gpios(dev); ++ default: ++ return -ENOTSUP; ++ } ++} ++#endif ++ ++static int sx126x_init(const struct device *dev) ++{ ++ struct sx126x_data *data = dev->data; + const struct sx126x_hal_config *config = dev->config; - int ret; - - /* Initialize data structures */ -@@ -1121,9 +3411,33 @@ static int sx126x_init(const struct device *dev) - k_msgq_init(&data->rx_msgq, (char *)&data->rx_result, - sizeof(struct sx126x_rx_result), 1); - k_work_init(&data->irq_work, sx126x_irq_work_handler); ++ int ret; ++ ++ /* Initialize data structures */ ++ k_mutex_init(&data->lock); ++ k_msgq_init(&data->tx_msgq, (char *)&data->tx_result, ++ sizeof(struct sx126x_tx_result), 1); ++ k_msgq_init(&data->rx_msgq, (char *)&data->rx_result, ++ sizeof(struct sx126x_rx_result), 1); ++ k_work_init(&data->irq_work, sx126x_irq_work_handler); + k_sem_init(&data->cad_sem, 0, 1); - data->dev = dev; - atomic_set(&data->state, SX126X_STATE_IDLE); - data->config_valid = false; ++ data->dev = dev; ++ atomic_set(&data->state, SX126X_STATE_IDLE); ++ data->config_valid = false; + data->rx_cb = NULL; + data->rx_cb_user_data = NULL; + atomic_set(&data->rx_cb_gen, 0); @@ -3375,12 +3521,12 @@ index 30243ba5dc7..d512fb59afc 100644 + /* Start dedicated DIO1 work queue */ + k_work_queue_start(&data->dio1_wq, sx126x_dio1_wq_stack, + K_THREAD_STACK_SIZEOF(sx126x_dio1_wq_stack), -+ CONFIG_SYSTEM_WORKQUEUE_PRIORITY, NULL); -+ k_thread_name_set(&data->dio1_wq.thread, "sx126x_dio1"); ++ CONFIG_SYSTEM_WORKQUEUE_PRIORITY, ++ &(const struct k_work_queue_config){ .name = "sx126x_dio1" }); /* Initialize HAL */ ret = sx126x_hal_init(dev); -@@ -1185,6 +3499,8 @@ static int sx126x_init(const struct device *dev) +@@ -1628,6 +3520,8 @@ static int sx126x_init(const struct device *dev) {0}), \ .rx_enable = GPIO_DT_SPEC_INST_GET_OR(inst, rx_enable_gpios, \ {0}), \ @@ -3389,7 +3535,7 @@ index 30243ba5dc7..d512fb59afc 100644 .dio2_tx_enable = DT_INST_PROP(inst, dio2_tx_enable), \ .dio3_tcxo_enable = DT_INST_NODE_HAS_PROP(inst, dio3_tcxo_voltage), \ .dio3_tcxo_voltage = DT_INST_PROP_OR(inst, dio3_tcxo_voltage, 0), \ -@@ -1239,6 +3555,8 @@ DT_INST_FOREACH_STATUS_OKAY_VARGS(SX126X_INIT, true) +@@ -1682,6 +3576,8 @@ DT_INST_FOREACH_STATUS_OKAY_VARGS(SX126X_INIT, true) {0}), \ .rx_enable = GPIO_DT_SPEC_INST_GET_OR(inst, rx_enable_gpios, \ {0}), \ @@ -3399,17 +3545,18 @@ index 30243ba5dc7..d512fb59afc 100644 .dio3_tcxo_enable = DT_INST_NODE_HAS_PROP(inst, dio3_tcxo_voltage), \ .dio3_tcxo_voltage = DT_INST_PROP_OR(inst, dio3_tcxo_voltage, 0), \ diff --git a/drivers/lora/native/sx126x/sx126x.h b/drivers/lora/native/sx126x/sx126x.h -index 9dbf3f26586..1d528604d1c 100644 +index 0122958c32a..bac80103ff7 100644 --- a/drivers/lora/native/sx126x/sx126x.h +++ b/drivers/lora/native/sx126x/sx126x.h -@@ -56,13 +56,143 @@ struct sx126x_data { +@@ -57,6 +57,7 @@ struct sx126x_data { /* Async RX callback */ lora_recv_cb rx_cb; void *rx_cb_user_data; + atomic_t rx_cb_gen; - /* Async TX signal */ - struct k_poll_signal *tx_async_signal; + /* RX duty cycle parameters (in SX126x timeout ticks) */ + struct { +@@ -69,7 +70,136 @@ struct sx126x_data { /* Deferred work for interrupt handling */ struct k_work irq_work; @@ -3655,7 +3802,7 @@ index e4b26ba60bd..558890ad54c 100644 uint8_t dio3_tcxo_voltage; bool dio2_tx_enable; diff --git a/drivers/lora/native/sx126x/sx126x_hal_common.c b/drivers/lora/native/sx126x/sx126x_hal_common.c -index b2dfc0d75b5..8ab8a0f5738 100644 +index 19c71594193..5f7eece3930 100644 --- a/drivers/lora/native/sx126x/sx126x_hal_common.c +++ b/drivers/lora/native/sx126x/sx126x_hal_common.c @@ -41,11 +41,29 @@ static int spi_transfer(const struct spi_dt_spec *spi, @@ -3692,10 +3839,10 @@ index b2dfc0d75b5..8ab8a0f5738 100644 return 0; diff --git a/drivers/lora/native/sx126x/sx126x_hal_stm32wl.c b/drivers/lora/native/sx126x/sx126x_hal_stm32wl.c -index c99a7204a72..08d20e44ae4 100644 +index b7ffbac7a6e..382acfc50b9 100644 --- a/drivers/lora/native/sx126x/sx126x_hal_stm32wl.c +++ b/drivers/lora/native/sx126x/sx126x_hal_stm32wl.c -@@ -246,6 +246,15 @@ int sx126x_hal_init(const struct device *dev) +@@ -254,6 +254,15 @@ int sx126x_hal_init(const struct device *dev) return ret; } @@ -3712,7 +3859,7 @@ index c99a7204a72..08d20e44ae4 100644 return 0; } diff --git a/drivers/lora/native/sx126x/sx126x_regs.h b/drivers/lora/native/sx126x/sx126x_regs.h -index 7f55c9b96e2..155d7dc2d81 100644 +index 1e6146f751f..5992e99969b 100644 --- a/drivers/lora/native/sx126x/sx126x_regs.h +++ b/drivers/lora/native/sx126x/sx126x_regs.h @@ -175,10 +175,45 @@ @@ -3762,29 +3909,6 @@ index 7f55c9b96e2..155d7dc2d81 100644 /* TX Clamp Config (workaround for SX1262) */ #define SX126X_REG_TX_CLAMP_CFG 0x08D8 -@@ -219,9 +254,19 @@ - #define SX126X_CMD_STATUS_EXEC_FAILURE 0x05 - #define SX126X_CMD_STATUS_CMD_TX_DONE 0x06 - --/* Timeout Special Values (in units of 15.625 us) */ --#define SX126X_RX_TIMEOUT_CONTINUOUS 0x000000 --#define SX126X_RX_TIMEOUT_SINGLE 0xFFFFFF -+/* Timeout special values for SetRx/SetTx (DS Table 13-9); any other value is -+ * a real timeout in units of 15.625 us. -+ * -+ * 0x000000 Rx Single -- chip returns to STDBY_RC once a packet is -+ * received, and is deaf until the host re-arms. -+ * 0xFFFFFF Rx Continuous -- chip stays in RX and searches for the next -+ * packet until the host changes mode. -+ * -+ * These two names were transposed in the upstream v4.4 driver we forked from, -+ * so every "continuous" RX here was really Rx Single. Upstream main fixed it -+ * the same way; see the patch preamble. */ -+#define SX126X_RX_TIMEOUT_SINGLE 0x000000 -+#define SX126X_RX_TIMEOUT_CONTINUOUS 0xFFFFFF - #define SX126X_TX_TIMEOUT_NONE 0x000000 - - /* Convert milliseconds to timeout ticks */ diff --git a/dts/bindings/lora/semtech,sx126x-base.yaml b/dts/bindings/lora/semtech,sx126x-base.yaml index 2d13d758695..04390be84e5 100644 --- a/dts/bindings/lora/semtech,sx126x-base.yaml diff --git a/zephcore/patches/zephyr/0007-spi-gpio-native-linux.patch b/zephcore/patches/zephyr/0007-spi-gpio-native-linux.patch index 08f06ed..1318f97 100644 --- a/zephcore/patches/zephyr/0007-spi-gpio-native-linux.patch +++ b/zephcore/patches/zephyr/0007-spi-gpio-native-linux.patch @@ -1,7 +1,44 @@ +diff --git a/drivers/gpio/CMakeLists.txt b/drivers/gpio/CMakeLists.txt +index eebf507541f..891cfda877b 100644 +--- a/drivers/gpio/CMakeLists.txt ++++ b/drivers/gpio/CMakeLists.txt +@@ -165,3 +165,13 @@ if(CONFIG_GPIO_SC18IS606) + zephyr_library_include_directories(${ZEPHYR_BASE}/drivers/mfd) + zephyr_library_sources(gpio_sc18is606.c) + endif() ++ ++# ZephCore patch: native_sim Linux GPIO driver via kernel V2 chardev ioctl. ++if(CONFIG_GPIO_NATIVE_LINUX) ++ if(${CMAKE_HOST_SYSTEM_NAME} STREQUAL Linux) ++ zephyr_library_sources(gpio_native_linux.c) ++ target_sources(native_simulator INTERFACE gpio_native_linux_adapt.c) ++ else() ++ message(FATAL_ERROR "CONFIG_GPIO_NATIVE_LINUX is only available on Linux hosts") ++ endif() ++endif() +diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig +index 4f4b7d44207..369917c409d 100644 +--- a/drivers/gpio/Kconfig ++++ b/drivers/gpio/Kconfig +@@ -1,4 +1,5 @@ + # GPIO configuration options ++# ZephCore patch: adds native_linux GPIO driver Kconfig. + + # Copyright (c) 2015 Intel Corporation + # SPDX-License-Identifier: Apache-2.0 +@@ -159,6 +160,7 @@ source "drivers/gpio/Kconfig.mfxstm32l152" + source "drivers/gpio/Kconfig.mmio32" + source "drivers/gpio/Kconfig.mmio_latch" + source "drivers/gpio/Kconfig.mspm0" ++source "drivers/gpio/Kconfig.native_linux" + source "drivers/gpio/Kconfig.nct38xx" + source "drivers/gpio/Kconfig.neorv32" + source "drivers/gpio/Kconfig.npcx" diff --git a/drivers/spi/CMakeLists.txt b/drivers/spi/CMakeLists.txt +index ab0825627ab..0b0268a83fa 100644 --- a/drivers/spi/CMakeLists.txt +++ b/drivers/spi/CMakeLists.txt -@@ -88,3 +88,16 @@ if(CONFIG_SPI_SC18IS606) +@@ -102,3 +102,16 @@ if(CONFIG_SPI_SC18IS606) zephyr_library_include_directories(${ZEPHYR_BASE}/drivers/mfd) zephyr_library_sources(spi_sc18is606.c) endif() @@ -19,53 +56,20 @@ diff --git a/drivers/spi/CMakeLists.txt b/drivers/spi/CMakeLists.txt + endif() +endif() diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig +index d6b1f20ae85..f616348fa58 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -1,4 +1,5 @@ # SPI driver configuration options +# ZephCore patch: adds native_linux SPI driver Kconfig. - + # Copyright (c) 2015-2016 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -@@ -129,6 +130,7 @@ source "drivers/spi/Kconfig.mcux_ecspi" - source "drivers/spi/Kconfig.mcux_flexcomm" +@@ -141,6 +142,7 @@ source "drivers/spi/Kconfig.mcux_flexcomm" source "drivers/spi/Kconfig.mcux_flexio" + source "drivers/spi/Kconfig.mspm0" source "drivers/spi/Kconfig.npcx" +source "drivers/spi/Kconfig.native_linux" source "drivers/spi/Kconfig.nrfx" source "drivers/spi/Kconfig.numaker" - source "drivers/spi/Kconfig.nxp_s32" -diff --git a/drivers/gpio/CMakeLists.txt b/drivers/gpio/CMakeLists.txt ---- a/drivers/gpio/CMakeLists.txt -+++ b/drivers/gpio/CMakeLists.txt -@@ -150,3 +150,13 @@ if(CONFIG_GPIO_SC18IS606) - zephyr_library_include_directories(${ZEPHYR_BASE}/drivers/mfd) - zephyr_library_sources(gpio_sc18is606.c) - endif() -+ -+# ZephCore patch: native_sim Linux GPIO driver via kernel V2 chardev ioctl. -+if(CONFIG_GPIO_NATIVE_LINUX) -+ if(${CMAKE_HOST_SYSTEM_NAME} STREQUAL Linux) -+ zephyr_library_sources(gpio_native_linux.c) -+ target_sources(native_simulator INTERFACE gpio_native_linux_adapt.c) -+ else() -+ message(FATAL_ERROR "CONFIG_GPIO_NATIVE_LINUX is only available on Linux hosts") -+ endif() -+endif() -diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig ---- a/drivers/gpio/Kconfig -+++ b/drivers/gpio/Kconfig -@@ -1,4 +1,5 @@ - # GPIO configuration options -+# ZephCore patch: adds native_linux GPIO driver Kconfig. - - # Copyright (c) 2015 Intel Corporation - # SPDX-License-Identifier: Apache-2.0 -@@ -154,6 +155,7 @@ source "drivers/gpio/Kconfig.mcux_rgpio" - source "drivers/gpio/Kconfig.mfxstm32l152" - source "drivers/gpio/Kconfig.mmio32" - source "drivers/gpio/Kconfig.mspm0" -+source "drivers/gpio/Kconfig.native_linux" - source "drivers/gpio/Kconfig.nct38xx" - source "drivers/gpio/Kconfig.neorv32" - source "drivers/gpio/Kconfig.npcx" + source "drivers/spi/Kconfig.nxp_kinetis_l" diff --git a/zephcore/patches/zephyr/0016-board-xiao-nrf54l15-openocd-rram-commit.patch b/zephcore/patches/zephyr/0016-board-xiao-nrf54l15-openocd-rram-commit.patch deleted file mode 100644 index b9e6d7e..0000000 --- a/zephcore/patches/zephyr/0016-board-xiao-nrf54l15-openocd-rram-commit.patch +++ /dev/null @@ -1,33 +0,0 @@ -ZephCore: backport of upstream zephyrproject-rtos/zephyr 1079b21dcda7 -("boards: seeed: commit RRAM write buffer after image load", PR #117961, -merged to main 2026-09-01). Not on v4.4-branch, and no backport requested. - -The board's `nrf54l-load` proc enables RRAMC writes with a one-line -(128-bit) write buffer and calls load_image, but never commits the buffer. -The image's final partial line is lost at reset, so those bytes keep -whatever the previously flashed firmware had there. Symptom depends on what -lands in that line: in the XIAO nRF54L15 battery-ADC build it was the -startup value of `sync_evt_pool.__bufs`, and flashing that image over a -debug build bus-faulted at the first BT HCI allocation (BFAR 0xf7ce720d, -the debug image's bytes). Erasing does not help; the lost line then reads -0xFF. Reproduced and fix verified on hardware 2026-09-18. - -DROP THIS PATCH once the Zephyr pin contains 1079b21dcda7 (or upstream's -later refactor ca77b788f93a, which moves the proc to -boards/seeed/common/openocd_nrf54l.cfg as `nrf54l_load`). It will then fail -`git apply --check`, which is fatal at configure time. - -diff --git a/boards/seeed/xiao_nrf54l15/support/openocd.cfg b/boards/seeed/xiao_nrf54l15/support/openocd.cfg -index 8ec6b496f65a..c0e227ff342e 100644 ---- a/boards/seeed/xiao_nrf54l15/support/openocd.cfg -+++ b/boards/seeed/xiao_nrf54l15/support/openocd.cfg -@@ -66,6 +66,9 @@ if {![using_hla]} { - proc nrf54l-load {file} { - mww 0x5004b500 0x101 - load_image $file -+ # Commit the final partial 128-bit RRAM write line left by load_image. -+ # RRAMC.TASKS_COMMITWRITEBUF @ 0x5004b008. -+ mww 0x5004b008 1 - } - - # Define CTRL_AP_NUM explicitly to avoid variable errors diff --git a/zephcore/prj.conf b/zephcore/prj.conf index ec115b3..d4238bc 100644 --- a/zephcore/prj.conf +++ b/zephcore/prj.conf @@ -14,7 +14,6 @@ CONFIG_CONSOLE=y CONFIG_UART_CONSOLE=y CONFIG_UART_INTERRUPT_DRIVEN=y -CONFIG_RING_BUFFER=y # Production by default — add boards/common/debug.conf to enable logging. CONFIG_LOG=n diff --git a/zephcore/src/main_companion.cpp b/zephcore/src/main_companion.cpp index f404465..8d5a9af 100644 --- a/zephcore/src/main_companion.cpp +++ b/zephcore/src/main_companion.cpp @@ -415,10 +415,12 @@ static void process_companion_rx(void) uint16_t len; uint8_t buf[MAX_FRAME_SIZE]; } f; + bool handled = false; /* Process all queued frames */ while (k_msgq_num_used_get(&pending_reply) == 0 && k_msgq_get(zephcore_ble_get_recv_queue(), &f, K_NO_WAIT) == 0) { + handled = true; #ifdef ZEPHCORE_LORA /* An in-flight contact dump deliberately survives commands parsed * here — it is only cancelled by CMD_APP_START (new session) or by @@ -431,6 +433,7 @@ static void process_companion_rx(void) } #endif } + ARG_UNUSED(handled); #if ZEPHCORE_USB_STACK /* Over USB, write_frame is synchronous and never kicks the BLE @@ -438,8 +441,17 @@ static void process_companion_rx(void) * sync, which only emits PACKET_CONTACT_START in the command handler and * relies on continueContactIteration() for the rest). Kick the pump here so * any iteration started by the command(s) just handled actually proceeds; - * the USB branch of tx_drain fires on_tx_idle and the loop self-sustains. */ - if (zephcore_ble_get_active_iface() == ZEPHCORE_IFACE_USB) { + * the USB branch of tx_drain fires on_tx_idle and the loop self-sustains. + * + * Only after a frame was actually handled. The USB tx_drain fires + * on_tx_idle whenever the (BLE) send queue is empty, which over USB is + * always, and on_tx_idle posts MESH_EVENT_CONTACT_ITER -- one of the + * events that calls this function. An unconditional kick therefore closed + * a loop through the system workqueue that never let the main thread + * block: 100% CPU for as long as USB was the active interface, starving + * every preemptible thread below main, including the LoRa TX-wait thread + * (every transmit then ran into its 5 s watchdog). */ + if (handled && zephcore_ble_get_active_iface() == ZEPHCORE_IFACE_USB) { zephcore_ble_kick_tx(); } #endif diff --git a/zephcore/src/main_repeater.cpp b/zephcore/src/main_repeater.cpp index a6ab9e7..8d802be 100644 --- a/zephcore/src/main_repeater.cpp +++ b/zephcore/src/main_repeater.cpp @@ -193,7 +193,12 @@ static void cli_uart_isr(const struct device *dev, void *user_data) { ARG_UNUSED(user_data); - while (uart_irq_update(dev) && uart_irq_is_pending(dev)) { + for (;;) { + uart_irq_update(dev); + if (uart_irq_is_pending(dev) <= 0) { + break; + } + if (uart_irq_rx_ready(dev)) { uint8_t buf[64]; int recv_len = uart_fifo_read(dev, buf, sizeof(buf)); diff --git a/zephcore/src/main_room_server.cpp b/zephcore/src/main_room_server.cpp index 0c7718e..e539768 100644 --- a/zephcore/src/main_room_server.cpp +++ b/zephcore/src/main_room_server.cpp @@ -176,7 +176,12 @@ static void cli_uart_isr(const struct device *dev, void *user_data) { ARG_UNUSED(user_data); - while (uart_irq_update(dev) && uart_irq_is_pending(dev)) { + for (;;) { + uart_irq_update(dev); + if (uart_irq_is_pending(dev) <= 0) { + break; + } + if (uart_irq_rx_ready(dev)) { uint8_t buf[64]; int recv_len = uart_fifo_read(dev, buf, sizeof(buf)); diff --git a/zephcore/sysbuild.conf b/zephcore/sysbuild.conf index 50b28bc..d521c5e 100644 --- a/zephcore/sysbuild.conf +++ b/zephcore/sysbuild.conf @@ -5,3 +5,14 @@ SB_CONFIG_BOOTLOADER_MCUBOOT=y SB_CONFIG_MCUBOOT_MODE_OVERWRITE_ONLY=y + +# Explicit since Zephyr 4.5: 51ef282b5f1 moved Espressif sysbuild to the generic +# MCUboot defaults, whose signature type is RSA. That silently turned the +# bootloader into an RSA-2048 verifier keyed to MCUboot's public development +# key -- no real security, ~19 KB of mbedTLS/PSA that overflowed the 64 KB +# MCUboot slot on several boards (the app written at 0x10000 then overwrites +# the bootloader's tail), and a bootloader that rejects the hash-only images +# every deployed ESP32 node and WiFi-OTA update uses. NONE = hash check only, +# exactly what v4.4 built. Moving to real signing (own key) is a separate +# decision with OTA consequences. +SB_CONFIG_BOOT_SIGNATURE_TYPE_NONE=y diff --git a/zephcore/west.yml b/zephcore/west.yml index e832e75..698692c 100644 --- a/zephcore/west.yml +++ b/zephcore/west.yml @@ -6,7 +6,7 @@ manifest: projects: - name: zephyr remote: zephyrproject-rtos - revision: 1d9ce23e364176b3ef050fd3b3d6255ca574c330 + revision: 3e8f38faf93704249745dbc475052848ce39fdaf import: true self: