All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Andreas Färber" <afaerber@suse.de>
To: netdev@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org,
	"Jian-Hong Pan" <starnight@g.ncu.edu.tw>,
	"Jiri Pirko" <jiri@resnulli.us>,
	"Marcel Holtmann" <marcel@holtmann.org>,
	"David S . Miller" <davem@davemloft.net>,
	"Matthias Brugger" <mbrugger@suse.com>,
	"Janus Piwek" <jpiwek@arroweurope.com>,
	"Michael Röder" <michael.roeder@avnet.eu>,
	"Dollar Chen" <dollar.chen@wtmec.com>,
	"Ken Yu" <ken.yu@rakwireless.com>,
	"Andreas Färber" <afaerber@suse.de>
Subject: [RFC net-next 07/15] net: lora: Add Semtech SX1276
Date: Sun,  1 Jul 2018 13:07:56 +0200	[thread overview]
Message-ID: <20180701110804.32415-8-afaerber@suse.de> (raw)
In-Reply-To: <20180701110804.32415-1-afaerber@suse.de>

Semtech SX1276/77/78/79 and SX1272/73 are LoRa transceivers with a SPI
interface. They also offer a non-LoRa mode (not exposed here).

Signed-off-by: Andreas Färber <afaerber@suse.de>
---
 drivers/net/lora/Kconfig  |  11 +
 drivers/net/lora/Makefile |   3 +
 drivers/net/lora/sx1276.c | 608 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 622 insertions(+)
 create mode 100644 drivers/net/lora/sx1276.c

diff --git a/drivers/net/lora/Kconfig b/drivers/net/lora/Kconfig
index 40969b148a50..0436f6b09a1c 100644
--- a/drivers/net/lora/Kconfig
+++ b/drivers/net/lora/Kconfig
@@ -15,4 +15,15 @@ config LORA_DEV
 # Alphabetically sorted.
 #
 
+if LORA_DEV
+
+config LORA_SX1276
+	tristate "Semtech SX127x SPI driver"
+	default y
+	depends on SPI
+	help
+	  Semtech SX1272/1276/1278
+
+endif
+
 endmenu
diff --git a/drivers/net/lora/Makefile b/drivers/net/lora/Makefile
index 8f9d25ea4e70..8845542dba50 100644
--- a/drivers/net/lora/Makefile
+++ b/drivers/net/lora/Makefile
@@ -8,3 +8,6 @@ lora-dev-y := dev.o
 #
 # Alphabetically sorted.
 #
