From 98eed4389264f4e0bce6d3a5e7dee58b5b20e424 Mon Sep 17 00:00:00 2001 From: abelino Date: Fri, 29 Aug 2025 13:48:52 -0700 Subject: [PATCH 01/12] scripts: Fix PosixPath passed to subprocess instead of string --- scripts/west_commands/packages.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/west_commands/packages.py b/scripts/west_commands/packages.py index 795f3d4fe139..9ae2c39f305e 100644 --- a/scripts/west_commands/packages.py +++ b/scripts/west_commands/packages.py @@ -159,7 +159,7 @@ def do_run_pip(self, args, manager_args): if len(requirements) > 0: subprocess.check_call( [sys.executable, "-m", "pip", "install"] - + list(chain.from_iterable([("-r", r) for r in requirements])) + + list(chain.from_iterable([("-r", r.as_posix()) for r in requirements])) + manager_args ) else: @@ -169,4 +169,4 @@ def do_run_pip(self, args, manager_args): if len(manager_args) > 0: self.die(f'west packages pip does not support unknown arguments: "{manager_args}"') - self.inf("\n".join([f"-r {r}" for r in requirements])) + self.inf("\n".join([f"-r {r.as_posix()}" for r in requirements])) From b58d64df45782b00dc35141ba23bceefbf71a425 Mon Sep 17 00:00:00 2001 From: Jon Ringle Date: Wed, 9 Apr 2025 13:59:40 -0400 Subject: [PATCH 02/12] shell: backends: Make UART shell backend line ending format configurable Add a new Kconfig option SHELL_BACKEND_SERIAL_OLF_CRLF that allows configuring the line ending format used by the shell UART backend. When enabled (default), the shell will use CR+LF (\r\n) line endings for output text. When disabled, it will use the system default (typically just LF, \n). This replaces the hardcoded SHELL_FLAG_OLF_CRLF setting with a conditional expression that selects the appropriate flag based on the new configuration option. This gives users more control over terminal output formatting when needed for specific applications or terminal emulators. --- subsys/shell/backends/Kconfig.backends | 8 ++++++++ subsys/shell/backends/shell_uart.c | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/subsys/shell/backends/Kconfig.backends b/subsys/shell/backends/Kconfig.backends index 13b699794726..54617d0f60aa 100644 --- a/subsys/shell/backends/Kconfig.backends +++ b/subsys/shell/backends/Kconfig.backends @@ -141,6 +141,14 @@ config SHELL_BACKEND_SERIAL_CHECK_DTR help Check DTR signal before TX. +config SHELL_BACKEND_SERIAL_OLF_CRLF + bool "Use CR+LF for output line endings" + default y + help + When enabled, the shell backend will use CR+LF (\r\n) for line endings + in output. When disabled, the system default line ending is used (typically + just LF, \n). This affects how the shell formats output text. + module = SHELL_BACKEND_SERIAL default-timeout = 100 source "subsys/shell/Kconfig.template.shell_log_queue_timeout" diff --git a/subsys/shell/backends/shell_uart.c b/subsys/shell/backends/shell_uart.c index 1d19ce19a318..e8fe7feb27e0 100644 --- a/subsys/shell/backends/shell_uart.c +++ b/subsys/shell/backends/shell_uart.c @@ -520,7 +520,8 @@ SHELL_UART_DEFINE(shell_transport_uart); SHELL_DEFINE(shell_uart, CONFIG_SHELL_PROMPT_UART, &shell_transport_uart, CONFIG_SHELL_BACKEND_SERIAL_LOG_MESSAGE_QUEUE_SIZE, CONFIG_SHELL_BACKEND_SERIAL_LOG_MESSAGE_QUEUE_TIMEOUT, - SHELL_FLAG_OLF_CRLF); + IS_ENABLED(CONFIG_SHELL_BACKEND_SERIAL_OLF_CRLF) ? + SHELL_FLAG_OLF_CRLF : SHELL_FLAG_CRLF_DEFAULT); #ifdef CONFIG_MCUMGR_TRANSPORT_SHELL struct smp_shell_data *shell_uart_smp_shell_data_get_ptr(void) From 4c2e758efe3948d4b2549cb040126d80e363f168 Mon Sep 17 00:00:00 2001 From: Jon Ringle Date: Tue, 6 Jan 2026 08:56:00 -0500 Subject: [PATCH 03/12] drivers: lora: Add noise floor support and RSSI callback Add noise floor measurement functionality to the SX12xx LoRa driver. This includes: - New lora_rssi.h header with noise floor API - sx12xx_lora_get_noise_floor() implementation - RSSI callback registration in Radio struct - Extension API for driver-specific noise floor implementations --- drivers/lora/loramac_node/sx127x.c | 3 + drivers/lora/loramac_node/sx12xx_common.c | 71 +++++++++++++++++++++++ drivers/lora/loramac_node/sx12xx_common.h | 2 + include/zephyr/drivers/lora_rssi.h | 44 ++++++++++++++ 4 files changed, 120 insertions(+) create mode 100644 include/zephyr/drivers/lora_rssi.h diff --git a/drivers/lora/loramac_node/sx127x.c b/drivers/lora/loramac_node/sx127x.c index d478b4081c71..d7c73bfe7ae2 100644 --- a/drivers/lora/loramac_node/sx127x.c +++ b/drivers/lora/loramac_node/sx127x.c @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -88,6 +89,7 @@ LOG_MODULE_REGISTER(sx127x, CONFIG_LORA_LOG_LEVEL); #define SX127xSetPublicNetwork SX1276SetPublicNetwork #define SX127xGetWakeupTime SX1276GetWakeupTime #define SX127xSetTxContinuousWave SX1276SetTxContinuousWave +#define SX127xReadRssi SX1276ReadRssi #else #error No SX127x instance in device tree. @@ -548,6 +550,7 @@ const struct Radio_s Radio = { .RxBoosted = NULL, .SetRxDutyCycle = NULL, .SetTxContinuousWave = SX127xSetTxContinuousWave, + .Rssi = SX127xReadRssi, }; static int sx127x_antenna_configure(void) diff --git a/drivers/lora/loramac_node/sx12xx_common.c b/drivers/lora/loramac_node/sx12xx_common.c index 25d72ff80611..83a307f69883 100644 --- a/drivers/lora/loramac_node/sx12xx_common.c +++ b/drivers/lora/loramac_node/sx12xx_common.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -379,6 +380,76 @@ int sx12xx_lora_test_cw(const struct device *dev, uint32_t frequency, return 0; } +/** + * @brief Get RF noise floor measurement + * + * @param dev Device pointer + * @return int16_t Noise floor value in dBm + */ +int16_t sx12xx_lora_get_noise_floor(const struct device *dev) +{ + int16_t noise_floor = NOISE_FLOOR_DEFAULT_VALUE; + int32_t noise_floor_sum = 0; + int valid_measurements = 0; + + /* Ensure available, released after measurement */ + if (!modem_acquire(&dev_data)) { + LOG_ERR("Radio is busy"); + return NOISE_FLOOR_DEFAULT_VALUE; + } + + LOG_DBG("Starting noise floor measurement using Radio.Rssi()"); + + /* Configure radio for optimal noise floor measurement */ + Radio.SetModem(MODEM_FSK); + + /* Set radio to continuous reception */ + Radio.Rx(0); + + /* Take multiple measurements to get a stable reading */ + for (int i = 0; i < 5; i++) { + /* Give radio time to settle before each measurement */ + k_sleep(K_MSEC(20)); + + /* Get noise floor directly from the radio API */ + int16_t current_noise_floor = Radio.Rssi(MODEM_FSK); + LOG_DBG("Noise floor measurement %d: %d dBm", i, current_noise_floor); + + /* Filter out clearly invalid measurements */ + if (current_noise_floor < 0) { /* Only accept negative values as valid */ + noise_floor_sum += current_noise_floor; + valid_measurements++; + } else { + LOG_WRN("Ignoring invalid noise floor: %d dBm", current_noise_floor); + } + } + + /* Calculate average */ + if (valid_measurements > 0) { + noise_floor = noise_floor_sum / valid_measurements; + LOG_DBG("Average noise floor: %d dBm from %d readings", noise_floor, valid_measurements); + } else { + LOG_WRN("No valid noise floor measurements obtained"); + } + + /* Restore original radio state - no need to sleep before this */ + Radio.Sleep(); + + /* Release modem when done */ + modem_release(&dev_data); + + return noise_floor; +} + +int16_t lora_get_noise_floor(const struct device *dev) +{ + if (!dev) { + return NOISE_FLOOR_DEFAULT_VALUE; + } + + return sx12xx_lora_get_noise_floor(dev); +} + int sx12xx_init(const struct device *dev) { atomic_set(&dev_data.modem_usage, 0); diff --git a/drivers/lora/loramac_node/sx12xx_common.h b/drivers/lora/loramac_node/sx12xx_common.h index 2a26485f2c07..b11aed83bcd2 100644 --- a/drivers/lora/loramac_node/sx12xx_common.h +++ b/drivers/lora/loramac_node/sx12xx_common.h @@ -38,6 +38,8 @@ int sx12xx_lora_test_cw(const struct device *dev, uint32_t frequency, int8_t tx_power, uint16_t duration); +int16_t sx12xx_lora_get_noise_floor(const struct device *dev); + int sx12xx_init(const struct device *dev); #endif /* ZEPHYR_DRIVERS_SX12XX_COMMON_H_ */ diff --git a/include/zephyr/drivers/lora_rssi.h b/include/zephyr/drivers/lora_rssi.h new file mode 100644 index 000000000000..d08f2a012437 --- /dev/null +++ b/include/zephyr/drivers/lora_rssi.h @@ -0,0 +1,44 @@ +#ifndef LORA_RSSI_H_ +#define LORA_RSSI_H_ + +#include +#include + +/* Noise floor measurement constants */ +#define NOISE_FLOOR_DEFAULT_VALUE -30 /* Default to "noisy" when measurement fails */ + +/** + * @brief Function pointer type for noise floor measurement implementations + */ +typedef int16_t (*lora_api_get_noise_floor)(const struct device *dev); + +/** + * @brief Extended API functions for LoRa drivers + */ +struct lora_api_extensions { + lora_api_get_noise_floor get_noise_floor; /* Noise floor measurement function */ +}; + +/** + * @brief Get the RF noise floor measurement at the current frequency + * + * @param dev LoRa device + * @return int16_t Noise floor in dBm + */ +int16_t lora_get_noise_floor(const struct device *dev); + +/** + * @brief Register noise floor measurement function with device + * + * @param dev_ptr Pointer to the device + * @param func_ptr Pointer to the get_noise_floor implementation + */ +#define LORA_REGISTER_GET_NOISE_FLOOR(dev_ptr, func_ptr) \ + do { \ + struct sx127x_data *data = dev_ptr->data; \ + if (data) { \ + data->ext_api.get_noise_floor = func_ptr; \ + } \ + } while (0) + +#endif /* LORA_RSSI_H_ */ From 4d2ce8afe39946b57334ed4286fc67561591ce39 Mon Sep 17 00:00:00 2001 From: Jon Ringle Date: Tue, 27 Jan 2026 21:42:51 -0500 Subject: [PATCH 04/12] scripts: footprint: size_report: Fix TLS symbol attribution TLS sections (e.g. .tdata/.tbss) use addresses as TLS offsets and can overlap normal VMA ranges. Avoid using TLS section address ranges for RAM bucketing and classify TLS symbols using section flags instead. Signed-off-by: Jon Ringle --- scripts/footprint/size_report | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/scripts/footprint/size_report b/scripts/footprint/size_report index 544efd492702..f1920af2c4a1 100755 --- a/scripts/footprint/size_report +++ b/scripts/footprint/size_report @@ -43,6 +43,7 @@ if version.parse(elftools.__version__) < version.parse('0.24'): SHF_WRITE = 0x1 SHF_ALLOC = 0x2 SHF_EXEC = 0x4 +SHF_TLS = 0x400 SHF_WRITE_ALLOC = SHF_WRITE | SHF_ALLOC SHF_ALLOC_EXEC = SHF_ALLOC | SHF_EXEC @@ -155,8 +156,28 @@ def get_symbols(elf, addr_ranges): if get_symbol_size(sym) == 0: continue - ram_sym = is_symbol_in_ranges(sym, ram_addr_ranges) - rom_sym = is_symbol_in_ranges(sym, rom_addr_ranges) + # TLS sections (e.g. .tdata/.tbss) use addresses as TLS offsets + # and can overlap normal VMA ranges. Don't classify based on + # address ranges for TLS. + sym_is_tls = False + tls_section_name = None + try: + shndx = sym['st_shndx'] + if isinstance(shndx, int): + sec = elf.get_section(shndx) + if sec['sh_flags'] & SHF_TLS: + sym_is_tls = True + tls_section_name = sec.name + except Exception: + sym_is_tls = False + tls_section_name = None + + if sym_is_tls: + ram_sym = {'name': tls_section_name} + rom_sym = None + else: + ram_sym = is_symbol_in_ranges(sym, ram_addr_ranges) + rom_sym = is_symbol_in_ranges(sym, rom_addr_ranges) # Determine the location(s) for this symbol. loc = [] @@ -232,10 +253,12 @@ def get_section_ranges(elf): sec_end = sec_start + (size - 1 if size else 0) bound = {'start': sec_start, 'end': sec_end, 'name': section.name} is_assigned = False + flags = section['sh_flags'] if section['sh_type'] == 'SHT_NOBITS': # BSS and noinit sections - ram_addr_ranges.append(bound) + if not (flags & SHF_TLS): + ram_addr_ranges.append(bound) ram_size += size total_size += size is_assigned = True @@ -243,7 +266,6 @@ def get_section_ranges(elf): elif section['sh_type'] == 'SHT_PROGBITS': # Sections to be in flash or memory - flags = section['sh_flags'] if (flags & SHF_ALLOC_EXEC) == SHF_ALLOC_EXEC: # Text section rom_addr_ranges.append(bound) @@ -259,7 +281,8 @@ def get_section_ranges(elf): # since at boot, content is copied from ROM to RAM rom_addr_ranges.append(bound) rom_size += size - ram_addr_ranges.append(bound) + if not (flags & SHF_TLS): + ram_addr_ranges.append(bound) ram_size += size total_size += size is_assigned = True From 92fd80db6279e583fd26059799554e0262310e02 Mon Sep 17 00:00:00 2001 From: Jon Ringle Date: Thu, 29 Jan 2026 23:00:50 -0500 Subject: [PATCH 05/12] scripts: footprint: plot: Guard against missing total_size When total_size is missing, skip the percentage line in hover text. Signed-off-by: Jon Ringle --- scripts/footprint/plot.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/footprint/plot.py b/scripts/footprint/plot.py index 9a278bbc1140..f37e0d10ae25 100755 --- a/scripts/footprint/plot.py +++ b/scripts/footprint/plot.py @@ -51,7 +51,7 @@ def main(): with open(args.input) as f: data = json.load(f) - totalsize = data.get('total_size') + totalsize = data.get('total_size', 0) ids = [] labels = [] parents = [] @@ -75,7 +75,9 @@ def iter_node(node: dict, parent=''): parents.append(parent) values.append(node.get('size', 0)) - details = [f'percentage: {node.get("size") / totalsize:.2%}'] + details = [] + if totalsize > 0: + details.append(f'percentage: {node.get("size") / totalsize:.2%}') if 'address' in node: details.append(f'address: 0x{node.get("address"):08x}') if 'section' in node: From ec4d477ca2a0d651170a65e65ade58111155f73c Mon Sep 17 00:00:00 2001 From: Jon Ringle Date: Tue, 27 Jan 2026 21:42:52 -0500 Subject: [PATCH 06/12] scripts: footprint: plot: Increase label font size Improve readability of sunburst labels in generated interactive plot. Signed-off-by: Jon Ringle --- scripts/footprint/plot.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/footprint/plot.py b/scripts/footprint/plot.py index f37e0d10ae25..a44c8c72712e 100755 --- a/scripts/footprint/plot.py +++ b/scripts/footprint/plot.py @@ -103,6 +103,7 @@ def iter_node(node: dict, parent=''): skip_invalid=True, ) fig.update_layout(margin={'t': 0, 'l': 0, 'r': 0, 'b': 0}) + fig.update_traces(textfont=dict(size=24)) if args.html: fig.write_html(args.html, auto_open=False) From 351db5b0cc3a6abe6c895fb12b79cd73ab602464 Mon Sep 17 00:00:00 2001 From: Jon Ringle Date: Tue, 26 Aug 2025 10:37:08 -0700 Subject: [PATCH 07/12] drivers: lora: sx126x: add low energy support Add optional low-power sequencing for SX126x (GPIO and SUBGHZ radio control) so the host can turn the radio off between operations. Remove debug helpers dump_gpio() and dump_rf_ctrl(). Drop gridpoint_stm32wl_subghz_radio compat; use st,stm32wl-subghz-radio only. Signed-off-by: Jon Ringle --- drivers/lora/loramac_node/sx126x.c | 69 ++++++++++++++++++++-- drivers/lora/loramac_node/sx126x_common.h | 7 +++ dts/bindings/lora/semtech,sx126x-base.yaml | 39 ++++++++++++ 3 files changed, 111 insertions(+), 4 deletions(-) diff --git a/drivers/lora/loramac_node/sx126x.c b/drivers/lora/loramac_node/sx126x.c index 6221943f5200..e37e8ceef656 100644 --- a/drivers/lora/loramac_node/sx126x.c +++ b/drivers/lora/loramac_node/sx126x.c @@ -50,8 +50,15 @@ static const struct sx126x_config dev_config = { #if HAVE_GPIO_RX_ENABLE .rx_enable = GPIO_DT_SPEC_INST_GET(0, rx_enable_gpios), #endif +#if HAVE_GPIO_FE_CTRL + .fe_ctrl1_enable = GPIO_DT_SPEC_INST_GET(0, fe_ctrl1_enable_gpios), + .fe_ctrl2_enable = GPIO_DT_SPEC_INST_GET(0, fe_ctrl2_enable_gpios), + .fe_ctrl3_enable = GPIO_DT_SPEC_INST_GET(0, fe_ctrl3_enable_gpios), +#endif + }; + static struct sx126x_data dev_data; void SX126xWaitOnBusy(void); @@ -253,29 +260,57 @@ void SX126xAntSwOff(void) #endif } +#if HAVE_GPIO_TX_ENABLE static void sx126x_set_tx_enable(int value) { -#if HAVE_GPIO_TX_ENABLE gpio_pin_set_dt(&dev_config.tx_enable, value); -#endif } +#endif +#if HAVE_GPIO_RX_ENABLE static void sx126x_set_rx_enable(int value) { -#if HAVE_GPIO_RX_ENABLE gpio_pin_set_dt(&dev_config.rx_enable, value); +} +#endif + + +static void sx126x_set_fe_ctrl(int ctrl1, int ctrl2, int ctrl3) +{ +#if HAVE_GPIO_FE_CTRL + gpio_pin_set_dt(&dev_config.fe_ctrl1_enable, ctrl1); + gpio_pin_set_dt(&dev_config.fe_ctrl2_enable, ctrl2); + gpio_pin_set_dt(&dev_config.fe_ctrl3_enable, ctrl3); #endif } + RadioOperatingModes_t SX126xGetOperatingMode(void) { return dev_data.mode; } +#if HAVE_GPIO_FE_CTRL +static const enum { + RFO_LP, + RFO_HP, +} pa_output = DT_INST_STRING_UPPER_TOKEN(0, power_amplifier_output); +#endif + +#define LORA_NODE DT_NODELABEL(lora) + + void SX126xSetOperatingMode(RadioOperatingModes_t mode) { LOG_DBG("SetOperatingMode: %s (%i)", sx126x_mode_name(mode), mode); +#if HAVE_GPIO_FE_CTRL + const int off_fe_ctrl_lines[] = DT_PROP(LORA_NODE, off_fe_ctrl_lines); + const int rx_fe_ctrl_lines[] = DT_PROP(LORA_NODE, rx_fe_ctrl_lines); + const int txhp_fe_ctrl_lines[] = DT_PROP(LORA_NODE, txhp_fe_ctrl_lines); + const int txlp_fe_ctrl_lines[] = DT_PROP(LORA_NODE, txlp_fe_ctrl_lines); +#endif + dev_data.mode = mode; /* To avoid inadvertently putting the RF switch in an @@ -284,15 +319,29 @@ void SX126xSetOperatingMode(RadioOperatingModes_t mode) */ switch (mode) { case MODE_TX: +#if HAVE_GPIO_TX_ENABLE sx126x_set_rx_enable(0); sx126x_set_tx_enable(1); +#endif +#if HAVE_GPIO_FE_CTRL + if (pa_output == RFO_LP) { + sx126x_set_fe_ctrl(txlp_fe_ctrl_lines[0], txlp_fe_ctrl_lines[1], txlp_fe_ctrl_lines[2]); + } else { + sx126x_set_fe_ctrl(txhp_fe_ctrl_lines[0], txhp_fe_ctrl_lines[1], txhp_fe_ctrl_lines[2]); + } +#endif break; case MODE_RX: case MODE_RX_DC: case MODE_CAD: +#if HAVE_GPIO_TX_ENABLE sx126x_set_tx_enable(0); sx126x_set_rx_enable(1); +#endif +#if HAVE_GPIO_FE_CTRL + sx126x_set_fe_ctrl(rx_fe_ctrl_lines[0], rx_fe_ctrl_lines[1], rx_fe_ctrl_lines[2]); +#endif break; case MODE_SLEEP: @@ -300,8 +349,13 @@ void SX126xSetOperatingMode(RadioOperatingModes_t mode) sx126x_dio1_irq_disable(&dev_data); __fallthrough; default: +#if HAVE_GPIO_TX_ENABLE sx126x_set_rx_enable(0); sx126x_set_tx_enable(0); +#endif +#if HAVE_GPIO_FE_CTRL + sx126x_set_fe_ctrl(off_fe_ctrl_lines[0], off_fe_ctrl_lines[1], off_fe_ctrl_lines[2]); +#endif break; } } @@ -437,7 +491,14 @@ static int sx126x_lora_init(const struct device *dev) if (sx12xx_configure_pin(antenna_enable, GPIO_OUTPUT_INACTIVE) || sx12xx_configure_pin(rx_enable, GPIO_OUTPUT_INACTIVE) || - sx12xx_configure_pin(tx_enable, GPIO_OUTPUT_INACTIVE)) { + sx12xx_configure_pin(tx_enable, GPIO_OUTPUT_INACTIVE) +#if HAVE_GPIO_FE_CTRL + || sx12xx_configure_pin(fe_ctrl1_enable, GPIO_OUTPUT_INACTIVE) + || sx12xx_configure_pin(fe_ctrl2_enable, GPIO_OUTPUT_INACTIVE) + || sx12xx_configure_pin(fe_ctrl3_enable, GPIO_OUTPUT_INACTIVE) +#endif + ) + { return -EIO; } diff --git a/drivers/lora/loramac_node/sx126x_common.h b/drivers/lora/loramac_node/sx126x_common.h index 81ac661859cc..8dbe8af620f0 100644 --- a/drivers/lora/loramac_node/sx126x_common.h +++ b/drivers/lora/loramac_node/sx126x_common.h @@ -34,6 +34,7 @@ DT_INST_NODE_HAS_PROP(0, antenna_enable_gpios) #define HAVE_GPIO_TX_ENABLE DT_INST_NODE_HAS_PROP(0, tx_enable_gpios) #define HAVE_GPIO_RX_ENABLE DT_INST_NODE_HAS_PROP(0, rx_enable_gpios) +#define HAVE_GPIO_FE_CTRL (DT_INST_NODE_HAS_PROP(0, fe_ctrl1_enable_gpios) && !DT_INST_NODE_HAS_PROP(0, tx_enable_gpios)) struct sx126x_config { struct spi_dt_spec bus; @@ -46,6 +47,12 @@ struct sx126x_config { #if HAVE_GPIO_RX_ENABLE struct gpio_dt_spec rx_enable; #endif +#if HAVE_GPIO_FE_CTRL + struct gpio_dt_spec fe_ctrl3_enable; + struct gpio_dt_spec fe_ctrl2_enable; + struct gpio_dt_spec fe_ctrl1_enable; +#endif + }; struct sx126x_data { diff --git a/dts/bindings/lora/semtech,sx126x-base.yaml b/dts/bindings/lora/semtech,sx126x-base.yaml index d4d1c36b6622..6d083dd62a80 100644 --- a/dts/bindings/lora/semtech,sx126x-base.yaml +++ b/dts/bindings/lora/semtech,sx126x-base.yaml @@ -35,6 +35,45 @@ properties: Antenna switch RX enable GPIO. If set, the driver tracks the state of the radio and controls the RF switch. + txlp-fe-ctrl-lines: + type: array + description: | + RF front-end GPIO bit values to enable transmit low-power. Array + of 3 binary values corresponding to FE_CTRL1, FE_CTRL2, FE_CTRL3 + + txhp-fe-ctrl-lines: + type: array + description: | + RF front-end GPIO bit values to enable transmit high-power. Array + of 3 binary values corresponding to FE_CTRL1, FE_CTRL2, FE_CTRL3 + + rx-fe-ctrl-lines: + type: array + description: | + RF front-end GPIO bit values to enable receive. Array + of 3 binary values corresponding to FE_CTRL1, FE_CTRL2, FE_CTRL3 + + off-fe-ctrl-lines: + type: array + description: | + RF front-end GPIO bit values to switch off antenna. Array + of 3 binary values corresponding to FE_CTRL1, FE_CTRL2, FE_CTRL3 + + fe-ctrl1-enable-gpios: + type: phandle-array + description: | + FE_CTRL1 signal GPIO controlling RF switch. + + fe-ctrl2-enable-gpios: + type: phandle-array + description: | + FE_CTRL2 signal GPIO controlling RF switch. + + fe-ctrl3-enable-gpios: + type: phandle-array + description: | + FE_CTRL3 signal GPIO controlling RF switch. + dio1-gpios: type: phandle-array description: | From f1b64035883d27ff10b15a5e7195eb93600e643f Mon Sep 17 00:00:00 2001 From: Jon Ringle Date: Tue, 11 Nov 2025 11:19:49 -0800 Subject: [PATCH 08/12] rtc: add subsecond support to stm32 driver Add support for RTC_ALARM_TIME_MASK_NSEC on STM32 series that implement the RTC_ALRMASSR/RTC_ALRMBSSR subsecond alarm registers. Infer whether NSEC matching was requested from the programmed MASKSS field in the alarm subsecond register to avoid storing additional alarm state. Signed-off-by: Jon Ringle --- drivers/rtc/rtc_ll_stm32.c | 181 +++++++++++++++++++++++++++++++++++-- 1 file changed, 175 insertions(+), 6 deletions(-) diff --git a/drivers/rtc/rtc_ll_stm32.c b/drivers/rtc/rtc_ll_stm32.c index 62f0f5a32a9a..de8361b6d13e 100644 --- a/drivers/rtc/rtc_ll_stm32.c +++ b/drivers/rtc/rtc_ll_stm32.c @@ -53,6 +53,16 @@ LOG_MODULE_REGISTER(rtc_stm32, CONFIG_RTC_LOG_LEVEL); #define HW_SUBSECOND_SUPPORT 1 #endif +/* STM32 devices that support subsecond alarms (require RTC_ALRMASSR/RTC_ALRMBSSR registers) */ +#if defined(CONFIG_SOC_SERIES_STM32F1X) || defined(CONFIG_SOC_SERIES_STM32F2X) \ + || defined(CONFIG_SOC_SERIES_STM32L1X) || defined(CONFIG_SOC_SERIES_STM32C0X) +/* STM32F1, STM32F2, STM32L1, and STM32C0 series do not support subsecond alarms */ +#define HW_SUBSECOND_ALARM_SUPPORT (0) +#else +/* STM32F3, STM32F4, STM32L0, STM32L4, STM32H7, STM32G0, STM32G4, STM32WL5, STM32WB, STM32U5 support subsecond alarms */ +#define HW_SUBSECOND_ALARM_SUPPORT (1) +#endif + /* RTC start time: 1st, Jan, 2000 */ #define RTC_YEAR_REF 2000 /* struct tm start time: 1st, Jan, 1900 */ @@ -94,7 +104,8 @@ LOG_MODULE_REGISTER(rtc_stm32, CONFIG_RTC_LOG_LEVEL); #define RTC_STM32_SUPPORTED_ALARM_FIELDS \ (RTC_ALARM_TIME_MASK_SECOND | RTC_ALARM_TIME_MASK_MINUTE \ | RTC_ALARM_TIME_MASK_HOUR | RTC_ALARM_TIME_MASK_WEEKDAY \ - | RTC_ALARM_TIME_MASK_MONTHDAY) + | RTC_ALARM_TIME_MASK_MONTHDAY \ + | (HW_SUBSECOND_ALARM_SUPPORT ? RTC_ALARM_TIME_MASK_NSEC : 0)) #define RTC_STM32_EXTI_LINE_NUM DT_INST_PROP_OR(0, alrm_exti_line, 0) @@ -701,8 +712,43 @@ static int rtc_stm32_get_time(const struct device *dev, struct rtc_time *timeptr return 0; } +#if HW_SUBSECOND_ALARM_SUPPORT +/* Subsecond conversion functions */ + +/** + * @brief Convert nanoseconds to RTC subsecond register value + * @param nsec Nanoseconds (0-999999999) + * @param sync_prescaler RTC sync prescaler value + * @return RTC subsecond register value + */ +static inline uint32_t rtc_stm32_nsec_to_subsecond(uint32_t nsec, uint32_t sync_prescaler) +{ + /* Convert nanoseconds to RTC subsecond register value + * Formula: rtc_subsecond = sync_prescaler - (nsec * (sync_prescaler + 1)) / 1000000000 + */ + uint64_t temp = (uint64_t)nsec * (sync_prescaler + 1); + return sync_prescaler - (uint32_t)(temp / 1000000000L); +} + +/** + * @brief Convert RTC subsecond register value to nanoseconds + * @param rtc_subsecond RTC subsecond register value + * @param sync_prescaler RTC sync prescaler value + * @return Nanoseconds (0-999999999) + */ +static inline uint32_t rtc_stm32_subsecond_to_nsec(uint32_t rtc_subsecond, uint32_t sync_prescaler) +{ + /* Convert RTC subsecond register value to nanoseconds + * Formula: nsec = ((sync_prescaler - rtc_subsecond) * 1000000000) / (sync_prescaler + 1) + */ + uint64_t temp = ((uint64_t)(sync_prescaler - rtc_subsecond)) * 1000000000L; + return (uint32_t)(temp / (sync_prescaler + 1)); +} +#endif /* HW_SUBSECOND_ALARM_SUPPORT */ + #ifdef STM32_RTC_ALARM_ENABLED -static void rtc_stm32_alarm_get_alrm_time(uint16_t id, struct rtc_time *timeptr) +static inline void rtc_stm32_get_ll_alrm_time(uint16_t id, struct rtc_time *timeptr, + uint32_t sync_prescaler) { if (id == RTC_STM32_ALRM_A) { timeptr->tm_sec = bcd2bin(LL_RTC_ALMA_GetSecond(RTC)); @@ -710,6 +756,12 @@ static void rtc_stm32_alarm_get_alrm_time(uint16_t id, struct rtc_time *timeptr) timeptr->tm_hour = bcd2bin(LL_RTC_ALMA_GetHour(RTC)); timeptr->tm_wday = bcd2bin(LL_RTC_ALMA_GetWeekDay(RTC)); timeptr->tm_mday = bcd2bin(LL_RTC_ALMA_GetDay(RTC)); +#if HW_SUBSECOND_ALARM_SUPPORT + uint32_t rtc_subsecond = LL_RTC_ALMA_GetSubSecond(RTC); + timeptr->tm_nsec = rtc_stm32_subsecond_to_nsec(rtc_subsecond, sync_prescaler); +#else + timeptr->tm_nsec = 0; +#endif return; } #if RTC_STM32_ALARMS_COUNT > 1 @@ -719,6 +771,12 @@ static void rtc_stm32_alarm_get_alrm_time(uint16_t id, struct rtc_time *timeptr) timeptr->tm_hour = bcd2bin(LL_RTC_ALMB_GetHour(RTC)); timeptr->tm_wday = bcd2bin(LL_RTC_ALMB_GetWeekDay(RTC)); timeptr->tm_mday = bcd2bin(LL_RTC_ALMB_GetDay(RTC)); +#if HW_SUBSECOND_ALARM_SUPPORT + uint32_t rtc_subsecond = LL_RTC_ALMB_GetSubSecond(RTC); + timeptr->tm_nsec = rtc_stm32_subsecond_to_nsec(rtc_subsecond, sync_prescaler); +#else + timeptr->tm_nsec = 0; +#endif } #endif /* RTC_STM32_ALARMS_COUNT > 1 */ } @@ -773,6 +831,90 @@ static inline uint16_t rtc_stm32_alarm_get_alrm_mask(uint16_t id) return zephyr_alarm_mask; } +#if HW_SUBSECOND_ALARM_SUPPORT +/* Subsecond register access functions */ + +/** + * @brief Read RTC subsecond register (RTC_SSR) + * @param rtc_subsecond Pointer to store the subsecond value + * @return 0 on success, negative error code on failure + */ +static inline int rtc_stm32_read_subsecond(uint32_t *rtc_subsecond) +{ + if (rtc_subsecond == NULL) { + return -EINVAL; + } + + *rtc_subsecond = LL_RTC_TIME_GetSubSecond(RTC); + return 0; +} + +/** + * @brief Read alarm subsecond register + * @param id Alarm ID (RTC_STM32_ALRM_A or RTC_STM32_ALRM_B) + * @param rtc_subsecond Pointer to store the subsecond value + * @return 0 on success, negative error code on failure + */ +static inline int rtc_stm32_read_alarm_subsecond(uint16_t id, uint32_t *rtc_subsecond) +{ + if (rtc_subsecond == NULL) { + return -EINVAL; + } + + if (id == RTC_STM32_ALRM_A) { + *rtc_subsecond = LL_RTC_ALMA_GetSubSecond(RTC); + } else if (id == RTC_STM32_ALRM_B) { + *rtc_subsecond = LL_RTC_ALMB_GetSubSecond(RTC); + } else { + return -EINVAL; + } + + return 0; +} + +static inline uint32_t rtc_stm32_alarm_get_subsecond_mask(uint16_t id) +{ + uint32_t reg; + + if (id == RTC_STM32_ALRM_A) { + reg = RTC->ALRMASSR; + } else if (id == RTC_STM32_ALRM_B) { + reg = RTC->ALRMBSSR; + } else { + return 0; + } + + /* + * MASKSS bitfield position/width differs across STM32 series (4..6 bits), + * but is consistently located starting at bit 24. + */ + return (reg >> 24) & 0x3F; +} + +/** + * @brief Write alarm subsecond register + * @param id Alarm ID (RTC_STM32_ALRM_A or RTC_STM32_ALRM_B) + * @param rtc_subsecond Subsecond value to write + * @return 0 on success, negative error code on failure + */ +static inline int rtc_stm32_write_alarm_subsecond(uint16_t id, uint32_t rtc_subsecond) +{ + if (id == RTC_STM32_ALRM_A) { + LL_RTC_ALMA_SetSubSecond(RTC, rtc_subsecond); + /* Compare SS[14:0] (15 bits) */ + LL_RTC_ALMA_SetSubSecondMask(RTC, 15); + } else if (id == RTC_STM32_ALRM_B) { + LL_RTC_ALMB_SetSubSecond(RTC, rtc_subsecond); + /* Compare SS[14:0] (15 bits) */ + LL_RTC_ALMB_SetSubSecondMask(RTC, 15); + } else { + return -EINVAL; + } + + return 0; +} +#endif /* HW_SUBSECOND_ALARM_SUPPORT */ + static int rtc_stm32_alarm_get_supported_fields(const struct device *dev, uint16_t id, uint16_t *mask) { @@ -795,6 +937,7 @@ static int rtc_stm32_alarm_get_time(const struct device *dev, uint16_t id, uint1 struct rtc_time *timeptr) { struct rtc_stm32_data *data = dev->data; + const struct rtc_stm32_config *cfg = dev->config; int err = 0; if ((mask == NULL) || (timeptr == NULL)) { @@ -812,12 +955,20 @@ static int rtc_stm32_alarm_get_time(const struct device *dev, uint16_t id, uint1 } memset(timeptr, -1, sizeof(struct rtc_time)); - rtc_stm32_alarm_get_alrm_time(id, timeptr); + rtc_stm32_get_ll_alrm_time(id, timeptr, cfg->sync_prescaler); *mask = rtc_stm32_alarm_get_alrm_mask(id); +#if HW_SUBSECOND_ALARM_SUPPORT + if (rtc_stm32_alarm_get_subsecond_mask(id) != 0) { + *mask |= RTC_ALARM_TIME_MASK_NSEC; + } else { + timeptr->tm_nsec = 0; + } +#else + timeptr->tm_nsec = 0; +#endif - LOG_DBG("get alarm: mday = %d, wday = %d, hour = %d, min = %d, sec = %d, " - "mask = 0x%04x", timeptr->tm_mday, timeptr->tm_wday, timeptr->tm_hour, - timeptr->tm_min, timeptr->tm_sec, *mask); + LOG_DBG("get alarm: %d/%d %d:%d:%d.%d mask=0x%04x", timeptr->tm_mday, timeptr->tm_wday, + timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec, timeptr->tm_nsec, *mask); unlock: k_spin_unlock(&data->lock, key); @@ -829,6 +980,7 @@ static int rtc_stm32_alarm_set_time(const struct device *dev, uint16_t id, uint1 const struct rtc_time *timeptr) { struct rtc_stm32_data *data = dev->data; + const struct rtc_stm32_config *cfg = dev->config; struct rtc_stm32_alrm *p_rtc_alrm; int err = 0; @@ -917,6 +1069,23 @@ static int rtc_stm32_alarm_set_time(const struct device *dev, uint16_t id, uint1 /* Disable the write protection for RTC registers */ LL_RTC_DisableWriteProtection(RTC); +#if HW_SUBSECOND_ALARM_SUPPORT + /* Handle subsecond alarm setting if requested */ + if (mask & RTC_ALARM_TIME_MASK_NSEC) { + uint32_t rtc_subsecond = rtc_stm32_nsec_to_subsecond(timeptr->tm_nsec, cfg->sync_prescaler); + rtc_stm32_write_alarm_subsecond(id, rtc_subsecond); + } else { + /* Disable subsecond comparison */ + if (id == RTC_STM32_ALRM_A) { + LL_RTC_ALMA_SetSubSecond(RTC, 0); + LL_RTC_ALMA_SetSubSecondMask(RTC, 0); + } else if (id == RTC_STM32_ALRM_B) { + LL_RTC_ALMB_SetSubSecond(RTC, 0); + LL_RTC_ALMB_SetSubSecondMask(RTC, 0); + } + } +#endif /* HW_SUBSECOND_ALARM_SUPPORT */ + /* Enable Alarm */ rtc_stm32_enable_alarm(RTC, id); /* Clear Alarm flag */ From c71d2eaada2e7cb5dd8e5fd22d5aad3a2755a11d Mon Sep 17 00:00:00 2001 From: Jon Ringle Date: Thu, 5 Feb 2026 12:53:46 -0500 Subject: [PATCH 09/12] kernel: queue: guard heap functions with CONFIG_KERNEL_MEM_POOL Wrap alloc_node struct, k_free, z_thread_malloc calls, and k_queue_alloc_append/prepend APIs with #ifdef CONFIG_KERNEL_MEM_POOL. This allows building with CONFIG_KERNEL_MEM_POOL=n without requiring stub implementations for heap functions. The alloc-based queue APIs are only meaningful when heap is available. Signed-off-by: GridPoint --- kernel/queue.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/kernel/queue.c b/kernel/queue.c index af6437879944..43c9a71f9bf6 100644 --- a/kernel/queue.c +++ b/kernel/queue.c @@ -22,15 +22,18 @@ #include #include +#ifdef CONFIG_KERNEL_MEM_POOL struct alloc_node { sys_sfnode_t node; void *data; }; +#endif /* CONFIG_KERNEL_MEM_POOL */ void *z_queue_node_peek(sys_sfnode_t *node, bool needs_free) { void *ret; +#ifdef CONFIG_KERNEL_MEM_POOL if ((node != NULL) && (sys_sfnode_flags_get(node) != (uint8_t)0)) { /* If the flag is set, then the enqueue operation for this item * did a behind-the scenes memory allocation of an alloc_node @@ -44,7 +47,9 @@ void *z_queue_node_peek(sys_sfnode_t *node, bool needs_free) if (needs_free) { k_free(anode); } - } else { + } else +#endif /* CONFIG_KERNEL_MEM_POOL */ + { /* Data was directly placed in the queue, the first word * reserved for the linked list. User mode isn't allowed to * do this, although it can get data sent this way. @@ -153,6 +158,7 @@ static int32_t queue_insert(struct k_queue *queue, void *prev, void *data, } /* Only need to actually allocate if no threads are pending */ +#ifdef CONFIG_KERNEL_MEM_POOL if (alloc) { struct alloc_node *anode; @@ -164,7 +170,10 @@ static int32_t queue_insert(struct k_queue *queue, void *prev, void *data, anode->data = data; sys_sfnode_init(&anode->node, 0x1); data = anode; - } else { + } else +#endif /* CONFIG_KERNEL_MEM_POOL */ + { + ARG_UNUSED(alloc); sys_sfnode_init(data, 0x0); } @@ -212,6 +221,7 @@ void k_queue_prepend(struct k_queue *queue, void *data) SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_queue, prepend, queue); } +#ifdef CONFIG_KERNEL_MEM_POOL int32_t z_impl_k_queue_alloc_append(struct k_queue *queue, void *data) { SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_queue, alloc_append, queue); @@ -253,6 +263,7 @@ static inline int32_t z_vrfy_k_queue_alloc_prepend(struct k_queue *queue, } #include #endif /* CONFIG_USERSPACE */ +#endif /* CONFIG_KERNEL_MEM_POOL */ int k_queue_append_list(struct k_queue *queue, void *head, void *tail) { From a422e9ea8bee4e11ce2a8be24f0ce0bebce68126 Mon Sep 17 00:00:00 2001 From: Jon Ringle Date: Wed, 25 Mar 2026 08:25:50 -0400 Subject: [PATCH 10/12] drivers: lora: sx12xx: clear async_rx_cb when cancelling async reception When lora_recv_async() is called with cb=NULL to cancel ongoing reception, modem_release() is called but async_rx_cb is left pointing to the previous callback. If a stale DIO1 work item fires after the radio has been reconfigured for TX, sx12xx_ev_rx_done() checks async_rx_cb (still set), calls Radio.Rx(0) during an active TX, and fires the stale callback. This corrupts the radio state and leaks packet buffers allocated by the callback. Clear async_rx_cb before calling modem_release() so that any stale DIO1 work item that fires after cancellation takes the synchronous path in sx12xx_ev_rx_done(), which properly checks modem_usage and bails out. Signed-off-by: Jon Ringle --- drivers/lora/loramac_node/sx12xx_common.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/lora/loramac_node/sx12xx_common.c b/drivers/lora/loramac_node/sx12xx_common.c index 83a307f69883..4488ea54088e 100644 --- a/drivers/lora/loramac_node/sx12xx_common.c +++ b/drivers/lora/loramac_node/sx12xx_common.c @@ -103,6 +103,12 @@ static void sx12xx_ev_rx_done(uint8_t *payload, uint16_t size, int16_t rssi, { struct k_poll_signal *sig = dev_data.operation_done; + if (!dev_data.async_rx_cb && !sig) { + LOG_WRN("RxDone event with no registered consumer " + "(modem_usage=%d)", + (int)atomic_get(&dev_data.modem_usage)); + } + /* Receiving in asynchronous mode */ if (dev_data.async_rx_cb) { /* Start receiving again */ @@ -312,6 +318,7 @@ int sx12xx_lora_recv_async(const struct device *dev, lora_recv_cb cb, void *user { /* Cancel ongoing reception */ if (cb == NULL) { + dev_data.async_rx_cb = NULL; if (!modem_release(&dev_data)) { /* Not receiving or already being stopped */ return -EINVAL; From 8226f34af37d1eedfaf871af0c93466133dde7e8 Mon Sep 17 00:00:00 2001 From: Jon Ringle Date: Wed, 6 May 2026 20:23:31 -0400 Subject: [PATCH 11/12] drivers: watchdog: stm32: add IWDG standby mode configuration Add Kconfig choice to control the IWDG_STDBY option byte, allowing users to configure whether the independent watchdog runs or is frozen during standby mode. Uses the public flash_ex_op API to read/write option bytes at APPLICATION init level (flash device must be ready). Three options: - No change (default): leave option byte as-is, no extra code - Freeze: clear IWDG_STDBY so IWDG is frozen in standby - Active: set IWDG_STDBY so IWDG continues running in standby The write triggers OBL_LAUNCH (system reset) only when the option byte differs from the desired value. On subsequent boots, no write occurs. Signed-off-by: Jon Ringle --- drivers/watchdog/Kconfig.stm32 | 42 ++++++++++++++++++++++++++++++ drivers/watchdog/wdt_iwdg_stm32.c | 43 +++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/drivers/watchdog/Kconfig.stm32 b/drivers/watchdog/Kconfig.stm32 index 35ad0e4b9c22..e0171b7cef7d 100644 --- a/drivers/watchdog/Kconfig.stm32 +++ b/drivers/watchdog/Kconfig.stm32 @@ -14,6 +14,48 @@ menuconfig IWDG_STM32 help Enable IWDG driver for STM32 line of MCUs +choice IWDG_STM32_STANDBY + prompt "IWDG behavior in standby mode" + depends on IWDG_STM32 + default IWDG_STM32_STANDBY_NO_CHANGE + help + Configure whether the IWDG runs during standby mode. + This modifies the IWDG_STDBY option byte if needed. + Selecting "no change" leaves the option byte as-is. + +config IWDG_STM32_STANDBY_NO_CHANGE + bool "No change" + help + Do not modify the IWDG_STDBY option byte. + +config IWDG_STM32_STANDBY_FREEZE + bool "Freeze IWDG in standby" + select FLASH_STM32_OPTION_BYTES + select FLASH_EX_OP_ENABLED + help + Clear the IWDG_STDBY option byte so the IWDG is frozen + during standby mode. Prevents spurious watchdog resets + when the system enters low-power standby. + +config IWDG_STM32_STANDBY_ACTIVE + bool "IWDG active in standby" + select FLASH_STM32_OPTION_BYTES + select FLASH_EX_OP_ENABLED + help + Set the IWDG_STDBY option byte so the IWDG continues + running during standby mode. + +endchoice + +config IWDG_STM32_STANDBY_INIT_PRIORITY + int "IWDG standby configuration init priority" + depends on IWDG_STM32_STANDBY_FREEZE || IWDG_STM32_STANDBY_ACTIVE + default 85 + help + Initialization priority for the IWDG standby option byte + configuration. It must be greater than FLASH_INIT_PRIORITY + to ensure the flash driver is available for option byte access. + config IWDG_STM32_INITIAL_TIMEOUT int "Value for IWDG timeout in ms" depends on IWDG_STM32 diff --git a/drivers/watchdog/wdt_iwdg_stm32.c b/drivers/watchdog/wdt_iwdg_stm32.c index 95dec9ad412c..439f2db756b5 100644 --- a/drivers/watchdog/wdt_iwdg_stm32.c +++ b/drivers/watchdog/wdt_iwdg_stm32.c @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -20,6 +21,11 @@ #include #include +#if defined(CONFIG_IWDG_STM32_STANDBY_FREEZE) || defined(CONFIG_IWDG_STM32_STANDBY_ACTIVE) +#include +#include +#endif + #include "wdt_iwdg_stm32.h" #define IWDG_PRESCALER_MIN (4U) @@ -266,3 +272,40 @@ DEVICE_DT_INST_DEFINE(0, iwdg_stm32_init, NULL, &iwdg_stm32_dev_data, &iwdg_stm32_dev_cfg, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &iwdg_stm32_api); + +#if defined(CONFIG_IWDG_STM32_STANDBY_FREEZE) || defined(CONFIG_IWDG_STM32_STANDBY_ACTIVE) +static int iwdg_stm32_configure_standby(void) +{ + const struct device *flash_dev = + DEVICE_DT_GET(DT_CHOSEN(zephyr_flash_controller)); + uint32_t option_register_value; + int rc; + + rc = flash_ex_op(flash_dev, FLASH_STM32_EX_OP_OPTB_READ, 0, + &option_register_value); + if (rc < 0) { + return rc; + } + + bool in_standby_mode = + (option_register_value & FLASH_OPTR_IWDG_STDBY) != 0; + + if (IS_ENABLED(CONFIG_IWDG_STM32_STANDBY_FREEZE) && in_standby_mode) { + option_register_value &= ~FLASH_OPTR_IWDG_STDBY; + } else if (IS_ENABLED(CONFIG_IWDG_STM32_STANDBY_ACTIVE) && + !in_standby_mode) { + option_register_value |= FLASH_OPTR_IWDG_STDBY; + } else { + return 0; + } + + return flash_ex_op(flash_dev, FLASH_STM32_EX_OP_OPTB_WRITE, + (uintptr_t)option_register_value, NULL); +} + +BUILD_ASSERT(CONFIG_IWDG_STM32_STANDBY_INIT_PRIORITY > CONFIG_FLASH_INIT_PRIORITY, + "IWDG standby init must run after flash driver"); + +SYS_INIT(iwdg_stm32_configure_standby, POST_KERNEL, + CONFIG_IWDG_STM32_STANDBY_INIT_PRIORITY); +#endif /* CONFIG_IWDG_STM32_STANDBY_FREEZE || CONFIG_IWDG_STM32_STANDBY_ACTIVE */ From 0ed59146ead6fe46b68a1eabfda22f9405004f8a Mon Sep 17 00:00:00 2001 From: Stevan Bowles Date: Tue, 24 Mar 2026 16:37:31 -0400 Subject: [PATCH 12/12] drivers: lora: add support for runtime power adjustments Make dts field power-amplifier-output optional. If defined, the radio will remain bound to the power mode defined. Otherwise, the radio will be free to dynamically switch between high and low power modes based off the power level input. Signed-off-by: Stevan Bowles --- drivers/lora/loramac_node/sx126x.c | 9 +----- drivers/lora/loramac_node/sx126x_common.h | 8 +++++ drivers/lora/loramac_node/sx126x_stm32wl.c | 30 ++++++++++++++++--- .../lora/st,stm32wl-subghz-radio.yaml | 1 - 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/drivers/lora/loramac_node/sx126x.c b/drivers/lora/loramac_node/sx126x.c index e37e8ceef656..4b869ab8ecdd 100644 --- a/drivers/lora/loramac_node/sx126x.c +++ b/drivers/lora/loramac_node/sx126x.c @@ -290,13 +290,6 @@ RadioOperatingModes_t SX126xGetOperatingMode(void) return dev_data.mode; } -#if HAVE_GPIO_FE_CTRL -static const enum { - RFO_LP, - RFO_HP, -} pa_output = DT_INST_STRING_UPPER_TOKEN(0, power_amplifier_output); -#endif - #define LORA_NODE DT_NODELABEL(lora) @@ -324,7 +317,7 @@ void SX126xSetOperatingMode(RadioOperatingModes_t mode) sx126x_set_tx_enable(1); #endif #if HAVE_GPIO_FE_CTRL - if (pa_output == RFO_LP) { + if (sx126x_get_tx_power_mode() == RFO_LP) { sx126x_set_fe_ctrl(txlp_fe_ctrl_lines[0], txlp_fe_ctrl_lines[1], txlp_fe_ctrl_lines[2]); } else { sx126x_set_fe_ctrl(txhp_fe_ctrl_lines[0], txhp_fe_ctrl_lines[1], txhp_fe_ctrl_lines[2]); diff --git a/drivers/lora/loramac_node/sx126x_common.h b/drivers/lora/loramac_node/sx126x_common.h index 8dbe8af620f0..d64d51a4a50c 100644 --- a/drivers/lora/loramac_node/sx126x_common.h +++ b/drivers/lora/loramac_node/sx126x_common.h @@ -35,6 +35,7 @@ #define HAVE_GPIO_TX_ENABLE DT_INST_NODE_HAS_PROP(0, tx_enable_gpios) #define HAVE_GPIO_RX_ENABLE DT_INST_NODE_HAS_PROP(0, rx_enable_gpios) #define HAVE_GPIO_FE_CTRL (DT_INST_NODE_HAS_PROP(0, fe_ctrl1_enable_gpios) && !DT_INST_NODE_HAS_PROP(0, tx_enable_gpios)) +#define HAVE_PA_OUTPUT_LOCKED DT_INST_NODE_HAS_PROP(0, power_amplifier_output) struct sx126x_config { struct spi_dt_spec bus; @@ -74,6 +75,13 @@ void sx126x_dio1_irq_disable(struct sx126x_data *dev_data); void sx126x_set_tx_params(int8_t power, RadioRampTimes_t ramp_time); +enum sx126x_pa_output { + RFO_LP, + RFO_HP, +}; + +enum sx126x_pa_output sx126x_get_tx_power_mode(void); + int sx126x_variant_init(const struct device *dev); #endif /* ZEPHYR_DRIVERS_SX126X_COMMON_H_ */ diff --git a/drivers/lora/loramac_node/sx126x_stm32wl.c b/drivers/lora/loramac_node/sx126x_stm32wl.c index c6a484ab34f6..03ea1f2fa1ff 100644 --- a/drivers/lora/loramac_node/sx126x_stm32wl.c +++ b/drivers/lora/loramac_node/sx126x_stm32wl.c @@ -16,10 +16,13 @@ #include LOG_MODULE_DECLARE(sx126x, CONFIG_LORA_LOG_LEVEL); -static const enum { - RFO_LP, - RFO_HP, -} pa_output = DT_INST_STRING_UPPER_TOKEN(0, power_amplifier_output); +#if HAVE_PA_OUTPUT_LOCKED +static enum sx126x_pa_output pa_output = + DT_INST_STRING_UPPER_TOKEN(0, power_amplifier_output); +#else +static enum sx126x_pa_output pa_output = RFO_LP; +#endif +static const bool pa_output_locked = HAVE_PA_OUTPUT_LOCKED; void sx126x_reset(struct sx126x_data *dev_data) { @@ -53,6 +56,18 @@ void sx126x_dio1_irq_disable(struct sx126x_data *dev_data) void sx126x_set_tx_params(int8_t power, RadioRampTimes_t ramp_time) { uint8_t buf[2]; + const int8_t lp_max = DT_INST_PROP(0, rfo_lp_max_power); + + if (!pa_output_locked) { + /* Auto-select PA output based on requested power level */ + if (power > lp_max) { + pa_output = RFO_HP; + } else { + pa_output = RFO_LP; + } + } + + LOG_DBG("tx_params PA mode: %s", pa_output == RFO_LP ? "RFO_LP" : "RFO_HP"); if (pa_output == RFO_LP) { const int8_t max_power = DT_INST_PROP(0, rfo_lp_max_power); @@ -110,6 +125,8 @@ void sx126x_set_tx_params(int8_t power, RadioRampTimes_t ramp_time) SX126xWriteRegister(REG_OCP, 0x38); } + LOG_DBG("tx_params final: reg_power=%d ramp=%u", power, ramp_time); + buf[0] = power; buf[1] = (uint8_t)ramp_time; SX126xWriteCommand(RADIO_SET_TXPARAMS, buf, 2); @@ -123,6 +140,11 @@ static void radio_isr(const struct device *dev) k_work_submit(&dev_data->dio1_irq_work); } +enum sx126x_pa_output sx126x_get_tx_power_mode(void) +{ + return pa_output; +} + int sx126x_variant_init(const struct device *dev) { IRQ_CONNECT(DT_INST_IRQN(0), diff --git a/dts/bindings/lora/st,stm32wl-subghz-radio.yaml b/dts/bindings/lora/st,stm32wl-subghz-radio.yaml index 1eaa264cf288..c68c4ddc08ec 100644 --- a/dts/bindings/lora/st,stm32wl-subghz-radio.yaml +++ b/dts/bindings/lora/st,stm32wl-subghz-radio.yaml @@ -15,7 +15,6 @@ properties: power-amplifier-output: type: string - required: true description: | Selects between the low- and high-power power amplifier output pin. enum: