commit fc8ed8fb5e3ee8855ba181ba395220a08dc2f3c4 Author: Fabian Montero Date: Sun Oct 6 15:18:16 2024 -0600 add lora sx127 driver diff --git a/lora_sx127_rpi3.c b/lora_sx127_rpi3.c new file mode 100644 index 0000000..8200ac7 --- /dev/null +++ b/lora_sx127_rpi3.c @@ -0,0 +1,812 @@ +/*- + * Copyright (c) 2017 Jian-Hong, Pan + * Copyright (c) 2024 Fabian Montero + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGES. + * + */ + +#define DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef F_XOSC +#define F_XOSC 32000000 +#endif +static u32 xosc_frq = F_XOSC; +module_param(xosc_frq, uint, 0000); +MODULE_PARM_DESC(xosc_frq, "Crystal oscillator frequency of the LoRa chip"); + +#define __POW_2_19 0x80000 + +#ifndef SX127X_SPRF +#define SX127X_SPRF 512 +#endif +static u32 sprf = SX127X_SPRF; +module_param(sprf, uint, 0000); +MODULE_PARM_DESC(sprf, "Spreading factor of Chirp Spread Spectrum modulation"); + +#ifndef SX127X_RX_BYTE_TIMEOUT +#define SX127X_RX_BYTE_TIMEOUT 1023 +#endif +static u32 rx_timeout = SX127X_RX_BYTE_TIMEOUT; +module_param(rx_timeout, uint, 0000); +MODULE_PARM_DESC(rx_timeout, "RX time-out value as number of symbols"); + +/* SX127X Registers addresses */ +#define SX127X_REG_FIFO 0x00 +#define SX127X_REG_OP_MODE 0x01 +#define SX127X_REG_FRF_MSB 0x06 +#define SX127X_REG_FRF_MID 0x07 +#define SX127X_REG_FRF_LSB 0x08 +#define SX127X_REG_PA_CONFIG 0x09 +#define SX127X_REG_PA_RAMP 0x0A +#define SX127X_REG_OCP 0x0B +#define SX127X_REG_LNA 0x0C +#define SX127X_REG_FIFO_ADDR_PTR 0x0D +#define SX127X_REG_FIFO_TX_BASE_ADDR 0x0E +#define SX127X_REG_FIFO_RX_BASE_ADDR 0x0F +#define SX127X_REG_FIFO_RX_CURRENT_ADDR 0x10 +#define SX127X_REG_IRQ_FLAGS_MASK 0x11 +#define SX127X_REG_IRQ_FLAGS 0x12 +#define SX127X_REG_RX_NB_BYTES 0x13 +#define SX127X_REG_RX_HEADER_CNT_VALUE_MSB 0x14 +#define SX127X_REG_RX_HEADER_CNT_VALUE_LSB 0x15 +#define SX127X_REG_RX_PACKET_CNT_VALUE_MSB 0x16 +#define SX127X_REG_RX_PACKET_CNT_VALUE_LSB 0x17 +#define SX127X_REG_MODEM_STAT 0x18 +#define SX127X_REG_PKT_SNR_VALUE 0x19 +#define SX127X_REG_PKT_RSSI_VALUE 0x1A +#define SX127X_REG_RSSI_VALUE 0x1B +#define SX127X_REG_HOP_CHANNEL 0x1C +#define SX127X_REG_MODEM_CONFIG1 0x1D +#define SX127X_REG_MODEM_CONFIG2 0x1E +#define SX127X_REG_SYMB_TIMEOUT_LSB 0x1F +#define SX127X_REG_PREAMBLE_MSB 0x20 +#define SX127X_REG_PREAMBLE_LSB 0x21 +#define SX127X_REG_PAYLOAD_LENGTH 0x22 +#define SX127X_REG_MAX_PAYLOAD_LENGTH 0x23 +#define SX127X_REG_HOP_PERIOD 0x24 +#define SX127X_REG_FIFO_RX_BYTE_ADDR 0x25 +#define SX127X_REG_MODEM_CONFIG3 0x26 +#define SX127X_REG_FEI_MSB 0x28 +#define SX127X_REG_FEI_MID 0x29 +#define SX127X_REG_FEI_LSB 0x2A +#define SX127X_REG_RSSI_WIDEBAND 0x2C +#define SX127X_REG_DETECT_OPTIMIZE 0x31 +#define SX127X_REG_INVERT_IRQ 0x33 +#define SX127X_REG_DETECTION_THRESHOLD 0x37 +#define SX127X_REG_SYNC_WORD 0x39 +#define SX127X_REG_VERSION 0x42 +#define SX127X_REG_TCXO 0x4B +#define SX127X_REG_PA_DAC 0x4D +#define SX127X_REG_FORMER_TEMP 0x5B +#define SX127X_REG_AGC_REF 0x61 +#define SX127X_REG_AGC_THRESH1 0x62 +#define SX127X_REG_AGC_THRESH2 0x63 +#define SX127X_REG_AGC_THRESH3 0x64 +#define SX127X_REG_PLL 0x70 +#define SX127X_MAX_REG SX127X_REG_PLL + +/* SX127X's operating states in LoRa mode */ +#define SX127X_MODE_MASK 0x07 +#define SX127X_SLEEP_MODE 0x00 +#define SX127X_STANDBY_MODE 0x01 +#define SX127X_FSTX_MODE 0x02 +#define SX127X_TX_MODE 0x03 +#define SX127X_FSRX_MODE 0x04 +#define SX127X_RXCONTINUOUS_MODE 0x05 +#define SX127X_RXSINGLE_MODE 0x06 +#define SX127X_CAD_MODE 0x07 +#define SX127X_LORA_MODE 0x80 + +/* SX127X_REG_OCP flags */ +#define SX127X_FLAG_OCPON 0x20 + +/* SX127X's IRQ flags in LoRa mode */ +#define SX127X_FLAG_RXTIMEOUT 0x80 +#define SX127X_FLAG_RXDONE 0x40 +#define SX127X_FLAG_PAYLOADCRCERROR 0x20 +#define SX127X_FLAG_VALIDHEADER 0x10 +#define SX127X_FLAG_TXDONE 0x08 +#define SX127X_FLAG_CADDONE 0x04 +#define SX127X_FLAG_FHSSCHANGECHANNEL 0x02 +#define SX127X_FLAG_CADDETECTED 0x01 + +/* SX127X's IRQ flags' mask for output pins in LoRa mode */ +#define SX127X_FLAGMASK_RXTIMEOUT 0x80 +#define SX127X_FLAGMASK_RXDONE 0x40 +#define SX127X_FLAGMASK_PAYLOADCRCERROR 0x20 +#define SX127X_FLAGMASK_VALIDHEADER 0x10 +#define SX127X_FLAGMASK_TXDONE 0x08 +#define SX127X_FLAGMASK_CADDONE 0x04 +#define SX127X_FLAGMASK_FHSSCHANGECHANNEL 0x02 +#define SX127X_FLAGMASK_CADDETECTED 0x01 + +#define SX127X_MODEM_CONFIG1_HEADER_MODE_MASK 0x01 +#define SX127X_MODEM_CONFIG1_HEADER_MODE_EXPLICIT 0x00 +#define SX127X_MODEM_CONFIG1_HEADER_MODE_IMPLICIT 0x01 + +/* SX127X's RX/TX FIFO base address */ +#define SX127X_FIFO_RX_BASE_ADDRESS 0x00 +#define SX127X_FIFO_TX_BASE_ADDRESS 0x80 + +#define POLL_JIFFIES (msecs_to_jiffies(10)) + +struct sx127x { + struct regmap *regmap; + struct ieee802154_hw *ieee; + struct spi_device *spi; + + spinlock_t lock; + bool busy; + struct sk_buff *tx_skb; + size_t delayed_tx_length; + + struct timer_list poll_timer; + struct work_struct poll_work; + struct work_struct tx_work; + + u32 oscillator_freq; +}; + +static int sx127x_reg_read(struct sx127x *hw, unsigned int address, u8 *data) +{ + int ret = regmap_raw_read(hw->regmap, address, data, 1); + if (ret < 0) + dev_err(&hw->spi->dev, "regmap_raw_read(0x%x) failed: %d\n", address, ret); + + return ret; +} + +static int sx127x_reg_write(struct sx127x *hw, unsigned int address, u8 data) +{ + int ret = regmap_raw_write(hw->regmap, address, &data, 1); + if (ret < 0) + dev_err(&hw->spi->dev, "regmap_raw_write(0x%x) failed: %d\n", address, ret); + + return ret; +} + +static int sx127x_mode(struct sx127x *hw, unsigned int mode) +{ + return sx127x_reg_write(hw, SX127X_REG_OP_MODE, SX127X_LORA_MODE | mode); +} + +static int sx127x_set_frequency(struct sx127x *hw, unsigned long frequency) +{ + u8 msb_mid_lsb[3]; + int i; + u64 frf; + + frf = (((u64) frequency) << 19) / hw->oscillator_freq; + for (i = sizeof msb_mid_lsb - 1; i >= 0; --i) { + msb_mid_lsb[i] = (frf & 0xff); + frf >>= 8; + } + + return regmap_raw_write(hw->regmap, SX127X_REG_FRF_MSB, msb_mid_lsb, sizeof msb_mid_lsb); +} + +static int sx127x_get_frequency(struct sx127x *hw, unsigned long *frequency) +{ + u8 msb_mid_lsb[3]; + int i, ret; + u64 frt; + + ret = regmap_raw_read(hw->regmap, SX127X_REG_FRF_MSB, msb_mid_lsb, sizeof msb_mid_lsb); + if (ret < 0) + return ret; + + frt = 0; + for (i = 0; i < sizeof msb_mid_lsb; ++i) + frt = (frt << 8) | msb_mid_lsb[i]; + + *frequency = (frt * hw->oscillator_freq) >> 19; + return 0; +} + +static int sx127x_set_ocp(struct sx127x *hw, u8 current_ma) +{ + u8 ocp_trim; + + /* P. 109 + * + * Trimming of OCP current: + * Imax = 45+5*OcpTrim [mA] if OcpTrim <= 15 (120 mA) / + * Imax = -30+10*OcpTrim [mA] if 15 < OcpTrim <= 27 (130 to + * 240 mA) + * Imax = 240mA for higher settings + * Default Imax = 100mA + */ + if (current_ma <= 45) + ocp_trim = 0; + else if (current_ma <= 120) + ocp_trim = (current_ma - 45) / 5; + else if (current_ma <= 240) + ocp_trim = (current_ma + 30) / 10; + else + ocp_trim = 28; + + return sx127x_reg_write(hw, SX127X_REG_OCP, SX127X_FLAG_OCPON | ocp_trim); +} + +static int sx127x_set_tx_power(struct sx127x *hw, int level) +{ + int ret; + + // PA BOOST + if (level > 17) { + if (level > 20) + level = 20; + + // subtract 3 from level, so 18 - 20 maps to 15 - 17 + level -= 3; + + // High Power +20 dBm Operation (Semtech SX1276/77/78/79 5.4.3.) + ret = sx127x_reg_write(hw, SX127X_REG_PA_DAC, 0x87); + if (ret < 0) + return ret; + + ret = sx127x_set_ocp(hw, 140); + if (ret < 0) + return ret; + } else { + if (level < 2) + level = 2; + + // Default value PA_HF/LF or +17dBm + ret = sx127x_reg_write(hw, SX127X_REG_PA_DAC, 0x84); + if (ret < 0) + return ret; + + ret = sx127x_set_ocp(hw, 100); + if (ret < 0) + return ret; + } + + // PA_CONFIG +#define PA_BOOST 0x80 + return sx127x_reg_write(hw, SX127X_REG_PA_CONFIG, PA_BOOST | (level - 2)); +} + +static int sx127x_init(struct sx127x *hw, u8 *major, u8 *minor) +{ + u8 modem_config1, lna, version; + int ret; + + ret = sx127x_reg_read(hw, SX127X_REG_VERSION, &version); + if (ret < 0) + return ret; + + if (version != 0x12) { + dev_err(&hw->spi->dev, "%s: bad version 0x%02x\n", __func__, version); + return -ENODEV; + } + + *major = (version >> 4) & 0xf; + *minor = (version >> 0) & 0xf; + + ret = sx127x_mode(hw, SX127X_SLEEP_MODE); + if (ret < 0) { + dev_err(&hw->spi->dev, "%s: sx127x_mode() failed: %d\n", __func__, ret); + return ret; + } + + ret = sx127x_reg_write(hw, SX127X_REG_FIFO_RX_BASE_ADDR, SX127X_FIFO_RX_BASE_ADDRESS); + if (ret < 0) + return ret; + + ret = sx127x_reg_write(hw, SX127X_REG_FIFO_TX_BASE_ADDR, SX127X_FIFO_TX_BASE_ADDRESS); + if (ret < 0) + return ret; + + ret = sx127x_reg_read(hw, SX127X_REG_LNA, &lna); + if (ret < 0) + return ret; + + lna |= 0x3; // ???, pero lo hace el sketch + ret = sx127x_reg_write(hw, SX127X_REG_LNA, lna); + if (ret < 0) + return ret; + + ret = sx127x_reg_read(hw, SX127X_REG_MODEM_CONFIG1, &modem_config1); + if (ret < 0) + return ret; + + modem_config1 &= ~SX127X_MODEM_CONFIG1_HEADER_MODE_MASK; + modem_config1 |= SX127X_MODEM_CONFIG1_HEADER_MODE_EXPLICIT; + + ret = sx127x_reg_write(hw, SX127X_REG_MODEM_CONFIG1, modem_config1); + if (ret < 0) + return ret; + + ret = sx127x_reg_write(hw, SX127X_REG_MODEM_CONFIG3, 0x4); + if (ret < 0) + return ret; + + return 0; +} + +static void sx127x_tx_work(struct work_struct *work) +{ + u8 mode; + int ret = 0; + bool set_busy = false; + size_t i, size; + const u8 *buffer; + struct sx127x *hw = container_of(work, struct sx127x, tx_work); + struct sk_buff *tx_skb; + unsigned long spin_irq; + + spin_lock_irqsave(&hw->lock, spin_irq); + + tx_skb = hw->tx_skb; + if (!hw->busy && !hw->delayed_tx_length) { + hw->busy = true; + set_busy = true; + } + + spin_unlock_irqrestore(&hw->lock, spin_irq); + + if (!set_busy) { + ret = -EBUSY; + goto exit_busy; + } else if (!tx_skb) { + ret = -EINVAL; + goto exit_busy; + } + + size = tx_skb->len; + buffer = tx_skb->data; + + if (!size || size > 128) { + ret = -EINVAL; + goto exit_busy; + } + + ret = sx127x_reg_read(hw, SX127X_REG_OP_MODE, &mode); + if (ret < 0) + goto exit_busy; + + if ((mode & SX127X_MODE_MASK) == SX127X_TX_MODE) { + ret = -EBUSY; + goto exit_busy; + } + + // reset FIFO address and payload length + ret = sx127x_reg_write(hw, SX127X_REG_FIFO_ADDR_PTR, SX127X_FIFO_TX_BASE_ADDRESS); + if (ret < 0) + goto exit_busy; + + // write data + for (i = 0; i < size; i++) { + ret = sx127x_reg_write(hw, SX127X_REG_FIFO, buffer[i]); + if (ret < 0) + goto exit_busy; + } + + ret = sx127x_reg_write(hw, SX127X_REG_PAYLOAD_LENGTH, (u8) size); + if (ret < 0) + goto exit_busy; + + ret = sx127x_reg_read(hw, SX127X_REG_OP_MODE, &mode); + if (ret < 0) + goto exit_busy; + + if ((mode & SX127X_MODE_MASK) == SX127X_STANDBY_MODE + || (mode & SX127X_MODE_MASK) == SX127X_RXCONTINUOUS_MODE) + ret = sx127x_mode(hw, SX127X_TX_MODE); + else + hw->delayed_tx_length = size; + +exit_busy: + spin_lock_irqsave(&hw->lock, spin_irq); + if (set_busy) + hw->busy = false; + + if (tx_skb) + hw->tx_skb = NULL; + spin_unlock_irqrestore(&hw->lock, spin_irq); + + if (tx_skb) { +#ifndef SX127X_LEGACY_KERNEL + if (ret) + ieee802154_xmit_error(hw->ieee, tx_skb, ret); + else +#endif + ieee802154_xmit_complete(hw->ieee, tx_skb, false); + } + + (void) ret; +} + +static void sx127x_poll_work(struct work_struct *work) +{ + u8 ack_flags, lqi, mode, read_flags, rx_base, *rx_data, rx_length; + bool do_rx = false, was_busy = false; + unsigned long spin_irq; + struct sx127x *hw = container_of(work, struct sx127x, poll_work); + struct sk_buff *rx_skb = NULL; + + spin_lock_irqsave(&hw->lock, spin_irq); + if (!hw->busy) + hw->busy = true; + else + was_busy = true; + spin_unlock_irqrestore(&hw->lock, spin_irq); + + if (was_busy) + return; + + if (sx127x_reg_read(hw, SX127X_REG_IRQ_FLAGS, &read_flags) < 0) + goto exit_busy; + + ack_flags = 0; + + if (read_flags & SX127X_FLAG_TXDONE) + ack_flags |= SX127X_FLAG_TXDONE; + + if (read_flags & SX127X_FLAG_RXDONE) { + do_rx = true; + ack_flags |= SX127X_FLAG_RXDONE; + + if (read_flags & SX127X_FLAG_VALIDHEADER) + ack_flags |= SX127X_FLAG_VALIDHEADER; + else + do_rx = false; + + if (read_flags & SX127X_FLAG_PAYLOADCRCERROR) { + do_rx = false; + ack_flags |= SX127X_FLAG_PAYLOADCRCERROR; + } + } + + if (do_rx) { + if (sx127x_reg_read(hw, SX127X_REG_FIFO_RX_CURRENT_ADDR, &rx_base) < 0 + || sx127x_reg_read(hw, SX127X_REG_RX_NB_BYTES, &rx_length) < 0) + goto exit_busy; + + if (sx127x_reg_write(hw, SX127X_REG_FIFO_ADDR_PTR, rx_base) < 0) + goto exit_busy; + + rx_skb = dev_alloc_skb(IEEE802154_MTU); + if (!rx_skb) + goto exit_busy; + + rx_data = skb_put(rx_skb, rx_length); + while (rx_length--) + if (sx127x_reg_read(hw, SX127X_REG_FIFO, rx_data++) < 0) + goto exit_busy; + + if (sx127x_reg_write(hw, SX127X_REG_FIFO_RX_BASE_ADDR, SX127X_FIFO_RX_BASE_ADDRESS) < 0) + goto exit_busy; + } + + if (sx127x_reg_write(hw, SX127X_REG_IRQ_FLAGS, ack_flags) < 0) + goto exit_busy; + + if (sx127x_reg_read(hw, SX127X_REG_OP_MODE, &mode) < 0) + goto exit_busy; + + if ((mode & SX127X_MODE_MASK) != SX127X_TX_MODE) { + if (hw->delayed_tx_length) { + sx127x_mode(hw, SX127X_TX_MODE); + hw->delayed_tx_length = 0; + } else if ((mode & SX127X_MODE_MASK) != SX127X_RXCONTINUOUS_MODE) + sx127x_mode(hw, SX127X_RXCONTINUOUS_MODE); + } + + if (rx_skb) { + lqi = 69; //TODO + ieee802154_rx_irqsafe(hw->ieee, rx_skb, lqi); + rx_skb = NULL; // rx_skb ownership transferred to the mac802154 layer + } + +exit_busy: + if (rx_skb) + kfree_skb(rx_skb); + + spin_lock_irqsave(&hw->lock, spin_irq); + hw->busy = false; + spin_unlock_irqrestore(&hw->lock, spin_irq); +} + +static void sx127x_poll_timer(struct timer_list *timer) +{ + struct sx127x *hw = container_of(timer, struct sx127x, poll_timer); + schedule_work(&hw->poll_work); + mod_timer(&hw->poll_timer, jiffies + POLL_JIFFIES); +} + +static int sx127x_802154_xmit_async(struct ieee802154_hw *ieee, struct sk_buff *skb) +{ + int ret = 0; + struct sx127x *hw = ieee->priv; + unsigned long spin_irq; + + spin_lock_irqsave(&hw->lock, spin_irq); + if (!hw->tx_skb) + hw->tx_skb = skb_get(skb); + else + ret = -EBUSY; + spin_unlock_irqrestore(&hw->lock, spin_irq); + + schedule_work(&hw->tx_work); + return ret; +} + +static int sx127x_802154_ed(struct ieee802154_hw *ieee, u8 *level) +{ + struct sx127x *hw = ieee->priv; + (void) hw; + return -ENOSYS; +} + +static int sx127x_802154_set_channel(struct ieee802154_hw *ieee, u8 page, u8 channel) +{ + struct sx127x *hw = ieee->priv; + (void) hw; + return -ENOSYS; +} + +static int sx127x_802154_set_txpower(struct ieee802154_hw *ieee, s32 mbm) +{ + struct sx127x *hw = ieee->priv; + (void) hw; + return -ENOSYS; +} + +static int sx127x_802154_start(struct ieee802154_hw *ieee) +{ + int ret; + struct sx127x *hw = ieee->priv; + + ret = sx127x_mode(hw, SX127X_STANDBY_MODE); + if (ret < 0) { + dev_err(&hw->spi->dev, "%s: sx127x_mode() failed: %d\n", __func__, ret); + return ret; + } + + mod_timer(&hw->poll_timer, jiffies + POLL_JIFFIES); + return 0; +} + +static void sx127x_802154_stop(struct ieee802154_hw *ieee) +{ + struct sx127x *hw = ieee->priv; + + del_timer_sync(&hw->poll_timer); + cancel_work_sync(&hw->poll_work); + + (void) sx127x_mode(hw, SX127X_SLEEP_MODE); +} + +static int sx127x_802154_set_promiscuous_mode(struct ieee802154_hw *ieee, const bool on) +{ + return 0; +} + +static const struct ieee802154_ops sx127x_802154_ops = { + .owner = THIS_MODULE, + .xmit_async = sx127x_802154_xmit_async, + .ed = sx127x_802154_ed, + .set_channel = sx127x_802154_set_channel, + .set_txpower = sx127x_802154_set_txpower, + .start = sx127x_802154_start, + .stop = sx127x_802154_stop, + .set_promiscuous_mode = sx127x_802154_set_promiscuous_mode, +}; + +static bool sx127x_reg_volatile(struct device *dev, unsigned int reg) +{ + return true; +} + +static const struct regmap_config sx127x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = SX127X_MAX_REG, + .read_flag_mask = 0x00, + .write_flag_mask = 0x80, + .volatile_reg = sx127x_reg_volatile, +}; + +/* in mbm */ +static const s32 sx127x_tx_powers[] = { + -200, -100, 0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, + 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900, 2000, 2100, 2200, 2300}; + +static int sx127x_spi_probe(struct spi_device *spi) +{ + u8 major, minor; + int ret = 0; + struct sx127x *hw; + unsigned long frequency; + struct ieee802154_hw *ieee; + struct device_node *of_node; + + dev_dbg(&spi->dev, "%s hello\n", __func__); + + ieee = ieee802154_alloc_hw(sizeof *hw, &sx127x_802154_ops); + if (!ieee) { + dev_err(&spi->dev, "ieee802154_alloc_hw(): out of memory\n"); + return -ENOMEM; + } + + hw = ieee->priv; + hw->spi = spi; + hw->ieee = ieee; + hw->busy = false; + hw->delayed_tx_length = 0; + spin_lock_init(&hw->lock); + + INIT_WORK(&hw->tx_work, sx127x_tx_work); + INIT_WORK(&hw->poll_work, sx127x_poll_work); + timer_setup(&hw->poll_timer, sx127x_poll_timer, 0); + + hw->regmap = devm_regmap_init_spi(spi, &sx127x_regmap_config); + if (IS_ERR(hw->regmap)) { + dev_err(&spi->dev, "%s: devm_regmap_init_spi() failed: %d\n", __func__, ret); + return PTR_ERR(hw->regmap); + } + + of_node = regmap_get_device(hw->regmap)->of_node; + if (of_property_read_u32(of_node, "clock-frequency", &hw->oscillator_freq)) { + dev_err(&spi->dev, "%s: 'clock-frequency' missing from DT node\n", __func__); + return -EINVAL; + } + + ieee->parent = &spi->dev; + spi_set_drvdata(spi, hw); + + //TODO: pin de reset + + ieee802154_random_extended_addr(&ieee->phy->perm_extended_addr); + + ieee->flags = IEEE802154_HW_TX_OMIT_CKSUM + | IEEE802154_HW_RX_OMIT_CKSUM + | IEEE802154_HW_PROMISCUOUS; + + ieee->phy->flags = WPAN_PHY_FLAG_TXPOWER; + ieee->phy->transmit_power = sx127x_tx_powers[12]; + ieee->phy->supported.tx_powers = sx127x_tx_powers; + ieee->phy->supported.tx_powers_size = ARRAY_SIZE(sx127x_tx_powers); + + /* SX1278 phy channel 11 as default */ + ieee->phy->current_channel = 11; + /* Define channels could be used. */ + ieee->phy->supported.channels[0] = 1 << 11; /*TODO: sx1278_ieee_channel_mask(hw);*/ + + ret = sx127x_init(hw, &major, &minor); + if (ret < 0) { + dev_err(&spi->dev, "%s: sx127x_init() failed: %d\n", __func__, ret); + goto fail_init; + } + + dev_info(&spi->dev, "IEEE 802.15.4 over LoRa SX127x compatible device v%u.%u\n", major, minor); + + // Valor viene del sketch + ret = sx127x_set_frequency(hw, 434000000); + if (ret < 0) { + dev_err(&spi->dev, "%s: sx127x_set_frequency() failed: %d\n", __func__, ret); + goto fail_init; + } + + ret = sx127x_get_frequency(hw, &frequency); + if (ret < 0) { + dev_err(&spi->dev, "%s: sx127x_get_frequency() failed: %d\n", __func__, ret); + goto fail_init; + } + + dev_dbg(&spi->dev, "%s: frequency sanity test: %lu Hz\n", __func__, frequency); + + ret = sx127x_set_tx_power(hw, 17); // 17 dBm + if (ret < 0) { + dev_err(&spi->dev, "%s: sx127x_set_tx_power() failed: %d\n", __func__, ret); + goto fail_init; + } + + ret = ieee802154_register_hw(ieee); + if (ret < 0) + dev_err(&spi->dev, "ieee802154_register_hw() failed: %d\n", ret); + + return ret; + +fail_init: + ieee802154_free_hw(ieee); + return ret; +} + +#ifdef SX127X_LEGACY_KERNEL +static int sx127x_spi_remove(struct spi_device *spi) +#else +static void sx127x_spi_remove(struct spi_device *spi) +#endif +{ + struct sx127x *hw = spi_get_drvdata(spi); + + dev_dbg(&hw->spi->dev, "%s bye\n", __func__); + + ieee802154_unregister_hw(hw->ieee); + ieee802154_free_hw(hw->ieee); + +#ifdef SX127X_LEGACY_KERNEL + return 0; +#endif +} + +static const struct of_device_id sx127x_of_ids[] = { + { .compatible = "semtech,sx1276" }, + { .compatible = "semtech,sx1277" }, + { .compatible = "semtech,sx1278" }, + { .compatible = "semtech,sx1279" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sx127x_of_ids); + +static const struct spi_device_id sx127x_spi_ids[] = { + { .name = "sx1276" }, + { .name = "sx1277" }, + { .name = "sx1278" }, + { .name = "sx1279" }, + {}, +}; +MODULE_DEVICE_TABLE(spi, sx127x_spi_ids); + +static struct spi_driver sx127x_spi_driver = { + .driver = { + .name = "sx127x", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(sx127x_of_ids), + }, + .probe = sx127x_spi_probe, + .remove = sx127x_spi_remove, + .id_table = sx127x_spi_ids, +}; + +module_spi_driver(sx127x_spi_driver); + +MODULE_AUTHOR("Jian-Hong Pan, "); +MODULE_AUTHOR("Fabian Montero "); +MODULE_DESCRIPTION("Semtech SX127x LoRa PHY driver with IEEE 802.15.4 MAC interface"); +MODULE_LICENSE("GPL"); diff --git a/lora_sx127_rpi4.c b/lora_sx127_rpi4.c new file mode 100644 index 0000000..f0ed080 --- /dev/null +++ b/lora_sx127_rpi4.c @@ -0,0 +1,829 @@ +/*- + * Copyright (c) 2017 Jian-Hong, Pan + * Copyright (c) 2024 Fabian Montero + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGES. + * + */ + +#define DEBUG +#define SX127X_LEGACY_KERNEL + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef F_XOSC +#define F_XOSC 32000000 +#endif +static u32 xosc_frq = F_XOSC; +module_param(xosc_frq, uint, 0000); +MODULE_PARM_DESC(xosc_frq, "Crystal oscillator frequency of the LoRa chip"); + +#define __POW_2_19 0x80000 + +#ifndef SX127X_SPRF +#define SX127X_SPRF 512 +#endif +static u32 sprf = SX127X_SPRF; +module_param(sprf, uint, 0000); +MODULE_PARM_DESC(sprf, "Spreading factor of Chirp Spread Spectrum modulation"); + +#ifndef SX127X_RX_BYTE_TIMEOUT +#define SX127X_RX_BYTE_TIMEOUT 1023 +#endif +static u32 rx_timeout = SX127X_RX_BYTE_TIMEOUT; +module_param(rx_timeout, uint, 0000); +MODULE_PARM_DESC(rx_timeout, "RX time-out value as number of symbols"); + +/* SX127X Registers addresses */ +#define SX127X_REG_FIFO 0x00 +#define SX127X_REG_OP_MODE 0x01 +#define SX127X_REG_FRF_MSB 0x06 +#define SX127X_REG_FRF_MID 0x07 +#define SX127X_REG_FRF_LSB 0x08 +#define SX127X_REG_PA_CONFIG 0x09 +#define SX127X_REG_PA_RAMP 0x0A +#define SX127X_REG_OCP 0x0B +#define SX127X_REG_LNA 0x0C +#define SX127X_REG_FIFO_ADDR_PTR 0x0D +#define SX127X_REG_FIFO_TX_BASE_ADDR 0x0E +#define SX127X_REG_FIFO_RX_BASE_ADDR 0x0F +#define SX127X_REG_FIFO_RX_CURRENT_ADDR 0x10 +#define SX127X_REG_IRQ_FLAGS_MASK 0x11 +#define SX127X_REG_IRQ_FLAGS 0x12 +#define SX127X_REG_RX_NB_BYTES 0x13 +#define SX127X_REG_RX_HEADER_CNT_VALUE_MSB 0x14 +#define SX127X_REG_RX_HEADER_CNT_VALUE_LSB 0x15 +#define SX127X_REG_RX_PACKET_CNT_VALUE_MSB 0x16 +#define SX127X_REG_RX_PACKET_CNT_VALUE_LSB 0x17 +#define SX127X_REG_MODEM_STAT 0x18 +#define SX127X_REG_PKT_SNR_VALUE 0x19 +#define SX127X_REG_PKT_RSSI_VALUE 0x1A +#define SX127X_REG_RSSI_VALUE 0x1B +#define SX127X_REG_HOP_CHANNEL 0x1C +#define SX127X_REG_MODEM_CONFIG1 0x1D +#define SX127X_REG_MODEM_CONFIG2 0x1E +#define SX127X_REG_SYMB_TIMEOUT_LSB 0x1F +#define SX127X_REG_PREAMBLE_MSB 0x20 +#define SX127X_REG_PREAMBLE_LSB 0x21 +#define SX127X_REG_PAYLOAD_LENGTH 0x22 +#define SX127X_REG_MAX_PAYLOAD_LENGTH 0x23 +#define SX127X_REG_HOP_PERIOD 0x24 +#define SX127X_REG_FIFO_RX_BYTE_ADDR 0x25 +#define SX127X_REG_MODEM_CONFIG3 0x26 +#define SX127X_REG_FEI_MSB 0x28 +#define SX127X_REG_FEI_MID 0x29 +#define SX127X_REG_FEI_LSB 0x2A +#define SX127X_REG_RSSI_WIDEBAND 0x2C +#define SX127X_REG_DETECT_OPTIMIZE 0x31 +#define SX127X_REG_INVERT_IRQ 0x33 +#define SX127X_REG_DETECTION_THRESHOLD 0x37 +#define SX127X_REG_SYNC_WORD 0x39 +#define SX127X_REG_VERSION 0x42 +#define SX127X_REG_TCXO 0x4B +#define SX127X_REG_PA_DAC 0x4D +#define SX127X_REG_FORMER_TEMP 0x5B +#define SX127X_REG_AGC_REF 0x61 +#define SX127X_REG_AGC_THRESH1 0x62 +#define SX127X_REG_AGC_THRESH2 0x63 +#define SX127X_REG_AGC_THRESH3 0x64 +#define SX127X_REG_PLL 0x70 +#define SX127X_MAX_REG SX127X_REG_PLL + +/* SX127X's operating states in LoRa mode */ +#define SX127X_MODE_MASK 0x07 +#define SX127X_SLEEP_MODE 0x00 +#define SX127X_STANDBY_MODE 0x01 +#define SX127X_FSTX_MODE 0x02 +#define SX127X_TX_MODE 0x03 +#define SX127X_FSRX_MODE 0x04 +#define SX127X_RXCONTINUOUS_MODE 0x05 +#define SX127X_RXSINGLE_MODE 0x06 +#define SX127X_CAD_MODE 0x07 +#define SX127X_LORA_MODE 0x80 + +/* SX127X_REG_OCP flags */ +#define SX127X_FLAG_OCPON 0x20 + +/* SX127X's IRQ flags in LoRa mode */ +#define SX127X_FLAG_RXTIMEOUT 0x80 +#define SX127X_FLAG_RXDONE 0x40 +#define SX127X_FLAG_PAYLOADCRCERROR 0x20 +#define SX127X_FLAG_VALIDHEADER 0x10 +#define SX127X_FLAG_TXDONE 0x08 +#define SX127X_FLAG_CADDONE 0x04 +#define SX127X_FLAG_FHSSCHANGECHANNEL 0x02 +#define SX127X_FLAG_CADDETECTED 0x01 + +/* SX127X's IRQ flags' mask for output pins in LoRa mode */ +#define SX127X_FLAGMASK_RXTIMEOUT 0x80 +#define SX127X_FLAGMASK_RXDONE 0x40 +#define SX127X_FLAGMASK_PAYLOADCRCERROR 0x20 +#define SX127X_FLAGMASK_VALIDHEADER 0x10 +#define SX127X_FLAGMASK_TXDONE 0x08 +#define SX127X_FLAGMASK_CADDONE 0x04 +#define SX127X_FLAGMASK_FHSSCHANGECHANNEL 0x02 +#define SX127X_FLAGMASK_CADDETECTED 0x01 + +#define SX127X_MODEM_CONFIG1_HEADER_MODE_MASK 0x01 +#define SX127X_MODEM_CONFIG1_HEADER_MODE_EXPLICIT 0x00 +#define SX127X_MODEM_CONFIG1_HEADER_MODE_IMPLICIT 0x01 + +#define SX127X_MODEM_CONFIG2_RXPAYLOADCRCON 0x04 + +#define SX127X_MODEM_CONFIG3_AGCAUTOON 0x04 + +/* SX127X's RX/TX FIFO base address */ +#define SX127X_FIFO_RX_BASE_ADDRESS 0x00 +#define SX127X_FIFO_TX_BASE_ADDRESS 0x80 + +#define POLL_JIFFIES (msecs_to_jiffies(10)) + +struct sx127x { + struct regmap *regmap; + struct ieee802154_hw *ieee; + struct spi_device *spi; + + spinlock_t lock; + bool busy; + struct sk_buff *tx_skb; + size_t delayed_tx_length; + + struct timer_list poll_timer; + struct work_struct poll_work; + struct work_struct tx_work; + + u32 oscillator_freq; +}; + +static int sx127x_reg_read(struct sx127x *hw, unsigned int address, u8 *data) +{ + int ret = regmap_raw_read(hw->regmap, address, data, 1); + if (ret < 0) + dev_err(&hw->spi->dev, "regmap_raw_read(0x%x) failed: %d\n", address, ret); + + return ret; +} + +static int sx127x_reg_write(struct sx127x *hw, unsigned int address, u8 data) +{ + int ret = regmap_raw_write(hw->regmap, address, &data, 1); + if (ret < 0) + dev_err(&hw->spi->dev, "regmap_raw_write(0x%x) failed: %d\n", address, ret); + + return ret; +} + +static int sx127x_mode(struct sx127x *hw, unsigned int mode) +{ + return sx127x_reg_write(hw, SX127X_REG_OP_MODE, SX127X_LORA_MODE | mode); +} + +static int sx127x_set_frequency(struct sx127x *hw, unsigned long frequency) +{ + u8 msb_mid_lsb[3]; + int i; + u64 frf; + + frf = (((u64) frequency) << 19) / hw->oscillator_freq; + for (i = sizeof msb_mid_lsb - 1; i >= 0; --i) { + msb_mid_lsb[i] = (frf & 0xff); + frf >>= 8; + } + + return regmap_raw_write(hw->regmap, SX127X_REG_FRF_MSB, msb_mid_lsb, sizeof msb_mid_lsb); +} + +static int sx127x_get_frequency(struct sx127x *hw, unsigned long *frequency) +{ + u8 msb_mid_lsb[3]; + int i, ret; + u64 frt; + + ret = regmap_raw_read(hw->regmap, SX127X_REG_FRF_MSB, msb_mid_lsb, sizeof msb_mid_lsb); + if (ret < 0) + return ret; + + frt = 0; + for (i = 0; i < sizeof msb_mid_lsb; ++i) + frt = (frt << 8) | msb_mid_lsb[i]; + + *frequency = (frt * hw->oscillator_freq) >> 19; + return 0; +} + +static int sx127x_set_ocp(struct sx127x *hw, u8 current_ma) +{ + u8 ocp_trim; + + /* P. 109 + * + * Trimming of OCP current: + * Imax = 45+5*OcpTrim [mA] if OcpTrim <= 15 (120 mA) / + * Imax = -30+10*OcpTrim [mA] if 15 < OcpTrim <= 27 (130 to + * 240 mA) + * Imax = 240mA for higher settings + * Default Imax = 100mA + */ + if (current_ma <= 45) + ocp_trim = 0; + else if (current_ma <= 120) + ocp_trim = (current_ma - 45) / 5; + else if (current_ma <= 240) + ocp_trim = (current_ma + 30) / 10; + else + ocp_trim = 28; + + return sx127x_reg_write(hw, SX127X_REG_OCP, SX127X_FLAG_OCPON | ocp_trim); +} + +static int sx127x_set_tx_power(struct sx127x *hw, int level) +{ + int ret; + + // PA BOOST + if (level > 17) { + if (level > 20) + level = 20; + + // subtract 3 from level, so 18 - 20 maps to 15 - 17 + level -= 3; + + // High Power +20 dBm Operation (Semtech SX1276/77/78/79 5.4.3.) + ret = sx127x_reg_write(hw, SX127X_REG_PA_DAC, 0x87); + if (ret < 0) + return ret; + + ret = sx127x_set_ocp(hw, 140); + if (ret < 0) + return ret; + } else { + if (level < 2) + level = 2; + + // Default value PA_HF/LF or +17dBm + ret = sx127x_reg_write(hw, SX127X_REG_PA_DAC, 0x84); + if (ret < 0) + return ret; + + ret = sx127x_set_ocp(hw, 100); + if (ret < 0) + return ret; + } + + // PA_CONFIG +#define PA_BOOST 0x80 + return sx127x_reg_write(hw, SX127X_REG_PA_CONFIG, PA_BOOST | (level - 2)); +} + +static int sx127x_init(struct sx127x *hw, u8 *major, u8 *minor) +{ + u8 modem_config1, modem_config2, lna, version; + int ret; + + ret = sx127x_reg_read(hw, SX127X_REG_VERSION, &version); + if (ret < 0) + return ret; + + if (version != 0x12) { + dev_err(&hw->spi->dev, "%s: bad version 0x%02x\n", __func__, version); + return -ENODEV; + } + + *major = (version >> 4) & 0xf; + *minor = (version >> 0) & 0xf; + + ret = sx127x_mode(hw, SX127X_SLEEP_MODE); + if (ret < 0) { + dev_err(&hw->spi->dev, "%s: sx127x_mode() failed: %d\n", __func__, ret); + return ret; + } + + ret = sx127x_reg_write(hw, SX127X_REG_FIFO_RX_BASE_ADDR, SX127X_FIFO_RX_BASE_ADDRESS); + if (ret < 0) + return ret; + + ret = sx127x_reg_write(hw, SX127X_REG_FIFO_TX_BASE_ADDR, SX127X_FIFO_TX_BASE_ADDRESS); + if (ret < 0) + return ret; + + ret = sx127x_reg_read(hw, SX127X_REG_LNA, &lna); + if (ret < 0) + return ret; + + lna |= 0x3; // ???, pero lo hace el sketch + ret = sx127x_reg_write(hw, SX127X_REG_LNA, lna); + if (ret < 0) + return ret; + + ret = sx127x_reg_read(hw, SX127X_REG_MODEM_CONFIG1, &modem_config1); + if (ret < 0) + return ret; + + ret = sx127x_reg_read(hw, SX127X_REG_MODEM_CONFIG2, &modem_config2); + if (ret < 0) + return ret; + + modem_config1 &= ~SX127X_MODEM_CONFIG1_HEADER_MODE_MASK; + modem_config1 |= SX127X_MODEM_CONFIG1_HEADER_MODE_EXPLICIT; + modem_config2 |= SX127X_MODEM_CONFIG2_RXPAYLOADCRCON; + + ret = sx127x_reg_write(hw, SX127X_REG_MODEM_CONFIG1, modem_config1); + if (ret < 0) + return ret; + + ret = sx127x_reg_write(hw, SX127X_REG_MODEM_CONFIG2, modem_config2); + if (ret < 0) + return ret; + + ret = sx127x_reg_write(hw, SX127X_REG_MODEM_CONFIG3, SX127X_MODEM_CONFIG3_AGCAUTOON); + if (ret < 0) + return ret; + + return 0; +} + +static void sx127x_tx_work(struct work_struct *work) +{ + u8 mode; + int ret = 0; + bool set_busy = false; + size_t i, size; + const u8 *buffer; + struct sx127x *hw = container_of(work, struct sx127x, tx_work); + struct sk_buff *tx_skb; + unsigned long spin_irq; + + spin_lock_irqsave(&hw->lock, spin_irq); + + tx_skb = hw->tx_skb; + if (!hw->busy && !hw->delayed_tx_length) { + hw->busy = true; + set_busy = true; + } + + spin_unlock_irqrestore(&hw->lock, spin_irq); + + if (!set_busy) { + ret = -EBUSY; + goto exit_busy; + } else if (!tx_skb) { + ret = -EINVAL; + goto exit_busy; + } + + size = tx_skb->len; + buffer = tx_skb->data; + + if (!size || size > 128) { + ret = -EINVAL; + goto exit_busy; + } + + ret = sx127x_reg_read(hw, SX127X_REG_OP_MODE, &mode); + if (ret < 0) + goto exit_busy; + + if ((mode & SX127X_MODE_MASK) == SX127X_TX_MODE) { + ret = -EBUSY; + goto exit_busy; + } + + // reset FIFO address and payload length + ret = sx127x_reg_write(hw, SX127X_REG_FIFO_ADDR_PTR, SX127X_FIFO_TX_BASE_ADDRESS); + if (ret < 0) + goto exit_busy; + + // write data + for (i = 0; i < size; i++) { + ret = sx127x_reg_write(hw, SX127X_REG_FIFO, buffer[i]); + if (ret < 0) + goto exit_busy; + } + + ret = sx127x_reg_write(hw, SX127X_REG_PAYLOAD_LENGTH, (u8) size); + if (ret < 0) + goto exit_busy; + + ret = sx127x_reg_read(hw, SX127X_REG_OP_MODE, &mode); + if (ret < 0) + goto exit_busy; + + if ((mode & SX127X_MODE_MASK) == SX127X_STANDBY_MODE + || (mode & SX127X_MODE_MASK) == SX127X_RXCONTINUOUS_MODE) + ret = sx127x_mode(hw, SX127X_TX_MODE); + else + hw->delayed_tx_length = size; + +exit_busy: + spin_lock_irqsave(&hw->lock, spin_irq); + if (set_busy) + hw->busy = false; + + if (tx_skb) + hw->tx_skb = NULL; + spin_unlock_irqrestore(&hw->lock, spin_irq); + + if (tx_skb) { +#ifndef SX127X_LEGACY_KERNEL + if (ret) + ieee802154_xmit_error(hw->ieee, tx_skb, ret); + else +#endif + ieee802154_xmit_complete(hw->ieee, tx_skb, false); + } + + (void) ret; +} + +static void sx127x_poll_work(struct work_struct *work) +{ + u8 ack_flags, lqi, mode, read_flags, rx_base, *rx_data, rx_length; + bool do_rx = false, was_busy = false; + unsigned long spin_irq; + struct sx127x *hw = container_of(work, struct sx127x, poll_work); + struct sk_buff *rx_skb = NULL; + + spin_lock_irqsave(&hw->lock, spin_irq); + if (!hw->busy) + hw->busy = true; + else + was_busy = true; + spin_unlock_irqrestore(&hw->lock, spin_irq); + + if (was_busy) + return; + + if (sx127x_reg_read(hw, SX127X_REG_IRQ_FLAGS, &read_flags) < 0) + goto exit_busy; + + ack_flags = 0; + + if (read_flags & SX127X_FLAG_TXDONE) + ack_flags |= SX127X_FLAG_TXDONE; + + if (read_flags & SX127X_FLAG_RXDONE) { + do_rx = true; + ack_flags |= SX127X_FLAG_RXDONE; + + if (read_flags & SX127X_FLAG_VALIDHEADER) + ack_flags |= SX127X_FLAG_VALIDHEADER; + else + do_rx = false; + + if (read_flags & SX127X_FLAG_PAYLOADCRCERROR) { + do_rx = false; + ack_flags |= SX127X_FLAG_PAYLOADCRCERROR; + } + } + + if (do_rx) { + if (sx127x_reg_read(hw, SX127X_REG_FIFO_RX_CURRENT_ADDR, &rx_base) < 0 + || sx127x_reg_read(hw, SX127X_REG_RX_NB_BYTES, &rx_length) < 0) + goto exit_busy; + + if (sx127x_reg_write(hw, SX127X_REG_FIFO_ADDR_PTR, rx_base) < 0) + goto exit_busy; + + rx_skb = dev_alloc_skb(IEEE802154_MTU); + if (!rx_skb) + goto exit_busy; + + if (rx_length > IEEE802154_MTU) + rx_length = IEEE802154_MTU; + + rx_data = skb_put(rx_skb, rx_length); + while (rx_length--) + if (sx127x_reg_read(hw, SX127X_REG_FIFO, rx_data++) < 0) + goto exit_busy; + + if (sx127x_reg_write(hw, SX127X_REG_FIFO_RX_BASE_ADDR, SX127X_FIFO_RX_BASE_ADDRESS) < 0) + goto exit_busy; + } + + if (sx127x_reg_write(hw, SX127X_REG_IRQ_FLAGS, ack_flags) < 0) + goto exit_busy; + + if (sx127x_reg_read(hw, SX127X_REG_OP_MODE, &mode) < 0) + goto exit_busy; + + if ((mode & SX127X_MODE_MASK) != SX127X_TX_MODE) { + if (hw->delayed_tx_length) { + sx127x_mode(hw, SX127X_TX_MODE); + hw->delayed_tx_length = 0; + } else if ((mode & SX127X_MODE_MASK) != SX127X_RXCONTINUOUS_MODE) + sx127x_mode(hw, SX127X_RXCONTINUOUS_MODE); + } + + if (rx_skb) { + lqi = 69; //TODO + ieee802154_rx_irqsafe(hw->ieee, rx_skb, lqi); + rx_skb = NULL; // rx_skb ownership transferred to the mac802154 layer + } + +exit_busy: + if (rx_skb) + kfree_skb(rx_skb); + + spin_lock_irqsave(&hw->lock, spin_irq); + hw->busy = false; + spin_unlock_irqrestore(&hw->lock, spin_irq); +} + +static void sx127x_poll_timer(struct timer_list *timer) +{ + struct sx127x *hw = container_of(timer, struct sx127x, poll_timer); + schedule_work(&hw->poll_work); + mod_timer(&hw->poll_timer, jiffies + POLL_JIFFIES); +} + +static int sx127x_802154_xmit_async(struct ieee802154_hw *ieee, struct sk_buff *skb) +{ + int ret = 0; + struct sx127x *hw = ieee->priv; + unsigned long spin_irq; + + spin_lock_irqsave(&hw->lock, spin_irq); + if (!hw->tx_skb) + hw->tx_skb = skb_get(skb); + else + ret = -EBUSY; + spin_unlock_irqrestore(&hw->lock, spin_irq); + + schedule_work(&hw->tx_work); + return ret; +} + +static int sx127x_802154_ed(struct ieee802154_hw *ieee, u8 *level) +{ + struct sx127x *hw = ieee->priv; + (void) hw; + return -ENOSYS; +} + +static int sx127x_802154_set_channel(struct ieee802154_hw *ieee, u8 page, u8 channel) +{ + struct sx127x *hw = ieee->priv; + (void) hw; + return -ENOSYS; +} + +static int sx127x_802154_set_txpower(struct ieee802154_hw *ieee, s32 mbm) +{ + struct sx127x *hw = ieee->priv; + (void) hw; + return -ENOSYS; +} + +static int sx127x_802154_start(struct ieee802154_hw *ieee) +{ + int ret; + struct sx127x *hw = ieee->priv; + + ret = sx127x_mode(hw, SX127X_STANDBY_MODE); + if (ret < 0) { + dev_err(&hw->spi->dev, "%s: sx127x_mode() failed: %d\n", __func__, ret); + return ret; + } + + mod_timer(&hw->poll_timer, jiffies + POLL_JIFFIES); + return 0; +} + +static void sx127x_802154_stop(struct ieee802154_hw *ieee) +{ + struct sx127x *hw = ieee->priv; + + del_timer_sync(&hw->poll_timer); + cancel_work_sync(&hw->poll_work); + + (void) sx127x_mode(hw, SX127X_SLEEP_MODE); +} + +static int sx127x_802154_set_promiscuous_mode(struct ieee802154_hw *ieee, const bool on) +{ + return 0; +} + +static const struct ieee802154_ops sx127x_802154_ops = { + .owner = THIS_MODULE, + .xmit_async = sx127x_802154_xmit_async, + .ed = sx127x_802154_ed, + .set_channel = sx127x_802154_set_channel, + .set_txpower = sx127x_802154_set_txpower, + .start = sx127x_802154_start, + .stop = sx127x_802154_stop, + .set_promiscuous_mode = sx127x_802154_set_promiscuous_mode, +}; + +static bool sx127x_reg_volatile(struct device *dev, unsigned int reg) +{ + return true; +} + +static const struct regmap_config sx127x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = SX127X_MAX_REG, + .read_flag_mask = 0x00, + .write_flag_mask = 0x80, + .volatile_reg = sx127x_reg_volatile, +}; + +/* in mbm */ +static const s32 sx127x_tx_powers[] = { + -200, -100, 0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, + 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900, 2000, 2100, 2200, 2300}; + +static int sx127x_spi_probe(struct spi_device *spi) +{ + u8 major, minor; + int ret = 0; + struct sx127x *hw; + unsigned long frequency; + struct ieee802154_hw *ieee; + struct device_node *of_node; + + dev_dbg(&spi->dev, "%s hello\n", __func__); + + ieee = ieee802154_alloc_hw(sizeof *hw, &sx127x_802154_ops); + if (!ieee) { + dev_err(&spi->dev, "ieee802154_alloc_hw(): out of memory\n"); + return -ENOMEM; + } + + hw = ieee->priv; + hw->spi = spi; + hw->ieee = ieee; + hw->busy = false; + hw->delayed_tx_length = 0; + spin_lock_init(&hw->lock); + + INIT_WORK(&hw->tx_work, sx127x_tx_work); + INIT_WORK(&hw->poll_work, sx127x_poll_work); + timer_setup(&hw->poll_timer, sx127x_poll_timer, 0); + + hw->regmap = devm_regmap_init_spi(spi, &sx127x_regmap_config); + if (IS_ERR(hw->regmap)) { + dev_err(&spi->dev, "%s: devm_regmap_init_spi() failed: %d\n", __func__, ret); + return PTR_ERR(hw->regmap); + } + + of_node = regmap_get_device(hw->regmap)->of_node; + if (of_property_read_u32(of_node, "clock-frequency", &hw->oscillator_freq)) { + dev_err(&spi->dev, "%s: 'clock-frequency' missing from DT node\n", __func__); + return -EINVAL; + } + + ieee->parent = &spi->dev; + spi_set_drvdata(spi, hw); + + //TODO: pin de reset + + ieee802154_random_extended_addr(&ieee->phy->perm_extended_addr); + + ieee->flags = IEEE802154_HW_TX_OMIT_CKSUM + | IEEE802154_HW_RX_OMIT_CKSUM + | IEEE802154_HW_PROMISCUOUS; + + ieee->phy->flags = WPAN_PHY_FLAG_TXPOWER; + ieee->phy->transmit_power = sx127x_tx_powers[12]; + ieee->phy->supported.tx_powers = sx127x_tx_powers; + ieee->phy->supported.tx_powers_size = ARRAY_SIZE(sx127x_tx_powers); + + /* SX1278 phy channel 11 as default */ + ieee->phy->current_channel = 11; + /* Define channels could be used. */ + ieee->phy->supported.channels[0] = 1 << 11; /*TODO: sx1278_ieee_channel_mask(hw);*/ + + ret = sx127x_init(hw, &major, &minor); + if (ret < 0) { + dev_err(&spi->dev, "%s: sx127x_init() failed: %d\n", __func__, ret); + goto fail_init; + } + + dev_info(&spi->dev, "IEEE 802.15.4 over LoRa SX127x compatible device v%u.%u\n", major, minor); + + // Valor viene del sketch + ret = sx127x_set_frequency(hw, 434000000); + if (ret < 0) { + dev_err(&spi->dev, "%s: sx127x_set_frequency() failed: %d\n", __func__, ret); + goto fail_init; + } + + ret = sx127x_get_frequency(hw, &frequency); + if (ret < 0) { + dev_err(&spi->dev, "%s: sx127x_get_frequency() failed: %d\n", __func__, ret); + goto fail_init; + } + + dev_dbg(&spi->dev, "%s: frequency sanity test: %lu Hz\n", __func__, frequency); + + ret = sx127x_set_tx_power(hw, 17); // 17 dBm + if (ret < 0) { + dev_err(&spi->dev, "%s: sx127x_set_tx_power() failed: %d\n", __func__, ret); + goto fail_init; + } + + ret = ieee802154_register_hw(ieee); + if (ret < 0) + dev_err(&spi->dev, "ieee802154_register_hw() failed: %d\n", ret); + + return ret; + +fail_init: + ieee802154_free_hw(ieee); + return ret; +} + +#ifdef SX127X_LEGACY_KERNEL +static int sx127x_spi_remove(struct spi_device *spi) +#else +static void sx127x_spi_remove(struct spi_device *spi) +#endif +{ + struct sx127x *hw = spi_get_drvdata(spi); + + dev_dbg(&hw->spi->dev, "%s bye\n", __func__); + + ieee802154_unregister_hw(hw->ieee); + ieee802154_free_hw(hw->ieee); + +#ifdef SX127X_LEGACY_KERNEL + return 0; +#endif +} + +static const struct of_device_id sx127x_of_ids[] = { + { .compatible = "semtech,sx1276" }, + { .compatible = "semtech,sx1277" }, + { .compatible = "semtech,sx1278" }, + { .compatible = "semtech,sx1279" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sx127x_of_ids); + +static const struct spi_device_id sx127x_spi_ids[] = { + { .name = "sx1276" }, + { .name = "sx1277" }, + { .name = "sx1278" }, + { .name = "sx1279" }, + {}, +}; +MODULE_DEVICE_TABLE(spi, sx127x_spi_ids); + +static struct spi_driver sx127x_spi_driver = { + .driver = { + .name = "sx127x", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(sx127x_of_ids), + }, + .probe = sx127x_spi_probe, + .remove = sx127x_spi_remove, + .id_table = sx127x_spi_ids, +}; + +module_spi_driver(sx127x_spi_driver); + +MODULE_AUTHOR("Jian-Hong Pan, "); +MODULE_AUTHOR("Fabian Montero "); +MODULE_DESCRIPTION("Semtech SX127x LoRa PHY driver with IEEE 802.15.4 MAC interface"); +MODULE_LICENSE("GPL"); diff --git a/setup-lora_pi3.c b/setup-lora_pi3.c new file mode 100755 index 0000000..a52c732 --- /dev/null +++ b/setup-lora_pi3.c @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e + +modprobe regmap_spi +modprobe mac802154 +insmod /home/loro/LoRo/LoRo/loro.ko +sleep 1 +ip link set down wpan0 +iwpan dev wpan0 set pan_id 0xbeef +ip link add link wpan0 name lowpan0 type lowpan +ip link set up wpan0 +ip link set up lowpan0 diff --git a/setup-lora_pi4.c b/setup-lora_pi4.c new file mode 100755 index 0000000..555797a --- /dev/null +++ b/setup-lora_pi4.c @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e + +modprobe regmap_spi +modprobe mac802154 +insmod ~/LoRo/LoRo/loro.ko +sleep 0.5 +ip link set down wpan0 +iwpan dev wpan0 set pan_id 0xbeef +ip link add link wpan0 name lowpan0 type lowpan +ip link set up wpan0 +ip link set up lowpan0