sysworkq 4K platform-wide, c3 heap 44K, native-linux mapped-partition, sx126x 0003 step B reduction

This commit is contained in:
liquidraver
2026-09-19 19:50:18 +02:00
parent edcc40a884
commit f6d637c484
9 changed files with 185 additions and 267 deletions
+6 -4
View File
@@ -417,10 +417,12 @@ 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")
# ESP32-S3 and ESP32-C3 (shared BLE controller): overrides esp32_common.conf,
# stays below board.conf. Every S3 board target carries the /esp32s3/
# qualifier; the C3 boards are single-SoC and are named without one
# (xiao_esp32c3), so match the chip in the name (see esp32c3s3_common.conf).
if(BOARD MATCHES "/esp32s3/" OR BOARD MATCHES "esp32c3")
list(APPEND ZEPHCORE_CONF_FILES "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/esp32c3s3_common.conf")
endif()
if(ZEPHCORE_BOARD_CONF AND EXISTS ${ZEPHCORE_BOARD_CONF})
list(APPEND ZEPHCORE_CONF_FILES ${ZEPHCORE_BOARD_CONF})
@@ -0,0 +1,29 @@
# ESP32-S3 and ESP32-C3 only. Loaded after esp32_common.conf and before
# board.conf (CMakeLists.txt, matched on the /esp32s3/ qualifier or an esp32c3
# board name). The two chips share one BLE controller: the S3 build compiles
# hal_espressif's components/bt/controller/esp32c3 sources and links the same
# btdm controller library family, so one heap figure covers both. The C6 runs
# a different controller and keeps esp32_common.conf's 64 KB.
#
# ========== 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 shared IRAM/DRAM SRAM of these chips: the new
# hal_espressif controller blob places ~15-19 KB of link-layer patch code
# (r_lld_*_hack / *_eco) in IRAM, and dram0 shrinks by the same amount. On the
# S3, Heltec V4/V43, Wireless Tracker V2 and Meshnology W12 overflowed in the
# sysbuild (MCUboot) layout; the XIAO ESP32-C3 companion went from 92.83% to
# 99.02% dram0.
#
# 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). Applied to the C3
# 2026-09-19 on the strength of the shared controller; not yet measured on C3
# hardware (`get mem` with CONFIG_ZEPHCORE_MEM_STATS=y is the check).
#
# Repeater builds do not use this value: wifi_ota.conf, appended later, sets
# the heap back to 64 KB for the WiFi driver.
CONFIG_HEAP_MEM_POOL_SIZE=45056
@@ -1,28 +0,0 @@
# 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
-8
View File
@@ -64,11 +64,3 @@ 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
+3 -3
View File
@@ -49,7 +49,7 @@ CONFIG_MBEDTLS_AES_ROM_TABLES=y
# juggles a couple of ephemeral AES keys at once; 8 is ample.
CONFIG_MBEDTLS_PSA_KEY_SLOT_COUNT=8
# Trim the common 8KB stacks to fit 64KB SRAM. Crypto (Ed25519/PSA) and the
# RNG jitter sampler run on the main thread, so keep main comfortable.
# Trim the common 8KB main stack to fit 64KB SRAM. Crypto (Ed25519/PSA) and the
# RNG jitter sampler run on the main thread, so keep main comfortable. (The
# system workqueue is already 4 KB in zephcore_common.conf.)
CONFIG_MAIN_STACK_SIZE=6144
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096
+11 -1
View File
@@ -7,7 +7,17 @@
# ========== Core Settings ==========
CONFIG_MAIN_STACK_SIZE=8192
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=8192
# System workqueue: measured 2026-09-19 (CONFIG_ZEPHCORE_MEM_STATS, Zephyr main
# 3e8f38faf93) at peak 808 B on nRF52 (Wio L1 SX1262 + T1000-E LR1110,
# companion and repeater, phone BLE session with contact sync, TX/RX + flood
# forwarding, GPS on/off, prefs writes) and 1092 B on a XIAO ESP32-S3
# (companion, plus a repeater with the WiFi-OTA AP/HTTP server up). Its load
# is ZephCore's own work items on every platform: no BLE/WiFi platform driver
# we build (nRF, ESP32, MG24, STM32WL) submits to it, and main moved the BT
# host's LE work to the BT workqueue (22896cb8d6f). 4096 = 3.75x the worst
# peak, and the floor healthcheck.conf names for DBG logging from ISR context.
# Was 8192 until 2026-09-19.
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096
CONFIG_EVENTS=y
CONFIG_CPP=y
@@ -332,6 +332,12 @@
* │ 0xED000 - 0x100000 (76KB) UF2 bootloader (reserved) │
* └─────────────────────────────────────────────────────────────────────┘
*
* NOTE (Zephyr main, since 2026-09-19): every partition node in the examples
* below also needs compatible = "zephyr,mapped-partition"; (omitted from
* each one for brevity). Without it, code that opens the partition fails to
* compile with "..._PARTITION_ID undeclared" — see any
* boards/esp32/<board>/partitions.overlay for the full form.
*
* /delete-node/ &boot_partition;
* /delete-node/ &slot0_partition;
* /delete-node/ &slot1_partition;
@@ -37,6 +37,7 @@
&flash0 {
partitions {
lfs_partition: partition@100000 {
compatible = "zephyr,mapped-partition";
label = "lfs";
reg = <0x00100000 0x00080000>; /* 512 KB */
};
@@ -576,9 +576,39 @@ rebase is deliberately behaviour-preserving ("step A"):
* 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
Step B (2026-09-19), each piece benched on a Wio L1 <-> T1000-E PDR run
with duty cycle on, 50 packets each way, all 50/50 (pieces 1-2 at 2 s and
800 ms gaps, piece 3 at 800 ms):
1. Upstream's sx126x_set_stop_timer_on_preamble() and
sx126x_set_rx_duty_cycle() restored verbatim and used by both of our
duty-cycle entry points instead of open-coded command writes. Same two
commands in the same order; at the entry site the RX-gain register write
now precedes StopTimerOnPreamble (all in STDBY). The duplicate
SX126X_REG_RETENTION_LIST / SX126X_MAX_RETENTION_REGS defines this patch
added to sx126x_regs.h were dropped -- upstream defines both.
2. Our dc_rx_time / dc_sleep_time fields dropped in favour of upstream's
data->duty_cycle.rx_period / .sleep_period (same 15.625 us units).
3. Upstream's sx126x_duty_cycle_stop() and its preamble-length explanation
restored, and used by the duty-cycle entry's cancel branch (identical
wake-if-BUSY -> STDBY_RC -> sleep sequence).
Net: +2509/-364 -> +2478/-287 lines against upstream. Deliberately not
done: replacing our STATE_RX + rx_duty_cycle_enabled model with upstream's
SX126X_STATE_RX_DUTY_CYCLE. Upstream's enum value already sits in the base
file unused, so the switch would shrink nothing, while rewriting the RX->TX
CAS, busy gate, watchdog and restart paths -- the riskiest code in the
driver. What this patch still deletes is upstream code ours replaces on
purpose: its duty-cycle driver (ours re-arms RX before running the
callback and has the parked-RX watchdog) and its RX-done/timeout handlers.
Open follow-up, not a reduction: sx126x_lora_recv_async()'s cancel branch
(ZephCore's hwCancelReceive()) issues SetStandby without a raw-NSS wake,
so under duty cycle it can block in wait_busy() until the chip's next RX
window (bounded by one sleep period). sx126x_duty_cycle_stop() would avoid
that, but it is a behaviour change on paths a PDR run does not reach
(reconfigure, CAD-timeout recovery, runtime rxduty toggle), so it waits for
a bench that exercises them. 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).
@@ -624,7 +654,7 @@ Regenerate with:
appended to this preamble.
diff --git a/drivers/lora/native/sx126x/sx126x.c b/drivers/lora/native/sx126x/sx126x.c
index c8e682ee84c..399b84e8086 100644
index c8e682ee8..b5ab10c4e 100644
--- a/drivers/lora/native/sx126x/sx126x.c
+++ b/drivers/lora/native/sx126x/sx126x.c
@@ -6,14 +6,52 @@
@@ -787,13 +817,11 @@ index c8e682ee84c..399b84e8086 100644
static bool should_enable_ldro(enum lora_datarate sf, enum lora_signal_bandwidth bw,
const struct sx126x_hal_config *config)
{
@@ -336,46 +474,138 @@ static int sx126x_set_sync_word(const struct device *dev, const struct lora_mode
@@ -336,10 +474,128 @@ 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
@@ -823,9 +851,7 @@ index c8e682ee84c..399b84e8086 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,
@@ -833,11 +859,7 @@ index c8e682ee84c..399b84e8086 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,
@@ -846,44 +868,30 @@ index c8e682ee84c..399b84e8086 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;
- sys_put_be24(rx_period, &buf[0]);
- sys_put_be24(sleep_period, &buf[3]);
+ int ret;
+
+ data->agc_sensi_adjust = cal->sensi_adjust;
+
+ /* Bits 4:0 only -- preserve the rest of 0x089C. */
@@ -902,15 +910,13 @@ index c8e682ee84c..399b84e8086 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;
}
- return sx126x_hal_write_cmd(dev, SX126X_CMD_SET_RX_DUTY_CYCLE, buf, 6);
+ if (ret < 0) {
+ return ret;
+ }
+
+ return sx126x_hal_write_regs(dev, SX126X_REG_AGC_GAIN_TUNE_1_2,
+ cal->gain_tune,
+ SX126X_AGC_GAIN_TUNE_LEN);
@@ -918,12 +924,13 @@ index c8e682ee84c..399b84e8086 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);
@@ -938,20 +945,10 @@ index c8e682ee84c..399b84e8086 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);
+}
+
+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);
return sx126x_hal_write_regs(dev, SX126X_REG_RX_GAIN, &val, 1);
}
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,
@@ -415,7 +671,7 @@ static int sx126x_get_packet_status(const struct device *dev,
uint8_t buf[3];
int ret;
@@ -960,7 +957,7 @@ index c8e682ee84c..399b84e8086 100644
if (ret == 0) {
/* RSSI is -value/2 dBm */
*rssi = -((int16_t)buf[0] >> 1);
@@ -443,6 +673,12 @@ static int sx126x_add_reg_to_retention(const struct device *dev, uint16_t addr)
@@ -443,6 +699,12 @@ static int sx126x_add_reg_to_retention(const struct device *dev, uint16_t addr)
n = buf[0];
@@ -973,7 +970,7 @@ index c8e682ee84c..399b84e8086 100644
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)
@@ -460,6 +722,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));
}
@@ -1066,7 +1063,7 @@ index c8e682ee84c..399b84e8086 100644
static int sx126x_chip_init(const struct device *dev)
{
const struct sx126x_hal_config *config = dev->config;
@@ -520,6 +842,26 @@ static int sx126x_chip_init(const struct device *dev)
@@ -520,6 +868,26 @@ static int sx126x_chip_init(const struct device *dev)
return ret;
}
@@ -1093,7 +1090,7 @@ index c8e682ee84c..399b84e8086 100644
/* Set packet type to LoRa */
ret = sx126x_set_packet_type(dev, SX126X_PACKET_TYPE_LORA);
if (ret < 0) {
@@ -527,10 +869,23 @@ static int sx126x_chip_init(const struct device *dev)
@@ -527,10 +895,23 @@ static int sx126x_chip_init(const struct device *dev)
return ret;
}
@@ -1120,7 +1117,7 @@ index c8e682ee84c..399b84e8086 100644
if (ret < 0) {
LOG_ERR("Set IRQ params failed: %d", ret);
return ret;
@@ -543,21 +898,9 @@ static int sx126x_chip_init(const struct device *dev)
@@ -543,21 +924,9 @@ static int sx126x_chip_init(const struct device *dev)
return ret;
}
@@ -1145,7 +1142,7 @@ index c8e682ee84c..399b84e8086 100644
LOG_INF("SX126x initialized");
return 0;
@@ -567,15 +910,44 @@ static void sx126x_dio1_callback(const struct device *dev)
@@ -567,15 +936,44 @@ static void sx126x_dio1_callback(const struct device *dev)
{
struct sx126x_data *data = dev->data;
@@ -1192,7 +1189,7 @@ index c8e682ee84c..399b84e8086 100644
sx126x_hal_set_rf_switch(dev, enable, tx);
}
}
@@ -595,6 +967,7 @@ static void sx126x_disconnect_rf_gpios(const struct device *dev)
@@ -595,6 +993,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);
@@ -1200,7 +1197,7 @@ index c8e682ee84c..399b84e8086 100644
}
static int sx126x_reconnect_rf_gpios(const struct device *dev)
@@ -620,10 +993,238 @@ static int sx126x_reconnect_rf_gpios(const struct device *dev)
@@ -620,10 +1019,232 @@ static int sx126x_reconnect_rf_gpios(const struct device *dev)
return ret;
}
@@ -1303,10 +1300,6 @@ index c8e682ee84c..399b84e8086 100644
+ * preamble arriving at the tail of the RX window can get
+ * cut off when the sleep phase begins. Kept as insurance --
+ * see the note above. */
+ uint8_t stop_on_preamble = 1;
+ sx126x_hal_write_cmd(dev, SX126X_CMD_STOP_TIMER_ON_PREAMBLE,
+ &stop_on_preamble, 1);
+
+ /* No retention-list write here. Keeping boosted RX gain alive
+ * across the chip-internal sleep->RX wakes is still MANDATORY
+ * (DS sec 9.6: the Rx gain configuration "is not saved when
@@ -1320,12 +1313,10 @@ index c8e682ee84c..399b84e8086 100644
+ * needs the list at all -- the LR2021 retains the gain
+ * (DS sec 6.3.8) and the LR11xx documents no caveat either way. */
+
+ /* Re-apply duty cycle with stored timing */
+ uint8_t dc_buf[6];
+ sys_put_be24(data->dc_rx_time, &dc_buf[0]);
+ sys_put_be24(data->dc_sleep_time, &dc_buf[3]);
+ sx126x_hal_write_cmd(dev, SX126X_CMD_SET_RX_DUTY_CYCLE,
+ dc_buf, 6);
+ /* Re-apply duty cycle with stored timing. Upstream's helper
+ * issues StopTimerOnPreamble (see above) and then SetRxDutyCycle. */
+ (void)sx126x_set_rx_duty_cycle(dev, data->duty_cycle.rx_period,
+ data->duty_cycle.sleep_period);
+
+ sx126x_dc_watchdog_kick(data);
+ } else {
@@ -1439,7 +1430,7 @@ index c8e682ee84c..399b84e8086 100644
static int sx126x_set_sleep(const struct device *dev)
{
struct sx126x_data *data = dev->data;
@@ -697,94 +1298,214 @@ static void sx126x_handle_irq_tx_done(const struct device *dev)
@@ -697,94 +1318,214 @@ static void sx126x_handle_irq_tx_done(const struct device *dev)
k_msgq_put(&data->tx_msgq, &result, K_NO_WAIT);
}
@@ -1714,7 +1705,7 @@ index c8e682ee84c..399b84e8086 100644
return;
}
@@ -827,13 +1548,97 @@ static void sx126x_irq_work_handler(struct k_work *work)
@@ -827,13 +1568,97 @@ static void sx126x_irq_work_handler(struct k_work *work)
}
if (irq_status & SX126X_IRQ_RX_DONE) {
@@ -1812,7 +1803,7 @@ index c8e682ee84c..399b84e8086 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);
@@ -894,10 +1699,19 @@ static int sx126x_lora_config(const struct device *dev,
@@ -894,10 +1719,19 @@ static int sx126x_lora_config(const struct device *dev,
goto out;
}
@@ -1833,30 +1824,20 @@ index c8e682ee84c..399b84e8086 100644
if (ret < 0) {
goto out;
}
@@ -915,13 +1729,19 @@ static int sx126x_lora_config(const struct device *dev,
@@ -915,6 +1749,12 @@ static int sx126x_lora_config(const struct device *dev,
goto out;
}
- /* 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;
}
- /* Set RX gain */
+ /* Set sync word */
+ ret = sx126x_set_sync_word(dev, config);
+ if (ret < 0) {
+ goto out;
+ }
+
+ /* Set RX gain */
ret = sx126x_set_rx_gain(dev, hal_config->rx_boosted);
/* Set sync word */
ret = sx126x_set_sync_word(dev, config);
if (ret < 0) {
goto out;
@@ -938,6 +1758,10 @@ out:
@@ -938,6 +1778,10 @@ out:
return ret;
}
@@ -1867,7 +1848,7 @@ index c8e682ee84c..399b84e8086 100644
static int sx126x_lora_send_async(const struct device *dev,
uint8_t *data_buf, uint32_t data_len,
struct k_poll_signal *async)
@@ -955,11 +1779,27 @@ static int sx126x_lora_send_async(const struct device *dev,
@@ -955,11 +1799,27 @@ static int sx126x_lora_send_async(const struct device *dev,
return -EINVAL;
}
@@ -1897,7 +1878,7 @@ index c8e682ee84c..399b84e8086 100644
k_mutex_lock(&data->lock, K_FOREVER);
ret = sx126x_ensure_ready(dev);
@@ -969,6 +1809,59 @@ static int sx126x_lora_send_async(const struct device *dev,
@@ -969,6 +1829,59 @@ static int sx126x_lora_send_async(const struct device *dev,
return ret;
}
@@ -1957,7 +1938,7 @@ index c8e682ee84c..399b84e8086 100644
data->tx_async_signal = async;
k_msgq_purge(&data->tx_msgq);
@@ -999,8 +1892,90 @@ static int sx126x_lora_send_async(const struct device *dev,
@@ -999,8 +1912,90 @@ static int sx126x_lora_send_async(const struct device *dev,
/* Enable antenna and set TX path */
sx126x_set_rf_path(dev, true, true);
@@ -2050,7 +2031,7 @@ index c8e682ee84c..399b84e8086 100644
if (ret < 0) {
goto out_error;
}
@@ -1069,6 +2044,8 @@ static int sx126x_lora_recv(const struct device *dev, uint8_t *data_buf,
@@ -1069,6 +2064,8 @@ static int sx126x_lora_recv(const struct device *dev, uint8_t *data_buf,
}
data->rx_cb = NULL;
@@ -2059,7 +2040,7 @@ index c8e682ee84c..399b84e8086 100644
k_msgq_purge(&data->rx_msgq);
/* Set packet parameters for variable length reception */
@@ -1140,6 +2117,8 @@ static int sx126x_lora_recv_async(const struct device *dev,
@@ -1140,6 +2137,8 @@ static int sx126x_lora_recv_async(const struct device *dev,
/* Stop async reception */
data->rx_cb = NULL;
data->rx_cb_user_data = NULL;
@@ -2068,7 +2049,7 @@ index c8e682ee84c..399b84e8086 100644
if (atomic_cas(&data->state, SX126X_STATE_RX, SX126X_STATE_IDLE)) {
sx126x_set_standby(dev, SX126X_STANDBY_RC);
sx126x_set_sleep(dev);
@@ -1154,6 +2133,19 @@ static int sx126x_lora_recv_async(const struct device *dev,
@@ -1154,6 +2153,19 @@ static int sx126x_lora_recv_async(const struct device *dev,
return -EINVAL;
}
@@ -2088,7 +2069,7 @@ index c8e682ee84c..399b84e8086 100644
if (!atomic_cas(&data->state, SX126X_REST_STATE, SX126X_STATE_RX)) {
LOG_ERR("Busy");
k_mutex_unlock(&data->lock);
@@ -1167,8 +2159,18 @@ static int sx126x_lora_recv_async(const struct device *dev,
@@ -1167,8 +2179,18 @@ static int sx126x_lora_recv_async(const struct device *dev,
return ret;
}
@@ -2107,7 +2088,7 @@ index c8e682ee84c..399b84e8086 100644
/* Set packet parameters */
ret = sx126x_set_packet_params(dev,
@@ -1181,6 +2183,7 @@ static int sx126x_lora_recv_async(const struct device *dev,
@@ -1181,6 +2203,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;
@@ -2115,7 +2096,7 @@ index c8e682ee84c..399b84e8086 100644
sx126x_set_sleep(dev);
k_mutex_unlock(&data->lock);
return ret;
@@ -1193,221 +2196,25 @@ static int sx126x_lora_recv_async(const struct device *dev,
@@ -1193,11 +2216,21 @@ static int sx126x_lora_recv_async(const struct device *dev,
ret = sx126x_set_rx(dev, 0);
if (ret < 0) {
data->rx_cb = NULL;
@@ -2126,63 +2107,21 @@ index c8e682ee84c..399b84e8086 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);
-}
-
+ /* 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);
+
+ sx126x_hal_write_regs(dev, SX126X_REG_RX_GAIN, &gain, 1);
+
k_mutex_unlock(&data->lock);
return 0;
}
@@ -1255,159 +2288,6 @@ static void sx126x_duty_cycle_stop(const struct device *dev)
sx126x_set_sleep(dev);
}
-static int sx126x_duty_cycle_start(const struct device *dev,
- k_timeout_t rx_period,
- k_timeout_t sleep_period)
@@ -2215,21 +2154,15 @@ index c8e682ee84c..399b84e8086 100644
- 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);
- k_mutex_unlock(&data->lock);
- return ret;
-}
-
@@ -2262,9 +2195,9 @@ index c8e682ee84c..399b84e8086 100644
- data->rx_cb = cb;
- data->rx_cb_user_data = user_data;
-
return 0;
}
- return 0;
-}
-
-static int sx126x_lora_recv_duty_cycle(const struct device *dev,
- k_timeout_t rx_period,
- k_timeout_t sleep_period,
@@ -2345,7 +2278,7 @@ index c8e682ee84c..399b84e8086 100644
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,
@@ -1491,7 +2371,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,
@@ -2354,7 +2287,7 @@ index c8e682ee84c..399b84e8086 100644
if (ret < 0) {
goto out_sleep;
}
@@ -1523,50 +2330,1135 @@ out_unlock:
@@ -1523,50 +2403,1122 @@ out_unlock:
return ret;
}
@@ -3232,16 +3165,10 @@ index c8e682ee84c..399b84e8086 100644
+ data->rx_duty_cycle_enabled = false;
+ sx126x_reset_rx_busy_signals(data);
+ if (atomic_cas(&data->state, SX126X_STATE_RX, SX126X_STATE_IDLE)) {
+ /* If we're called during the duty-cycle sleep phase the
+ * chip is actually asleep and BUSY is asserted -- issuing
+ * SetStandby straight away stalls the HAL in
+ * wait_busy(). Poke CS via sx126x_hal_wakeup() first so
+ * BUSY drops before we try to talk to the chip. */
+ if (sx126x_hal_is_busy(dev)) {
+ sx126x_hal_wakeup(dev);
+ }
+ sx126x_set_standby(dev, SX126X_STANDBY_RC);
+ sx126x_set_sleep(dev);
+ /* May be called in the duty-cycle sleep phase with BUSY
+ * asserted; upstream's stop wakes via a raw NSS edge
+ * before STDBY_RC + sleep. */
+ sx126x_duty_cycle_stop(dev);
+ }
+ k_mutex_unlock(&data->lock);
+ return 0;
@@ -3293,8 +3220,8 @@ index c8e682ee84c..399b84e8086 100644
+ * dropped RX window. */
+ if (atomic_get(&data->state) == SX126X_STATE_RX) {
+ const bool rearm = !data->rx_duty_cycle_enabled ||
+ data->dc_rx_time != rx_time ||
+ data->dc_sleep_time != sleep_time;
+ data->duty_cycle.rx_period != rx_time ||
+ data->duty_cycle.sleep_period != sleep_time;
+
+ data->rx_cb = cb;
+ data->rx_cb_user_data = user_data;
@@ -3305,8 +3232,8 @@ index c8e682ee84c..399b84e8086 100644
+ return 0;
+ }
+
+ data->dc_rx_time = rx_time;
+ data->dc_sleep_time = sleep_time;
+ data->duty_cycle.rx_period = rx_time;
+ data->duty_cycle.sleep_period = sleep_time;
+ data->rx_duty_cycle_enabled = true;
+
+ /* Mid-cycle the chip may be in its duty-cycle sleep phase with
@@ -3417,18 +3344,14 @@ index c8e682ee84c..399b84e8086 100644
+
+ /* Store for restart_rx to re-use (periods validated and converted at
+ * function entry, before the state was claimed). */
+ data->dc_rx_time = rx_time;
+ data->dc_sleep_time = sleep_time;
+ data->duty_cycle.rx_period = rx_time;
+ data->duty_cycle.sleep_period = sleep_time;
+ data->rx_duty_cycle_enabled = true;
+
+ /* StopTimerOnPreamble: keep the RX timer from expiring mid-packet
+ * when a preamble is detected late in the RX window. Without it,
+ * a packet whose preamble lands near the tail of the RX window
+ * gets cut off by the sleep transition. */
+ uint8_t stop_on_preamble = 1;
+ sx126x_hal_write_cmd(dev, SX126X_CMD_STOP_TIMER_ON_PREAMBLE,
+ &stop_on_preamble, 1);
+
+ /* Apply RX gain. The warm-start retention list that makes the
+ * chip-internal sleep->RX wakes restore it (DS sec 9.6: mandatory for
+ * SetRxDutyCycle -- without it every window after the first sleep
@@ -3438,11 +3361,8 @@ index c8e682ee84c..399b84e8086 100644
+
+ sx126x_hal_write_regs(dev, SX126X_REG_RX_GAIN, &gain, 1);
+
+ uint8_t buf[6];
+
+ sys_put_be24(rx_time, &buf[0]);
+ sys_put_be24(sleep_time, &buf[3]);
+ sx126x_hal_write_cmd(dev, SX126X_CMD_SET_RX_DUTY_CYCLE, buf, 6);
+ /* StopTimerOnPreamble (comment above) + SetRxDutyCycle. */
+ (void)sx126x_set_rx_duty_cycle(dev, rx_time, sleep_time);
+
+ sx126x_dc_watchdog_kick(data);
+
@@ -3526,7 +3446,7 @@ index c8e682ee84c..399b84e8086 100644
/* Initialize HAL */
ret = sx126x_hal_init(dev);
@@ -1628,6 +3520,8 @@ static int sx126x_init(const struct device *dev)
@@ -1628,6 +3580,8 @@ static int sx126x_init(const struct device *dev)
{0}), \
.rx_enable = GPIO_DT_SPEC_INST_GET_OR(inst, rx_enable_gpios, \
{0}), \
@@ -3535,7 +3455,7 @@ index c8e682ee84c..399b84e8086 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), \
@@ -1682,6 +3576,8 @@ DT_INST_FOREACH_STATUS_OKAY_VARGS(SX126X_INIT, true)
@@ -1682,6 +3636,8 @@ DT_INST_FOREACH_STATUS_OKAY_VARGS(SX126X_INIT, true)
{0}), \
.rx_enable = GPIO_DT_SPEC_INST_GET_OR(inst, rx_enable_gpios, \
{0}), \
@@ -3545,7 +3465,7 @@ index c8e682ee84c..399b84e8086 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 0122958c32a..bac80103ff7 100644
index 0122958c3..9d39100a7 100644
--- a/drivers/lora/native/sx126x/sx126x.h
+++ b/drivers/lora/native/sx126x/sx126x.h
@@ -57,6 +57,7 @@ struct sx126x_data {
@@ -3556,7 +3476,7 @@ index 0122958c32a..bac80103ff7 100644
/* RX duty cycle parameters (in SX126x timeout ticks) */
struct {
@@ -69,7 +70,136 @@ struct sx126x_data {
@@ -69,7 +70,133 @@ struct sx126x_data {
/* Deferred work for interrupt handling */
struct k_work irq_work;
@@ -3628,9 +3548,6 @@ index 0122958c32a..bac80103ff7 100644
+ * Reset together with the other RX-busy signals. */
+ atomic_t raw_header_seen_at_ms;
+
+ uint32_t dc_rx_time; /* stored duty cycle rx period (15.625us steps) */
+ uint32_t dc_sleep_time; /* stored duty cycle sleep period (15.625us steps) */
+
+ /* Parked-RX watchdog. StopTimerOnPreamble=1 means a false preamble
+ * detect parks the chip in full RX "until the next received frame"
+ * (AN1200.36 sec 3) with no IRQ fired -- nothing is lost (parked RX
@@ -3694,7 +3611,7 @@ index 0122958c32a..bac80103ff7 100644
#endif /* ZEPHYR_DRIVERS_LORA_SX126X_SX126X_INTERNAL_H_ */
diff --git a/drivers/lora/native/sx126x/sx126x_hal.c b/drivers/lora/native/sx126x/sx126x_hal.c
index 34f7089584f..394578956de 100644
index 34f708958..394578956 100644
--- a/drivers/lora/native/sx126x/sx126x_hal.c
+++ b/drivers/lora/native/sx126x/sx126x_hal.c
@@ -150,7 +150,13 @@ int sx126x_hal_init(const struct device *dev)
@@ -3787,7 +3704,7 @@ index 34f7089584f..394578956de 100644
uint8_t buf[2] = { (uint8_t)tx_power, ramp_time };
diff --git a/drivers/lora/native/sx126x/sx126x_hal.h b/drivers/lora/native/sx126x/sx126x_hal.h
index e4b26ba60bd..558890ad54c 100644
index e4b26ba60..558890ad5 100644
--- a/drivers/lora/native/sx126x/sx126x_hal.h
+++ b/drivers/lora/native/sx126x/sx126x_hal.h
@@ -29,6 +29,10 @@ struct sx126x_hal_config {
@@ -3802,7 +3719,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 19c71594193..5f7eece3930 100644
index 19c715941..5f7eece39 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,
@@ -3839,7 +3756,7 @@ index 19c71594193..5f7eece3930 100644
return 0;
diff --git a/drivers/lora/native/sx126x/sx126x_hal_stm32wl.c b/drivers/lora/native/sx126x/sx126x_hal_stm32wl.c
index b7ffbac7a6e..382acfc50b9 100644
index b7ffbac7a..382acfc50 100644
--- a/drivers/lora/native/sx126x/sx126x_hal_stm32wl.c
+++ b/drivers/lora/native/sx126x/sx126x_hal_stm32wl.c
@@ -254,6 +254,15 @@ int sx126x_hal_init(const struct device *dev)
@@ -3859,10 +3776,10 @@ index b7ffbac7a6e..382acfc50b9 100644
return 0;
}
diff --git a/drivers/lora/native/sx126x/sx126x_regs.h b/drivers/lora/native/sx126x/sx126x_regs.h
index 1e6146f751f..5992e99969b 100644
index 1e6146f75..03e72ff90 100644
--- a/drivers/lora/native/sx126x/sx126x_regs.h
+++ b/drivers/lora/native/sx126x/sx126x_regs.h
@@ -175,10 +175,45 @@
@@ -175,10 +175,34 @@
#define SX126X_LORA_SYNC_WORD_PUBLIC 0x3444
#define SX126X_LORA_SYNC_WORD_PRIVATE 0x1424
@@ -3895,22 +3812,11 @@ index 1e6146f751f..5992e99969b 100644
+/* AgcSensiAdjust (0x08AC bits 7:2) per band */
+#define SX126X_AGC_SENSI_ADJUST_HIGH_BAND 0x25 /* 868-915 MHz (default) */
+#define SX126X_AGC_SENSI_ADJUST_LOW_BAND 0x22 /* 470-490 MHz */
+
+/* Register retention list (DS Sec 9.6). Registers listed here are restored
+ * by the chip on a warm-start wake -- which is what every RxDutyCycle
+ * sleep->RX transition is. Layout: entry count at 0x029F, then one
+ * big-endian 16-bit register address per entry, four entries max.
+ *
+ * The datasheet scopes the loss to sleep/warm-start. It does NOT say SetRx
+ * resets the Rx gain: in continuous RX, with no sleep in the loop, 0x08AC
+ * simply persists. */
+#define SX126X_REG_RETENTION_LIST 0x029F
+#define SX126X_MAX_RETENTION_REGS 4
/* TX Clamp Config (workaround for SX1262) */
#define SX126X_REG_TX_CLAMP_CFG 0x08D8
diff --git a/dts/bindings/lora/semtech,sx126x-base.yaml b/dts/bindings/lora/semtech,sx126x-base.yaml
index 2d13d758695..04390be84e5 100644
index 2d13d7586..04390be84 100644
--- a/dts/bindings/lora/semtech,sx126x-base.yaml
+++ b/dts/bindings/lora/semtech,sx126x-base.yaml
@@ -35,6 +35,24 @@ properties: