drivers: lora: Add SX126x LoRa radio support

Add support for the SX126x series of LoRa radios using the
LoRaMAC-Node HAL.

This driver currently makes the following assumptions:

  * DIO1 is used as an interrupt line.

  * There is an RF switch selecting between the TX and RX ports and
    that switch is controlled by DIO2.

  * There is either no TCXO or the TCXO is controlled by DIO3.

Specifically, the limitations above mean that modules that use GPIOs
to control the RF switch are currently not supported. Support for such
modules would need changes to the LoRaMAC-Node code.

Signed-off-by: Andreas Sandberg <andreas@sandberg.pp.se>
This commit is contained in:
Andreas Sandberg 2020-04-17 13:36:36 +01:00 committed by Anas Nashif
parent 3d1fa70e80
commit 6a4ebe5949
5 changed files with 491 additions and 1 deletions

View file

@ -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)

View file

@ -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

475
drivers/lora/sx126x.c Normal file
View file

@ -0,0 +1,475 @@
/*
* Copyright (c) 2019 Manivannan Sadhasivam
* Copyright (c) 2020 Andreas Sandberg
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <drivers/gpio.h>
#include <drivers/lora.h>
#include <drivers/spi.h>
#include <zephyr.h>
#include <sx126x/sx126x.h>
#include "sx12xx_common.h"
#include <logging/log.h>
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);

View file

@ -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

View file

@ -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