/* * SPDX-License-Identifier: Apache-2.0 * * Semtech LR11xx LoRa driver for Zephyr RTOS. * * Implements Zephyr's lora_driver_api for LoRa packet mode operation. * Supports: LR1110, LR1120. * * The LR11xx SPI protocol uses 2-byte opcodes and requires BUSY polling * between transactions. See lr11xx.h for opcode definitions. * * Only LoRa packet mode is implemented. LoRaWAN and GNSS functions * of the LR11xx are not used by the KISS modem application. */ #define DT_DRV_COMPAT semtech_lr11xx #include "lr11xx.h" #include #include #include #include LOG_MODULE_REGISTER(lr11xx, CONFIG_LORA_LOG_LEVEL); /* ── Helpers to map Zephyr lora_modem_config to LR11xx codes ─────────────── */ static uint8_t zephyr_sf_to_lr11xx(enum lora_datarate dr) { switch (dr) { case SF_5: return LR11XX_LORA_SF5; case SF_6: return LR11XX_LORA_SF6; case SF_7: return LR11XX_LORA_SF7; case SF_8: return LR11XX_LORA_SF8; case SF_9: return LR11XX_LORA_SF9; case SF_10: return LR11XX_LORA_SF10; case SF_11: return LR11XX_LORA_SF11; case SF_12: /* fall through */ default: return LR11XX_LORA_SF12; } } static uint8_t zephyr_bw_to_lr11xx(enum lora_signal_bandwidth bw) { switch (bw) { case BW_500_KHZ: return LR11XX_LORA_BW_500; case BW_250_KHZ: return LR11XX_LORA_BW_250; case BW_125_KHZ: /* fall through */ default: return LR11XX_LORA_BW_125; } } static uint8_t zephyr_cr_to_lr11xx(enum lora_coding_rate cr) { switch (cr) { case CR_4_5: return LR11XX_LORA_CR_4_5; case CR_4_6: return LR11XX_LORA_CR_4_6; case CR_4_7: return LR11XX_LORA_CR_4_7; case CR_4_8: /* fall through */ default: return LR11XX_LORA_CR_4_8; } } /* ── Radio configuration ─────────────────────────────────────────────────── */ static int lr11xx_configure(const struct device *dev, struct lora_modem_config *cfg) { int ret; uint8_t params[8]; /* Enter standby (RC oscillator) */ params[0] = LR11XX_STANDBY_RC; ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_STANDBY, params, 1); if (ret < 0) { return ret; } /* Set packet type to LoRa */ params[0] = LR11XX_PKT_TYPE_LORA; ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_PACKET_TYPE, params, 1); if (ret < 0) { return ret; } /* Set RF frequency */ uint32_t freq = (uint32_t)cfg->frequency; params[0] = (freq >> 24) & 0xFF; params[1] = (freq >> 16) & 0xFF; params[2] = (freq >> 8) & 0xFF; params[3] = (freq >> 0) & 0xFF; ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_RF_FREQUENCY, params, 4); if (ret < 0) { return ret; } /* Set TX power and ramp time (ramp 40 us = 0x04) */ params[0] = (uint8_t)cfg->tx_power; params[1] = 0x04; /* Ramp time */ ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_TX_PARAMS, params, 2); if (ret < 0) { return ret; } /* * Set modulation parameters (LoRa): * [0] SF, [1] BW, [2] CR, [3] LDRO (low data rate optimize) */ params[0] = zephyr_sf_to_lr11xx(cfg->datarate); params[1] = zephyr_bw_to_lr11xx(cfg->bandwidth); params[2] = zephyr_cr_to_lr11xx(cfg->coding_rate); params[3] = 0x00; /* LDRO: auto would require SF/BW calculation */ ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_MODULATION_PARAMS, params, 4); if (ret < 0) { return ret; } /* * Set packet parameters (LoRa): * [0:1] preamble length, [2] header type (0=explicit), * [3] payload length, [4] CRC mode (1=on), * [5] IQ inversion (0=standard) */ uint16_t preamble = (uint16_t)cfg->preamble_len; params[0] = (preamble >> 8) & 0xFF; params[1] = (preamble >> 0) & 0xFF; params[2] = 0x00; /* explicit header */ params[3] = 0xFF; /* max payload (will be overridden per-packet) */ params[4] = 0x01; /* CRC on */ params[5] = 0x00; /* standard IQ */ ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_PACKET_PARAMS, params, 6); if (ret < 0) { return ret; } /* Set buffer base addresses (TX=0, RX=0) */ params[0] = 0x00; params[1] = 0x00; ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_BUFFER_BASE_ADDR, params, 2); if (ret < 0) { return ret; } /* Configure DIO IRQ routing to EVENT/DIO9 */ uint32_t irq_mask = LR11XX_DIO9_MASK; params[0] = (irq_mask >> 24) & 0xFF; params[1] = (irq_mask >> 16) & 0xFF; params[2] = (irq_mask >> 8) & 0xFF; params[3] = (irq_mask >> 0) & 0xFF; /* DIO9 enable mask = same as IRQ mask */ params[4] = params[0]; params[5] = params[1]; params[6] = params[2]; params[7] = params[3]; ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_DIO_IRQ_PARAMS, params, 8); return ret; } /* ── Send ────────────────────────────────────────────────────────────────── */ static int lr11xx_send(const struct device *dev, uint8_t *data, uint32_t data_len) { struct lr11xx_data *drv = dev->data; int ret; uint8_t params[3]; /* Clear pending IRQ flags */ uint32_t clr = LR11XX_IRQ_ALL; params[0] = (clr >> 24) & 0xFF; params[1] = (clr >> 16) & 0xFF; params[2] = (clr >> 8) & 0xFF; params[3] = (clr >> 0) & 0xFF; lr11xx_hal_write_cmd(dev, LR11XX_CMD_CLEAR_IRQ_STATUS, params, 4); /* Write payload to TX buffer at offset 0 */ uint8_t *tx_buf = k_malloc(data_len + 1); if (!tx_buf) { return -ENOMEM; } tx_buf[0] = 0x00; /* buffer offset */ memcpy(&tx_buf[1], data, data_len); /* Use opcode 0x010A (WriteBuffer8) to write payload */ ret = lr11xx_hal_write_cmd(dev, 0x010Au, tx_buf, data_len + 1); k_free(tx_buf); if (ret < 0) { return ret; } /* Update packet params with actual payload length */ uint8_t pkt_params[6]; pkt_params[0] = 0x00; pkt_params[1] = 0x08; /* preamble length high byte */ pkt_params[2] = 0x00; /* explicit header */ pkt_params[3] = (uint8_t)data_len; pkt_params[4] = 0x01; /* CRC on */ pkt_params[5] = 0x00; /* standard IQ */ ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_PACKET_PARAMS, pkt_params, 6); if (ret < 0) { return ret; } /* Reset the event semaphore and trigger TX */ k_sem_reset(&drv->event_sem); params[0] = 0x00; /* TX timeout = 0 (no timeout) */ params[1] = 0x00; params[2] = 0x00; ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_TX, params, 3); if (ret < 0) { return ret; } /* Wait for TX_DONE IRQ */ ret = k_sem_take(&drv->event_sem, K_MSEC(5000)); if (ret < 0) { LOG_WRN("TX timeout"); return -ETIMEDOUT; } /* Read and check IRQ status */ ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_GET_IRQ_STATUS, NULL, 0); if (ret < 0) { return ret; } uint8_t irq_buf[4]; ret = lr11xx_hal_read_resp(dev, irq_buf, sizeof(irq_buf)); if (ret < 0) { return ret; } uint32_t irq = ((uint32_t)irq_buf[0] << 24) | ((uint32_t)irq_buf[1] << 16) | ((uint32_t)irq_buf[2] << 8) | ((uint32_t)irq_buf[3]); if (!(irq & LR11XX_IRQ_TX_DONE)) { LOG_WRN("TX: unexpected IRQ 0x%08x", irq); return -EIO; } /* Clear IRQ flags */ params[0] = (irq >> 24) & 0xFF; params[1] = (irq >> 16) & 0xFF; params[2] = (irq >> 8) & 0xFF; params[3] = (irq >> 0) & 0xFF; lr11xx_hal_write_cmd(dev, LR11XX_CMD_CLEAR_IRQ_STATUS, params, 4); return 0; } /* ── Receive ─────────────────────────────────────────────────────────────── */ static int lr11xx_recv(const struct device *dev, uint8_t *data, uint8_t size, k_timeout_t timeout, int16_t *rssi, int8_t *snr) { struct lr11xx_data *drv = dev->data; int ret; uint8_t params[3]; /* Clear pending IRQs */ uint32_t clr = LR11XX_IRQ_ALL; params[0] = (clr >> 24) & 0xFF; params[1] = (clr >> 16) & 0xFF; params[2] = (clr >> 8) & 0xFF; params[3] = (clr >> 0) & 0xFF; lr11xx_hal_write_cmd(dev, LR11XX_CMD_CLEAR_IRQ_STATUS, params, 4); /* Enter continuous RX */ k_sem_reset(&drv->event_sem); params[0] = 0xFF; /* RX timeout: 0xFFFFFF = single with preamble timeout */ params[1] = 0xFF; params[2] = 0xFF; ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_RX, params, 3); if (ret < 0) { return ret; } /* Wait for RX_DONE (or timeout) */ ret = k_sem_take(&drv->event_sem, timeout); if (ret < 0) { return -ETIMEDOUT; } /* Read IRQ status */ ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_GET_IRQ_STATUS, NULL, 0); if (ret < 0) { return ret; } uint8_t irq_buf[4]; ret = lr11xx_hal_read_resp(dev, irq_buf, sizeof(irq_buf)); if (ret < 0) { return ret; } uint32_t irq = ((uint32_t)irq_buf[0] << 24) | ((uint32_t)irq_buf[1] << 16) | ((uint32_t)irq_buf[2] << 8) | ((uint32_t)irq_buf[3]); /* Clear IRQ flags */ params[0] = (irq >> 24) & 0xFF; params[1] = (irq >> 16) & 0xFF; params[2] = (irq >> 8) & 0xFF; params[3] = (irq >> 0) & 0xFF; lr11xx_hal_write_cmd(dev, LR11XX_CMD_CLEAR_IRQ_STATUS, params, 4); if (irq & (LR11XX_IRQ_TIMEOUT | LR11XX_IRQ_CRC_ERROR)) { LOG_DBG("RX error IRQ=0x%08x", irq); return -EIO; } if (!(irq & LR11XX_IRQ_RX_DONE)) { return -EIO; } /* Get RX buffer status: payload_len, rx_start_ptr */ ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_GET_RX_BUFFER_STATUS, NULL, 0); if (ret < 0) { return ret; } uint8_t rxbuf_status[2]; ret = lr11xx_hal_read_resp(dev, rxbuf_status, sizeof(rxbuf_status)); if (ret < 0) { return ret; } uint8_t payload_len = rxbuf_status[0]; uint8_t offset = rxbuf_status[1]; if (payload_len > size) { payload_len = size; } /* Read payload from RX buffer */ uint8_t read_params[2] = { offset, payload_len }; ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_READ_BUFFER, read_params, sizeof(read_params)); if (ret < 0) { return ret; } ret = lr11xx_hal_read_resp(dev, data, payload_len); if (ret < 0) { return ret; } /* Get packet status (RSSI, SNR) */ ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_GET_PACKET_STATUS, NULL, 0); if (ret < 0) { goto skip_status; } uint8_t pkt_status[3]; ret = lr11xx_hal_read_resp(dev, pkt_status, sizeof(pkt_status)); if (ret == 0) { /* rssi_pkt = -pkt_status[0] / 2 dBm */ if (rssi) { *rssi = -(int16_t)pkt_status[0] / 2; } /* snr_pkt = pkt_status[1] / 4 dB (signed) */ if (snr) { *snr = (int8_t)pkt_status[1] / 4; } } skip_status: return (int)payload_len; } /* ── Initialization ──────────────────────────────────────────────────────── */ static int lr11xx_init(const struct device *dev) { const struct lr11xx_config *cfg = dev->config; struct lr11xx_data *drv = dev->data; int ret; if (!spi_is_ready_dt(&cfg->spi)) { LOG_ERR("SPI bus not ready"); return -ENODEV; } ret = gpio_pin_configure_dt(&cfg->reset, GPIO_OUTPUT_ACTIVE); if (ret < 0) { return ret; } ret = gpio_pin_configure_dt(&cfg->busy, GPIO_INPUT); if (ret < 0) { return ret; } ret = gpio_pin_configure_dt(&cfg->event, GPIO_INPUT); if (ret < 0) { return ret; } k_sem_init(&drv->event_sem, 0, 1); /* Set up interrupt on EVENT/DIO9 pin */ ret = gpio_pin_interrupt_configure_dt(&cfg->event, GPIO_INT_EDGE_TO_ACTIVE); if (ret < 0) { return ret; } gpio_init_callback(&drv->event_cb, lr11xx_hal_event_handler, BIT(cfg->event.pin)); ret = gpio_add_callback(cfg->event.port, &drv->event_cb); if (ret < 0) { return ret; } /* Hardware reset */ ret = lr11xx_hal_reset(dev); if (ret < 0) { LOG_ERR("Reset failed: %d", ret); return ret; } LOG_INF("LR11xx initialized"); return 0; } /* ── Driver API ──────────────────────────────────────────────────────────── */ static const struct lora_driver_api lr11xx_api = { .config = lr11xx_configure, .send = lr11xx_send, .recv = lr11xx_recv, }; /* ── Device instantiation macro ─────────────────────────────────────────── */ #define LR11XX_DEFINE(n) \ static struct lr11xx_data lr11xx_data_##n; \ \ static const struct lr11xx_config lr11xx_config_##n = { \ .spi = SPI_DT_SPEC_INST_GET(n, \ SPI_WORD_SET(8) | SPI_TRANSFER_MSB, 0), \ .reset = GPIO_DT_SPEC_INST_GET(n, reset_gpios), \ .busy = GPIO_DT_SPEC_INST_GET(n, busy_gpios), \ .event = GPIO_DT_SPEC_INST_GET(n, event_gpios), \ }; \ \ DEVICE_DT_INST_DEFINE(n, \ lr11xx_init, NULL, \ &lr11xx_data_##n, &lr11xx_config_##n, \ POST_KERNEL, CONFIG_LORA_INIT_PRIORITY, \ &lr11xx_api); DT_INST_FOREACH_STATUS_OKAY(LR11XX_DEFINE)