+
+obj-$(CONFIG_LORA_SX1276) += lora-sx1276.o
+lora-sx1276-y := sx1276.o
diff --git a/drivers/net/lora/sx1276.c b/drivers/net/lora/sx1276.c
new file mode 100644
index 000000000000..d6732111247a
--- /dev/null
+++ b/drivers/net/lora/sx1276.c
@@ -0,0 +1,608 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Semtech SX1272/SX1276 LoRa transceiver
+ *
+ * Copyright (c) 2016-2018 Andreas Färber
+ */
+
+#include <linux/delay.h>
+#include <linux/lora.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/lora/dev.h>
+#include <linux/spi/spi.h>
+
+#define REG_FIFO			0x00
+#define REG_OPMODE			0x01
+#define REG_FRF_MSB			0x06
+#define REG_FRF_MID			0x07
+#define REG_FRF_LSB			0x08
+#define REG_PA_CONFIG			0x09
+#define LORA_REG_FIFO_ADDR_PTR		0x0d
+#define LORA_REG_FIFO_TX_BASE_ADDR	0x0e
+#define LORA_REG_IRQ_FLAGS_MASK		0x11
+#define LORA_REG_IRQ_FLAGS		0x12
+#define LORA_REG_PAYLOAD_LENGTH		0x22
+#define LORA_REG_SYNC_WORD		0x39
+#define REG_DIO_MAPPING1		0x40
+#define REG_DIO_MAPPING2		0x41
+#define REG_VERSION			0x42
+#define REG_PA_DAC			0x4d
+
+#define REG_OPMODE_LONG_RANGE_MODE		BIT(7)
+#define REG_OPMODE_LOW_FREQUENCY_MODE_ON	BIT(3)
+#define REG_OPMODE_MODE_MASK			GENMASK(2, 0)
+#define REG_OPMODE_MODE_SLEEP			(0x0 << 0)
+#define REG_OPMODE_MODE_STDBY			(0x1 << 0)
+#define REG_OPMODE_MODE_TX			(0x3 << 0)
+#define REG_OPMODE_MODE_RXCONTINUOUS		(0x5 << 0)
+#define REG_OPMODE_MODE_RXSINGLE		(0x6 << 0)
+
+#define REG_PA_CONFIG_PA_SELECT			BIT(7)
+
+#define LORA_REG_IRQ_FLAGS_TX_DONE		BIT(3)
+
+#define REG_DIO_MAPPING1_DIO0_MASK	GENMASK(7, 6)
+
+struct sx1276_priv {
+	struct lora_priv lora;
+	struct spi_device *spi;
+
+	size_t fifosize;
+	int dio_gpio[6];
+
+	struct mutex spi_lock;
+
+	struct sk_buff *tx_skb;
+	int tx_len;
+
+	struct workqueue_struct *wq;
+	struct work_struct tx_work;
+};
+
+static int sx1276_read_single(struct spi_device *spi, u8 reg, u8 *val)
+{
+	u8 addr = reg & 0x7f;
+	return spi_write_then_read(spi, &addr, 1, val, 1);
+}
+
+static int sx1276_write_single(struct spi_device *spi, u8 reg, u8 val)
+{
+	u8 buf[2];
+
+	buf[0] = reg | BIT(7);
+	buf[1] = val;
+	return spi_write(spi, buf, 2);
+}
+
+static int sx1276_write_burst(struct spi_device *spi, u8 reg, size_t len, void *val)
+{
+	u8 buf = reg | BIT(7);
+	struct spi_transfer xfers[2] = {
+		[0] = {
+			.tx_buf = &buf,
+			.len = 1,
+		},
+		[1] = {
+			.tx_buf = val,
+			.len = len,
+		},
+	};
+
+	return spi_sync_transfer(spi, xfers, 2);
+}
+
+static int sx1276_write_fifo(struct spi_device *spi, size_t len, void *val)
+{
+	return sx1276_write_burst(spi, REG_FIFO, len, val);
+}
+
+static netdev_tx_t sx1276_loradev_start_xmit(struct sk_buff *skb, struct net_device *netdev)
+{
+	struct sx1276_priv *priv = netdev_priv(netdev);
+
+	netdev_dbg(netdev, "%s\n", __func__);
+
+	if (priv->tx_skb || priv->tx_len) {
+		netdev_warn(netdev, "TX busy\n");
+		return NETDEV_TX_BUSY;
+	}
+
+	if (skb->protocol != htons(ETH_P_LORA)) {
+		kfree_skb(skb);
+		netdev->stats.tx_dropped++;
+		return NETDEV_TX_OK;
+	}
+
+	netif_stop_queue(netdev);
+	priv->tx_skb = skb;
+	queue_work(priv->wq, &priv->tx_work);
+
+	return NETDEV_TX_OK;
+}
+
+static int sx1276_tx(struct spi_device *spi, void *data, int data_len)
+{
+	u8 addr, val;
+	int ret;
+
+	dev_dbg(&spi->dev, "%s\n", __func__);
+
+	ret = sx1276_read_single(spi, REG_OPMODE, &val);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to read RegOpMode (%d)\n", ret);
+		return ret;
+	}
+	dev_dbg(&spi->dev, "RegOpMode = 0x%02x\n", val);
+	if (!(val & REG_OPMODE_LONG_RANGE_MODE))
+		dev_err(&spi->dev, "LongRange Mode not active!\n");
+	if ((val & REG_OPMODE_MODE_MASK) == REG_OPMODE_MODE_SLEEP)
+		dev_err(&spi->dev, "Cannot access FIFO in Sleep Mode!\n");
+
+	ret = sx1276_read_single(spi, LORA_REG_FIFO_TX_BASE_ADDR, &addr);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to read RegFifoTxBaseAddr (%d)\n", ret);
+		return ret;
+	}
+	dev_dbg(&spi->dev, "RegFifoTxBaseAddr = 0x%02x\n", addr);
+
+	ret = sx1276_write_single(spi, LORA_REG_FIFO_ADDR_PTR, addr);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to write RegFifoAddrPtr (%d)\n", ret);
+		return ret;
+	}
+
+	ret = sx1276_write_single(spi, LORA_REG_PAYLOAD_LENGTH, data_len);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to write RegPayloadLength (%d)\n", ret);
+		return ret;
+	}
+
+	ret = sx1276_write_fifo(spi, data_len, data);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to write into FIFO (%d)\n", ret);
+		return ret;
+	}
+
+	ret = sx1276_read_single(spi, LORA_REG_IRQ_FLAGS, &val);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to read RegIrqFlags (%d)\n", ret);
+		return ret;
+	}
+	dev_dbg(&spi->dev, "RegIrqFlags = 0x%02x\n", val);
+
+	ret = sx1276_write_single(spi, LORA_REG_IRQ_FLAGS, LORA_REG_IRQ_FLAGS_TX_DONE);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to write RegIrqFlags (%d)\n", ret);
+		return ret;
+	}
+
+	ret = sx1276_read_single(spi, LORA_REG_IRQ_FLAGS_MASK, &val);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to read RegIrqFlagsMask (%d)\n", ret);
+		return ret;
+	}
+	dev_dbg(&spi->dev, "RegIrqFlagsMask = 0x%02x\n", val);
+
+	val &= ~LORA_REG_IRQ_FLAGS_TX_DONE;
+	ret = sx1276_write_single(spi, LORA_REG_IRQ_FLAGS_MASK, val);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to write RegIrqFlagsMask (%d)\n", ret);
+		return ret;
+	}
+
+	ret = sx1276_read_single(spi, REG_DIO_MAPPING1, &val);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to read RegDioMapping1 (%d)\n", ret);
+		return ret;
+	}
+
+	val &= ~REG_DIO_MAPPING1_DIO0_MASK;
+	val |= 0x1 << 6;
+	ret = sx1276_write_single(spi, REG_DIO_MAPPING1, val);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to write RegDioMapping1 (%d)\n", ret);
+		return ret;
+	}
+
+	ret = sx1276_read_single(spi, REG_OPMODE, &val);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to read RegOpMode (%d)\n", ret);
+		return ret;
+	}
+
+	val &= ~REG_OPMODE_MODE_MASK;
+	val |= REG_OPMODE_MODE_TX;
+	ret = sx1276_write_single(spi, REG_OPMODE, val);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to write RegOpMode (%d)\n", ret);
+		return ret;
+	}
+
+	dev_dbg(&spi->dev, "%s: done\n", __func__);
+
+	return 0;
+}
+
+static void sx1276_tx_work_handler(struct work_struct *ws)
+{
+	struct sx1276_priv *priv = container_of(ws, struct sx1276_priv, tx_work);
+	struct spi_device *spi = priv->spi;
+	struct net_device *netdev = spi_get_drvdata(spi);
+
+	netdev_dbg(netdev, "%s\n", __func__);
+
+	mutex_lock(&priv->spi_lock);
+
+	if (priv->tx_skb) {
+		sx1276_tx(spi, priv->tx_skb->data, priv->tx_skb->data_len);
+		priv->tx_len = 1 + priv->tx_skb->data_len;
+		if (!(netdev->flags & IFF_ECHO) ||
+			priv->tx_skb->pkt_type != PACKET_LOOPBACK ||
+			priv->tx_skb->protocol != htons(ETH_P_LORA))
+			kfree_skb(priv->tx_skb);
+		priv->tx_skb = NULL;
+	}
+
+	mutex_unlock(&priv->spi_lock);
+}
+
+static irqreturn_t sx1276_dio_interrupt(int irq, void *dev_id)
+{
+	struct net_device *netdev = dev_id;
+	struct sx1276_priv *priv = netdev_priv(netdev);
+	struct spi_device *spi = priv->spi;
+	u8 val;
+	int ret;
+
+	netdev_dbg(netdev, "%s\n", __func__);
+
+	mutex_lock(&priv->spi_lock);
+
+	ret = sx1276_read_single(spi, LORA_REG_IRQ_FLAGS, &val);
+	if (ret) {
+		netdev_warn(netdev, "Failed to read RegIrqFlags (%d)\n", ret);
+		val = 0;
+	}
+
+	if (val & LORA_REG_IRQ_FLAGS_TX_DONE) {
+		netdev_info(netdev, "TX done.\n");
+		netdev->stats.tx_packets++;
+		netdev->stats.tx_bytes += priv->tx_len - 1;
+		priv->tx_len = 0;
+		netif_wake_queue(netdev);
+
+		ret = sx1276_write_single(spi, LORA_REG_IRQ_FLAGS, LORA_REG_IRQ_FLAGS_TX_DONE);
+		if (ret)
+			netdev_warn(netdev, "Failed to write RegIrqFlags (%d)\n", ret);
+	}
+
+	mutex_unlock(&priv->spi_lock);
+
+	return IRQ_HANDLED;
+}
+
+static int sx1276_loradev_open(struct net_device *netdev)
+{
+	struct sx1276_priv *priv = netdev_priv(netdev);
+	struct spi_device *spi = to_spi_device(netdev->dev.parent);
+	u8 val;
+	int ret, irq;
+
+	netdev_dbg(netdev, "%s\n", __func__);
+
+	ret = open_loradev(netdev);
+	if (ret)
+		return ret;
+
+	mutex_lock(&priv->spi_lock);
+
+	ret = sx1276_read_single(spi, REG_OPMODE, &val);
+	if (ret) {
+		netdev_err(netdev, "Failed to read RegOpMode (%d)\n", ret);
+		goto err_opmode;
+	}
+
+	val &= ~REG_OPMODE_MODE_MASK;
+	val |= REG_OPMODE_MODE_STDBY;
+	ret = sx1276_write_single(spi, REG_OPMODE, val);
+	if (ret) {
+		netdev_err(netdev, "Failed to write RegOpMode (%d)\n", ret);
+		goto err_opmode;
+	}
+
+	priv->tx_skb = NULL;
+	priv->tx_len = 0;
+
+	priv->wq = alloc_workqueue("sx1276_wq", WQ_FREEZABLE | WQ_MEM_RECLAIM, 0);
+	INIT_WORK(&priv->tx_work, sx1276_tx_work_handler);
+
+	if (gpio_is_valid(priv->dio_gpio[0])) {
+		irq = gpio_to_irq(priv->dio_gpio[0]);
+		if (irq <= 0)
+			netdev_warn(netdev, "Failed to obtain interrupt for DIO0 (%d)\n", irq);
+		else {
+			netdev_info(netdev, "Succeeded in obtaining interrupt for DIO0: %d\n", irq);
+			ret = request_threaded_irq(irq, NULL, sx1276_dio_interrupt, IRQF_ONESHOT | IRQF_TRIGGER_RISING, netdev->name, netdev);
+			if (ret) {
+				netdev_err(netdev, "Failed to request interrupt for DIO0 (%d)\n", ret);
+				goto err_irq;
+			}
+		}
+	}
+
+	netif_wake_queue(netdev);
+
+	mutex_unlock(&priv->spi_lock);
+
+	return 0;
+
+err_irq:
+	destroy_workqueue(priv->wq);
+	priv->wq = NULL;
+err_opmode:
+	close_loradev(netdev);
+	mutex_unlock(&priv->spi_lock);
+	return ret;
+}
+
+static int sx1276_loradev_stop(struct net_device *netdev)
+{
+	struct sx1276_priv *priv = netdev_priv(netdev);
+	struct spi_device *spi = to_spi_device(netdev->dev.parent);
+	u8 val;
+	int ret, irq;
+
+	netdev_dbg(netdev, "%s\n", __func__);
+
+	close_loradev(netdev);
+
+	mutex_lock(&priv->spi_lock);
+
+	ret = sx1276_write_single(spi, LORA_REG_IRQ_FLAGS_MASK, 0xff);
+	if (ret) {
+		netdev_err(netdev, "Failed to write RegIrqFlagsMask (%d)\n", ret);
+		goto err_irqmask;
+	}
+
+	ret = sx1276_read_single(spi, REG_OPMODE, &val);
+	if (ret) {
+		netdev_err(netdev, "Failed to read RegOpMode (%d)\n", ret);
+		goto err_opmode;
+	}
+
+	val &= ~REG_OPMODE_MODE_MASK;
+	val |= REG_OPMODE_MODE_SLEEP;
+	ret = sx1276_write_single(spi, REG_OPMODE, val);
+	if (ret) {
+		netdev_err(netdev, "Failed to write RegOpMode (%d)\n", ret);
+		goto err_opmode;
+	}
+
+	if (gpio_is_valid(priv->dio_gpio[0])) {
+		irq = gpio_to_irq(priv->dio_gpio[0]);
+		if (irq > 0) {
+			netdev_dbg(netdev, "Freeing IRQ %d\n", irq);
+			free_irq(irq, netdev);
+		}
+	}
+
+	destroy_workqueue(priv->wq);
+	priv->wq = NULL;
+
+	if (priv->tx_skb || priv->tx_len)
+		netdev->stats.tx_errors++;
+	if (priv->tx_skb)
+		dev_kfree_skb(priv->tx_skb);
+	priv->tx_skb = NULL;
+	priv->tx_len = 0;
+
+	mutex_unlock(&priv->spi_lock);
+
+	return 0;
+
+err_opmode:
+err_irqmask:
+	mutex_unlock(&priv->spi_lock);
+	return ret;
+}
+
+static const struct net_device_ops sx1276_netdev_ops =  {
+	.ndo_open = sx1276_loradev_open,
+	.ndo_stop = sx1276_loradev_stop,
+	.ndo_start_xmit = sx1276_loradev_start_xmit,
+};
+
+static int sx1276_probe(struct spi_device *spi)
+{
+	struct net_device *netdev;
+	struct sx1276_priv *priv;
+	int rst, dio[6], ret, model, i;
+	u32 freq_xosc, freq_band;
+	unsigned long long freq_rf;
+	u8 val;
+
+	rst = of_get_named_gpio(spi->dev.of_node, "reset-gpio", 0);
+	if (rst == -ENOENT)
+		dev_warn(&spi->dev, "no reset GPIO available, ignoring");
+
+	for (i = 0; i < 6; i++) {
+		dio[i] = of_get_named_gpio(spi->dev.of_node, "dio-gpios", i);
+		if (dio[i] == -ENOENT)
+			dev_dbg(&spi->dev, "DIO%d not available, ignoring", i);
+		else {
+			ret = gpio_direction_input(dio[i]);
+			if (ret)
+				dev_err(&spi->dev, "couldn't set DIO%d to input", i);
+		}
+	}
+
+	if (gpio_is_valid(rst)) {
+		gpio_set_value(rst, 1);
+		udelay(100);
+		gpio_set_value(rst, 0);
+		msleep(5);
+	}
+
+	spi->bits_per_word = 8;
+	spi_setup(spi);
+
+	ret = sx1276_read_single(spi, REG_VERSION, &val);
+	if (ret) {
+		dev_err(&spi->dev, "version read failed");
+		return ret;
+	}
+
+	if (val == 0x22)
+		model = 1272;
+	else {
+		if (gpio_is_valid(rst)) {
+			gpio_set_value(rst, 0);
+			udelay(100);
+			gpio_set_value(rst, 1);
+			msleep(5);
+		}
+
+		ret = sx1276_read_single(spi, REG_VERSION, &val);
+		if (ret) {
+			dev_err(&spi->dev, "version read failed");
+			return ret;
+		}
+
+		if (val == 0x12)
+			model = 1276;
+		else {
+			dev_err(&spi->dev, "transceiver not recognized (RegVersion = 0x%02x)", (unsigned)val);
+			return -EINVAL;
+		}
+	}
+
+	ret = of_property_read_u32(spi->dev.of_node, "clock-frequency", &freq_xosc);
+	if (ret) {
+		dev_err(&spi->dev, "failed reading clock-frequency");
+		return ret;
+	}
+
+	ret = of_property_read_u32(spi->dev.of_node, "radio-frequency", &freq_band);
+	if (ret) {
+		dev_err(&spi->dev, "failed reading radio-frequency");
+		return ret;
+	}
+
+	val = REG_OPMODE_LONG_RANGE_MODE | REG_OPMODE_MODE_SLEEP;
+	if (freq_band < 525000000)
+		val |= REG_OPMODE_LOW_FREQUENCY_MODE_ON;
+	ret = sx1276_write_single(spi, REG_OPMODE, val);
+	if (ret) {
+		dev_err(&spi->dev, "failed writing opmode");
+		return ret;
+	}
+
+	freq_rf = freq_band;
+	freq_rf *= (1 << 19);
+	freq_rf /= freq_xosc;
+	dev_dbg(&spi->dev, "Frf = %llu", freq_rf);
+
+	ret = sx1276_write_single(spi, REG_FRF_MSB, freq_rf >> 16);
+	if (!ret)
+		ret = sx1276_write_single(spi, REG_FRF_MID, freq_rf >> 8);
+	if (!ret)
+		ret = sx1276_write_single(spi, REG_FRF_LSB, freq_rf);
+	if (ret) {
+		dev_err(&spi->dev, "failed writing frequency (%d)", ret);
+		return ret;
+	}
+
+	ret = sx1276_read_single(spi, REG_PA_CONFIG, &val);
+	if (ret) {
+		dev_err(&spi->dev, "failed reading RegPaConfig\n");
+		return ret;
+	}
+	if (true)
+		val |= REG_PA_CONFIG_PA_SELECT;
+	val &= ~GENMASK(3, 0);
+	val |= (23 - 3) - 5;
+	ret = sx1276_write_single(spi, REG_PA_CONFIG, val);
+	if (ret) {
+		dev_err(&spi->dev, "failed writing RegPaConfig\n");
+		return ret;
+	}
+
+	ret = sx1276_read_single(spi, REG_PA_DAC, &val);
+	if (ret) {
+		dev_err(&spi->dev, "failed reading RegPaDac\n");
+		return ret;
+	}
+	val &= ~GENMASK(2, 0);
+	val |= 0x7;
+	ret = sx1276_write_single(spi, REG_PA_DAC, val);
+	if (ret) {
+		dev_err(&spi->dev, "failed writing RegPaDac\n");
+		return ret;
+	}
+
+	netdev = alloc_loradev(sizeof(struct sx1276_priv));
+	if (!netdev)
+		return -ENOMEM;
+
+	netdev->netdev_ops = &sx1276_netdev_ops;
+	netdev->flags |= IFF_ECHO;
+
+	priv = netdev_priv(netdev);
+	priv->spi = spi;
+	mutex_init(&priv->spi_lock);
+	for (i = 0; i < 6; i++)
+		priv->dio_gpio[i] = dio[i];
+
+	spi_set_drvdata(spi, netdev);
+	SET_NETDEV_DEV(netdev, &spi->dev);
+
+	ret = register_loradev(netdev);
+	if (ret) {
+		free_loradev(netdev);
+		return ret;
+	}
+
+	dev_info(&spi->dev, "SX1276 module probed (SX%d)", model);
+
+	return 0;
+}
+
+static int sx1276_remove(struct spi_device *spi)
+{
+	struct net_device *netdev = spi_get_drvdata(spi);
+
+	unregister_loradev(netdev);
+	free_loradev(netdev);
+
+	dev_info(&spi->dev, "SX1276 module removed");
+
+	return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id sx1276_dt_ids[] = {
+	{ .compatible = "semtech,sx1272" },
+	{ .compatible = "semtech,sx1276" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, sx1276_dt_ids);
+#endif
+
+static struct spi_driver sx1276_spi_driver = {
+	.driver = {
+		.name = "sx1276",
+		.of_match_table = of_match_ptr(sx1276_dt_ids),
+	},
+	.probe = sx1276_probe,
+	.remove = sx1276_remove,
+};
+
+module_spi_driver(sx1276_spi_driver);
+
+MODULE_DESCRIPTION("SX1276 SPI driver");
+MODULE_AUTHOR("Andreas Färber <afaerber@suse.de>");
+MODULE_LICENSE("GPL");
-- 
2.16.4


WARNING: multiple messages have this Message-ID (diff)
From: afaerber@suse.de (Andreas Färber)
To: linux-arm-kernel@lists.infradead.org
Subject: [RFC net-next 07/15] net: lora: Add Semtech SX1276
Date: Sun,  1 Jul 2018 13:07:56 +0200	[thread overview]
Message-ID: <20180701110804.32415-8-afaerber@suse.de> (raw)
In-Reply-To: <20180701110804.32415-1-afaerber@suse.de>

Semtech SX1276/77/78/79 and SX1272/73 are LoRa transceivers with a SPI
interface. They also offer a non-LoRa mode (not exposed here).

Signed-off-by: Andreas F?rber <afaerber@suse.de>
---
 drivers/net/lora/Kconfig  |  11 +
 drivers/net/lora/Makefile |   3 +
 drivers/net/lora/sx1276.c | 608 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 622 insertions(+)
 create mode 100644 drivers/net/lora/sx1276.c

diff --git a/drivers/net/lora/Kconfig b/drivers/net/lora/Kconfig
index 40969b148a50..0436f6b09a1c 100644
--- a/drivers/net/lora/Kconfig
+++ b/drivers/net/lora/Kconfig
@@ -15,4 +15,15 @@ config LORA_DEV
 # Alphabetically sorted.
 #
 
+if LORA_DEV
+
+config LORA_SX1276
+	tristate "Semtech SX127x SPI driver"
+	default y
+	depends on SPI
+	help
+	  Semtech SX1272/1276/1278
+
+endif
+
 endmenu
diff --git a/drivers/net/lora/Makefile b/drivers/net/lora/Makefile
index 8f9d25ea4e70..8845542dba50 100644
--- a/drivers/net/lora/Makefile
+++ b/drivers/net/lora/Makefile
@@ -8,3 +8,6 @@ lora-dev-y := dev.o
 #
 # Alphabetically sorted.
 #
+
+obj-$(CONFIG_LORA_SX1276) += lora-sx1276.o
+lora-sx1276-y := sx1276.o
diff --git a/drivers/net/lora/sx1276.c b/drivers/net/lora/sx1276.c
new file mode 100644
index 000000000000..d6732111247a
--- /dev/null
+++ b/drivers/net/lora/sx1276.c
@@ -0,0 +1,608 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Semtech SX1272/SX1276 LoRa transceiver
+ *
+ * Copyright (c) 2016-2018 Andreas F?rber
+ */
+
+#include <linux/delay.h>
+#include <linux/lora.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/lora/dev.h>
+#include <linux/spi/spi.h>
+
+#define REG_FIFO			0x00
+#define REG_OPMODE			0x01
+#define REG_FRF_MSB			0x06
+#define REG_FRF_MID			0x07
+#define REG_FRF_LSB			0x08
+#define REG_PA_CONFIG			0x09
+#define LORA_REG_FIFO_ADDR_PTR		0x0d
+#define LORA_REG_FIFO_TX_BASE_ADDR	0x0e
+#define LORA_REG_IRQ_FLAGS_MASK		0x11
+#define LORA_REG_IRQ_FLAGS		0x12
+#define LORA_REG_PAYLOAD_LENGTH		0x22
+#define LORA_REG_SYNC_WORD		0x39
+#define REG_DIO_MAPPING1		0x40
+#define REG_DIO_MAPPING2		0x41
+#define REG_VERSION			0x42
+#define REG_PA_DAC			0x4d
+
+#define REG_OPMODE_LONG_RANGE_MODE		BIT(7)
+#define REG_OPMODE_LOW_FREQUENCY_MODE_ON	BIT(3)
+#define REG_OPMODE_MODE_MASK			GENMASK(2, 0)
+#define REG_OPMODE_MODE_SLEEP			(0x0 << 0)
+#define REG_OPMODE_MODE_STDBY			(0x1 << 0)
+#define REG_OPMODE_MODE_TX			(0x3 << 0)
+#define REG_OPMODE_MODE_RXCONTINUOUS		(0x5 << 0)
+#define REG_OPMODE_MODE_RXSINGLE		(0x6 << 0)
+
+#define REG_PA_CONFIG_PA_SELECT			BIT(7)
+
+#define LORA_REG_IRQ_FLAGS_TX_DONE		BIT(3)
+
+#define REG_DIO_MAPPING1_DIO0_MASK	GENMASK(7, 6)
+
+struct sx1276_priv {
+	struct lora_priv lora;
+	struct spi_device *spi;
+
+	size_t fifosize;
+	int dio_gpio[6];
+
+	struct mutex spi_lock;
+
+	struct sk_buff *tx_skb;
+	int tx_len;
+
+	struct workqueue_struct *wq;
+	struct work_struct tx_work;
+};
+
+static int sx1276_read_single(struct spi_device *spi, u8 reg, u8 *val)
+{
+	u8 addr = reg & 0x7f;
+	return spi_write_then_read(spi, &addr, 1, val, 1);
+}
+
+static int sx1276_write_single(struct spi_device *spi, u8 reg, u8 val)
+{
+	u8 buf[2];
+
+	buf[0] = reg | BIT(7);
+	buf[1] = val;
+	return spi_write(spi, buf, 2);
+}
+
+static int sx1276_write_burst(struct spi_device *spi, u8 reg, size_t len, void *val)
+{
+	u8 buf = reg | BIT(7);
+	struct spi_transfer xfers[2] = {
+		[0] = {
+			.tx_buf = &buf,
+			.len = 1,
+		},
+		[1] = {
+			.tx_buf = val,
+			.len = len,
+		},
+	};
+
+	return spi_sync_transfer(spi, xfers, 2);
+}
+
+static int sx1276_write_fifo(struct spi_device *spi, size_t len, void *val)
+{
+	return sx1276_write_burst(spi, REG_FIFO, len, val);
+}
+
+static netdev_tx_t sx1276_loradev_start_xmit(struct sk_buff *skb, struct net_device *netdev)
+{
+	struct sx1276_priv *priv = netdev_priv(netdev);
+
+	netdev_dbg(netdev, "%s\n", __func__);
+
+	if (priv->tx_skb || priv->tx_len) {
+		netdev_warn(netdev, "TX busy\n");
+		return NETDEV_TX_BUSY;
+	}
+
+	if (skb->protocol != htons(ETH_P_LORA)) {
+		kfree_skb(skb);
+		netdev->stats.tx_dropped++;
+		return NETDEV_TX_OK;
+	}
+
+	netif_stop_queue(netdev);
+	priv->tx_skb = skb;
+	queue_work(priv->wq, &priv->tx_work);
+
+	return NETDEV_TX_OK;
+}
+
+static int sx1276_tx(struct spi_device *spi, void *data, int data_len)
+{
+	u8 addr, val;
+	int ret;
+
+	dev_dbg(&spi->dev, "%s\n", __func__);
+
+	ret = sx1276_read_single(spi, REG_OPMODE, &val);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to read RegOpMode (%d)\n", ret);
+		return ret;
+	}
+	dev_dbg(&spi->dev, "RegOpMode = 0x%02x\n", val);
+	if (!(val & REG_OPMODE_LONG_RANGE_MODE))
+		dev_err(&spi->dev, "LongRange Mode not active!\n");
+	if ((val & REG_OPMODE_MODE_MASK) == REG_OPMODE_MODE_SLEEP)
+		dev_err(&spi->dev, "Cannot access FIFO in Sleep Mode!\n");
+
+	ret = sx1276_read_single(spi, LORA_REG_FIFO_TX_BASE_ADDR, &addr);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to read RegFifoTxBaseAddr (%d)\n", ret);
+		return ret;
+	}
+	dev_dbg(&spi->dev, "RegFifoTxBaseAddr = 0x%02x\n", addr);
+
+	ret = sx1276_write_single(spi, LORA_REG_FIFO_ADDR_PTR, addr);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to write RegFifoAddrPtr (%d)\n", ret);
+		return ret;
+	}
+
+	ret = sx1276_write_single(spi, LORA_REG_PAYLOAD_LENGTH, data_len);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to write RegPayloadLength (%d)\n", ret);
+		return ret;
+	}
+
+	ret = sx1276_write_fifo(spi, data_len, data);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to write into FIFO (%d)\n", ret);
+		return ret;
+	}
+
+	ret = sx1276_read_single(spi, LORA_REG_IRQ_FLAGS, &val);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to read RegIrqFlags (%d)\n", ret);
+		return ret;
+	}
+	dev_dbg(&spi->dev, "RegIrqFlags = 0x%02x\n", val);
+
+	ret = sx1276_write_single(spi, LORA_REG_IRQ_FLAGS, LORA_REG_IRQ_FLAGS_TX_DONE);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to write RegIrqFlags (%d)\n", ret);
+		return ret;
+	}
+
+	ret = sx1276_read_single(spi, LORA_REG_IRQ_FLAGS_MASK, &val);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to read RegIrqFlagsMask (%d)\n", ret);
+		return ret;
+	}
+	dev_dbg(&spi->dev, "RegIrqFlagsMask = 0x%02x\n", val);
+
+	val &= ~LORA_REG_IRQ_FLAGS_TX_DONE;
+	ret = sx1276_write_single(spi, LORA_REG_IRQ_FLAGS_MASK, val);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to write RegIrqFlagsMask (%d)\n", ret);
+		return ret;
+	}
+
+	ret = sx1276_read_single(spi, REG_DIO_MAPPING1, &val);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to read RegDioMapping1 (%d)\n", ret);
+		return ret;
+	}
+
+	val &= ~REG_DIO_MAPPING1_DIO0_MASK;
+	val |= 0x1 << 6;
+	ret = sx1276_write_single(spi, REG_DIO_MAPPING1, val);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to write RegDioMapping1 (%d)\n", ret);
+		return ret;
+	}
+
+	ret = sx1276_read_single(spi, REG_OPMODE, &val);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to read RegOpMode (%d)\n", ret);
+		return ret;
+	}
+
+	val &= ~REG_OPMODE_MODE_MASK;
+	val |= REG_OPMODE_MODE_TX;
+	ret = sx1276_write_single(spi, REG_OPMODE, val);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to write RegOpMode (%d)\n", ret);
+		return ret;
+	}
+
+	dev_dbg(&spi->dev, "%s: done\n", __func__);
+
+	return 0;
+}
+
+static void sx1276_tx_work_handler(struct work_struct *ws)
+{
+	struct sx1276_priv *priv = container_of(ws, struct sx1276_priv, tx_work);
+	struct spi_device *spi = priv->spi;
+	struct net_device *netdev = spi_get_drvdata(spi);
+
+	netdev_dbg(netdev, "%s\n", __func__);
+
+	mutex_lock(&priv->spi_lock);
+
+	if (priv->tx_skb) {
+		sx1276_tx(spi, priv->tx_skb->data, priv->tx_skb->data_len);
+		priv->tx_len = 1 + priv->tx_skb->data_len;
+		if (!(netdev->flags & IFF_ECHO) ||
+			priv->tx_skb->pkt_type != PACKET_LOOPBACK ||
+			priv->tx_skb->protocol != htons(ETH_P_LORA))
+			kfree_skb(priv->tx_skb);
+		priv->tx_skb = NULL;
+	}
+
+	mutex_unlock(&priv->spi_lock);
+}
+
+static irqreturn_t sx1276_dio_interrupt(int irq, void *dev_id)
+{
+	struct net_device *netdev = dev_id;
+	struct sx1276_priv *priv = netdev_priv(netdev);
+	struct spi_device *spi = priv->spi;
+	u8 val;
+	int ret;
+
+	netdev_dbg(netdev, "%s\n", __func__);
+
+	mutex_lock(&priv->spi_lock);
+
+	ret = sx1276_read_single(spi, LORA_REG_IRQ_FLAGS, &val);
+	if (ret) {
+		netdev_warn(netdev, "Failed to read RegIrqFlags (%d)\n", ret);
+		val = 0;
+	}
+
+	if (val & LORA_REG_IRQ_FLAGS_TX_DONE) {
+		netdev_info(netdev, "TX done.\n");
+		netdev->stats.tx_packets++;
+		netdev->stats.tx_bytes += priv->tx_len - 1;
+		priv->tx_len = 0;
+		netif_wake_queue(netdev);
+
+		ret = sx1276_write_single(spi, LORA_REG_IRQ_FLAGS, LORA_REG_IRQ_FLAGS_TX_DONE);
+		if (ret)
+			netdev_warn(netdev, "Failed to write RegIrqFlags (%d)\n", ret);
+	}
+
+	mutex_unlock(&priv->spi_lock);
+
+	return IRQ_HANDLED;
+}
+
+static int sx1276_loradev_open(struct net_device *netdev)
+{
+	struct sx1276_priv *priv = netdev_priv(netdev);
+	struct spi_device *spi = to_spi_device(netdev->dev.parent);
+	u8 val;
+	int ret, irq;
+
+	netdev_dbg(netdev, "%s\n", __func__);
+
+	ret = open_loradev(netdev);
+	if (ret)
+		return ret;
+
+	mutex_lock(&priv->spi_lock);
+
+	ret = sx1276_read_single(spi, REG_OPMODE, &val);
+	if (ret) {
+		netdev_err(netdev, "Failed to read RegOpMode (%d)\n", ret);
+		goto err_opmode;
+	}
+
+	val &= ~REG_OPMODE_MODE_MASK;
+	val |= REG_OPMODE_MODE_STDBY;
+	ret = sx1276_write_single(spi, REG_OPMODE, val);
+	if (ret) {
+		netdev_err(netdev, "Failed to write RegOpMode (%d)\n", ret);
+		goto err_opmode;
+	}
+
+	priv->tx_skb = NULL;
+	priv->tx_len = 0;
+
+	priv->wq = alloc_workqueue("sx1276_wq", WQ_FREEZABLE | WQ_MEM_RECLAIM, 0);
+	INIT_WORK(&priv->tx_work, sx1276_tx_work_handler);
+
+	if (gpio_is_valid(priv->dio_gpio[0])) {
+		irq = gpio_to_irq(priv->dio_gpio[0]);
+		if (irq <= 0)
+			netdev_warn(netdev, "Failed to obtain interrupt for DIO0 (%d)\n", irq);
+		else {
+			netdev_info(netdev, "Succeeded in obtaining interrupt for DIO0: %d\n", irq);
+			ret = request_threaded_irq(irq, NULL, sx1276_dio_interrupt, IRQF_ONESHOT | IRQF_TRIGGER_RISING, netdev->name, netdev);
+			if (ret) {
+				netdev_err(netdev, "Failed to request interrupt for DIO0 (%d)\n", ret);
+				goto err_irq;
+			}
+		}
+	}
+
+	netif_wake_queue(netdev);
+
+	mutex_unlock(&priv->spi_lock);
+
+	return 0;
+
+err_irq:
+	destroy_workqueue(priv->wq);
+	priv->wq = NULL;
+err_opmode:
+	close_loradev(netdev);
+	mutex_unlock(&priv->spi_lock);
+	return ret;
+}
+
+static int sx1276_loradev_stop(struct net_device *netdev)
+{
+	struct sx1276_priv *priv = netdev_priv(netdev);
+	struct spi_device *spi = to_spi_device(netdev->dev.parent);
+	u8 val;
+	int ret, irq;
+
+	netdev_dbg(netdev, "%s\n", __func__);
+
+	close_loradev(netdev);
+
+	mutex_lock(&priv->spi_lock);
+
+	ret = sx1276_write_single(spi, LORA_REG_IRQ_FLAGS_MASK, 0xff);
+	if (ret) {
+		netdev_err(netdev, "Failed to write RegIrqFlagsMask (%d)\n", ret);
+		goto err_irqmask;
+	}
+
+	ret = sx1276_read_single(spi, REG_OPMODE, &val);
+	if (ret) {
+		netdev_err(netdev, "Failed to read RegOpMode (%d)\n", ret);
+		goto err_opmode;
+	}
+
+	val &= ~REG_OPMODE_MODE_MASK;
+	val |= REG_OPMODE_MODE_SLEEP;
+	ret = sx1276_write_single(spi, REG_OPMODE, val);
+	if (ret) {
+		netdev_err(netdev, "Failed to write RegOpMode (%d)\n", ret);
+		goto err_opmode;
+	}
+
+	if (gpio_is_valid(priv->dio_gpio[0])) {
+		irq = gpio_to_irq(priv->dio_gpio[0]);
+		if (irq > 0) {
+			netdev_dbg(netdev, "Freeing IRQ %d\n", irq);
+			free_irq(irq, netdev);
+		}
+	}
+
+	destroy_workqueue(priv->wq);
+	priv->wq = NULL;
+
+	if (priv->tx_skb || priv->tx_len)
+		netdev->stats.tx_errors++;
+	if (priv->tx_skb)
+		dev_kfree_skb(priv->tx_skb);
+	priv->tx_skb = NULL;
+	priv->tx_len = 0;
+
+	mutex_unlock(&priv->spi_lock);
+
+	return 0;
+
+err_opmode:
+err_irqmask:
+	mutex_unlock(&priv->spi_lock);
+	return ret;
+}
+
+static const struct net_device_ops sx1276_netdev_ops =  {
+	.ndo_open = sx1276_loradev_open,
+	.ndo_stop = sx1276_loradev_stop,
+	.ndo_start_xmit = sx1276_loradev_start_xmit,
+};
+
+static int sx1276_probe(struct spi_device *spi)
+{
+	struct net_device *netdev;
+	struct sx1276_priv *priv;
+	int rst, dio[6], ret, model, i;
+	u32 freq_xosc, freq_band;
+	unsigned long long freq_rf;
+	u8 val;
+
+	rst = of_get_named_gpio(spi->dev.of_node, "reset-gpio", 0);
+	if (rst == -ENOENT)
+		dev_warn(&spi->dev, "no reset GPIO available, ignoring");
+
+	for (i = 0; i < 6; i++) {
+		dio[i] = of_get_named_gpio(spi->dev.of_node, "dio-gpios", i);
+		if (dio[i] == -ENOENT)
+			dev_dbg(&spi->dev, "DIO%d not available, ignoring", i);
+		else {
+			ret = gpio_direction_input(dio[i]);
+			if (ret)
+				dev_err(&spi->dev, "couldn't set DIO%d to input", i);
+		}
+	}
+
+	if (gpio_is_valid(rst)) {
+		gpio_set_value(rst, 1);
+		udelay(100);
+		gpio_set_value(rst, 0);
+		msleep(5);
+	}
+
+	spi->bits_per_word = 8;
+	spi_setup(spi);
+
+	ret = sx1276_read_single(spi, REG_VERSION, &val);
+	if (ret) {
+		dev_err(&spi->dev, "version read failed");
+		return ret;
+	}
+
+	if (val == 0x22)
+		model = 1272;
+	else {
+		if (gpio_is_valid(rst)) {
+			gpio_set_value(rst, 0);
+			udelay(100);
+			gpio_set_value(rst, 1);
+			msleep(5);
+		}
+
+		ret = sx1276_read_single(spi, REG_VERSION, &val);
+		if (ret) {
+			dev_err(&spi->dev, "version read failed");
+			return ret;
+		}
+
+		if (val == 0x12)
+			model = 1276;
+		else {
+			dev_err(&spi->dev, "transceiver not recognized (RegVersion = 0x%02x)", (unsigned)val);
+			return -EINVAL;
+		}
+	}
+
+	ret = of_property_read_u32(spi->dev.of_node, "clock-frequency", &freq_xosc);
+	if (ret) {
+		dev_err(&spi->dev, "failed reading clock-frequency");
+		return ret;
+	}
+
+	ret = of_property_read_u32(spi->dev.of_node, "radio-frequency", &freq_band);
+	if (ret) {
+		dev_err(&spi->dev, "failed reading radio-frequency");
+		return ret;
+	}
+
+	val = REG_OPMODE_LONG_RANGE_MODE | REG_OPMODE_MODE_SLEEP;
+	if (freq_band < 525000000)
+		val |= REG_OPMODE_LOW_FREQUENCY_MODE_ON;
+	ret = sx1276_write_single(spi, REG_OPMODE, val);
+	if (ret) {
+		dev_err(&spi->dev, "failed writing opmode");
+		return ret;
+	}
+
+	freq_rf = freq_band;
+	freq_rf *= (1 << 19);
+	freq_rf /= freq_xosc;
+	dev_dbg(&spi->dev, "Frf = %llu", freq_rf);
+
+	ret = sx1276_write_single(spi, REG_FRF_MSB, freq_rf >> 16);
+	if (!ret)
+		ret = sx1276_write_single(spi, REG_FRF_MID, freq_rf >> 8);
+	if (!ret)
+		ret = sx1276_write_single(spi, REG_FRF_LSB, freq_rf);
+	if (ret) {
+		dev_err(&spi->dev, "failed writing frequency (%d)", ret);
+		return ret;
+	}
+
+	ret = sx1276_read_single(spi, REG_PA_CONFIG, &val);
+	if (ret) {
+		dev_err(&spi->dev, "failed reading RegPaConfig\n");
+		return ret;
+	}
+	if (true)
+		val |= REG_PA_CONFIG_PA_SELECT;
+	val &= ~GENMASK(3, 0);
+	val |= (23 - 3) - 5;
+	ret = sx1276_write_single(spi, REG_PA_CONFIG, val);
+	if (ret) {
+		dev_err(&spi->dev, "failed writing RegPaConfig\n");
+		return ret;
+	}
+
+	ret = sx1276_read_single(spi, REG_PA_DAC, &val);
+	if (ret) {
+		dev_err(&spi->dev, "failed reading RegPaDac\n");
+		return ret;
+	}
+	val &= ~GENMASK(2, 0);
+	val |= 0x7;
+	ret = sx1276_write_single(spi, REG_PA_DAC, val);
+	if (ret) {
+		dev_err(&spi->dev, "failed writing RegPaDac\n");
+		return ret;
+	}
+
+	netdev = alloc_loradev(sizeof(struct sx1276_priv));
+	if (!netdev)
+		return -ENOMEM;
+
+	netdev->netdev_ops = &sx1276_netdev_ops;
+	netdev->flags |= IFF_ECHO;
+
+	priv = netdev_priv(netdev);
+	priv->spi = spi;
+	mutex_init(&priv->spi_lock);
+	for (i = 0; i < 6; i++)
+		priv->dio_gpio[i] = dio[i];
+
+	spi_set_drvdata(spi, netdev);
+	SET_NETDEV_DEV(netdev, &spi->dev);
+
+	ret = register_loradev(netdev);
+	if (ret) {
+		free_loradev(netdev);
+		return ret;
+	}
+
+	dev_info(&spi->dev, "SX1276 module probed (SX%d)", model);
+
+	return 0;
+}
+
+static int sx1276_remove(struct spi_device *spi)
+{
+	struct net_device *netdev = spi_get_drvdata(spi);
+
+	unregister_loradev(netdev);
+	free_loradev(netdev);
+
+	dev_info(&spi->dev, "SX1276 module removed");
+
+	return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id sx1276_dt_ids[] = {
+	{ .compatible = "semtech,sx1272" },
+	{ .compatible = "semtech,sx1276" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, sx1276_dt_ids);
+#endif
+
+static struct spi_driver sx1276_spi_driver = {
+	.driver = {
+		.name = "sx1276",
+		.of_match_table = of_match_ptr(sx1276_dt_ids),
+	},
+	.probe = sx1276_probe,
+	.remove = sx1276_remove,
+};
+
+module_spi_driver(sx1276_spi_driver);
+
+MODULE_DESCRIPTION("SX1276 SPI driver");
+MODULE_AUTHOR("Andreas F?rber <afaerber@suse.de>");
+MODULE_LICENSE("GPL");
-- 
2.16.4

  parent reply	other threads:[~2018-07-01 11:12 UTC|newest]

Thread overview: 173+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-07-01 11:07 [RFC net-next 00/15] net: A socket API for LoRa Andreas Färber
2018-07-01 11:07 ` Andreas Färber
2018-07-01 11:07 ` Andreas Färber
2018-07-01 11:07 ` [RFC net-next 01/15] net: Reserve protocol numbers " Andreas Färber
2018-07-01 11:07   ` Andreas Färber
2018-07-01 11:07   ` Andreas Färber
2018-07-01 11:07 ` [RFC net-next 02/15] net: lora: Define sockaddr_lora Andreas Färber
2018-07-01 11:07   ` Andreas Färber
2018-07-01 11:07   ` Andreas Färber
2018-07-01 11:07 ` [RFC net-next 03/15] net: lora: Add protocol numbers Andreas Färber
2018-07-01 11:07   ` Andreas Färber
2018-07-01 11:07 ` [RFC net-next 04/15] net: Add lora subsystem Andreas Färber
2018-07-01 11:07   ` Andreas Färber
2018-07-01 11:07   ` Andreas Färber
2018-07-01 11:07 ` [RFC net-next 05/15] HACK: net: lora: Deal with .poll_mask in 4.18-rc2 Andreas Färber
2018-07-01 11:07   ` Andreas Färber
2018-07-01 11:07   ` Andreas Färber
2018-07-02 16:22   ` Jiri Pirko
2018-07-02 16:22     ` Jiri Pirko
2018-07-02 16:59     ` Andreas Färber
2018-07-02 16:59       ` Andreas Färber
2018-07-01 11:07 ` [RFC net-next 06/15] net: lora: Prepare for device drivers Andreas Färber
2018-07-01 11:07   ` Andreas Färber
2018-07-01 11:07   ` Andreas Färber
2018-07-01 11:07 ` Andreas Färber [this message]
2018-07-01 11:07   ` [RFC net-next 07/15] net: lora: Add Semtech SX1276 Andreas Färber
2018-07-01 12:02   ` Andreas Färber
2018-07-01 12:02     ` Andreas Färber
2018-07-01 11:07 ` [RFC net-next 08/15] net: lora: sx1276: Add debugfs Andreas Färber
2018-07-01 11:07   ` Andreas Färber
2018-07-02 16:26   ` Jiri Pirko
2018-07-02 16:26     ` Jiri Pirko
2018-07-02 17:57     ` Andreas Färber
2018-07-02 17:57       ` Andreas Färber
2018-07-01 11:07 ` [RFC net-next 09/15] net: lora: Prepare EUI helpers Andreas Färber
2018-07-01 11:07   ` Andreas Färber
2018-07-01 11:07 ` [RFC net-next 10/15] net: lora: Add Microchip RN2483 Andreas Färber
2018-07-01 11:07   ` Andreas Färber
2018-07-01 11:08 ` [RFC net-next 11/15] net: lora: Add IMST WiMOD Andreas Färber
2018-07-01 11:08   ` Andreas Färber
2019-01-06 14:57   ` Heinrich Schuchardt
2019-01-06 14:57     ` Heinrich Schuchardt
2019-01-07 11:29     ` Andreas Färber
2019-01-07 11:29       ` Andreas Färber
2018-07-01 11:08 ` [RFC net-next 12/15] net: lora: Add USI WM-SG-SM-42 Andreas Färber
2018-07-01 11:08   ` Andreas Färber
2018-07-01 11:08 ` [RFC net-next 13/15] net: lora: Prepare RAK RAK811 Andreas Färber
2018-07-01 11:08   ` Andreas Färber
2018-07-01 11:08 ` [RFC net-next 14/15] net: lora: Prepare Semtech SX1257 Andreas Färber
2018-07-01 11:08   ` Andreas Färber
2018-07-01 11:08 ` [RFC net-next 15/15] net: lora: Add Semtech SX1301 Andreas Färber
2018-07-01 11:08   ` Andreas Färber
2018-07-02 11:51   ` Ben Whitten
2018-07-02 11:51     ` Ben Whitten
2018-07-03  3:01     ` Andreas Färber
2018-07-03  3:01       ` Andreas Färber
2018-07-05  8:59       ` Ben Whitten
2018-07-05  8:59         ` Ben Whitten
2018-07-05  8:59         ` Ben Whitten
2018-07-02 16:12   ` Mark Brown
2018-07-02 16:12     ` Mark Brown
2018-07-02 16:12     ` Mark Brown
2018-07-02 17:34     ` Andreas Färber
2018-07-02 17:34       ` Andreas Färber
2018-07-02 20:43       ` Ben Whitten
2018-07-02 20:43         ` Ben Whitten
2018-07-03  3:21         ` Andreas Färber
2018-07-03  3:21           ` Andreas Färber
2018-07-03  3:21           ` Andreas Färber
2018-07-05  8:43           ` Ben Whitten
2018-07-05  8:43             ` Ben Whitten
2018-07-05  8:43             ` Ben Whitten
2018-07-03 14:50       ` Mark Brown
2018-07-03 14:50         ` Mark Brown
2018-07-03 15:09         ` Andreas Färber
2018-07-03 15:09           ` Andreas Färber
2018-07-03 15:09           ` Andreas Färber
2018-07-03 15:31           ` Mark Brown
2018-07-03 15:31             ` Mark Brown
2018-07-03 15:31             ` Mark Brown
2018-07-03 16:40             ` Andreas Färber
2018-07-03 16:40               ` Andreas Färber
2018-07-04 11:43               ` Mark Brown
2018-07-04 11:43                 ` Mark Brown
2018-07-04 13:41                 ` Ben Whitten
2018-07-04 13:41                   ` Ben Whitten
2018-07-04 13:41                   ` Ben Whitten
2018-07-04 14:32                   ` Mark Brown
2018-07-04 14:32                     ` Mark Brown
2018-07-04 14:32                     ` Mark Brown
2018-07-03 15:11 ` [RFC net-next 00/15] net: A socket API for LoRa Jian-Hong Pan
2018-07-03 15:11   ` Jian-Hong Pan
2018-07-03 15:11   ` Jian-Hong Pan
2018-08-05  0:11   ` Andreas Färber
2018-08-05  0:11     ` Andreas Färber
2018-08-05  0:11     ` Andreas Färber
2018-08-08 20:36     ` Alan Cox
2018-08-08 20:36       ` Alan Cox
2018-08-08 20:36       ` Alan Cox
2018-08-08 22:42       ` Andreas Färber
2018-08-08 22:42         ` Andreas Färber
2018-08-08 22:42         ` Andreas Färber
2018-08-09 11:59         ` Alan Cox
2018-08-09 11:59           ` Alan Cox
2018-08-09 11:59           ` Alan Cox
2018-08-09 15:02           ` Jian-Hong Pan
2018-08-09 15:02             ` Jian-Hong Pan
2018-08-09 15:02             ` Jian-Hong Pan
2018-08-09 15:21             ` Alexander Aring
2018-08-09 15:21               ` Alexander Aring
2018-08-09 15:21               ` Alexander Aring
2018-08-10 15:57             ` Alan Cox
2018-08-10 15:57               ` Alan Cox
2018-08-10 15:57               ` Alan Cox
2018-08-11 18:30               ` Stefan Schmidt
2018-08-11 18:30                 ` Stefan Schmidt
2018-08-11 18:30                 ` Stefan Schmidt
2018-08-12 16:49                 ` Andreas Färber
2018-08-12 16:49                   ` Andreas Färber
2018-08-12 16:49                   ` Andreas Färber
2018-08-12 16:37               ` Jian-Hong Pan
2018-08-12 16:37                 ` Jian-Hong Pan
2018-08-12 16:37                 ` Jian-Hong Pan
2018-08-12 17:59                 ` Andreas Färber
2018-08-12 17:59                   ` Andreas Färber
2018-08-12 17:59                   ` Andreas Färber
2018-08-13 12:36                   ` Alan Cox
2018-08-13 12:36                     ` Alan Cox
2018-08-13 12:36                     ` Alan Cox
2018-08-09 15:12           ` Alexander Aring
2018-08-09 15:12             ` Alexander Aring
2018-08-09 15:12             ` Alexander Aring
2018-08-09 15:12             ` Alexander Aring
2018-08-09  0:50     ` Andreas Färber
2018-08-09  0:50       ` Andreas Färber
2018-08-09  0:50       ` Andreas Färber
2018-07-04 18:26 ` Stefan Schmidt
2018-07-04 18:26   ` Stefan Schmidt
2018-07-04 18:26   ` Stefan Schmidt
2018-07-05 10:43   ` Helmut Tschemernjak
2018-07-05 10:43     ` Helmut Tschemernjak
2018-07-05 10:43     ` Helmut Tschemernjak
2018-07-11  2:07     ` Andreas Färber
2018-07-11  2:07       ` Andreas Färber
2018-07-11  2:07       ` Andreas Färber
2018-07-11 11:45       ` Helmut Tschemernjak
2018-07-11 11:45         ` Helmut Tschemernjak
2018-07-11 11:45         ` Helmut Tschemernjak
2018-07-11 15:21 ` Ben Whitten
2018-07-11 15:21   ` Ben Whitten
2018-07-11 15:21   ` Ben Whitten
2018-07-15 18:13   ` Andreas Färber
2018-07-15 18:13     ` Andreas Färber
2018-07-15 18:13     ` Andreas Färber
2018-07-18 11:28     ` Ben Whitten
2018-07-18 11:28       ` Ben Whitten
2018-07-18 11:28       ` Ben Whitten
2018-07-18 11:28       ` Ben Whitten
2018-08-02  7:52       ` Jian-Hong Pan
2018-08-02  7:52         ` Jian-Hong Pan
2018-08-02  7:52         ` Jian-Hong Pan
2018-08-02  7:52         ` Jian-Hong Pan
2018-08-03  8:44         ` linux-lora.git and LoRaWAN (was: [RFC net-next 00/15] net: A socket API for LoRa) Andreas Färber
2018-08-03  8:44           ` Andreas Färber
2018-08-05 12:49           ` Jian-Hong Pan
2018-08-05 12:49             ` Jian-Hong Pan
2018-08-05 12:49             ` Jian-Hong Pan
2018-08-05 12:49             ` Jian-Hong Pan
     [not found]           ` <20180803150258.791b9942@alans-desktop>
2018-08-05 14:08             ` Jian-Hong Pan
2018-08-05 14:08               ` Jian-Hong Pan
2018-08-05 13:49       ` [RFC net-next 00/15] net: A socket API for LoRa Andreas Färber
2018-08-05 13:49         ` Andreas Färber
2018-08-05 13:49         ` Andreas Färber

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20180701110804.32415-8-afaerber@suse.de \
    --to=afaerber@suse.de \
    --cc=davem@davemloft.net \
    --cc=dollar.chen@wtmec.com \
    --cc=jiri@resnulli.us \
    --cc=jpiwek@arroweurope.com \
    --cc=ken.yu@rakwireless.com \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=marcel@holtmann.org \
    --cc=mbrugger@suse.com \
    --cc=michael.roeder@avnet.eu \
    --cc=netdev@vger.kernel.org \
    --cc=starnight@g.ncu.edu.tw \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.