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: 140706 Delivered-To: patch@linaro.org Received: by 2002:a2e:9754:0:0:0:0:0 with SMTP id f20-v6csp2849848ljj; Sun, 1 Jul 2018 04:12:10 -0700 (PDT) X-Google-Smtp-Source: ADUXVKKgDeup+Lp08HOAf3DacwYKC78gRHxe5AhXRuxR1/gQctAKqolgYxCaT4mDscgxWLhzuS0G X-Received: by 2002:a63:6a45:: with SMTP id f66-v6mr17811192pgc.81.1530443530636; Sun, 01 Jul 2018 04:12:10 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1530443530; cv=none; d=google.com; s=arc-20160816; b=bxUr5OiwtczJ2wsXAw+uAIpHNTe2SOyeutg9BRc8FkrbFCqAKLKuWkct4Y1U1mvjs6 mojmCZrQ+tQ5Q5vfKDP9VW/qhiZNo3eDEbOwW/qZObWaXRcfvDB1dGJ2K+RWsnDVSzJF EBlLkuZc1NKPmcFA833RxFWGoWjIsc3cGkoen/tDnJsc9txR7wCoUulvEEeooSAA0pLw FmilHeofskKHOAsTNV7uU2tCklljPrghi0DQPpmDuANzb9p+a4J7+5P1lcqds7hf744W HIxlgr2kzR5M07VYBIFlPQc0MpLGzPZFW943DadlbwvZt9Z+NZoIMUYKyP+lM86c6Iem 3Liw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :arc-authentication-results; bh=eiBkK8Mi4og66I/dgG8VHlb/zw0geW8cu7EBGMcoQiY=; b=SDjeIvLGwQ2pPyH+AhJ+7AiCpoT697yik4cay2C9sE+tVlGZU7xUTT7IeAD0ctp5GD fRVkavU5i/VFNQazAQoY/lDRgwa65RC8w7FkA/xkpBsK1WAY9S84jYlhpqbu/Tg0ptsL m/hMWzfmw0/oVS8LsGCzfkwwSWVb8vOjQ7ZWKYsUAA8albhZVc9JclRka9vEmCFv2fnA AsJye6zI+7SKJ9ZcZqxsSST5vbZnmqv7kjO/pY87ayVc68GhY3ovvGeS5qlHxk6fdgs/ 6UO5pWo1fn44Gqrz5aQvrL3UPJHuVSHYnPzEyQoaEafsbb1R7IQ0WXH3RQB9Xe/ucsKm WrSw== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: best guess record for domain of netdev-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=netdev-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id i12-v6si12282000pgp.325.2018.07.01.04.12.10; Sun, 01 Jul 2018 04:12:10 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of netdev-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of netdev-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=netdev-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752437AbeGALMI (ORCPT + 9 others); Sun, 1 Jul 2018 07:12:08 -0400 Received: from mx2.suse.de ([195.135.220.15]:47812 "EHLO mx1.suse.de" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1752153AbeGALJB (ORCPT ); Sun, 1 Jul 2018 07:09:01 -0400 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 Cc: linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, Jian-Hong Pan , Jiri Pirko , Marcel Holtmann , "David S . Miller" , Matthias Brugger , Janus Piwek , =?utf-8?q?Michael_R=C3=B6der?= , Dollar Chen , Ken Yu , =?utf-8?q?Andreas_F=C3=A4rber?= 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 Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.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 -- 2.16.4 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");