/* * 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 HAVE_GPIO_ANTENNA_ENABLE \ DT_INST_NODE_HAS_PROP(0, antenna_enable_gpios) #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_CS_FLAGS DT_INST_SPI_DEV_CS_GPIOS_FLAGS(0) #define GPIO_RESET_PIN DT_INST_GPIO_PIN(0, reset_gpios) #define GPIO_BUSY_PIN DT_INST_GPIO_PIN(0, busy_gpios) #define GPIO_DIO1_PIN DT_INST_GPIO_PIN(0, dio1_gpios) #define GPIO_ANTENNA_ENABLE_PIN DT_INST_GPIO_PIN(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)); if (sx12xx_configure_pin(reset, GPIO_OUTPUT_ACTIVE) || sx12xx_configure_pin(busy, GPIO_INPUT) || sx12xx_configure_pin(dio1, GPIO_INPUT | GPIO_INT_DEBOUNCE) || sx12xx_configure_pin(antenna_enable, GPIO_OUTPUT_INACTIVE)) { return -EIO; } 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); 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); if (!dev_data.spi_cs.gpio_dev) { LOG_ERR("Cannot get pointer to %s device", GPIO_CS_LABEL); return -EIO; } dev_data.spi_cs.gpio_pin = GPIO_CS_PIN; dev_data.spi_cs.gpio_dt_flags = GPIO_CS_FLAGS; dev_data.spi_cs.delay = 0U; 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);