835 lines
22 KiB
C
835 lines
22 KiB
C
/*-
|
|
* Copyright (c) 2017 Jian-Hong, Pan <starnight@g.ncu.edu.tw>
|
|
* Copyright (c) 2024 Fabian Montero <fabian@posixlycorrect.com>
|
|
*
|
|
* 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 SX127X_LEGACY_KERNEL
|
|
|
|
#define DEBUG
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/device.h>
|
|
#include <linux/acpi.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/spi/spi.h>
|
|
#include <linux/regmap.h>
|
|
#include <net/mac802154.h>
|
|
|
|
static unsigned tx_poll_div = 10;
|
|
module_param(tx_poll_div, uint, 0400);
|
|
MODULE_PARM_DESC(tx_poll_div, "Number of RX poll cycles to perform before attempting a single TX op");
|
|
|
|
/* 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;
|
|
unsigned tx_wait;
|
|
|
|
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_TX_BASE_ADDR, SX127X_FIFO_TX_BASE_ADDRESS);
|
|
if (ret < 0)
|
|
goto exit_busy;
|
|
|
|
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;
|
|
|
|
hw->delayed_tx_length = size;
|
|
|
|
exit_busy:
|
|
spin_lock_irqsave(&hw->lock, spin_irq);
|
|
if (set_busy)
|
|
hw->busy = false;
|
|
|
|
if (ret && tx_skb)
|
|
hw->tx_skb = NULL;
|
|
spin_unlock_irqrestore(&hw->lock, spin_irq);
|
|
|
|
if (ret && tx_skb) {
|
|
#ifdef SX127X_LEGACY_KERNEL
|
|
ieee802154_xmit_complete(hw->ieee, tx_skb, false);
|
|
#else
|
|
ieee802154_xmit_error(hw->ieee, tx_skb, ret);
|
|
#endif
|
|
}
|
|
|
|
(void) ret;
|
|
}
|
|
|
|
static void sx127x_poll_work(struct work_struct *work)
|
|
{
|
|
u8 ack_flags, lqi, mode, read_flags, rx_base, *rx_data, rx_length = 0;
|
|
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, *tx_skb = NULL;
|
|
|
|
spin_lock_irqsave(&hw->lock, spin_irq);
|
|
if (!hw->busy)
|
|
hw->busy = true;
|
|
else
|
|
was_busy = true;
|
|
|
|
tx_skb = hw->tx_skb;
|
|
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;
|
|
else
|
|
tx_skb = NULL;
|
|
|
|
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;
|
|
|
|
if (rx_length <= IEEE802154_MTU) {
|
|
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;
|
|
} else
|
|
dev_err(&hw->spi->dev, "%s: rx_length overflow: %u > IEEE802154_MTU\n", __func__, rx_length);
|
|
|
|
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->tx_wait >= tx_poll_div)
|
|
hw->tx_wait = 0;
|
|
|
|
if (hw->delayed_tx_length && hw->tx_wait == 0) {
|
|
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;
|
|
if (tx_skb)
|
|
hw->tx_skb = NULL;
|
|
spin_unlock_irqrestore(&hw->lock, spin_irq);
|
|
|
|
if (tx_skb)
|
|
ieee802154_xmit_complete(hw->ieee, tx_skb, false);
|
|
}
|
|
|
|
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;
|
|
else
|
|
ret = -EBUSY;
|
|
spin_unlock_irqrestore(&hw->lock, spin_irq);
|
|
|
|
if (!ret)
|
|
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->tx_wait = 0;
|
|
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;
|
|
/*u8 buffer[] = { 0x36, 0x39 }; // 69?
|
|
ret = sx127x_mode(hw, SX127X_STANDBY_MODE);
|
|
if (ret < 0) {
|
|
dev_err(&spi->dev, "%s: sx127x_mode() failed: %d\n", __func__, ret);
|
|
goto fail_begin;
|
|
}
|
|
|
|
ret = sx127x_begin_packet(hw);
|
|
if (ret < 0) {
|
|
dev_err(&spi->dev, "%s: sx127x_begin_packet() failed: %d\n", __func__, ret);
|
|
goto fail_begin;
|
|
}
|
|
|
|
ret = sx127x_write_buffer(hw, buffer, sizeof buffer);
|
|
if (ret < 0) {
|
|
dev_err(&spi->dev, "%s: sx127x_send() failed: %d\n", __func__, ret);
|
|
goto fail_begin;
|
|
}
|
|
|
|
ret = sx127x_end_packet(hw, 0);
|
|
if (ret < 0) {
|
|
dev_err(&spi->dev, "%s: sx127x_end_packet() failed: %d\n", __func__, ret);
|
|
goto fail_begin;
|
|
}*/
|
|
|
|
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, <starnight@g.ncu.edu.tw>");
|
|
MODULE_AUTHOR("Fabian Montero <fabian@posixlycorrect.com>");
|
|
MODULE_DESCRIPTION("Semtech SX127x LoRa PHY driver with IEEE 802.15.4 MAC interface");
|
|
MODULE_LICENSE("GPL");
|