From 4aa014031d7bacdc08e2b5d5e1cfadbf0c5a964d Mon Sep 17 00:00:00 2001 From: ZiB Date: Sat, 7 Nov 2026 03:35:03 -0800 Subject: [PATCH 2/3] drivers: lora: native: sx126x: implement Channel Activity Detection Wires the sx126x native driver into the lora.h CAD API that was already fully specified there (lora_cad()/lora_cad_async(), lora_cad_mode, and the cad sub-struct in lora_modem_config) but never had a backend: .cad/.cad_async were both unset in the driver's DEVICE_API struct. Adds sx126x_set_cad_params()/sx126x_set_cad(), a new SX126X_STATE_CAD state following the existing CAS-based busy protocol, and a CadDone/CadActivityDetected IRQ path alongside the existing TX/RX/ timeout ones - folded into SX126X_IRQ_MASK_REQUIRED since those bits only ever fire after a SetCAD. Signed-off-by: ZiB --- drivers/lora/native/sx126x/sx126x.c | 284 ++++++++++++++++++++++- drivers/lora/native/sx126x/sx126x.h | 13 ++ drivers/lora/native/sx126x/sx126x_regs.h | 11 + include/zephyr/drivers/lora/sx126x.h | 21 +- 4 files changed, 317 insertions(+), 12 deletions(-) diff --git a/drivers/lora/native/sx126x/sx126x.c b/drivers/lora/native/sx126x/sx126x.c index c3f9d63f3f5..8ba8416ed0c 100644 --- a/drivers/lora/native/sx126x/sx126x.c +++ b/drivers/lora/native/sx126x/sx126x.c @@ -22,6 +22,11 @@ LOG_MODULE_REGISTER(sx126x, CONFIG_LORA_LOG_LEVEL); #define SX126X_SF56_MIN_PREAMBLE_LEN 12 +/* IRQ sources the driver's own TX/RX/CAD/timeout handling needs on DIO1. */ +#define SX126X_IRQ_MASK_REQUIRED \ + (SX126X_IRQ_TX_DONE | SX126X_IRQ_RX_DONE | SX126X_IRQ_RX_TX_TIMEOUT | SX126X_IRQ_CRC_ERR | \ + SX126X_IRQ_CAD_DONE | SX126X_IRQ_CAD_ACTIVITY_DETECTED) + #ifdef CONFIG_LORA_GFSK #define SX126X_IS_GFSK(data) ((data)->gfsk) #else @@ -406,6 +411,102 @@ static int sx126x_set_rx(const struct device *dev, uint32_t timeout_ms) return sx126x_hal_write_cmd(dev, SX126X_CMD_SET_RX, buf, 3); } +static int sx126x_cad_symbol_num_to_reg(enum lora_cad_symbol_num symbol_num, uint8_t *reg) +{ + switch (symbol_num) { + case LORA_CAD_SYMB_1: + *reg = SX126X_CAD_ON_1_SYMB; + return 0; + case LORA_CAD_SYMB_2: + *reg = SX126X_CAD_ON_2_SYMB; + return 0; + case LORA_CAD_SYMB_4: + *reg = SX126X_CAD_ON_4_SYMB; + return 0; + case LORA_CAD_SYMB_8: + *reg = SX126X_CAD_ON_8_SYMB; + return 0; + case LORA_CAD_SYMB_16: + *reg = SX126X_CAD_ON_16_SYMB; + return 0; + default: + return -EINVAL; + } +} + +/* + * Per-SF detDetMin/cadDetPeak/cadSymbolNum defaults, from Semtech AN1200.48 + * ("SX126x CAD Performance Evaluation") Tables 1 and 43 - the "best + * settings" rows, which pick the symbol count each SF needs for reliable + * detection without false positives, rather than the lower-power 2-symbol + * alternative also published there. AN1200.48 only characterizes 125kHz + * and 500kHz; a config using any other bandwidth must supply explicit + * cad.detection_peak/detection_minimum (and usually symbol_num) of its own. + */ +struct sx126x_cad_defaults { + uint8_t det_min; + uint8_t det_peak; + enum lora_cad_symbol_num symbol_num; +}; + +static int sx126x_cad_lookup_defaults(enum lora_signal_bandwidth bandwidth, + enum lora_datarate datarate, + struct sx126x_cad_defaults *out) +{ + static const struct sx126x_cad_defaults bw_125khz[] = { + [SF_7] = { 10, 22, LORA_CAD_SYMB_2 }, + [SF_8] = { 10, 22, LORA_CAD_SYMB_2 }, + [SF_9] = { 10, 23, LORA_CAD_SYMB_4 }, + [SF_10] = { 10, 24, LORA_CAD_SYMB_4 }, + [SF_11] = { 10, 25, LORA_CAD_SYMB_4 }, + [SF_12] = { 10, 28, LORA_CAD_SYMB_4 }, + }; + static const struct sx126x_cad_defaults bw_500khz[] = { + [SF_7] = { 10, 21, LORA_CAD_SYMB_4 }, + [SF_8] = { 10, 22, LORA_CAD_SYMB_4 }, + [SF_9] = { 10, 22, LORA_CAD_SYMB_4 }, + [SF_10] = { 10, 23, LORA_CAD_SYMB_4 }, + [SF_11] = { 10, 25, LORA_CAD_SYMB_4 }, + [SF_12] = { 10, 29, LORA_CAD_SYMB_8 }, + }; + const struct sx126x_cad_defaults *table; + + if (bandwidth == BW_125_KHZ) { + table = bw_125khz; + } else if (bandwidth == BW_500_KHZ) { + table = bw_500khz; + } else { + return -ENOTSUP; + } + + if (datarate < SF_7 || datarate > SF_12) { + return -EINVAL; + } + + *out = table[datarate]; + return 0; +} + +static int sx126x_set_cad_params(const struct device *dev, uint8_t symbol_num_reg, + uint8_t det_peak, uint8_t det_min, uint8_t exit_mode, + uint32_t timeout) +{ + uint8_t buf[7]; + + buf[0] = symbol_num_reg; + buf[1] = det_peak; + buf[2] = det_min; + buf[3] = exit_mode; + sys_put_be24(timeout, &buf[4]); + + return sx126x_hal_write_cmd(dev, SX126X_CMD_SET_CAD_PARAMS, buf, 7); +} + +static int sx126x_set_cad(const struct device *dev) +{ + return sx126x_hal_write_cmd(dev, SX126X_CMD_SET_CAD, NULL, 0); +} + static int sx126x_get_rx_buffer_status(const struct device *dev, uint8_t *payload_len, uint8_t *offset) { @@ -558,9 +659,9 @@ static int sx126x_chip_init(const struct device *dev) return ret; } - /* Configure IRQs on DIO1: TX done, RX done, timeout */ - uint16_t irq_mask = SX126X_IRQ_TX_DONE | SX126X_IRQ_RX_DONE | - SX126X_IRQ_RX_TX_TIMEOUT | SX126X_IRQ_CRC_ERR; + /* Configure IRQs on DIO1: TX done, RX done, CAD done, timeout */ + uint16_t irq_mask = SX126X_IRQ_MASK_REQUIRED; + ret = sx126x_set_dio_irq_params(dev, irq_mask, irq_mask, 0, 0); if (ret < 0) { LOG_ERR("Set IRQ params failed: %d", ret); @@ -804,6 +905,33 @@ out: } } +static void sx126x_handle_irq_cad_done(const struct device *dev, uint16_t irq_status) +{ + struct sx126x_data *data = dev->data; + struct sx126x_cad_result result = { + .activity_detected = (irq_status & SX126X_IRQ_CAD_ACTIVITY_DETECTED) != 0, + }; + + LOG_DBG("CAD done: activity=%d", result.activity_detected); + + /* CAD_ONLY already left the chip in STDBY_RC; this just folds it + * back into the driver's own rest state. + */ + sx126x_set_sleep(dev); + + if (data->cad_cb != NULL) { + lora_cad_cb cb = data->cad_cb; + void *user_data = data->cad_cb_user_data; + + data->cad_cb = NULL; + data->cad_cb_user_data = NULL; + cb(dev, result.activity_detected, user_data); + return; + } + + k_msgq_put(&data->cad_msgq, &result, K_NO_WAIT); +} + static void sx126x_handle_irq_timeout(const struct device *dev) { struct sx126x_data *data = dev->data; @@ -874,6 +1002,10 @@ static void sx126x_irq_work_handler(struct k_work *work) sx126x_handle_irq_timeout(dev); } + if (irq_status & SX126X_IRQ_CAD_DONE) { + sx126x_handle_irq_cad_done(dev, irq_status); + } + /* 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); @@ -1573,6 +1705,148 @@ static int sx126x_lora_rssi(const struct device *dev, int16_t *rssi) return ret; } +/* + * Shared setup for lora_cad()/lora_cad_async(): resolve CAD parameters, + * claim the radio and issue SetCadParams + SetCAD. On return 0, the SPI + * transaction is complete and the chip is already searching; the caller's + * msgq/cb delivers the CadDone result. On error, state and mutex are + * already unwound. + */ +static int sx126x_cad_start(const struct device *dev) +{ + struct sx126x_data *data = dev->data; + struct sx126x_cad_defaults defaults; + enum lora_cad_symbol_num symbol_num; + uint8_t det_peak, det_min, symbol_num_reg; + int ret; + + if (!data->config_valid) { + LOG_ERR("Not configured"); + return -EINVAL; + } + + if (SX126X_IS_GFSK(data)) { + LOG_ERR("CAD is a LoRa-only operation"); + return -ENOTSUP; + } + + symbol_num = data->config.cad.symbol_num; + det_peak = data->config.cad.detection_peak; + det_min = data->config.cad.detection_minimum; + + if (symbol_num == 0 || det_peak == 0 || det_min == 0) { + ret = sx126x_cad_lookup_defaults(data->config.bandwidth, + data->config.datarate, &defaults); + if (ret < 0) { + LOG_ERR("No CAD defaults for this bandwidth/SF; " + "set cad.symbol_num/detection_peak/detection_minimum explicitly"); + return ret; + } + if (symbol_num == 0) { + symbol_num = defaults.symbol_num; + } + if (det_peak == 0) { + det_peak = defaults.det_peak; + } + if (det_min == 0) { + det_min = defaults.det_min; + } + } + + ret = sx126x_cad_symbol_num_to_reg(symbol_num, &symbol_num_reg); + if (ret < 0) { + LOG_ERR("Invalid CAD symbol count: %d", symbol_num); + return ret; + } + + if (!atomic_cas(&data->state, SX126X_REST_STATE, SX126X_STATE_CAD)) { + LOG_ERR("Busy"); + return -EBUSY; + } + + k_mutex_lock(&data->lock, K_FOREVER); + + ret = sx126x_ensure_ready(dev); + if (ret < 0) { + goto out_error; + } + + ret = sx126x_set_cad_params(dev, symbol_num_reg, det_peak, det_min, + SX126X_CAD_EXIT_MODE_CAD_ONLY, 0); + if (ret < 0) { + goto out_error; + } + + ret = sx126x_set_cad(dev); + if (ret < 0) { + goto out_error; + } + + k_mutex_unlock(&data->lock); + return 0; + +out_error: + sx126x_set_sleep(dev); + k_mutex_unlock(&data->lock); + return ret; +} + +static int sx126x_lora_cad(const struct device *dev, k_timeout_t timeout) +{ + struct sx126x_data *data = dev->data; + struct sx126x_cad_result result; + int ret; + + k_msgq_purge(&data->cad_msgq); + + ret = sx126x_cad_start(dev); + if (ret < 0) { + return ret; + } + + ret = k_msgq_get(&data->cad_msgq, &result, timeout); + if (ret < 0) { + LOG_DBG("CAD timeout"); + k_mutex_lock(&data->lock, K_FOREVER); + sx126x_set_standby(dev, SX126X_STANDBY_RC); + sx126x_set_sleep(dev); + k_mutex_unlock(&data->lock); + return -ETIMEDOUT; + } + + return result.activity_detected ? 1 : 0; +} + +static int sx126x_lora_cad_async(const struct device *dev, lora_cad_cb cb, void *user_data) +{ + struct sx126x_data *data = dev->data; + int ret; + + if (cb == NULL) { + k_mutex_lock(&data->lock, K_FOREVER); + data->cad_cb = NULL; + data->cad_cb_user_data = NULL; + if (atomic_cas(&data->state, SX126X_STATE_CAD, SX126X_STATE_IDLE)) { + sx126x_set_standby(dev, SX126X_STANDBY_RC); + sx126x_set_sleep(dev); + } + k_mutex_unlock(&data->lock); + return 0; + } + + data->cad_cb = cb; + data->cad_cb_user_data = user_data; + + ret = sx126x_cad_start(dev); + if (ret < 0) { + data->cad_cb = NULL; + data->cad_cb_user_data = NULL; + return ret; + } + + return 0; +} + static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency, int8_t tx_power, uint16_t duration) { @@ -1644,6 +1918,8 @@ static DEVICE_API(lora, sx126x_lora_api) = { .airtime = sx126x_lora_airtime, .test_cw = sx126x_lora_test_cw, .rssi = sx126x_lora_rssi, + .cad = sx126x_lora_cad, + .cad_async = sx126x_lora_cad_async, }; #ifdef CONFIG_PM_DEVICE @@ -1673,6 +1949,8 @@ static int sx126x_init(const struct device *dev) sizeof(struct sx126x_tx_result), 1); k_msgq_init(&data->rx_msgq, (char *)&data->rx_result, sizeof(struct sx126x_rx_result), 1); + k_msgq_init(&data->cad_msgq, (char *)&data->cad_result, + sizeof(struct sx126x_cad_result), 1); k_work_init(&data->irq_work, sx126x_irq_work_handler); data->dev = dev; atomic_set(&data->state, SX126X_STATE_IDLE); diff --git a/drivers/lora/native/sx126x/sx126x.h b/drivers/lora/native/sx126x/sx126x.h index 0ea5154e901..5f22f620f35 100644 --- a/drivers/lora/native/sx126x/sx126x.h +++ b/drivers/lora/native/sx126x/sx126x.h @@ -19,6 +19,7 @@ enum sx126x_state { SX126X_STATE_TX, SX126X_STATE_RX, SX126X_STATE_RX_DUTY_CYCLE, + SX126X_STATE_CAD, }; struct sx126x_tx_result { @@ -32,6 +33,10 @@ struct sx126x_rx_result { int status; }; +struct sx126x_cad_result { + bool activity_detected; +}; + struct sx126x_data { struct sx126x_hal_data hal; @@ -63,6 +68,14 @@ struct sx126x_data { lora_recv_cb rx_cb; void *rx_cb_user_data; + /* CAD completion via message queue */ + struct k_msgq cad_msgq; + struct sx126x_cad_result cad_result; + + /* Async CAD callback */ + lora_cad_cb cad_cb; + void *cad_cb_user_data; + /* RX duty cycle parameters (in SX126x timeout ticks) */ struct { uint32_t rx_period; diff --git a/drivers/lora/native/sx126x/sx126x_regs.h b/drivers/lora/native/sx126x/sx126x_regs.h index 9b342f473f3..819ef81b2fd 100644 --- a/drivers/lora/native/sx126x/sx126x_regs.h +++ b/drivers/lora/native/sx126x/sx126x_regs.h @@ -201,6 +201,17 @@ #define SX126X_LORA_IQ_STANDARD 0x00 #define SX126X_LORA_IQ_INVERTED 0x01 +/* CAD symbol count (for SET_CAD_PARAMS) */ +#define SX126X_CAD_ON_1_SYMB 0x00 +#define SX126X_CAD_ON_2_SYMB 0x01 +#define SX126X_CAD_ON_4_SYMB 0x02 +#define SX126X_CAD_ON_8_SYMB 0x03 +#define SX126X_CAD_ON_16_SYMB 0x04 + +/* CAD exit mode (for SET_CAD_PARAMS) */ +#define SX126X_CAD_EXIT_MODE_CAD_ONLY 0x00 +#define SX126X_CAD_EXIT_MODE_CAD_RX 0x01 + /* TX Power Ramp Time (for SET_TX_PARAMS) */ #define SX126X_RAMP_10_US 0x00 #define SX126X_RAMP_20_US 0x01 diff --git a/include/zephyr/drivers/lora/sx126x.h b/include/zephyr/drivers/lora/sx126x.h index 7e134f434af..4052f86aa41 100644 --- a/include/zephyr/drivers/lora/sx126x.h +++ b/include/zephyr/drivers/lora/sx126x.h @@ -21,17 +21,20 @@ extern "C" { * @name SX126x IRQ source bits * * Mirror the chip's GetIrqStatus bit positions (DS.SX1261-2.W.APP, - * 13.3.1), for use with @ref sx126x_irq_timestamp_get. + * 13.3.1), for use with @ref sx126x_irq_timestamp_get. Limited to the + * sources always enabled on DIO1 (SX126X_IRQ_MASK_REQUIRED in the + * driver) - the finer-grained demod sub-stages (preamble detected, sync + * word valid, header valid) aren't exposed here: bench testing found no + * useful added precision over RX_DONE/TX_DONE, which is where the + * driver's own TX/RX dwell time is stable enough that what's actually + * being measured past that point is MCU/radio clock skew, not timing + * resolution. * @{ */ -#define SX126X_IRQ_TS_TX_DONE BIT(0) -#define SX126X_IRQ_TS_RX_DONE BIT(1) -#define SX126X_IRQ_TS_PREAMBLE_DETECTED BIT(2) -#define SX126X_IRQ_TS_SYNC_WORD_VALID BIT(3) -#define SX126X_IRQ_TS_HEADER_VALID BIT(4) -#define SX126X_IRQ_TS_HEADER_ERR BIT(5) -#define SX126X_IRQ_TS_CRC_ERR BIT(6) -#define SX126X_IRQ_TS_RX_TX_TIMEOUT BIT(9) +#define SX126X_IRQ_TS_TX_DONE BIT(0) +#define SX126X_IRQ_TS_RX_DONE BIT(1) +#define SX126X_IRQ_TS_CRC_ERR BIT(6) +#define SX126X_IRQ_TS_RX_TX_TIMEOUT BIT(9) /** @} */ /** -- 2.54.0 (Apple Git-157)