| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467 |
- From 4aa014031d7bacdc08e2b5d5e1cfadbf0c5a964d Mon Sep 17 00:00:00 2001
- From: ZiB <zib@zi3.xyz>
- 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 <zib@zi3.xyz>
- ---
- 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)
|