From ad6f0655104ef75663ec450d11fda952e6f10cc2 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Mon, 16 Mar 2026 21:58:30 +0100 Subject: [PATCH] lr2021 fixes --- zephcore/Kconfig | 7 +- .../promicro_lr2021/promicro_lr2021.dts | 25 +- .../drivers/lora/lr20xx/lr20xx_lora.c | 309 +++++++++++++++--- 3 files changed, 288 insertions(+), 53 deletions(-) diff --git a/zephcore/Kconfig b/zephcore/Kconfig index b787332..c7f6776 100644 --- a/zephcore/Kconfig +++ b/zephcore/Kconfig @@ -339,15 +339,16 @@ menu "LoRa Power Saving" config ZEPHCORE_LORA_RX_DUTY_CYCLE bool "Enable LoRa RX duty cycle power saving" - default y if ZEPHCORE_ROLE_COMPANION && !ZEPHCORE_RADIO_LR1110 + default y if ZEPHCORE_ROLE_COMPANION && !ZEPHCORE_RADIO_LR1110 && !ZEPHCORE_RADIO_LR2021 default n help Enable RX duty cycling for power saving on battery-powered devices. Uses RadioLib's algorithm to calculate optimal timing based on SF/BW. Auto-enabled for companion devices with SX1262/LLCC68 radios. - Disabled for repeaters (latency matters more than battery life) - and LR1110 (cannot lock on mid-preamble, causes packet loss). + Disabled for repeaters (latency matters more than battery life), + LR1110 (cannot lock on mid-preamble, causes packet loss), and + LR2021 (untested — enable manually after verifying no packet loss). The key insight is that LoRa preamble symbols are all identical, so CAD/RX can detect preamble even if we wake mid-stream. The diff --git a/zephcore/boards/nrf52840/promicro_lr2021/promicro_lr2021.dts b/zephcore/boards/nrf52840/promicro_lr2021/promicro_lr2021.dts index 069d0f9..d5bc96b 100644 --- a/zephcore/boards/nrf52840/promicro_lr2021/promicro_lr2021.dts +++ b/zephcore/boards/nrf52840/promicro_lr2021/promicro_lr2021.dts @@ -143,20 +143,19 @@ tcxo-startup-delay-ms = <5>; rx-boosted; - /* RF switch via LR2021 internal DIOs (DIO5=bit0..DIO8=bit3) - * Standard Semtech reference design RF switch config: - * DIO5: ANT_SW (high in all active modes) - * DIO6: TX_SW (high in TX) - * DIO7: unused - * DIO8: RX_SW (high in RX) - * rfswitch-rx=0x09 → DIO5+DIO8 HIGH in RX - * rfswitch-tx=0x03 → DIO5+DIO6 HIGH in LF TX - * rfswitch-tx-hp=0x03 → DIO5+DIO6 HIGH in HF TX */ - rfswitch-enable = <0x0F>; + /* RF switch via LR2021 internal DIOs (DIO5=bit0..DIO8=bit3). + * DIO5 is the IRQ line to the MCU — it must be FUNC_IRQ, not + * FUNC_RF_SWITCH. Exclude DIO5 (bit 0) from rfswitch-enable so + * configure_rfswitch does not claim it, and the driver overrides + * DIO5 to FUNC_IRQ unconditionally after rfswitch setup. + * DIO6: TX_SW (high in TX — bit 1) + * DIO7: unused (bit 2) + * DIO8: RX_SW (high in RX — bit 3) */ + rfswitch-enable = <0x0E>; rfswitch-standby = <0x00>; - rfswitch-rx = <0x09>; - rfswitch-tx = <0x03>; - rfswitch-tx-hp = <0x03>; + rfswitch-rx = <0x08>; + rfswitch-tx = <0x02>; + rfswitch-tx-hp = <0x02>; pa-hp-sel = <7>; pa-duty-cycle = <4>; 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 86e2ede..f23b7da 100644 --- a/zephcore/patches/zephyr-new/drivers/lora/lr20xx/lr20xx_lora.c +++ b/zephcore/patches/zephyr-new/drivers/lora/lr20xx/lr20xx_lora.c @@ -26,6 +26,8 @@ #include "lr20xx_radio_fifo.h" #include "lr20xx_system.h" #include "lr20xx_system_types.h" +#include "lr20xx_workarounds.h" +#include "lr20xx_regmem.h" LOG_MODULE_REGISTER(lr20xx_lora, CONFIG_LORA_LOG_LEVEL); @@ -191,6 +193,78 @@ static void lr20xx_configure_rfswitch(void *ctx, const struct lr20xx_config *cfg } } +/* ── PA power lookup table (from Semtech lr20xx_pa_pwr_cfg.h) ──────── */ + +struct lr20xx_pa_pwr_entry { + int8_t half_power; + uint8_t pa_duty_cycle; + uint8_t pa_lf_slices; +}; + +#define LR20XX_LF_MIN_PWR (-10) +#define LR20XX_LF_MAX_PWR 22 + +/* Calibrated per-dBm PA config — each row is [half_power, duty_cycle, slices] + * for the corresponding output power from -10 to +22 dBm inclusive. */ +static const struct lr20xx_pa_pwr_entry pa_lf_table[] = { + { -18, 3, 6 }, /* -10 dBm */ + { -13, 2, 5 }, /* -9 dBm */ + { -13, 6, 1 }, /* -8 dBm */ + { -6, 6, 0 }, /* -7 dBm */ + { 4, 1, 0 }, /* -6 dBm */ + { 4, 2, 0 }, /* -5 dBm */ + { 2, 1, 3 }, /* -4 dBm */ + { 14, 0, 0 }, /* -3 dBm */ + { 9, 0, 3 }, /* -2 dBm */ + { 11, 3, 0 }, /* -1 dBm */ + { 16, 1, 0 }, /* 0 dBm */ + { 11, 7, 0 }, /* 1 dBm */ + { 18, 2, 0 }, /* 2 dBm */ + { 16, 5, 0 }, /* 3 dBm */ + { 17, 7, 0 }, /* 4 dBm */ + { 21, 1, 2 }, /* 5 dBm */ + { 25, 3, 0 }, /* 6 dBm */ + { 32, 0, 1 }, /* 7 dBm */ + { 32, 2, 0 }, /* 8 dBm */ + { 27, 3, 1 }, /* 9 dBm */ + { 32, 2, 1 }, /* 10 dBm */ + { 28, 5, 1 }, /* 11 dBm */ + { 30, 5, 1 }, /* 12 dBm */ + { 34, 4, 1 }, /* 13 dBm */ + { 31, 5, 4 }, /* 14 dBm */ + { 34, 4, 4 }, /* 15 dBm */ + { 34, 5, 6 }, /* 16 dBm */ + { 39, 3, 5 }, /* 17 dBm */ + { 37, 6, 6 }, /* 18 dBm */ + { 40, 5, 5 }, /* 19 dBm */ + { 41, 7, 4 }, /* 20 dBm */ + { 43, 7, 4 }, /* 21 dBm */ + { 44, 7, 7 }, /* 22 dBm */ +}; + +static void lr20xx_get_pa_cfg_for_power(int8_t power_dbm, + lr20xx_radio_common_pa_cfg_t *pa, + int8_t *half_power_out) +{ + if (power_dbm < LR20XX_LF_MIN_PWR) { + power_dbm = LR20XX_LF_MIN_PWR; + } + if (power_dbm > LR20XX_LF_MAX_PWR) { + power_dbm = LR20XX_LF_MAX_PWR; + } + + int idx = power_dbm - LR20XX_LF_MIN_PWR; + const struct lr20xx_pa_pwr_entry *e = &pa_lf_table[idx]; + + pa->pa_sel = LR20XX_RADIO_COMMON_PA_SEL_LF; + pa->pa_lf_mode = LR20XX_RADIO_COMMON_PA_LF_MODE_FSM; + pa->pa_lf_duty_cycle = e->pa_duty_cycle; + pa->pa_lf_slices = e->pa_lf_slices; + pa->pa_hf_duty_cycle = 16; + + *half_power_out = e->half_power; +} + /* ── Hardware reset (BUSY stuck recovery) ───────────────────────────── */ static void lr20xx_hardware_reset(struct lr20xx_data *data, @@ -202,26 +276,53 @@ static void lr20xx_hardware_reset(struct lr20xx_data *data, lr20xx_hal_reset(ctx); + /* SIMO DC-DC workaround — immediately after reset */ + { + const uint32_t simo_freq = (uint32_t)(2.8e6 * 1.048576); + lr20xx_regmem_write_regmem32(ctx, 0x80004c, &simo_freq, 1); + } + if (cfg->tcxo_voltage_mv > 0) { lr20xx_system_set_tcxo_mode(ctx, get_tcxo_voltage(cfg->tcxo_voltage_mv), (cfg->tcxo_startup_delay_ms * 1000) / 31); } + lr20xx_system_cfg_lfclk(ctx, LR20XX_SYSTEM_LFCLK_RC); + lr20xx_system_set_reg_mode(ctx, LR20XX_SYSTEM_REG_MODE_DCDC); lr20xx_configure_rfswitch(ctx, cfg); - lr20xx_system_calibrate(ctx, 0x6F); + /* DIO5 is the physical IRQ line — always override to FUNC_IRQ */ + lr20xx_system_set_dio_function(ctx, LR20XX_SYSTEM_DIO_5, + LR20XX_SYSTEM_DIO_FUNC_IRQ, + LR20XX_SYSTEM_DIO_DRIVE_NONE); lr20xx_radio_common_set_rx_tx_fallback_mode(ctx, LR20XX_RADIO_FALLBACK_STDBY_RC); lr20xx_radio_common_set_pkt_type(ctx, LR20XX_RADIO_COMMON_PKT_TYPE_LORA); + /* DCDC errata: reset after set_pkt_type */ + lr20xx_workarounds_dcdc_reset(ctx); + lr20xx_system_clear_errors(ctx); lr20xx_system_clear_irq_status(ctx, LR20XX_SYSTEM_IRQ_ALL_MASK); + /* Front-end calibration — same 3 frequencies as hw_init */ + { + lr20xx_radio_common_front_end_calibration_value_t fe_cal[3] = { + { .rx_path = LR20XX_RADIO_COMMON_RX_PATH_LF, + .frequency_in_hertz = 470000000 }, + { .rx_path = LR20XX_RADIO_COMMON_RX_PATH_LF, + .frequency_in_hertz = 869618000 }, + { .rx_path = LR20XX_RADIO_COMMON_RX_PATH_HF, + .frequency_in_hertz = 2441000000UL }, + }; + lr20xx_radio_common_calibrate_front_end_helper(ctx, fe_cal, 3); + } + data->rx_boost_applied = false; lr20xx_hal_enable_dio1_irq(&data->hal_ctx); @@ -238,8 +339,19 @@ static void lr20xx_apply_modem_config(struct lr20xx_data *data, void *ctx = &data->hal_ctx; struct lora_modem_config *mc = &data->modem_cfg; + lr20xx_radio_common_set_pkt_type(ctx, LR20XX_RADIO_COMMON_PKT_TYPE_LORA); + lr20xx_radio_common_set_rf_freq(ctx, mc->frequency); + /* Always configure the RX path after setting frequency + * (reference does this on every set_rf_freq call). */ + lr20xx_radio_common_set_rx_path( + ctx, LR20XX_RADIO_COMMON_RX_PATH_LF, + data->rx_boost_enabled + ? LR20XX_RADIO_COMMON_RX_PATH_BOOST_MODE_4 + : LR20XX_RADIO_COMMON_RX_PATH_BOOST_MODE_NONE); + data->rx_boost_applied = data->rx_boost_enabled; + /* LR20xx uses PPM offset instead of explicit LDRO. * PPM_1_4 (1 bin every 4) is equivalent to LDRO for high-SF * wide-time-on-air configurations. Use recommended value. */ @@ -253,6 +365,10 @@ static void lr20xx_apply_modem_config(struct lr20xx_data *data, }; lr20xx_radio_lora_set_modulation_params(ctx, &mod); + /* DCDC errata: reconfigure DCDC switcher after set_modulation_params + * when using DCDC mode at sub-GHz for RX */ + lr20xx_workarounds_dcdc_configure(ctx); + lr20xx_radio_lora_pkt_params_t pkt = { .preamble_len_in_symb = mc->preamble_len, .pkt_mode = LR20XX_RADIO_LORA_PKT_EXPLICIT, @@ -268,21 +384,16 @@ static void lr20xx_apply_modem_config(struct lr20xx_data *data, mc->public_network ? 0x34 : 0x12); if (tx_mode) { - /* LR20xx set_tx_params uses half-dBm (multiply by 2) */ - lr20xx_radio_common_set_tx_params(ctx, - (int8_t)(mc->tx_power * 2), - LR20XX_RADIO_COMMON_RAMP_48_US); + /* Use calibrated PA config from Semtech lookup table. + * Reference: set_pa_cfg → set_tx_params (no select_pa). */ + lr20xx_radio_common_pa_cfg_t pa; + int8_t half_power; - lr20xx_radio_common_select_pa(ctx, LR20XX_RADIO_COMMON_PA_SEL_LF); - - lr20xx_radio_common_pa_cfg_t pa = { - .pa_sel = LR20XX_RADIO_COMMON_PA_SEL_LF, - .pa_lf_mode = LR20XX_RADIO_COMMON_PA_LF_MODE_FSM, - .pa_lf_duty_cycle = cfg->pa_duty_cycle, - .pa_lf_slices = cfg->pa_hp_sel, - .pa_hf_duty_cycle = 16, /* unused for LF, default */ - }; + lr20xx_get_pa_cfg_for_power(mc->tx_power, &pa, &half_power); lr20xx_radio_common_set_pa_cfg(ctx, &pa); + + lr20xx_radio_common_set_tx_params(ctx, half_power, + LR20XX_RADIO_COMMON_RAMP_48_US); } /* Route IRQ events to DIO5 (physical DIO1 pin on the board) */ @@ -292,6 +403,82 @@ static void lr20xx_apply_modem_config(struct lr20xx_data *data, LR20XX_SYSTEM_IRQ_LORA_HEADER_ERROR); } +/* ── RX duty cycle ──────────────────────────────────────────────────── */ + +/* Minimum preamble symbols needed for detection (same as SX126x) */ +#define LR20XX_DC_MIN_SYMBOLS_SF7_PLUS 8 +#define LR20XX_DC_MIN_SYMBOLS_SF6_LESS 12 +#define LR20XX_DC_TCXO_DELAY_US 1000 + +/** + * Compute duty cycle timing from current modem config and issue the + * SetRxDutyCycle command. Returns true on success, false if the + * preamble is too short (falls back to continuous RX). + * + * Mirrors the SX126x symbol-based calculation: sleep through + * (preamble_len - min_symbols - 2) symbols, wake for the remainder + * plus margin. + */ +static bool lr20xx_apply_rx_duty_cycle(struct lr20xx_data *data) +{ + void *ctx = &data->hal_ctx; + struct lora_modem_config *mc = &data->modem_cfg; + + uint8_t sf = (uint8_t)mc->datarate; + float bw_khz = bw_enum_to_khz(mc->bandwidth); + uint16_t preamble_len = mc->preamble_len; + + uint16_t min_symbols = (sf >= 7) ? LR20XX_DC_MIN_SYMBOLS_SF7_PLUS + : LR20XX_DC_MIN_SYMBOLS_SF6_LESS; + + int16_t sleep_symbols = (int16_t)preamble_len - (int16_t)min_symbols; + if (sleep_symbols <= 0) { + LOG_WRN("Preamble too short for duty cycle " + "(need >%d symbols, have %d) — continuous RX", + min_symbols, preamble_len); + data->rx_duty_cycle_enabled = false; + lr20xx_radio_common_set_rx_with_timeout_in_rtc_step( + ctx, 0xFFFFFF); + return false; + } + + /* symbol_us = 2^SF / BW_kHz * 1000 */ + uint32_t symbol_us = (uint32_t)((float)(1 << sf) * 1000.0f / bw_khz); + + /* Shave 2 symbols off sleep for timing margin */ + int16_t sleep_symbols_safe = sleep_symbols - 2; + if (sleep_symbols_safe < 1) { + sleep_symbols_safe = 1; + } + uint32_t sleep_period_us = (uint16_t)sleep_symbols_safe * symbol_us; + + uint32_t preamble_total_us = (preamble_len + 1) * symbol_us; + int32_t wake_calc1 = ((int32_t)preamble_total_us - + ((int32_t)sleep_period_us - + LR20XX_DC_TCXO_DELAY_US)) / 2; + uint32_t wake_calc2 = (min_symbols + 1) * symbol_us; + + uint32_t wake_period_us = + (wake_calc1 > 0 && (uint32_t)wake_calc1 > wake_calc2) + ? (uint32_t)wake_calc1 : wake_calc2; + + uint32_t rx_ms = (wake_period_us + 999) / 1000; /* round up */ + uint32_t slp_ms = (sleep_period_us + 999) / 1000; + + if (rx_ms < 1) { + rx_ms = 1; + } + if (slp_ms < 1) { + slp_ms = 1; + } + + lr20xx_radio_common_set_rx_duty_cycle(ctx, rx_ms, slp_ms, + LR20XX_RADIO_COMMON_RX_DUTY_CYCLE_MODE_RX); + + LOG_DBG("RX duty cycle: SF%d rx=%ums sleep=%ums", sf, rx_ms, slp_ms); + return true; +} + /* ── Start RX (internal) ────────────────────────────────────────────── */ static void lr20xx_start_rx(struct lr20xx_data *data, @@ -301,8 +488,8 @@ static void lr20xx_start_rx(struct lr20xx_data *data, LOG_DBG("start_rx: t=%lld", k_uptime_get()); - /* Standby first — wake from any sleep state */ - data->hal_ctx.radio_is_sleeping = true; + /* Standby first — wake from any state (radio_is_sleeping is managed + * by the HAL via sleep opcode detection; do not set it here). */ lr20xx_status_t rc = lr20xx_system_set_standby_mode(ctx, LR20XX_SYSTEM_STANDBY_MODE_RC); if (rc != LR20XX_STATUS_OK) { @@ -312,18 +499,21 @@ static void lr20xx_start_rx(struct lr20xx_data *data, lr20xx_system_clear_irq_status(ctx, LR20XX_SYSTEM_IRQ_ALL_MASK); + /* Clear RX FIFO before entering RX (Semtech reference does this) */ + lr20xx_radio_fifo_clear_rx(ctx); + lr20xx_apply_modem_config(data, cfg, false); - /* Apply RX boost if needed */ - if (data->rx_boost_enabled && !data->rx_boost_applied) { - lr20xx_radio_common_set_rx_path( - ctx, LR20XX_RADIO_COMMON_RX_PATH_LF, - LR20XX_RADIO_COMMON_RX_PATH_BOOST_MODE_4); - data->rx_boost_applied = true; - } + /* set_rx_path is now always called inside apply_modem_config, + * with boost mode set according to rx_boost_enabled. */ - /* Start continuous RX using RTC-step API (0xFFFFFF = continuous) */ - lr20xx_radio_common_set_rx_with_timeout_in_rtc_step(ctx, 0xFFFFFF); + if (data->rx_duty_cycle_enabled) { + lr20xx_apply_rx_duty_cycle(data); + /* apply may have disabled duty cycle if preamble too short */ + } else { + lr20xx_radio_common_set_rx_with_timeout_in_rtc_step( + ctx, 0xFFFFFF); + } /* Clear any IRQ flags set during modem configuration */ lr20xx_system_clear_irq_status(ctx, LR20XX_SYSTEM_IRQ_ALL_MASK); @@ -339,7 +529,13 @@ static void lr20xx_restart_rx(struct lr20xx_data *data) void *ctx = &data->hal_ctx; lr20xx_system_clear_irq_status(ctx, LR20XX_SYSTEM_IRQ_ALL_MASK); - lr20xx_radio_common_set_rx_with_timeout_in_rtc_step(ctx, 0xFFFFFF); + + if (data->rx_duty_cycle_enabled) { + lr20xx_apply_rx_duty_cycle(data); + } else { + lr20xx_radio_common_set_rx_with_timeout_in_rtc_step( + ctx, 0xFFFFFF); + } data->in_rx_mode = true; } @@ -397,7 +593,7 @@ static void lr20xx_dio1_work_handler(struct k_work *work) /* When SNR < 0, use signal RSSI for a more * accurate reading on weak links. */ int16_t rssi = pkt_stat.rssi_pkt_in_dbm; - int8_t snr = pkt_stat.snr_pkt_raw / 4; + int8_t snr = ((int8_t)pkt_stat.snr_pkt_raw + 2) >> 2; if (snr < 0 && pkt_stat.rssi_signal_pkt_in_dbm > rssi) { @@ -582,8 +778,9 @@ static int lr20xx_lora_send_async(const struct device *dev, lr20xx_hal_disable_dio1_irq(&data->hal_ctx); - /* Standby — wake from sleep if needed */ - data->hal_ctx.radio_is_sleeping = true; + /* Standby — transition from RX to standby. + * radio_is_sleeping is managed by the HAL when sleep command is sent; + * don't set it here since we're coming from RX, not sleep. */ lr20xx_status_t rc = lr20xx_system_set_standby_mode(ctx, LR20XX_SYSTEM_STANDBY_MODE_RC); if (rc != LR20XX_STATUS_OK) { @@ -673,7 +870,10 @@ static int lr20xx_lora_recv_async(const struct device *dev, k_mutex_unlock(&data->spi_mutex); - LOG_INF("recv_async started (continuous RX%s)", + LOG_INF("recv_async: %s%s", + data->rx_duty_cycle_enabled + ? "RX duty cycle" + : "continuous RX", data->rx_boost_enabled ? ", boosted" : ""); return 0; @@ -730,8 +930,13 @@ void lr20xx_set_rx_duty_cycle(const struct device *dev, bool enable) { struct lr20xx_data *data = dev->data; + if (data->rx_duty_cycle_enabled == enable) { + return; + } + data->rx_duty_cycle_enabled = enable; - LOG_INF("RX duty cycle %s", enable ? "enabled" : "disabled"); + LOG_INF("RX duty cycle %s (takes effect on next RX start)", + enable ? "enabled" : "disabled"); } void lr20xx_set_rx_boost(const struct device *dev, bool enable) @@ -751,6 +956,8 @@ void lr20xx_set_rx_boost(const struct device *dev, bool enable) &data->hal_ctx, LR20XX_RADIO_COMMON_RX_PATH_LF, enable ? LR20XX_RADIO_COMMON_RX_PATH_BOOST_MODE_4 : LR20XX_RADIO_COMMON_RX_PATH_BOOST_MODE_NONE); + /* DCDC errata: reconfigure after set_rx_path */ + lr20xx_workarounds_dcdc_configure(&data->hal_ctx); data->rx_boost_applied = enable; k_mutex_unlock(&data->spi_mutex); } else { @@ -806,6 +1013,8 @@ void lr20xx_reset_agc(const struct device *dev) lr20xx_radio_common_set_rx_path( ctx, LR20XX_RADIO_COMMON_RX_PATH_LF, LR20XX_RADIO_COMMON_RX_PATH_BOOST_MODE_4); + /* DCDC errata: reconfigure after set_rx_path */ + lr20xx_workarounds_dcdc_configure(ctx); data->rx_boost_applied = true; } @@ -849,6 +1058,14 @@ static int lr20xx_hw_init(struct lr20xx_data *data, LOG_INF("LR20xx v%u.%u", ver.major, ver.minor); + /* SIMO DC-DC workaround — must be applied immediately after reset. + * Sets the SIMO switching frequency to prevent power supply issues. + * From Semtech reference ral_lr20xx_init(). */ + { + const uint32_t simo_freq = (uint32_t)(2.8e6 * 1.048576); + lr20xx_regmem_write_regmem32(ctx, 0x80004c, &simo_freq, 1); + } + if (cfg->tcxo_voltage_mv > 0) { /* Convert ms to RTC steps (31.25 us per step) */ uint32_t rtc_steps = (cfg->tcxo_startup_delay_ms * 1000) / 31; @@ -858,23 +1075,27 @@ static int lr20xx_hw_init(struct lr20xx_data *data, LOG_DBG("TCXO: %dmV", cfg->tcxo_voltage_mv); } + /* Configure LF clock source (reference init requires this) */ + lr20xx_system_cfg_lfclk(ctx, LR20XX_SYSTEM_LFCLK_RC); + lr20xx_system_set_reg_mode(ctx, LR20XX_SYSTEM_REG_MODE_DCDC); lr20xx_configure_rfswitch(ctx, cfg); + /* DIO5 is the physical IRQ line to the MCU. configure_rfswitch may + * have set it to FUNC_RF_SWITCH if bit 0 of rfswitch_enable was set. + * Always override to FUNC_IRQ so set_dio_irq_cfg routes events here. */ + lr20xx_system_set_dio_function(ctx, LR20XX_SYSTEM_DIO_5, + LR20XX_SYSTEM_DIO_FUNC_IRQ, + LR20XX_SYSTEM_DIO_DRIVE_NONE); + LOG_INF("RF switch: en=0x%02x rx=0x%02x tx=0x%02x txhp=0x%02x", cfg->rfswitch_enable, cfg->rfswitch_rx, cfg->rfswitch_tx, cfg->rfswitch_tx_hp); - /* Calibrate all 7 blocks (LF_RC=1, HF_RC=2, PLL=4, AAF=8, MU=32, PA_OFF=64) */ - lr20xx_system_calibrate(ctx, 0x6F); - LOG_INF("Calibration OK"); - lr20xx_radio_common_set_rx_tx_fallback_mode(ctx, LR20XX_RADIO_FALLBACK_STDBY_RC); - lr20xx_radio_common_set_pkt_type(ctx, LR20XX_RADIO_COMMON_PKT_TYPE_LORA); - lr20xx_system_errors_t sys_errors = 0; lr20xx_system_get_errors(ctx, &sys_errors); if (sys_errors) { @@ -883,6 +1104,20 @@ static int lr20xx_hw_init(struct lr20xx_data *data, lr20xx_system_clear_errors(ctx); lr20xx_system_clear_irq_status(ctx, LR20XX_SYSTEM_IRQ_ALL_MASK); + /* Front-end calibration at 3 standard frequencies (Semtech reference). + * Calibrates ADC offset, poly-phase filter, and image rejection. + * RF operations should be within 50 MHz of a calibrated point. + * 869.618 MHz entry is tuned for EU sub-GHz LoRa. */ + lr20xx_radio_common_front_end_calibration_value_t fe_cal[3] = { + { .rx_path = LR20XX_RADIO_COMMON_RX_PATH_LF, + .frequency_in_hertz = 470000000 }, + { .rx_path = LR20XX_RADIO_COMMON_RX_PATH_LF, + .frequency_in_hertz = 869618000 }, + { .rx_path = LR20XX_RADIO_COMMON_RX_PATH_HF, + .frequency_in_hertz = 2441000000UL }, + }; + lr20xx_radio_common_calibrate_front_end_helper(ctx, fe_cal, 3); + lr20xx_hal_enable_dio1_irq(&data->hal_ctx); data->rx_boost_enabled = cfg->rx_boosted;