diff --git a/drivers/lora/CMakeLists.txt b/drivers/lora/CMakeLists.txt index 2533745e4e4..a10ec20c5a2 100644 --- a/drivers/lora/CMakeLists.txt +++ b/drivers/lora/CMakeLists.txt @@ -6,3 +6,4 @@ zephyr_library_sources_ifdef(CONFIG_LORA_SHELL shell.c) zephyr_library_sources_ifdef(CONFIG_HAS_SEMTECH_RADIO_DRIVERS hal_common.c) zephyr_library_sources_ifdef(CONFIG_HAS_SEMTECH_RADIO_DRIVERS sx12xx_common.c) zephyr_library_sources_ifdef(CONFIG_LORA_SX1276 sx1276.c) +zephyr_library_sources_ifdef(CONFIG_LORA_SX126X sx126x.c) diff --git a/drivers/lora/Kconfig.sx12xx b/drivers/lora/Kconfig.sx12xx index 2814121bdae..214efa1ea78 100644 --- a/drivers/lora/Kconfig.sx12xx +++ b/drivers/lora/Kconfig.sx12xx @@ -5,6 +5,8 @@ # DT_COMPAT_SEMTECH_SX1276 := semtech,sx1276 +DT_COMPAT_SEMTECH_SX1261 := semtech,sx1261 +DT_COMPAT_SEMTECH_SX1262 := semtech,sx1262 menuconfig LORA_SX12XX bool "Semtech SX-series driver" @@ -19,6 +21,8 @@ if LORA_SX12XX choice prompt "LoRa Radio chipset" default LORA_SX1276 if $(dt_compat_enabled,$(DT_COMPAT_SEMTECH_SX1276)) + default LORA_SX126X if $(dt_compat_enabled,$(DT_COMPAT_SEMTECH_SX1261)) + default LORA_SX126X if $(dt_compat_enabled,$(DT_COMPAT_SEMTECH_SX1262)) help Select the LoRa modem used on your board. The default value is discovered from the device tree and should be correct for @@ -30,6 +34,12 @@ config LORA_SX1276 help Enable LoRa driver for Semtech SX1276. +config LORA_SX126X + bool "Semtech SX126x driver" + select HAS_SEMTECH_SX126X + help + Enable LoRa driver for Semtech SX1261 and SX1262. + endchoice endif diff --git a/drivers/lora/sx126x.c b/drivers/lora/sx126x.c new file mode 100644 index 00000000000..a6740e86e86 --- /dev/null +++ b/drivers/lora/sx126x.c @@ -0,0 +1,475 @@ +/* + * Copyright (c) 2019 Manivannan Sadhasivam + * Copyright (c) 2020 Andreas Sandberg + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include + +#include "sx12xx_common.h" + +#include +LOG_MODULE_REGISTER(sx126x, CONFIG_LORA_LOG_LEVEL); + +#if DT_HAS_COMPAT_STATUS_OKAY(semtech_sx1261) +#define DT_DRV_COMPAT semtech_sx1261 +#define SX126X_DEVICE_ID SX1261 +#elif DT_HAS_COMPAT_STATUS_OKAY(semtech_sx1262) +#define DT_DRV_COMPAT semtech_sx1262 +#define SX126X_DEVICE_ID SX1262 +#else +#error No SX126x instance in device tree. +#endif + +BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(semtech_sx1261) + + DT_NUM_INST_STATUS_OKAY(semtech_sx1262) <= 1, + "Multiple SX12xx instances in DT"); + +#define HAVE_GPIO_CS DT_INST_SPI_DEV_HAS_CS_GPIOS(0) +#define GPIO_CS_LABEL DT_INST_SPI_DEV_CS_GPIOS_LABEL(0) +#define GPIO_CS_PIN DT_INST_SPI_DEV_CS_GPIOS_PIN(0) + +#define GPIO_RESET_LABEL DT_INST_GPIO_LABEL(0, reset_gpios) +#define GPIO_RESET_PIN DT_INST_GPIO_PIN(0, reset_gpios) +#define GPIO_RESET_FLAGS DT_INST_GPIO_FLAGS(0, reset_gpios) + +#define GPIO_BUSY_LABEL DT_INST_GPIO_LABEL(0, busy_gpios) +#define GPIO_BUSY_PIN DT_INST_GPIO_PIN(0, busy_gpios) +#define GPIO_BUSY_FLAGS DT_INST_GPIO_FLAGS(0, busy_gpios) + +#define GPIO_DIO1_LABEL DT_INST_GPIO_LABEL(0, dio1_gpios) +#define GPIO_DIO1_PIN DT_INST_GPIO_PIN(0, dio1_gpios) +#define GPIO_DIO1_FLAGS DT_INST_GPIO_FLAGS(0, dio1_gpios) + +#define HAVE_GPIO_ANTENNA_ENABLE \ + DT_INST_NODE_HAS_PROP(0, antenna_enable_gpios) +#define GPIO_ANTENNA_ENABLE_LABEL \ + DT_INST_GPIO_LABEL(0, antenna_enable_gpios) +#define GPIO_ANTENNA_ENABLE_PIN \ + DT_INST_GPIO_PIN(0, antenna_enable_gpios) +#define GPIO_ANTENNA_ENABLE_FLAGS \ + DT_INST_GPIO_FLAGS(0, antenna_enable_gpios) + +#define DIO2_TX_ENABLE DT_INST_PROP(0, dio2_tx_enable) + +BUILD_ASSERT(DIO2_TX_ENABLE, + "Modules without DIO2 TX control currently unsupported"); + +#define HAVE_DIO3_TCXO DT_INST_NODE_HAS_PROP(0, dio3_tcxo_voltage) +#if HAVE_DIO3_TCXO +#define TCXO_DIO3_VOLTAGE DT_INST_PROP(0, dio3_tcxo_voltage) +#endif + +#if DT_INST_NODE_HAS_PROP(0, tcxo_power_startup_delay_ms) +#define TCXO_POWER_STARTUP_DELAY_MS \ + DT_INST_PROP(0, tcxo_power_startup_delay_ms) +#else +#define TCXO_POWER_STARTUP_DELAY_MS 0 +#endif + +#define SX126X_CALIBRATION_ALL 0x7f + +struct sx126x_data { + struct device *reset; + struct device *busy; + struct device *dio1; + struct gpio_callback dio1_irq_callback; + struct k_work dio1_irq_work; + DioIrqHandler *radio_dio_irq; +#if HAVE_GPIO_ANTENNA_ENABLE + struct device *antenna_enable; +#endif + struct device *spi; + struct spi_config spi_cfg; +#if HAVE_GPIO_CS + struct spi_cs_control spi_cs; +#endif +} dev_data; + + +void SX126xWaitOnBusy(void); + +static int sx126x_spi_transceive(uint8_t *req_tx, uint8_t *req_rx, + size_t req_len, void *data_tx, void *data_rx, + size_t data_len) +{ + int ret; + + const struct spi_buf tx_buf[] = { + { + .buf = req_tx, + .len = req_len, + }, + { + .buf = data_tx, + .len = data_len + } + }; + + const struct spi_buf rx_buf[] = { + { + .buf = req_rx, + .len = req_len, + }, + { + .buf = data_rx, + .len = data_len + } + }; + + const struct spi_buf_set tx = { + .buffers = tx_buf, + .count = ARRAY_SIZE(tx_buf), + }; + + const struct spi_buf_set rx = { + .buffers = rx_buf, + .count = ARRAY_SIZE(rx_buf) + }; + + /* Wake the device if necessary */ + SX126xCheckDeviceReady(); + + if (!req_rx && !data_rx) { + ret = spi_write(dev_data.spi, &dev_data.spi_cfg, &tx); + } else { + ret = spi_transceive(dev_data.spi, &dev_data.spi_cfg, + &tx, &rx); + } + + if (ret < 0) { + LOG_ERR("SPI transaction failed: %i", ret); + } + + if (req_len >= 1 && req_tx[0] != RADIO_SET_SLEEP) { + SX126xWaitOnBusy(); + } + return ret; +} + +uint8_t SX126xReadRegister(uint16_t address) +{ + uint8_t data; + + SX126xReadRegisters(address, &data, 1); + + return data; +} + +void SX126xReadRegisters(uint16_t address, uint8_t *buffer, uint16_t size) +{ + uint8_t req[] = { + RADIO_READ_REGISTER, + (address >> 8) & 0xff, + address & 0xff, + 0, + }; + + LOG_DBG("Reading %" PRIu16 " registers @ 0x%" PRIx16, size, address); + sx126x_spi_transceive(req, NULL, sizeof(req), NULL, buffer, size); + LOG_HEXDUMP_DBG(buffer, size, "register_value"); +} + +void SX126xWriteRegister(uint16_t address, uint8_t value) +{ + SX126xWriteRegisters(address, &value, 1); +} + +void SX126xWriteRegisters(uint16_t address, uint8_t *buffer, uint16_t size) +{ + uint8_t req[] = { + RADIO_WRITE_REGISTER, + (address >> 8) & 0xff, + address & 0xff, + }; + + LOG_DBG("Writing %" PRIu16 " registers @ 0x%" PRIx16 + ": 0x%" PRIx8 " , ...", + size, address, buffer[0]); + sx126x_spi_transceive(req, NULL, sizeof(req), buffer, NULL, size); +} + +uint8_t SX126xReadCommand(RadioCommands_t opcode, + uint8_t *buffer, uint16_t size) +{ + uint8_t tx_req[] = { + opcode, + 0x00, + }; + + uint8_t rx_req[sizeof(tx_req)]; + + LOG_DBG("Issuing opcode 0x%x (data size: %" PRIx16 ")", + opcode, size); + sx126x_spi_transceive(tx_req, rx_req, sizeof(rx_req), + NULL, buffer, size); + LOG_DBG("-> status: 0x%" PRIx8, rx_req[1]); + return rx_req[1]; +} + +void SX126xWriteCommand(RadioCommands_t opcode, uint8_t *buffer, uint16_t size) +{ + uint8_t req[] = { + opcode, + }; + + LOG_DBG("Issuing opcode 0x%x w. %" PRIu16 " bytes of data", + opcode, size); + sx126x_spi_transceive(req, NULL, sizeof(req), buffer, NULL, size); +} + +void SX126xReadBuffer(uint8_t offset, uint8_t *buffer, uint8_t size) +{ + uint8_t req[] = { + RADIO_READ_BUFFER, + offset, + 0x00, + }; + + LOG_DBG("Reading buffers @ 0x%" PRIx8 " (%" PRIu8 " bytes)", + offset, size); + sx126x_spi_transceive(req, NULL, sizeof(req), NULL, buffer, size); +} + +void SX126xWriteBuffer(uint8_t offset, uint8_t *buffer, uint8_t size) +{ + uint8_t req[] = { + RADIO_WRITE_BUFFER, + offset, + }; + + LOG_DBG("Writing buffers @ 0x%" PRIx8 " (%" PRIu8 " bytes)", + offset, size); + sx126x_spi_transceive(req, NULL, sizeof(req), buffer, NULL, size); +} + +void SX126xAntSwOn(void) +{ +#if HAVE_GPIO_ANTENNA_ENABLE + LOG_DBG("Enabling antenna switch"); + gpio_pin_set(dev_data.antenna_enable, GPIO_ANTENNA_ENABLE_PIN, 1); +#else + LOG_DBG("No antenna switch configured"); +#endif +} + +void SX126xAntSwOff(void) +{ +#if HAVE_GPIO_ANTENNA_ENABLE + LOG_DBG("Disabling antenna switch"); + gpio_pin_set(dev_data.antenna_enable, GPIO_ANTENNA_ENABLE_PIN, 0); +#else + LOG_DBG("No antenna switch configured"); +#endif +} + +uint32_t SX126xGetBoardTcxoWakeupTime(void) +{ + return TCXO_POWER_STARTUP_DELAY_MS; +} + +uint8_t SX126xGetDeviceId(void) +{ + return SX126X_DEVICE_ID; +} + +void SX126xIoIrqInit(DioIrqHandler dioIrq) +{ + LOG_DBG("Configuring DIO IRQ callback"); + dev_data.radio_dio_irq = dioIrq; +} + +void SX126xIoTcxoInit(void) +{ +#if HAVE_DIO3_TCXO + CalibrationParams_t cal = { + .Value = SX126X_CALIBRATION_ALL, + }; + + LOG_DBG("TCXO on DIO3"); + + /* Delay in units of 15.625 us (1/64 ms) */ + SX126xSetDio3AsTcxoCtrl(TCXO_DIO3_VOLTAGE, + TCXO_POWER_STARTUP_DELAY_MS << 6); + SX126xCalibrate(cal); +#else + LOG_DBG("No TCXO configured"); +#endif +} + +void SX126xReset(void) +{ + LOG_DBG("Resetting radio"); + gpio_pin_set(dev_data.reset, GPIO_RESET_PIN, 1); + k_sleep(K_MSEC(20)); + gpio_pin_set(dev_data.reset, GPIO_RESET_PIN, 0); + k_sleep(K_MSEC(10)); +} + +void SX126xSetRfTxPower(int8_t power) +{ + LOG_DBG("power: %" PRIi8, power); + SX126xSetTxParams(power, RADIO_RAMP_40_US); +} + +void SX126xWaitOnBusy(void) +{ + while (gpio_pin_get(dev_data.busy, GPIO_BUSY_PIN)) { + k_sleep(K_MSEC(1)); + } +} + +void SX126xWakeup(void) +{ + int ret; + + uint8_t req[] = { RADIO_GET_STATUS, 0 }; + const struct spi_buf tx_buf = { + .buf = req, + .len = sizeof(req), + }; + + const struct spi_buf_set tx = { + .buffers = &tx_buf, + .count = 1, + }; + + LOG_DBG("Sending GET_STATUS"); + ret = spi_write(dev_data.spi, &dev_data.spi_cfg, &tx); + if (ret < 0) { + LOG_ERR("SPI transaction failed: %i", ret); + return; + } + + LOG_DBG("Waiting for device..."); + SX126xWaitOnBusy(); + LOG_DBG("Device ready"); +} + +static void sx126x_dio1_irq_work_handler(struct k_work *work) +{ + LOG_DBG("Processing DIO1 interrupt"); + if (!dev_data.radio_dio_irq) { + LOG_WRN("DIO1 interrupt without valid HAL IRQ callback."); + return; + } + + dev_data.radio_dio_irq(NULL); + if (Radio.IrqProcess) { + Radio.IrqProcess(); + } +} + +static void sx126x_dio1_irq_callback(struct device *dev, + struct gpio_callback *cb, uint32_t pins) +{ + if (pins & BIT(GPIO_DIO1_PIN)) { + k_work_submit(&dev_data.dio1_irq_work); + } +} + +static int sx126x_lora_init(struct device *dev) +{ + int ret; + + LOG_DBG("Initializing %s", DT_INST_LABEL(0)); + + dev_data.reset = device_get_binding(GPIO_RESET_LABEL); + if (!dev_data.reset) { + LOG_ERR("Cannot get pointer to %s device", GPIO_RESET_LABEL); + return -EIO; + } + + gpio_pin_configure(dev_data.reset, GPIO_RESET_PIN, + GPIO_OUTPUT_ACTIVE | GPIO_RESET_FLAGS); + + + dev_data.busy = device_get_binding(GPIO_BUSY_LABEL); + if (!dev_data.busy) { + LOG_ERR("Cannot get pointer to %s device", GPIO_BUSY_LABEL); + return -EIO; + } + + gpio_pin_configure(dev_data.busy, GPIO_BUSY_PIN, + GPIO_INPUT | GPIO_BUSY_FLAGS); + + dev_data.dio1 = device_get_binding(GPIO_DIO1_LABEL); + if (!dev_data.dio1) { + LOG_ERR("Cannot get pointer to %s device", GPIO_DIO1_LABEL); + return -EIO; + } + + gpio_pin_configure(dev_data.dio1, GPIO_DIO1_PIN, + GPIO_INPUT | GPIO_INT_DEBOUNCE | GPIO_DIO1_FLAGS); + + k_work_init(&dev_data.dio1_irq_work, sx126x_dio1_irq_work_handler); + + gpio_init_callback(&dev_data.dio1_irq_callback, + sx126x_dio1_irq_callback, BIT(GPIO_DIO1_PIN)); + if (gpio_add_callback(dev_data.dio1, &dev_data.dio1_irq_callback) < 0) { + LOG_ERR("Could not set GPIO callback for DIO1 interrupt."); + return -EIO; + } + + gpio_pin_interrupt_configure(dev_data.dio1, GPIO_DIO1_PIN, + GPIO_INT_EDGE_TO_ACTIVE); + +#if HAVE_GPIO_ANTENNA_ENABLE + dev_data.antenna_enable = device_get_binding(GPIO_ANTENNA_ENABLE_LABEL); + if (!dev_data.antenna_enable) { + LOG_ERR("Cannot get pointer to %s device", + GPIO_ANTENNA_ENABLE_LABEL); + return -EIO; + } + + gpio_pin_configure(dev_data.antenna_enable, GPIO_ANTENNA_ENABLE_PIN, + GPIO_OUTPUT_ACTIVE | GPIO_ANTENNA_ENABLE_FLAGS); +#endif + + dev_data.spi = device_get_binding(DT_INST_BUS_LABEL(0)); + if (!dev_data.spi) { + LOG_ERR("Cannot get pointer to %s device", + DT_INST_BUS_LABEL(0)); + return -EINVAL; + } + +#if HAVE_GPIO_CS + dev_data.spi_cs.gpio_dev = device_get_binding(GPIO_CS_LABEL); + dev_data.spi_cs.gpio_pin = GPIO_CS_PIN; + if (!dev_data.spi_cs.gpio_dev) { + LOG_ERR("Cannot get pointer to %s device", GPIO_CS_LABEL); + return -EIO; + } + + dev_data.spi_cfg.cs = &dev_data.spi_cs; +#endif + dev_data.spi_cfg.operation = SPI_WORD_SET(8) | SPI_TRANSFER_MSB; + dev_data.spi_cfg.frequency = DT_INST_PROP(0, spi_max_frequency); + dev_data.spi_cfg.slave = DT_INST_REG_ADDR(0); + + ret = sx12xx_init(dev); + if (ret < 0) { + LOG_ERR("Failed to initialize SX12xx common"); + return ret; + } + + return 0; +} + +static const struct lora_driver_api sx126x_lora_api = { + .config = sx12xx_lora_config, + .send = sx12xx_lora_send, + .recv = sx12xx_lora_recv, + .test_cw = sx12xx_lora_test_cw, +}; + +DEVICE_AND_API_INIT(sx126x_lora, DT_INST_LABEL(0), + &sx126x_lora_init, NULL, + NULL, POST_KERNEL, CONFIG_LORA_INIT_PRIORITY, + &sx126x_lora_api); diff --git a/modules/Kconfig.loramac-node b/modules/Kconfig.loramac-node index fc775d30b25..168b53e1058 100644 --- a/modules/Kconfig.loramac-node +++ b/modules/Kconfig.loramac-node @@ -17,3 +17,7 @@ config HAS_SEMTECH_RADIO_DRIVERS config HAS_SEMTECH_SX1276 bool depends on HAS_SEMTECH_RADIO_DRIVERS + +config HAS_SEMTECH_SX126X + bool + depends on HAS_SEMTECH_RADIO_DRIVERS diff --git a/west.yml b/west.yml index 8416a89e9e4..4c974a0b499 100644 --- a/west.yml +++ b/west.yml @@ -104,7 +104,7 @@ manifest: revision: 724f7e2a4519d7e1d40ef330042682dea950c991 path: modules/lib/open-amp - name: loramac-node - revision: 29e516ec585b1a909af2b5f1c60d83e7d4d563e3 + revision: 170a2579dd890f78f5056f0959cdb9c9bea259a1 path: modules/lib/loramac-node - name: openthread revision: e3b80d6e4c8d368c856dfce774bc792ffde54cfe