From patchwork Sun Jul 1 11:07:56 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Andreas_F=C3=A4rber?= X-Patchwork-Id: 937557 Return-Path: X-Original-To: incoming-imx@patchwork.ozlabs.org Delivered-To: patchwork-incoming-imx@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=lists.infradead.org (client-ip=2607:7c80:54:e::133; helo=bombadil.infradead.org; envelope-from=linux-arm-kernel-bounces+incoming-imx=patchwork.ozlabs.org@lists.infradead.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=suse.de Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=lists.infradead.org header.i=@lists.infradead.org header.b="bCb+xPrl"; dkim-atps=neutral Received: from bombadil.infradead.org (bombadil.infradead.org [IPv6:2607:7c80:54:e::133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 41JSR306djz9s1b for ; Sun, 1 Jul 2018 21:12:47 +1000 (AEST) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:Cc:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-Id:Date:Subject:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=cygT9pgUviePxbN8Zcr6v693XMF9LX1fGlgZNET9x7w=; b=bCb+xPrlL9pf1B as3CZaGgtv2dbILguCaeWUCXfbhLQ0nbgKAzqYBcWxAVeGuuOzMdhn9XAZQsN2sHpBlKNr6SFk5vs ptBdMLOm/8tPTE5sXEjtFcD1ly2DQjLQpG9uiS14JJUl/wCPkT4mxLzgARaAPqj92H5vbmcNvSCcT tzPSj4vr+KRJsNyu5mKnLYqgr4lxNaFDcGJ3kdeH9qYPz/ViUppaDpnQ3p7OVolAFZp4J3NZi36z3 2LTif3I32MCM6NCDnkG75aRoEzgnVNoAN29wvjBLANQjz2D0b+um6cgMd605Rw2AYggpP3XWgzusC +UK9ZjnaSXxXzCJav/Lw==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.90_1 #2 (Red Hat Linux)) id 1fZaHW-00087m-JP; Sun, 01 Jul 2018 11:12:38 +0000 Received: from mx2.suse.de ([195.135.220.15] helo=mx1.suse.de) by bombadil.infradead.org with esmtps (Exim 4.90_1 #2 (Red Hat Linux)) id 1fZaEP-0004XT-8c for linux-arm-kernel@lists.infradead.org; Sun, 01 Jul 2018 11:09:48 +0000 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay2.suse.de (unknown [195.135.220.254]) by mx1.suse.de (Postfix) with ESMTP id 0856BAEC9; Sun, 1 Jul 2018 11:08:59 +0000 (UTC) From: =?utf-8?q?Andreas_F=C3=A4rber?= To: netdev@vger.kernel.org Subject: [RFC net-next 07/15] net: lora: Add Semtech SX1276 Date: Sun, 1 Jul 2018 13:07:56 +0200 Message-Id: <20180701110804.32415-8-afaerber@suse.de> X-Mailer: git-send-email 2.16.4 In-Reply-To: <20180701110804.32415-1-afaerber@suse.de> References: <20180701110804.32415-1-afaerber@suse.de> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20180701_040926_094952_8380B5CB X-CRM114-Status: GOOD ( 18.30 ) X-Spam-Score: -2.3 (--) X-Spam-Report: SpamAssassin version 3.4.1 on bombadil.infradead.org summary: Content analysis details: (-2.3 points) pts rule name description ---- ---------------------- -------------------------------------------------- -2.3 RCVD_IN_DNSWL_MED RBL: Sender listed at http://www.dnswl.org/, medium trust [195.135.220.15 listed in list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.21 Precedence: list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Matthias Brugger , Jiri Pirko , Marcel Holtmann , Dollar Chen , linux-kernel@vger.kernel.org, =?utf-8?q?Michael_R=C3=B6der?= , Janus Piwek , =?utf-8?q?Andreas_F=C3=A4rber?= , Jian-Hong Pan , Ken Yu , "David S . Miller" , linux-arm-kernel@lists.infradead.org Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+incoming-imx=patchwork.ozlabs.org@lists.infradead.org List-Id: linux-imx-kernel.lists.patchwork.ozlabs.org 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 --- 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_LICENSE("GPL");