lr2021 cleanup

This commit is contained in:
liquidraver committed 2026-08-16 22:56:28 +02:00
1 parent 6705ff4507
commit 9ab9a9b950
7 files changed
+114 -110

No files matched your search

@@ -1427,44 +1427,6 @@ uint32_t lr11xx_get_random(const struct device *dev)
return random;
}
void lr11xx_reset_agc(const struct device *dev)
{
struct lr11xx_data *data = dev->data;
k_mutex_lock(&data->spi_mutex, K_FOREVER);
/* Warm sleep — powers down analog frontend (resets AGC gain state) */
lr11xx_system_sleep_cfg_t sleep_cfg = {
.is_warm_start = true,
.is_rtc_timeout = false,
};
lr11xx_system_set_sleep(&data->hal_ctx, sleep_cfg, 0);
k_sleep(K_USEC(500));
/* Wake to STDBY_RC for calibration */
lr11xx_system_set_standby(&data->hal_ctx, LR11XX_SYSTEM_STANDBY_CFG_RC);
/* Recalibrate all blocks */
lr11xx_system_calibrate(&data->hal_ctx, 0x3F);
/* Re-calibrate image rejection for the actual operating frequency */
if (data->configured) {
uint16_t freq_mhz = data->modem_cfg.frequency / 1000000;
lr11xx_system_calibrate_image_in_mhz(&data->hal_ctx,
freq_mhz - 4, freq_mhz + 4);
}
/* Re-apply RX boost if it was enabled */
if (data->rx_boost_enabled) {
lr11xx_radio_cfg_rx_boosted(&data->hal_ctx, true);
lr11xx_system_clear_irq_status(&data->hal_ctx,
LR11XX_SYSTEM_IRQ_ALL_MASK);
data->rx_boost_applied = true;
}
k_mutex_unlock(&data->spi_mutex);
}
/* ── Deferred hardware init (runs on first lora_config call) ────────── */
/* ── Driver API: CAD ────────────────────────────────────────────────── */
@@ -95,19 +95,6 @@ void lr11xx_reset_dc_timeout_restarts(const struct device *dev);
*/
uint32_t lr11xx_get_random(const struct device *dev);
/**
* @brief Reset AGC by performing warm sleep + full recalibration
*
* Warm sleep powers down the analog frontend (resets AGC gain state),
* then Calibrate(0x3F) refreshes all blocks. Re-applies image
* calibration for the operating frequency and RX boost afterward.
*
* Must be called while NOT actively receiving a packet.
*
* @param dev LoRa device
*/
void lr11xx_reset_agc(const struct device *dev);
/**
* @brief Set the adaptive-CAD operating detPeak offset
*
@@ -491,10 +491,10 @@ static lr20xx_status_t lr20xx_calibrate_front_end(void *ctx, uint32_t freq_hz);
* because the patch supersedes them.
*
* The image is volatile: lost on reset and on cold start, preserved by sleep
* with retention (§22.3). Every sleep this driver issues sets retention
* (lr20xx_reset_agc), and duty-cycle sleep is retention by definition (§6.3.8),
* so the reset paths are the only places it has to be (re)loaded. Costs 2240
* bytes of flash and +80 nA of retention sleep current.
* with retention (§22.3). Duty-cycle sleep is retention by definition (§6.3.8)
* and this driver issues no other sleep, so the reset paths are the only places
* it has to be (re)loaded. Costs 2240 bytes of flash and +80 nA of retention
* sleep current.
*
* Best-effort: a chip without the patch still works, so a failure here is a
* warning, not a reason to fail init. */
@@ -609,7 +609,7 @@ static void lr20xx_hardware_reset(struct lr20xx_data *data,
* caller of this function is post-config (the DIO1 stuck-recovery path
* and three standby-failure paths), and lr20xx_start_rx() follows
* immediately, so calibrate for the frequency actually about to be
* received on. Same shape as lr20xx_reset_agc(). */
* received on. */
if (data->configured) {
lr20xx_calibrate_front_end(ctx, data->modem_cfg.frequency);
}
@@ -975,6 +975,32 @@ static bool lr20xx_apply_rx_duty_cycle(struct lr20xx_data *data)
return false;
}
/* Re-apply RX boost before re-arming — same warm-start caution as
* lr11xx_restart_rx(), which has always done this on its duty-cycle
* path. A duty cycle sleeps the chip between every window (DS §6.3.8),
* and DS §6.3.4 (SetAdditionalRegToRetain, "specifies the address of an
* additional register to retain in Sleep mode") establishes that not
* every register survives that on its own. This driver holds no
* retention entries at all, and the only place set_rx_path was ever
* issued is apply_modem_config() — which the re-arm path never calls, so
* before this the boost was programmed once at RX entry and never again.
* If it does not survive the sleep, every window after the first ran
* 3 dB down: the same failure the SX126x hit, where the fix was pinning
* RX gain into the warm-start retention list.
*
* One SPI command, correct either way. Whether the LR2021 actually needs
* it — or wants a retention entry instead — is still an open question;
* read the gain back after a window, or A/B it on PDR.
*
* Not conditioned on rx_boost_applied: that flag tracks what we
* programmed, not what survived. */
if (data->rx_boost_enabled) {
lr20xx_radio_common_set_rx_path(
ctx, LR20XX_RADIO_COMMON_RX_PATH_LF,
LR20XX_RADIO_COMMON_RX_PATH_BOOST_MODE_7);
data->rx_boost_applied = true;
}
/* Clear the CMD_PERR the HAL's clockless wake frame provokes: it is
* self-inflicted by the wake and says nothing about the command below.
* Left set it holds DIO1 high and poisons the re-arm. */
@@ -2120,6 +2146,28 @@ static int lr20xx_lora_recv_async(const struct device *dev,
k_mutex_lock(&data->spi_mutex, K_FOREVER);
/* Idempotent fast path: the chip is already in continuous RX, so there
* is nothing to program — refresh the callback and leave the receiver on
* air. The usual caller is LoRaRadioBase::startSendRaw()'s failure path:
* the LBT branch of send_async has already run lr20xx_start_rx() before
* returning -EBUSY, so without this every CAD-busy verdict paid a SECOND
* full rebuild (standby -> ClearRxFifo -> apply_modem_config -> SetRx) on
* top of the driver's own. startSendRaw's comment has asserted this was
* "a no-op there" ever since the SX126x gained its Phase 2 fast path; on
* this driver it was not, until now.
*
* rx_duty_cycle_enabled is part of the guard, not decoration. This is
* the CONTINUOUS entry point and it clears that flag below, so a caller
* moving a duty-cycled radio to continuous RX has to take the full path:
* fast-pathing there would leave the cycle running on a chip whose
* bookkeeping claims continuous, and nothing would ever stand it down. */
if (data->in_rx_mode && !data->rx_duty_cycle_enabled) {
data->async_rx_cb = cb;
data->async_rx_user_data = user_data;
k_mutex_unlock(&data->spi_mutex);
return 0;
}
data->async_rx_cb = cb;
data->async_rx_user_data = user_data;
data->rx_duty_cycle_enabled = false;
@@ -2511,42 +2559,6 @@ uint32_t lr20xx_get_random(const struct device *dev)
return random;
}
void lr20xx_reset_agc(const struct device *dev)
{
struct lr20xx_data *data = dev->data;
void *ctx = &data->hal_ctx;
k_mutex_lock(&data->spi_mutex, K_FOREVER);
/* Warm sleep — powers down analog frontend (resets AGC gain state).
* is_ram_retention_enabled=true = warm sleep (equivalent to LR11xx warm_start). */
lr20xx_system_sleep_cfg_t sleep_cfg = {
.is_clk_32k_enabled = false,
.is_ram_retention_enabled = true,
};
/* No delay after the sleep opcode — lr20xx_hal_write() already does a
* k_busy_wait(1000) when it sees 0x0127, so this was a duplicate guard
* (and k_sleep() with a sub-tick duration rounds up to a full tick). */
lr20xx_system_set_sleep_mode(ctx, &sleep_cfg, 0);
lr20xx_system_set_standby_mode(ctx, LR20XX_SYSTEM_STANDBY_MODE_RC);
lr20xx_system_calibrate(ctx, 0x6F);
if (data->configured) {
lr20xx_calibrate_front_end(ctx, data->modem_cfg.frequency);
}
if (data->rx_boost_enabled) {
lr20xx_radio_common_set_rx_path(
ctx, LR20XX_RADIO_COMMON_RX_PATH_LF,
LR20XX_RADIO_COMMON_RX_PATH_BOOST_MODE_7);
data->rx_boost_applied = true;
}
k_mutex_unlock(&data->spi_mutex);
}
/* ── Driver API: CAD ────────────────────────────────────────────────── */
/* Datasheet Table 6-19 in full: rows = CAD symbol count 1..4, columns =
@@ -2852,8 +2864,31 @@ static int lr20xx_lora_recv_duty_cycle(const struct device *dev,
return -EINVAL;
}
uint32_t rx_ms = k_ticks_to_ms_ceil32(rx_period.ticks);
uint32_t slp_ms = k_ticks_to_ms_ceil32(sleep_period.ticks);
if (rx_ms < 1) rx_ms = 1;
if (slp_ms < 1) slp_ms = 1;
k_mutex_lock(&data->spi_mutex, K_FOREVER);
/* Idempotent fast path, the duty-cycle counterpart of the one in
* recv_async and the same shape as the SX126x's: a cycle already armed
* on exactly these periods has nothing to re-arm, so refresh the
* callback and stay on air instead of tearing a live cycle down and
* paying a full rebuild for it. Timings are computed before the lock so
* they can be compared here.
*
* Any other combination — continuous RX, or a cycle on different periods
* — falls through to the full path below, which re-arms via
* lr20xx_apply_rx_duty_cycle() and therefore still gets the AGC reset. */
if (data->in_rx_mode && data->rx_duty_cycle_enabled &&
data->dc_rx_ms == rx_ms && data->dc_sleep_ms == slp_ms) {
data->async_rx_cb = cb;
data->async_rx_user_data = user_data;
k_mutex_unlock(&data->spi_mutex);
return 0;
}
data->async_rx_cb = cb;
data->async_rx_user_data = user_data;
@@ -2864,11 +2899,6 @@ static int lr20xx_lora_recv_duty_cycle(const struct device *dev,
lr20xx_radio_fifo_clear_rx(ctx);
lr20xx_apply_modem_config(data, cfg, false);
uint32_t rx_ms = k_ticks_to_ms_ceil32(rx_period.ticks);
uint32_t slp_ms = k_ticks_to_ms_ceil32(sleep_period.ticks);
if (rx_ms < 1) rx_ms = 1;
if (slp_ms < 1) slp_ms = 1;
/* Store for the re-arm paths (lr20xx_start_rx / lr20xx_restart_rx)
* so every re-entry uses exactly this timing. */
data->dc_rx_ms = rx_ms;
@@ -168,13 +168,6 @@ int lr20xx_configure_side_detectors(const struct device *dev,
*/
uint32_t lr20xx_get_random(const struct device *dev);
/**
* @brief Reset AGC by performing warm sleep + full recalibration
*
* @param dev LoRa device
*/
void lr20xx_reset_agc(const struct device *dev);
/**
* @brief Set the adaptive-CAD operating detPeak offset
*
+32
View File
@@ -517,6 +517,32 @@ void Dispatcher::checkSend()
* continuously on a busy site and buries real
* faults. */
LOG_DBG("checkSend: startSendRaw refused, re-queuing delay=%u", retry);
/* Escalate a refusal that will not end. This branch
* used to leave cad_busy_start alone, so a packet the
* driver's own LBT kept rejecting — the pre-TX gate
* having passed — looped indefinitely with no counter,
* no error flag and nothing above DBG. At default log
* level a node in that state looks completely idle
* while it never transmits, which is exactly how a
* chip-side CAD (LR20xx CAD_LBT) fails.
*
* Deliberately no recoverRxState() here, unlike the
* isReceiving() branch above: that one recovers a chip
* suspected of being stuck in RX, whereas a busy
* channel is a true reading and walking the radio
* through REST would only add deaf time. Report and
* keep retrying. */
if (cad_busy_start == 0) {
cad_busy_start = now;
} else if (now - cad_busy_start > getCADFailMaxDuration()) {
_err_flags |= ERR_EVENT_CAD_TIMEOUT;
LOG_WRN("checkSend: LBT has refused TX for %ums "
"(len=%d, noise=%d) — channel busy or CAD too sensitive",
(unsigned)(now - cad_busy_start), len,
_radio->getNoiseFloor());
cad_busy_start = now;
}
logTxFail(outbound, outbound->getRawLength());
_mgr->queueOutbound(outbound, outbound_priority, futureMillis((int)retry));
outbound = nullptr;
@@ -525,6 +551,12 @@ void Dispatcher::checkSend()
}
} else {
outbound_expiry = futureMillis((int)max_airtime);
/* A transmit got out, so the refusal streak above is
* over. Without this the timer keeps running across
* successful sends and the next isolated refusal
* inherits a stale start, reporting a stall that
* already ended. */
cad_busy_start = 0;
}
}
}
+3 -3
View File
@@ -511,9 +511,9 @@ static void mesh_event_loop(void)
/* Periodic housekeeping — maintenance + UI refresh */
if (events & MESH_EVENT_HOUSEKEEPING) {
/* Radio maintenance: noise floor calibration, AGC reset,
* RX watchdog. Separated from loop() so these never run
* on packet-driven events. */
/* Radio maintenance: noise floor calibration, adaptive-CAD
* probe, RX watchdog. Separated from loop() so these
* never run on packet-driven events. */
if (companion_mesh_ptr) {
companion_mesh_ptr->maintenanceLoop();
}
+3 -3
View File
@@ -443,9 +443,9 @@ static void room_event_loop(void)
/* Periodic housekeeping — maintenance + display refresh */
if (events & MESH_EVENT_HOUSEKEEPING) {
#ifdef ZEPHCORE_LORA
/* Radio maintenance: noise floor calibration, AGC reset,
* RX watchdog. Separated from loop() so these never run
* on packet-driven events. */
/* Radio maintenance: noise floor calibration, adaptive-CAD
* probe, RX watchdog. Separated from loop() so these
* never run on packet-driven events. */
if (room_mesh_ptr) {
room_mesh_ptr->maintenanceLoop();
/* Also drive loop() so time-based actions (advert