summaryrefslogtreecommitdiffstats
path: root/drivers/net/ieee802154
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
commitace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch)
treeb2d64bc10158fdd5497876388cd68142ca374ed3 /drivers/net/ieee802154
parentInitial commit. (diff)
downloadlinux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz
linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/net/ieee802154')
-rw-r--r--drivers/net/ieee802154/Kconfig122
-rw-r--r--drivers/net/ieee802154/Makefile10
-rw-r--r--drivers/net/ieee802154/adf7242.c1353
-rw-r--r--drivers/net/ieee802154/at86rf230.c1675
-rw-r--r--drivers/net/ieee802154/at86rf230.h220
-rw-r--r--drivers/net/ieee802154/atusb.c1116
-rw-r--r--drivers/net/ieee802154/atusb.h95
-rw-r--r--drivers/net/ieee802154/ca8210.c3181
-rw-r--r--drivers/net/ieee802154/cc2520.c1192
-rw-r--r--drivers/net/ieee802154/fakelb.c263
-rw-r--r--drivers/net/ieee802154/mac802154_hwsim.c1094
-rw-r--r--drivers/net/ieee802154/mac802154_hwsim.h73
-rw-r--r--drivers/net/ieee802154/mcr20a.c1366
-rw-r--r--drivers/net/ieee802154/mcr20a.h489
-rw-r--r--drivers/net/ieee802154/mrf24j40.c1401
15 files changed, 13650 insertions, 0 deletions
diff --git a/drivers/net/ieee802154/Kconfig b/drivers/net/ieee802154/Kconfig
new file mode 100644
index 0000000000..95da876c56
--- /dev/null
+++ b/drivers/net/ieee802154/Kconfig
@@ -0,0 +1,122 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menuconfig IEEE802154_DRIVERS
+ tristate "IEEE 802.15.4 drivers"
+ depends on NETDEVICES && IEEE802154
+ default y
+ help
+ Say Y here to get to see options for IEEE 802.15.4 Low-Rate
+ Wireless Personal Area Network device drivers. This option alone
+ does not add any kernel code.
+
+ If you say N, all options in this submenu will be skipped and
+ disabled.
+
+config IEEE802154_FAKELB
+ depends on IEEE802154_DRIVERS && MAC802154
+ tristate "IEEE 802.15.4 loopback driver"
+ help
+ Say Y here to enable the fake driver that can emulate a net
+ of several interconnected radio devices.
+
+ This driver can also be built as a module. To do so say M here.
+ The module will be called 'fakelb'.
+
+config IEEE802154_AT86RF230
+ depends on IEEE802154_DRIVERS && MAC802154
+ tristate "AT86RF230/231/233/212 transceiver driver"
+ depends on SPI
+ select REGMAP_SPI
+ help
+ Say Y here to enable the at86rf230/231/233/212 SPI 802.15.4 wireless
+ controller.
+
+ This driver can also be built as a module. To do so, say M here.
+ the module will be called 'at86rf230'.
+
+config IEEE802154_MRF24J40
+ tristate "Microchip MRF24J40 transceiver driver"
+ depends on IEEE802154_DRIVERS && MAC802154
+ depends on SPI
+ select REGMAP_SPI
+ help
+ Say Y here to enable the MRF24J20 SPI 802.15.4 wireless
+ controller.
+
+ This driver can also be built as a module. To do so, say M here.
+ the module will be called 'mrf24j40'.
+
+config IEEE802154_CC2520
+ depends on IEEE802154_DRIVERS && MAC802154
+ tristate "CC2520 transceiver driver"
+ depends on SPI
+ help
+ Say Y here to enable the CC2520 SPI 802.15.4 wireless
+ controller.
+
+ This driver can also be built as a module. To do so, say M here.
+ the module will be called 'cc2520'.
+
+config IEEE802154_ATUSB
+ tristate "ATUSB transceiver driver"
+ depends on IEEE802154_DRIVERS && MAC802154 && USB
+ help
+ Say Y here to enable the ATUSB IEEE 802.15.4 wireless
+ controller.
+
+ This driver can also be built as a module. To do so say M here.
+ The module will be called 'atusb'.
+
+config IEEE802154_ADF7242
+ tristate "ADF7242 transceiver driver"
+ depends on IEEE802154_DRIVERS && MAC802154
+ depends on SPI
+ help
+ Say Y here to enable the ADF7242 SPI 802.15.4 wireless
+ controller.
+
+ This driver can also be built as a module. To do so, say M here.
+ the module will be called 'adf7242'.
+
+config IEEE802154_CA8210
+ tristate "Cascoda CA8210 transceiver driver"
+ depends on IEEE802154_DRIVERS && MAC802154
+ depends on COMMON_CLK
+ depends on SPI
+ help
+ Say Y here to enable the CA8210 SPI 802.15.4 wireless
+ controller.
+
+ This driver can also be built as a module. To do so, say M here.
+ the module will be called 'ca8210'.
+
+config IEEE802154_CA8210_DEBUGFS
+ bool "CA8210 debugfs interface"
+ depends on IEEE802154_CA8210
+ depends on DEBUG_FS
+ help
+ This option compiles debugfs code for the ca8210 driver. This
+ exposes a debugfs node for each CA8210 instance which allows
+ direct use of the Cascoda API, exposing the 802.15.4 MAC
+ management entities.
+
+config IEEE802154_MCR20A
+ tristate "MCR20A transceiver driver"
+ depends on IEEE802154_DRIVERS && MAC802154
+ depends on SPI
+ help
+ Say Y here to enable the MCR20A SPI 802.15.4 wireless
+ controller.
+
+ This driver can also be built as a module. To do so, say M here.
+ the module will be called 'mcr20a'.
+
+config IEEE802154_HWSIM
+ depends on IEEE802154_DRIVERS && MAC802154
+ tristate "Simulated radio testing tool for mac802154"
+ help
+ This driver is a developer testing tool that can be used to test
+ IEEE 802.15.4 networking stack (mac802154) functionality. This is not
+ needed for normal wpan usage and is only for testing.
+
+ This driver can also be built as a module. To do so say M here.
+ The module will be called 'mac802154_hwsim'.
diff --git a/drivers/net/ieee802154/Makefile b/drivers/net/ieee802154/Makefile
new file mode 100644
index 0000000000..0c78b62980
--- /dev/null
+++ b/drivers/net/ieee802154/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_IEEE802154_FAKELB) += fakelb.o
+obj-$(CONFIG_IEEE802154_AT86RF230) += at86rf230.o
+obj-$(CONFIG_IEEE802154_MRF24J40) += mrf24j40.o
+obj-$(CONFIG_IEEE802154_CC2520) += cc2520.o
+obj-$(CONFIG_IEEE802154_ATUSB) += atusb.o
+obj-$(CONFIG_IEEE802154_ADF7242) += adf7242.o
+obj-$(CONFIG_IEEE802154_CA8210) += ca8210.o
+obj-$(CONFIG_IEEE802154_MCR20A) += mcr20a.o
+obj-$(CONFIG_IEEE802154_HWSIM) += mac802154_hwsim.o
diff --git a/drivers/net/ieee802154/adf7242.c b/drivers/net/ieee802154/adf7242.c
new file mode 100644
index 0000000000..cc7ddc4002
--- /dev/null
+++ b/drivers/net/ieee802154/adf7242.c
@@ -0,0 +1,1353 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Analog Devices ADF7242 Low-Power IEEE 802.15.4 Transceiver
+ *
+ * Copyright 2009-2017 Analog Devices Inc.
+ *
+ * https://www.analog.com/ADF7242
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <linux/spinlock.h>
+#include <linux/firmware.h>
+#include <linux/spi/spi.h>
+#include <linux/skbuff.h>
+#include <linux/of.h>
+#include <linux/irq.h>
+#include <linux/debugfs.h>
+#include <linux/bitops.h>
+#include <linux/ieee802154.h>
+#include <net/mac802154.h>
+#include <net/cfg802154.h>
+
+#define FIRMWARE "adf7242_firmware.bin"
+#define MAX_POLL_LOOPS 200
+
+/* All Registers */
+
+#define REG_EXT_CTRL 0x100 /* RW External LNA/PA and internal PA control */
+#define REG_TX_FSK_TEST 0x101 /* RW TX FSK test mode configuration */
+#define REG_CCA1 0x105 /* RW RSSI threshold for CCA */
+#define REG_CCA2 0x106 /* RW CCA mode configuration */
+#define REG_BUFFERCFG 0x107 /* RW RX_BUFFER overwrite control */
+#define REG_PKT_CFG 0x108 /* RW FCS evaluation configuration */
+#define REG_DELAYCFG0 0x109 /* RW RC_RX command to SFD or sync word delay */
+#define REG_DELAYCFG1 0x10A /* RW RC_TX command to TX state */
+#define REG_DELAYCFG2 0x10B /* RW Mac delay extension */
+#define REG_SYNC_WORD0 0x10C /* RW sync word bits [7:0] of [23:0] */
+#define REG_SYNC_WORD1 0x10D /* RW sync word bits [15:8] of [23:0] */
+#define REG_SYNC_WORD2 0x10E /* RW sync word bits [23:16] of [23:0] */
+#define REG_SYNC_CONFIG 0x10F /* RW sync word configuration */
+#define REG_RC_CFG 0x13E /* RW RX / TX packet configuration */
+#define REG_RC_VAR44 0x13F /* RW RESERVED */
+#define REG_CH_FREQ0 0x300 /* RW Channel Frequency Settings - Low */
+#define REG_CH_FREQ1 0x301 /* RW Channel Frequency Settings - Middle */
+#define REG_CH_FREQ2 0x302 /* RW Channel Frequency Settings - High */
+#define REG_TX_FD 0x304 /* RW TX Frequency Deviation Register */
+#define REG_DM_CFG0 0x305 /* RW RX Discriminator BW Register */
+#define REG_TX_M 0x306 /* RW TX Mode Register */
+#define REG_RX_M 0x307 /* RW RX Mode Register */
+#define REG_RRB 0x30C /* R RSSI Readback Register */
+#define REG_LRB 0x30D /* R Link Quality Readback Register */
+#define REG_DR0 0x30E /* RW bits [15:8] of [15:0] data rate setting */
+#define REG_DR1 0x30F /* RW bits [7:0] of [15:0] data rate setting */
+#define REG_PRAMPG 0x313 /* RW RESERVED */
+#define REG_TXPB 0x314 /* RW TX Packet Storage Base Address */
+#define REG_RXPB 0x315 /* RW RX Packet Storage Base Address */
+#define REG_TMR_CFG0 0x316 /* RW Wake up Timer Conf Register - High */
+#define REG_TMR_CFG1 0x317 /* RW Wake up Timer Conf Register - Low */
+#define REG_TMR_RLD0 0x318 /* RW Wake up Timer Value Register - High */
+#define REG_TMR_RLD1 0x319 /* RW Wake up Timer Value Register - Low */
+#define REG_TMR_CTRL 0x31A /* RW Wake up Timer Timeout flag */
+#define REG_PD_AUX 0x31E /* RW Battmon enable */
+#define REG_GP_CFG 0x32C /* RW GPIO Configuration */
+#define REG_GP_OUT 0x32D /* RW GPIO Configuration */
+#define REG_GP_IN 0x32E /* R GPIO Configuration */
+#define REG_SYNT 0x335 /* RW bandwidth calibration timers */
+#define REG_CAL_CFG 0x33D /* RW Calibration Settings */
+#define REG_PA_BIAS 0x36E /* RW PA BIAS */
+#define REG_SYNT_CAL 0x371 /* RW Oscillator and Doubler Configuration */
+#define REG_IIRF_CFG 0x389 /* RW BB Filter Decimation Rate */
+#define REG_CDR_CFG 0x38A /* RW CDR kVCO */
+#define REG_DM_CFG1 0x38B /* RW Postdemodulator Filter */
+#define REG_AGCSTAT 0x38E /* R RXBB Ref Osc Calibration Engine Readback */
+#define REG_RXCAL0 0x395 /* RW RX BB filter tuning, LSB */
+#define REG_RXCAL1 0x396 /* RW RX BB filter tuning, MSB */
+#define REG_RXFE_CFG 0x39B /* RW RXBB Ref Osc & RXFE Calibration */
+#define REG_PA_RR 0x3A7 /* RW Set PA ramp rate */
+#define REG_PA_CFG 0x3A8 /* RW PA enable */
+#define REG_EXTPA_CFG 0x3A9 /* RW External PA BIAS DAC */
+#define REG_EXTPA_MSC 0x3AA /* RW PA Bias Mode */
+#define REG_ADC_RBK 0x3AE /* R Readback temp */
+#define REG_AGC_CFG1 0x3B2 /* RW GC Parameters */
+#define REG_AGC_MAX 0x3B4 /* RW Slew rate */
+#define REG_AGC_CFG2 0x3B6 /* RW RSSI Parameters */
+#define REG_AGC_CFG3 0x3B7 /* RW RSSI Parameters */
+#define REG_AGC_CFG4 0x3B8 /* RW RSSI Parameters */
+#define REG_AGC_CFG5 0x3B9 /* RW RSSI & NDEC Parameters */
+#define REG_AGC_CFG6 0x3BA /* RW NDEC Parameters */
+#define REG_OCL_CFG1 0x3C4 /* RW OCL System Parameters */
+#define REG_IRQ1_EN0 0x3C7 /* RW Interrupt Mask set bits for IRQ1 */
+#define REG_IRQ1_EN1 0x3C8 /* RW Interrupt Mask set bits for IRQ1 */
+#define REG_IRQ2_EN0 0x3C9 /* RW Interrupt Mask set bits for IRQ2 */
+#define REG_IRQ2_EN1 0x3CA /* RW Interrupt Mask set bits for IRQ2 */
+#define REG_IRQ1_SRC0 0x3CB /* RW Interrupt Source bits for IRQ */
+#define REG_IRQ1_SRC1 0x3CC /* RW Interrupt Source bits for IRQ */
+#define REG_OCL_BW0 0x3D2 /* RW OCL System Parameters */
+#define REG_OCL_BW1 0x3D3 /* RW OCL System Parameters */
+#define REG_OCL_BW2 0x3D4 /* RW OCL System Parameters */
+#define REG_OCL_BW3 0x3D5 /* RW OCL System Parameters */
+#define REG_OCL_BW4 0x3D6 /* RW OCL System Parameters */
+#define REG_OCL_BWS 0x3D7 /* RW OCL System Parameters */
+#define REG_OCL_CFG13 0x3E0 /* RW OCL System Parameters */
+#define REG_GP_DRV 0x3E3 /* RW I/O pads Configuration and bg trim */
+#define REG_BM_CFG 0x3E6 /* RW Batt. Monitor Threshold Voltage setting */
+#define REG_SFD_15_4 0x3F4 /* RW Option to set non standard SFD */
+#define REG_AFC_CFG 0x3F7 /* RW AFC mode and polarity */
+#define REG_AFC_KI_KP 0x3F8 /* RW AFC ki and kp */
+#define REG_AFC_RANGE 0x3F9 /* RW AFC range */
+#define REG_AFC_READ 0x3FA /* RW Readback frequency error */
+
+/* REG_EXTPA_MSC */
+#define PA_PWR(x) (((x) & 0xF) << 4)
+#define EXTPA_BIAS_SRC BIT(3)
+#define EXTPA_BIAS_MODE(x) (((x) & 0x7) << 0)
+
+/* REG_PA_CFG */
+#define PA_BRIDGE_DBIAS(x) (((x) & 0x1F) << 0)
+#define PA_DBIAS_HIGH_POWER 21
+#define PA_DBIAS_LOW_POWER 13
+
+/* REG_PA_BIAS */
+#define PA_BIAS_CTRL(x) (((x) & 0x1F) << 1)
+#define REG_PA_BIAS_DFL BIT(0)
+#define PA_BIAS_HIGH_POWER 63
+#define PA_BIAS_LOW_POWER 55
+
+#define REG_PAN_ID0 0x112
+#define REG_PAN_ID1 0x113
+#define REG_SHORT_ADDR_0 0x114
+#define REG_SHORT_ADDR_1 0x115
+#define REG_IEEE_ADDR_0 0x116
+#define REG_IEEE_ADDR_1 0x117
+#define REG_IEEE_ADDR_2 0x118
+#define REG_IEEE_ADDR_3 0x119
+#define REG_IEEE_ADDR_4 0x11A
+#define REG_IEEE_ADDR_5 0x11B
+#define REG_IEEE_ADDR_6 0x11C
+#define REG_IEEE_ADDR_7 0x11D
+#define REG_FFILT_CFG 0x11E
+#define REG_AUTO_CFG 0x11F
+#define REG_AUTO_TX1 0x120
+#define REG_AUTO_TX2 0x121
+#define REG_AUTO_STATUS 0x122
+
+/* REG_FFILT_CFG */
+#define ACCEPT_BEACON_FRAMES BIT(0)
+#define ACCEPT_DATA_FRAMES BIT(1)
+#define ACCEPT_ACK_FRAMES BIT(2)
+#define ACCEPT_MACCMD_FRAMES BIT(3)
+#define ACCEPT_RESERVED_FRAMES BIT(4)
+#define ACCEPT_ALL_ADDRESS BIT(5)
+
+/* REG_AUTO_CFG */
+#define AUTO_ACK_FRAMEPEND BIT(0)
+#define IS_PANCOORD BIT(1)
+#define RX_AUTO_ACK_EN BIT(3)
+#define CSMA_CA_RX_TURNAROUND BIT(4)
+
+/* REG_AUTO_TX1 */
+#define MAX_FRAME_RETRIES(x) ((x) & 0xF)
+#define MAX_CCA_RETRIES(x) (((x) & 0x7) << 4)
+
+/* REG_AUTO_TX2 */
+#define CSMA_MAX_BE(x) ((x) & 0xF)
+#define CSMA_MIN_BE(x) (((x) & 0xF) << 4)
+
+#define CMD_SPI_NOP 0xFF /* No operation. Use for dummy writes */
+#define CMD_SPI_PKT_WR 0x10 /* Write telegram to the Packet RAM
+ * starting from the TX packet base address
+ * pointer tx_packet_base
+ */
+#define CMD_SPI_PKT_RD 0x30 /* Read telegram from the Packet RAM
+ * starting from RX packet base address
+ * pointer rxpb.rx_packet_base
+ */
+#define CMD_SPI_MEM_WR(x) (0x18 + (x >> 8)) /* Write data to MCR or
+ * Packet RAM sequentially
+ */
+#define CMD_SPI_MEM_RD(x) (0x38 + (x >> 8)) /* Read data from MCR or
+ * Packet RAM sequentially
+ */
+#define CMD_SPI_MEMR_WR(x) (0x08 + (x >> 8)) /* Write data to MCR or Packet
+ * RAM as random block
+ */
+#define CMD_SPI_MEMR_RD(x) (0x28 + (x >> 8)) /* Read data from MCR or
+ * Packet RAM random block
+ */
+#define CMD_SPI_PRAM_WR 0x1E /* Write data sequentially to current
+ * PRAM page selected
+ */
+#define CMD_SPI_PRAM_RD 0x3E /* Read data sequentially from current
+ * PRAM page selected
+ */
+#define CMD_RC_SLEEP 0xB1 /* Invoke transition of radio controller
+ * into SLEEP state
+ */
+#define CMD_RC_IDLE 0xB2 /* Invoke transition of radio controller
+ * into IDLE state
+ */
+#define CMD_RC_PHY_RDY 0xB3 /* Invoke transition of radio controller
+ * into PHY_RDY state
+ */
+#define CMD_RC_RX 0xB4 /* Invoke transition of radio controller
+ * into RX state
+ */
+#define CMD_RC_TX 0xB5 /* Invoke transition of radio controller
+ * into TX state
+ */
+#define CMD_RC_MEAS 0xB6 /* Invoke transition of radio controller
+ * into MEAS state
+ */
+#define CMD_RC_CCA 0xB7 /* Invoke Clear channel assessment */
+#define CMD_RC_CSMACA 0xC1 /* initiates CSMA-CA channel access
+ * sequence and frame transmission
+ */
+#define CMD_RC_PC_RESET 0xC7 /* Program counter reset */
+#define CMD_RC_RESET 0xC8 /* Resets the ADF7242 and puts it in
+ * the sleep state
+ */
+#define CMD_RC_PC_RESET_NO_WAIT (CMD_RC_PC_RESET | BIT(31))
+
+/* STATUS */
+
+#define STAT_SPI_READY BIT(7)
+#define STAT_IRQ_STATUS BIT(6)
+#define STAT_RC_READY BIT(5)
+#define STAT_CCA_RESULT BIT(4)
+#define RC_STATUS_IDLE 1
+#define RC_STATUS_MEAS 2
+#define RC_STATUS_PHY_RDY 3
+#define RC_STATUS_RX 4
+#define RC_STATUS_TX 5
+#define RC_STATUS_MASK 0xF
+
+/* AUTO_STATUS */
+
+#define SUCCESS 0
+#define SUCCESS_DATPEND 1
+#define FAILURE_CSMACA 2
+#define FAILURE_NOACK 3
+#define AUTO_STATUS_MASK 0x3
+
+#define PRAM_PAGESIZE 256
+
+/* IRQ1 */
+
+#define IRQ_CCA_COMPLETE BIT(0)
+#define IRQ_SFD_RX BIT(1)
+#define IRQ_SFD_TX BIT(2)
+#define IRQ_RX_PKT_RCVD BIT(3)
+#define IRQ_TX_PKT_SENT BIT(4)
+#define IRQ_FRAME_VALID BIT(5)
+#define IRQ_ADDRESS_VALID BIT(6)
+#define IRQ_CSMA_CA BIT(7)
+
+#define AUTO_TX_TURNAROUND BIT(3)
+#define ADDON_EN BIT(4)
+
+#define FLAG_XMIT 0
+#define FLAG_START 1
+
+#define ADF7242_REPORT_CSMA_CA_STAT 0 /* framework doesn't handle yet */
+
+struct adf7242_local {
+ struct spi_device *spi;
+ struct completion tx_complete;
+ struct ieee802154_hw *hw;
+ struct mutex bmux; /* protect SPI messages */
+ struct spi_message stat_msg;
+ struct spi_transfer stat_xfer;
+ struct dentry *debugfs_root;
+ struct delayed_work work;
+ struct workqueue_struct *wqueue;
+ unsigned long flags;
+ int tx_stat;
+ bool promiscuous;
+ s8 rssi;
+ u8 max_frame_retries;
+ u8 max_cca_retries;
+ u8 max_be;
+ u8 min_be;
+
+ /* DMA (thus cache coherency maintenance) requires the
+ * transfer buffers to live in their own cache lines.
+ */
+
+ u8 buf[3] ____cacheline_aligned;
+ u8 buf_reg_tx[3];
+ u8 buf_read_tx[4];
+ u8 buf_read_rx[4];
+ u8 buf_stat_rx;
+ u8 buf_stat_tx;
+ u8 buf_cmd;
+};
+
+static int adf7242_soft_reset(struct adf7242_local *lp, int line);
+
+static int adf7242_status(struct adf7242_local *lp, u8 *stat)
+{
+ int status;
+
+ mutex_lock(&lp->bmux);
+ status = spi_sync(lp->spi, &lp->stat_msg);
+ *stat = lp->buf_stat_rx;
+ mutex_unlock(&lp->bmux);
+
+ return status;
+}
+
+static int adf7242_wait_status(struct adf7242_local *lp, unsigned int status,
+ unsigned int mask, int line)
+{
+ int cnt = 0, ret = 0;
+ u8 stat;
+
+ do {
+ adf7242_status(lp, &stat);
+ cnt++;
+ } while (((stat & mask) != status) && (cnt < MAX_POLL_LOOPS));
+
+ if (cnt >= MAX_POLL_LOOPS) {
+ ret = -ETIMEDOUT;
+
+ if (!(stat & STAT_RC_READY)) {
+ adf7242_soft_reset(lp, line);
+ adf7242_status(lp, &stat);
+
+ if ((stat & mask) == status)
+ ret = 0;
+ }
+
+ if (ret < 0)
+ dev_warn(&lp->spi->dev,
+ "%s:line %d Timeout status 0x%x (%d)\n",
+ __func__, line, stat, cnt);
+ }
+
+ dev_vdbg(&lp->spi->dev, "%s : loops=%d line %d\n", __func__, cnt, line);
+
+ return ret;
+}
+
+static int adf7242_wait_rc_ready(struct adf7242_local *lp, int line)
+{
+ return adf7242_wait_status(lp, STAT_RC_READY | STAT_SPI_READY,
+ STAT_RC_READY | STAT_SPI_READY, line);
+}
+
+static int adf7242_wait_spi_ready(struct adf7242_local *lp, int line)
+{
+ return adf7242_wait_status(lp, STAT_SPI_READY,
+ STAT_SPI_READY, line);
+}
+
+static int adf7242_write_fbuf(struct adf7242_local *lp, u8 *data, u8 len)
+{
+ u8 *buf = lp->buf;
+ int status;
+ struct spi_message msg;
+ struct spi_transfer xfer_head = {
+ .len = 2,
+ .tx_buf = buf,
+
+ };
+ struct spi_transfer xfer_buf = {
+ .len = len,
+ .tx_buf = data,
+ };
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&xfer_head, &msg);
+ spi_message_add_tail(&xfer_buf, &msg);
+
+ adf7242_wait_spi_ready(lp, __LINE__);
+
+ mutex_lock(&lp->bmux);
+ buf[0] = CMD_SPI_PKT_WR;
+ buf[1] = len + 2;
+
+ status = spi_sync(lp->spi, &msg);
+ mutex_unlock(&lp->bmux);
+
+ return status;
+}
+
+static int adf7242_read_fbuf(struct adf7242_local *lp,
+ u8 *data, size_t len, bool packet_read)
+{
+ u8 *buf = lp->buf;
+ int status;
+ struct spi_message msg;
+ struct spi_transfer xfer_head = {
+ .len = 3,
+ .tx_buf = buf,
+ .rx_buf = buf,
+ };
+ struct spi_transfer xfer_buf = {
+ .len = len,
+ .rx_buf = data,
+ };
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&xfer_head, &msg);
+ spi_message_add_tail(&xfer_buf, &msg);
+
+ adf7242_wait_spi_ready(lp, __LINE__);
+
+ mutex_lock(&lp->bmux);
+ if (packet_read) {
+ buf[0] = CMD_SPI_PKT_RD;
+ buf[1] = CMD_SPI_NOP;
+ buf[2] = 0; /* PHR */
+ } else {
+ buf[0] = CMD_SPI_PRAM_RD;
+ buf[1] = 0;
+ buf[2] = CMD_SPI_NOP;
+ }
+
+ status = spi_sync(lp->spi, &msg);
+
+ mutex_unlock(&lp->bmux);
+
+ return status;
+}
+
+static int adf7242_read_reg(struct adf7242_local *lp, u16 addr, u8 *data)
+{
+ int status;
+ struct spi_message msg;
+
+ struct spi_transfer xfer = {
+ .len = 4,
+ .tx_buf = lp->buf_read_tx,
+ .rx_buf = lp->buf_read_rx,
+ };
+
+ adf7242_wait_spi_ready(lp, __LINE__);
+
+ mutex_lock(&lp->bmux);
+ lp->buf_read_tx[0] = CMD_SPI_MEM_RD(addr);
+ lp->buf_read_tx[1] = addr;
+ lp->buf_read_tx[2] = CMD_SPI_NOP;
+ lp->buf_read_tx[3] = CMD_SPI_NOP;
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&xfer, &msg);
+
+ status = spi_sync(lp->spi, &msg);
+ if (msg.status)
+ status = msg.status;
+
+ if (!status)
+ *data = lp->buf_read_rx[3];
+
+ mutex_unlock(&lp->bmux);
+
+ dev_vdbg(&lp->spi->dev, "%s : REG 0x%X, VAL 0x%X\n", __func__,
+ addr, *data);
+
+ return status;
+}
+
+static int adf7242_write_reg(struct adf7242_local *lp, u16 addr, u8 data)
+{
+ int status;
+
+ adf7242_wait_spi_ready(lp, __LINE__);
+
+ mutex_lock(&lp->bmux);
+ lp->buf_reg_tx[0] = CMD_SPI_MEM_WR(addr);
+ lp->buf_reg_tx[1] = addr;
+ lp->buf_reg_tx[2] = data;
+ status = spi_write(lp->spi, lp->buf_reg_tx, 3);
+ mutex_unlock(&lp->bmux);
+
+ dev_vdbg(&lp->spi->dev, "%s : REG 0x%X, VAL 0x%X\n",
+ __func__, addr, data);
+
+ return status;
+}
+
+static int adf7242_cmd(struct adf7242_local *lp, unsigned int cmd)
+{
+ int status;
+
+ dev_vdbg(&lp->spi->dev, "%s : CMD=0x%X\n", __func__, cmd);
+
+ if (cmd != CMD_RC_PC_RESET_NO_WAIT)
+ adf7242_wait_rc_ready(lp, __LINE__);
+
+ mutex_lock(&lp->bmux);
+ lp->buf_cmd = cmd;
+ status = spi_write(lp->spi, &lp->buf_cmd, 1);
+ mutex_unlock(&lp->bmux);
+
+ return status;
+}
+
+static int adf7242_upload_firmware(struct adf7242_local *lp, u8 *data, u16 len)
+{
+ struct spi_message msg;
+ struct spi_transfer xfer_buf = { };
+ int status, i, page = 0;
+ u8 *buf = lp->buf;
+
+ struct spi_transfer xfer_head = {
+ .len = 2,
+ .tx_buf = buf,
+ };
+
+ buf[0] = CMD_SPI_PRAM_WR;
+ buf[1] = 0;
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&xfer_head, &msg);
+ spi_message_add_tail(&xfer_buf, &msg);
+
+ for (i = len; i >= 0; i -= PRAM_PAGESIZE) {
+ adf7242_write_reg(lp, REG_PRAMPG, page);
+
+ xfer_buf.len = (i >= PRAM_PAGESIZE) ? PRAM_PAGESIZE : i;
+ xfer_buf.tx_buf = &data[page * PRAM_PAGESIZE];
+
+ mutex_lock(&lp->bmux);
+ status = spi_sync(lp->spi, &msg);
+ mutex_unlock(&lp->bmux);
+ page++;
+ }
+
+ return status;
+}
+
+static int adf7242_verify_firmware(struct adf7242_local *lp,
+ const u8 *data, size_t len)
+{
+#ifdef DEBUG
+ int i, j;
+ unsigned int page;
+ u8 *buf = kmalloc(PRAM_PAGESIZE, GFP_KERNEL);
+
+ if (!buf)
+ return -ENOMEM;
+
+ for (page = 0, i = len; i >= 0; i -= PRAM_PAGESIZE, page++) {
+ size_t nb = (i >= PRAM_PAGESIZE) ? PRAM_PAGESIZE : i;
+
+ adf7242_write_reg(lp, REG_PRAMPG, page);
+ adf7242_read_fbuf(lp, buf, nb, false);
+
+ for (j = 0; j < nb; j++) {
+ if (buf[j] != data[page * PRAM_PAGESIZE + j]) {
+ kfree(buf);
+ return -EIO;
+ }
+ }
+ }
+ kfree(buf);
+#endif
+ return 0;
+}
+
+static void adf7242_clear_irqstat(struct adf7242_local *lp)
+{
+ adf7242_write_reg(lp, REG_IRQ1_SRC1, IRQ_CCA_COMPLETE | IRQ_SFD_RX |
+ IRQ_SFD_TX | IRQ_RX_PKT_RCVD | IRQ_TX_PKT_SENT |
+ IRQ_FRAME_VALID | IRQ_ADDRESS_VALID | IRQ_CSMA_CA);
+}
+
+static int adf7242_cmd_rx(struct adf7242_local *lp)
+{
+ /* Wait until the ACK is sent */
+ adf7242_wait_status(lp, RC_STATUS_PHY_RDY, RC_STATUS_MASK, __LINE__);
+ adf7242_clear_irqstat(lp);
+ mod_delayed_work(lp->wqueue, &lp->work, msecs_to_jiffies(400));
+
+ return adf7242_cmd(lp, CMD_RC_RX);
+}
+
+static void adf7242_rx_cal_work(struct work_struct *work)
+{
+ struct adf7242_local *lp =
+ container_of(work, struct adf7242_local, work.work);
+
+ /* Reissuing RC_RX every 400ms - to adjust for offset
+ * drift in receiver (datasheet page 61, OCL section)
+ */
+
+ if (!test_bit(FLAG_XMIT, &lp->flags)) {
+ adf7242_cmd(lp, CMD_RC_PHY_RDY);
+ adf7242_cmd_rx(lp);
+ }
+}
+
+static int adf7242_set_txpower(struct ieee802154_hw *hw, int mbm)
+{
+ struct adf7242_local *lp = hw->priv;
+ u8 pwr, bias_ctrl, dbias, tmp;
+ int db = mbm / 100;
+
+ dev_vdbg(&lp->spi->dev, "%s : Power %d dB\n", __func__, db);
+
+ if (db > 5 || db < -26)
+ return -EINVAL;
+
+ db = DIV_ROUND_CLOSEST(db + 29, 2);
+
+ if (db > 15) {
+ dbias = PA_DBIAS_HIGH_POWER;
+ bias_ctrl = PA_BIAS_HIGH_POWER;
+ } else {
+ dbias = PA_DBIAS_LOW_POWER;
+ bias_ctrl = PA_BIAS_LOW_POWER;
+ }
+
+ pwr = clamp_t(u8, db, 3, 15);
+
+ adf7242_read_reg(lp, REG_PA_CFG, &tmp);
+ tmp &= ~PA_BRIDGE_DBIAS(~0);
+ tmp |= PA_BRIDGE_DBIAS(dbias);
+ adf7242_write_reg(lp, REG_PA_CFG, tmp);
+
+ adf7242_read_reg(lp, REG_PA_BIAS, &tmp);
+ tmp &= ~PA_BIAS_CTRL(~0);
+ tmp |= PA_BIAS_CTRL(bias_ctrl);
+ adf7242_write_reg(lp, REG_PA_BIAS, tmp);
+
+ adf7242_read_reg(lp, REG_EXTPA_MSC, &tmp);
+ tmp &= ~PA_PWR(~0);
+ tmp |= PA_PWR(pwr);
+
+ return adf7242_write_reg(lp, REG_EXTPA_MSC, tmp);
+}
+
+static int adf7242_set_csma_params(struct ieee802154_hw *hw, u8 min_be,
+ u8 max_be, u8 retries)
+{
+ struct adf7242_local *lp = hw->priv;
+ int ret;
+
+ dev_vdbg(&lp->spi->dev, "%s : min_be=%d max_be=%d retries=%d\n",
+ __func__, min_be, max_be, retries);
+
+ if (min_be > max_be || max_be > 8 || retries > 5)
+ return -EINVAL;
+
+ ret = adf7242_write_reg(lp, REG_AUTO_TX1,
+ MAX_FRAME_RETRIES(lp->max_frame_retries) |
+ MAX_CCA_RETRIES(retries));
+ if (ret)
+ return ret;
+
+ lp->max_cca_retries = retries;
+ lp->max_be = max_be;
+ lp->min_be = min_be;
+
+ return adf7242_write_reg(lp, REG_AUTO_TX2, CSMA_MAX_BE(max_be) |
+ CSMA_MIN_BE(min_be));
+}
+
+static int adf7242_set_frame_retries(struct ieee802154_hw *hw, s8 retries)
+{
+ struct adf7242_local *lp = hw->priv;
+ int ret = 0;
+
+ dev_vdbg(&lp->spi->dev, "%s : Retries = %d\n", __func__, retries);
+
+ if (retries < -1 || retries > 15)
+ return -EINVAL;
+
+ if (retries >= 0)
+ ret = adf7242_write_reg(lp, REG_AUTO_TX1,
+ MAX_FRAME_RETRIES(retries) |
+ MAX_CCA_RETRIES(lp->max_cca_retries));
+
+ lp->max_frame_retries = retries;
+
+ return ret;
+}
+
+static int adf7242_ed(struct ieee802154_hw *hw, u8 *level)
+{
+ struct adf7242_local *lp = hw->priv;
+
+ *level = lp->rssi;
+
+ dev_vdbg(&lp->spi->dev, "%s :Exit level=%d\n",
+ __func__, *level);
+
+ return 0;
+}
+
+static int adf7242_start(struct ieee802154_hw *hw)
+{
+ struct adf7242_local *lp = hw->priv;
+
+ adf7242_cmd(lp, CMD_RC_PHY_RDY);
+ adf7242_clear_irqstat(lp);
+ enable_irq(lp->spi->irq);
+ set_bit(FLAG_START, &lp->flags);
+
+ return adf7242_cmd_rx(lp);
+}
+
+static void adf7242_stop(struct ieee802154_hw *hw)
+{
+ struct adf7242_local *lp = hw->priv;
+
+ disable_irq(lp->spi->irq);
+ cancel_delayed_work_sync(&lp->work);
+ adf7242_cmd(lp, CMD_RC_IDLE);
+ clear_bit(FLAG_START, &lp->flags);
+ adf7242_clear_irqstat(lp);
+}
+
+static int adf7242_channel(struct ieee802154_hw *hw, u8 page, u8 channel)
+{
+ struct adf7242_local *lp = hw->priv;
+ unsigned long freq;
+
+ dev_dbg(&lp->spi->dev, "%s :Channel=%d\n", __func__, channel);
+
+ might_sleep();
+
+ WARN_ON(page != 0);
+ WARN_ON(channel < 11);
+ WARN_ON(channel > 26);
+
+ freq = (2405 + 5 * (channel - 11)) * 100;
+ adf7242_cmd(lp, CMD_RC_PHY_RDY);
+
+ adf7242_write_reg(lp, REG_CH_FREQ0, freq);
+ adf7242_write_reg(lp, REG_CH_FREQ1, freq >> 8);
+ adf7242_write_reg(lp, REG_CH_FREQ2, freq >> 16);
+
+ if (test_bit(FLAG_START, &lp->flags))
+ return adf7242_cmd_rx(lp);
+ else
+ return adf7242_cmd(lp, CMD_RC_PHY_RDY);
+}
+
+static int adf7242_set_hw_addr_filt(struct ieee802154_hw *hw,
+ struct ieee802154_hw_addr_filt *filt,
+ unsigned long changed)
+{
+ struct adf7242_local *lp = hw->priv;
+ u8 reg;
+
+ dev_dbg(&lp->spi->dev, "%s :Changed=0x%lX\n", __func__, changed);
+
+ might_sleep();
+
+ if (changed & IEEE802154_AFILT_IEEEADDR_CHANGED) {
+ u8 addr[8], i;
+
+ memcpy(addr, &filt->ieee_addr, 8);
+
+ for (i = 0; i < 8; i++)
+ adf7242_write_reg(lp, REG_IEEE_ADDR_0 + i, addr[i]);
+ }
+
+ if (changed & IEEE802154_AFILT_SADDR_CHANGED) {
+ u16 saddr = le16_to_cpu(filt->short_addr);
+
+ adf7242_write_reg(lp, REG_SHORT_ADDR_0, saddr);
+ adf7242_write_reg(lp, REG_SHORT_ADDR_1, saddr >> 8);
+ }
+
+ if (changed & IEEE802154_AFILT_PANID_CHANGED) {
+ u16 pan_id = le16_to_cpu(filt->pan_id);
+
+ adf7242_write_reg(lp, REG_PAN_ID0, pan_id);
+ adf7242_write_reg(lp, REG_PAN_ID1, pan_id >> 8);
+ }
+
+ if (changed & IEEE802154_AFILT_PANC_CHANGED) {
+ adf7242_read_reg(lp, REG_AUTO_CFG, &reg);
+ if (filt->pan_coord)
+ reg |= IS_PANCOORD;
+ else
+ reg &= ~IS_PANCOORD;
+ adf7242_write_reg(lp, REG_AUTO_CFG, reg);
+ }
+
+ return 0;
+}
+
+static int adf7242_set_promiscuous_mode(struct ieee802154_hw *hw, bool on)
+{
+ struct adf7242_local *lp = hw->priv;
+
+ dev_dbg(&lp->spi->dev, "%s : mode %d\n", __func__, on);
+
+ lp->promiscuous = on;
+
+ if (on) {
+ adf7242_write_reg(lp, REG_AUTO_CFG, 0);
+ return adf7242_write_reg(lp, REG_FFILT_CFG,
+ ACCEPT_BEACON_FRAMES |
+ ACCEPT_DATA_FRAMES |
+ ACCEPT_MACCMD_FRAMES |
+ ACCEPT_ALL_ADDRESS |
+ ACCEPT_ACK_FRAMES |
+ ACCEPT_RESERVED_FRAMES);
+ } else {
+ adf7242_write_reg(lp, REG_FFILT_CFG,
+ ACCEPT_BEACON_FRAMES |
+ ACCEPT_DATA_FRAMES |
+ ACCEPT_MACCMD_FRAMES |
+ ACCEPT_RESERVED_FRAMES);
+
+ return adf7242_write_reg(lp, REG_AUTO_CFG, RX_AUTO_ACK_EN);
+ }
+}
+
+static int adf7242_set_cca_ed_level(struct ieee802154_hw *hw, s32 mbm)
+{
+ struct adf7242_local *lp = hw->priv;
+ s8 level = clamp_t(s8, mbm / 100, S8_MIN, S8_MAX);
+
+ dev_dbg(&lp->spi->dev, "%s : level %d\n", __func__, level);
+
+ return adf7242_write_reg(lp, REG_CCA1, level);
+}
+
+static int adf7242_xmit(struct ieee802154_hw *hw, struct sk_buff *skb)
+{
+ struct adf7242_local *lp = hw->priv;
+ int ret;
+
+ /* ensure existing instances of the IRQ handler have completed */
+ disable_irq(lp->spi->irq);
+ set_bit(FLAG_XMIT, &lp->flags);
+ cancel_delayed_work_sync(&lp->work);
+ reinit_completion(&lp->tx_complete);
+ adf7242_cmd(lp, CMD_RC_PHY_RDY);
+ adf7242_clear_irqstat(lp);
+
+ ret = adf7242_write_fbuf(lp, skb->data, skb->len);
+ if (ret)
+ goto err;
+
+ ret = adf7242_cmd(lp, CMD_RC_CSMACA);
+ if (ret)
+ goto err;
+ enable_irq(lp->spi->irq);
+
+ ret = wait_for_completion_interruptible_timeout(&lp->tx_complete,
+ HZ / 10);
+ if (ret < 0)
+ goto err;
+ if (ret == 0) {
+ dev_dbg(&lp->spi->dev, "Timeout waiting for TX interrupt\n");
+ ret = -ETIMEDOUT;
+ goto err;
+ }
+
+ if (lp->tx_stat != SUCCESS) {
+ dev_dbg(&lp->spi->dev,
+ "Error xmit: Retry count exceeded Status=0x%x\n",
+ lp->tx_stat);
+ ret = -ECOMM;
+ } else {
+ ret = 0;
+ }
+
+err:
+ clear_bit(FLAG_XMIT, &lp->flags);
+ adf7242_cmd_rx(lp);
+
+ return ret;
+}
+
+static int adf7242_rx(struct adf7242_local *lp)
+{
+ struct sk_buff *skb;
+ size_t len;
+ int ret;
+ u8 lqi, len_u8, *data;
+
+ ret = adf7242_read_reg(lp, 0, &len_u8);
+ if (ret)
+ return ret;
+
+ len = len_u8;
+
+ if (!ieee802154_is_valid_psdu_len(len)) {
+ dev_dbg(&lp->spi->dev,
+ "corrupted frame received len %d\n", (int)len);
+ len = IEEE802154_MTU;
+ }
+
+ skb = dev_alloc_skb(len);
+ if (!skb) {
+ adf7242_cmd_rx(lp);
+ return -ENOMEM;
+ }
+
+ data = skb_put(skb, len);
+ ret = adf7242_read_fbuf(lp, data, len, true);
+ if (ret < 0) {
+ kfree_skb(skb);
+ adf7242_cmd_rx(lp);
+ return ret;
+ }
+
+ lqi = data[len - 2];
+ lp->rssi = data[len - 1];
+
+ ret = adf7242_cmd_rx(lp);
+
+ skb_trim(skb, len - 2); /* Don't put RSSI/LQI or CRC into the frame */
+
+ ieee802154_rx_irqsafe(lp->hw, skb, lqi);
+
+ dev_dbg(&lp->spi->dev, "%s: ret=%d len=%d lqi=%d rssi=%d\n",
+ __func__, ret, (int)len, (int)lqi, lp->rssi);
+
+ return ret;
+}
+
+static const struct ieee802154_ops adf7242_ops = {
+ .owner = THIS_MODULE,
+ .xmit_sync = adf7242_xmit,
+ .ed = adf7242_ed,
+ .set_channel = adf7242_channel,
+ .set_hw_addr_filt = adf7242_set_hw_addr_filt,
+ .start = adf7242_start,
+ .stop = adf7242_stop,
+ .set_csma_params = adf7242_set_csma_params,
+ .set_frame_retries = adf7242_set_frame_retries,
+ .set_txpower = adf7242_set_txpower,
+ .set_promiscuous_mode = adf7242_set_promiscuous_mode,
+ .set_cca_ed_level = adf7242_set_cca_ed_level,
+};
+
+static void adf7242_debug(struct adf7242_local *lp, u8 irq1)
+{
+#ifdef DEBUG
+ u8 stat;
+
+ adf7242_status(lp, &stat);
+
+ dev_dbg(&lp->spi->dev, "%s IRQ1 = %X:\n%s%s%s%s%s%s%s%s\n",
+ __func__, irq1,
+ irq1 & IRQ_CCA_COMPLETE ? "IRQ_CCA_COMPLETE\n" : "",
+ irq1 & IRQ_SFD_RX ? "IRQ_SFD_RX\n" : "",
+ irq1 & IRQ_SFD_TX ? "IRQ_SFD_TX\n" : "",
+ irq1 & IRQ_RX_PKT_RCVD ? "IRQ_RX_PKT_RCVD\n" : "",
+ irq1 & IRQ_TX_PKT_SENT ? "IRQ_TX_PKT_SENT\n" : "",
+ irq1 & IRQ_CSMA_CA ? "IRQ_CSMA_CA\n" : "",
+ irq1 & IRQ_FRAME_VALID ? "IRQ_FRAME_VALID\n" : "",
+ irq1 & IRQ_ADDRESS_VALID ? "IRQ_ADDRESS_VALID\n" : "");
+
+ dev_dbg(&lp->spi->dev, "%s STATUS = %X:\n%s\n%s\n%s\n%s\n%s%s%s%s%s\n",
+ __func__, stat,
+ stat & STAT_SPI_READY ? "SPI_READY" : "SPI_BUSY",
+ stat & STAT_IRQ_STATUS ? "IRQ_PENDING" : "IRQ_CLEAR",
+ stat & STAT_RC_READY ? "RC_READY" : "RC_BUSY",
+ stat & STAT_CCA_RESULT ? "CHAN_IDLE" : "CHAN_BUSY",
+ (stat & 0xf) == RC_STATUS_IDLE ? "RC_STATUS_IDLE" : "",
+ (stat & 0xf) == RC_STATUS_MEAS ? "RC_STATUS_MEAS" : "",
+ (stat & 0xf) == RC_STATUS_PHY_RDY ? "RC_STATUS_PHY_RDY" : "",
+ (stat & 0xf) == RC_STATUS_RX ? "RC_STATUS_RX" : "",
+ (stat & 0xf) == RC_STATUS_TX ? "RC_STATUS_TX" : "");
+#endif
+}
+
+static irqreturn_t adf7242_isr(int irq, void *data)
+{
+ struct adf7242_local *lp = data;
+ unsigned int xmit;
+ u8 irq1;
+
+ mod_delayed_work(lp->wqueue, &lp->work, msecs_to_jiffies(400));
+ adf7242_read_reg(lp, REG_IRQ1_SRC1, &irq1);
+
+ if (!(irq1 & (IRQ_RX_PKT_RCVD | IRQ_CSMA_CA)))
+ dev_err(&lp->spi->dev, "%s :ERROR IRQ1 = 0x%X\n",
+ __func__, irq1);
+
+ adf7242_debug(lp, irq1);
+
+ xmit = test_bit(FLAG_XMIT, &lp->flags);
+
+ if (xmit && (irq1 & IRQ_CSMA_CA)) {
+ adf7242_wait_status(lp, RC_STATUS_PHY_RDY,
+ RC_STATUS_MASK, __LINE__);
+
+ if (ADF7242_REPORT_CSMA_CA_STAT) {
+ u8 astat;
+
+ adf7242_read_reg(lp, REG_AUTO_STATUS, &astat);
+ astat &= AUTO_STATUS_MASK;
+
+ dev_dbg(&lp->spi->dev, "AUTO_STATUS = %X:\n%s%s%s%s\n",
+ astat,
+ astat == SUCCESS ? "SUCCESS" : "",
+ astat ==
+ SUCCESS_DATPEND ? "SUCCESS_DATPEND" : "",
+ astat == FAILURE_CSMACA ? "FAILURE_CSMACA" : "",
+ astat == FAILURE_NOACK ? "FAILURE_NOACK" : "");
+
+ /* save CSMA-CA completion status */
+ lp->tx_stat = astat;
+ } else {
+ lp->tx_stat = SUCCESS;
+ }
+ complete(&lp->tx_complete);
+ adf7242_clear_irqstat(lp);
+ } else if (!xmit && (irq1 & IRQ_RX_PKT_RCVD) &&
+ (irq1 & IRQ_FRAME_VALID)) {
+ adf7242_rx(lp);
+ } else if (!xmit && test_bit(FLAG_START, &lp->flags)) {
+ /* Invalid packet received - drop it and restart */
+ dev_dbg(&lp->spi->dev, "%s:%d : ERROR IRQ1 = 0x%X\n",
+ __func__, __LINE__, irq1);
+ adf7242_cmd(lp, CMD_RC_PHY_RDY);
+ adf7242_cmd_rx(lp);
+ } else {
+ /* This can only be xmit without IRQ, likely a RX packet.
+ * we get an TX IRQ shortly - do nothing or let the xmit
+ * timeout handle this
+ */
+
+ dev_dbg(&lp->spi->dev, "%s:%d : ERROR IRQ1 = 0x%X, xmit %d\n",
+ __func__, __LINE__, irq1, xmit);
+ adf7242_wait_status(lp, RC_STATUS_PHY_RDY,
+ RC_STATUS_MASK, __LINE__);
+ complete(&lp->tx_complete);
+ adf7242_clear_irqstat(lp);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int adf7242_soft_reset(struct adf7242_local *lp, int line)
+{
+ dev_warn(&lp->spi->dev, "%s (line %d)\n", __func__, line);
+
+ if (test_bit(FLAG_START, &lp->flags))
+ disable_irq_nosync(lp->spi->irq);
+
+ adf7242_cmd(lp, CMD_RC_PC_RESET_NO_WAIT);
+ usleep_range(200, 250);
+ adf7242_write_reg(lp, REG_PKT_CFG, ADDON_EN | BIT(2));
+ adf7242_cmd(lp, CMD_RC_PHY_RDY);
+ adf7242_set_promiscuous_mode(lp->hw, lp->promiscuous);
+ adf7242_set_csma_params(lp->hw, lp->min_be, lp->max_be,
+ lp->max_cca_retries);
+ adf7242_clear_irqstat(lp);
+
+ if (test_bit(FLAG_START, &lp->flags)) {
+ enable_irq(lp->spi->irq);
+ return adf7242_cmd(lp, CMD_RC_RX);
+ }
+
+ return 0;
+}
+
+static int adf7242_hw_init(struct adf7242_local *lp)
+{
+ int ret;
+ const struct firmware *fw;
+
+ adf7242_cmd(lp, CMD_RC_RESET);
+ adf7242_cmd(lp, CMD_RC_IDLE);
+
+ /* get ADF7242 addon firmware
+ * build this driver as module
+ * and place under /lib/firmware/adf7242_firmware.bin
+ * or compile firmware into the kernel.
+ */
+ ret = request_firmware(&fw, FIRMWARE, &lp->spi->dev);
+ if (ret) {
+ dev_err(&lp->spi->dev,
+ "request_firmware() failed with %d\n", ret);
+ return ret;
+ }
+
+ ret = adf7242_upload_firmware(lp, (u8 *)fw->data, fw->size);
+ if (ret) {
+ dev_err(&lp->spi->dev,
+ "upload firmware failed with %d\n", ret);
+ release_firmware(fw);
+ return ret;
+ }
+
+ ret = adf7242_verify_firmware(lp, (u8 *)fw->data, fw->size);
+ if (ret) {
+ dev_err(&lp->spi->dev,
+ "verify firmware failed with %d\n", ret);
+ release_firmware(fw);
+ return ret;
+ }
+
+ adf7242_cmd(lp, CMD_RC_PC_RESET);
+
+ release_firmware(fw);
+
+ adf7242_write_reg(lp, REG_FFILT_CFG,
+ ACCEPT_BEACON_FRAMES |
+ ACCEPT_DATA_FRAMES |
+ ACCEPT_MACCMD_FRAMES |
+ ACCEPT_RESERVED_FRAMES);
+
+ adf7242_write_reg(lp, REG_AUTO_CFG, RX_AUTO_ACK_EN);
+
+ adf7242_write_reg(lp, REG_PKT_CFG, ADDON_EN | BIT(2));
+
+ adf7242_write_reg(lp, REG_EXTPA_MSC, 0xF1);
+ adf7242_write_reg(lp, REG_RXFE_CFG, 0x1D);
+
+ adf7242_write_reg(lp, REG_IRQ1_EN0, 0);
+ adf7242_write_reg(lp, REG_IRQ1_EN1, IRQ_RX_PKT_RCVD | IRQ_CSMA_CA);
+
+ adf7242_clear_irqstat(lp);
+ adf7242_write_reg(lp, REG_IRQ1_SRC0, 0xFF);
+
+ adf7242_cmd(lp, CMD_RC_IDLE);
+
+ return 0;
+}
+
+static int adf7242_stats_show(struct seq_file *file, void *offset)
+{
+ struct adf7242_local *lp = spi_get_drvdata(file->private);
+ u8 stat, irq1;
+
+ adf7242_status(lp, &stat);
+ adf7242_read_reg(lp, REG_IRQ1_SRC1, &irq1);
+
+ seq_printf(file, "IRQ1 = %X:\n%s%s%s%s%s%s%s%s\n", irq1,
+ irq1 & IRQ_CCA_COMPLETE ? "IRQ_CCA_COMPLETE\n" : "",
+ irq1 & IRQ_SFD_RX ? "IRQ_SFD_RX\n" : "",
+ irq1 & IRQ_SFD_TX ? "IRQ_SFD_TX\n" : "",
+ irq1 & IRQ_RX_PKT_RCVD ? "IRQ_RX_PKT_RCVD\n" : "",
+ irq1 & IRQ_TX_PKT_SENT ? "IRQ_TX_PKT_SENT\n" : "",
+ irq1 & IRQ_CSMA_CA ? "IRQ_CSMA_CA\n" : "",
+ irq1 & IRQ_FRAME_VALID ? "IRQ_FRAME_VALID\n" : "",
+ irq1 & IRQ_ADDRESS_VALID ? "IRQ_ADDRESS_VALID\n" : "");
+
+ seq_printf(file, "STATUS = %X:\n%s\n%s\n%s\n%s\n%s%s%s%s%s\n", stat,
+ stat & STAT_SPI_READY ? "SPI_READY" : "SPI_BUSY",
+ stat & STAT_IRQ_STATUS ? "IRQ_PENDING" : "IRQ_CLEAR",
+ stat & STAT_RC_READY ? "RC_READY" : "RC_BUSY",
+ stat & STAT_CCA_RESULT ? "CHAN_IDLE" : "CHAN_BUSY",
+ (stat & 0xf) == RC_STATUS_IDLE ? "RC_STATUS_IDLE" : "",
+ (stat & 0xf) == RC_STATUS_MEAS ? "RC_STATUS_MEAS" : "",
+ (stat & 0xf) == RC_STATUS_PHY_RDY ? "RC_STATUS_PHY_RDY" : "",
+ (stat & 0xf) == RC_STATUS_RX ? "RC_STATUS_RX" : "",
+ (stat & 0xf) == RC_STATUS_TX ? "RC_STATUS_TX" : "");
+
+ seq_printf(file, "RSSI = %d\n", lp->rssi);
+
+ return 0;
+}
+
+static void adf7242_debugfs_init(struct adf7242_local *lp)
+{
+ char debugfs_dir_name[DNAME_INLINE_LEN + 1];
+
+ snprintf(debugfs_dir_name, sizeof(debugfs_dir_name),
+ "adf7242-%s", dev_name(&lp->spi->dev));
+
+ lp->debugfs_root = debugfs_create_dir(debugfs_dir_name, NULL);
+
+ debugfs_create_devm_seqfile(&lp->spi->dev, "status", lp->debugfs_root,
+ adf7242_stats_show);
+}
+
+static const s32 adf7242_powers[] = {
+ 500, 400, 300, 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, -2400, -2500, -2600,
+};
+
+static const s32 adf7242_ed_levels[] = {
+ -9000, -8900, -8800, -8700, -8600, -8500, -8400, -8300, -8200, -8100,
+ -8000, -7900, -7800, -7700, -7600, -7500, -7400, -7300, -7200, -7100,
+ -7000, -6900, -6800, -6700, -6600, -6500, -6400, -6300, -6200, -6100,
+ -6000, -5900, -5800, -5700, -5600, -5500, -5400, -5300, -5200, -5100,
+ -5000, -4900, -4800, -4700, -4600, -4500, -4400, -4300, -4200, -4100,
+ -4000, -3900, -3800, -3700, -3600, -3500, -3400, -3200, -3100, -3000
+};
+
+static int adf7242_probe(struct spi_device *spi)
+{
+ struct ieee802154_hw *hw;
+ struct adf7242_local *lp;
+ int ret, irq_type;
+
+ if (!spi->irq) {
+ dev_err(&spi->dev, "no IRQ specified\n");
+ return -EINVAL;
+ }
+
+ hw = ieee802154_alloc_hw(sizeof(*lp), &adf7242_ops);
+ if (!hw)
+ return -ENOMEM;
+
+ lp = hw->priv;
+ lp->hw = hw;
+ lp->spi = spi;
+
+ hw->priv = lp;
+ hw->parent = &spi->dev;
+ hw->extra_tx_headroom = 0;
+
+ /* We support only 2.4 Ghz */
+ hw->phy->supported.channels[0] = 0x7FFF800;
+
+ hw->flags = IEEE802154_HW_OMIT_CKSUM |
+ IEEE802154_HW_CSMA_PARAMS |
+ IEEE802154_HW_FRAME_RETRIES | IEEE802154_HW_AFILT |
+ IEEE802154_HW_PROMISCUOUS;
+
+ hw->phy->flags = WPAN_PHY_FLAG_TXPOWER |
+ WPAN_PHY_FLAG_CCA_ED_LEVEL |
+ WPAN_PHY_FLAG_CCA_MODE;
+
+ hw->phy->supported.cca_modes = BIT(NL802154_CCA_ENERGY);
+
+ hw->phy->supported.cca_ed_levels = adf7242_ed_levels;
+ hw->phy->supported.cca_ed_levels_size = ARRAY_SIZE(adf7242_ed_levels);
+
+ hw->phy->cca.mode = NL802154_CCA_ENERGY;
+
+ hw->phy->supported.tx_powers = adf7242_powers;
+ hw->phy->supported.tx_powers_size = ARRAY_SIZE(adf7242_powers);
+
+ hw->phy->supported.min_minbe = 0;
+ hw->phy->supported.max_minbe = 8;
+
+ hw->phy->supported.min_maxbe = 3;
+ hw->phy->supported.max_maxbe = 8;
+
+ hw->phy->supported.min_frame_retries = 0;
+ hw->phy->supported.max_frame_retries = 15;
+
+ hw->phy->supported.min_csma_backoffs = 0;
+ hw->phy->supported.max_csma_backoffs = 5;
+
+ ieee802154_random_extended_addr(&hw->phy->perm_extended_addr);
+
+ mutex_init(&lp->bmux);
+ init_completion(&lp->tx_complete);
+
+ /* Setup Status Message */
+ lp->stat_xfer.len = 1;
+ lp->stat_xfer.tx_buf = &lp->buf_stat_tx;
+ lp->stat_xfer.rx_buf = &lp->buf_stat_rx;
+ lp->buf_stat_tx = CMD_SPI_NOP;
+
+ spi_message_init(&lp->stat_msg);
+ spi_message_add_tail(&lp->stat_xfer, &lp->stat_msg);
+
+ spi_set_drvdata(spi, lp);
+ INIT_DELAYED_WORK(&lp->work, adf7242_rx_cal_work);
+ lp->wqueue = alloc_ordered_workqueue(dev_name(&spi->dev),
+ WQ_MEM_RECLAIM);
+ if (unlikely(!lp->wqueue)) {
+ ret = -ENOMEM;
+ goto err_alloc_wq;
+ }
+
+ ret = adf7242_hw_init(lp);
+ if (ret)
+ goto err_hw_init;
+
+ irq_type = irq_get_trigger_type(spi->irq);
+ if (!irq_type)
+ irq_type = IRQF_TRIGGER_HIGH;
+
+ ret = devm_request_threaded_irq(&spi->dev, spi->irq, NULL, adf7242_isr,
+ irq_type | IRQF_ONESHOT,
+ dev_name(&spi->dev), lp);
+ if (ret)
+ goto err_hw_init;
+
+ disable_irq(spi->irq);
+
+ ret = ieee802154_register_hw(lp->hw);
+ if (ret)
+ goto err_hw_init;
+
+ dev_set_drvdata(&spi->dev, lp);
+
+ adf7242_debugfs_init(lp);
+
+ dev_info(&spi->dev, "mac802154 IRQ-%d registered\n", spi->irq);
+
+ return ret;
+
+err_hw_init:
+ destroy_workqueue(lp->wqueue);
+err_alloc_wq:
+ mutex_destroy(&lp->bmux);
+ ieee802154_free_hw(lp->hw);
+
+ return ret;
+}
+
+static void adf7242_remove(struct spi_device *spi)
+{
+ struct adf7242_local *lp = spi_get_drvdata(spi);
+
+ debugfs_remove_recursive(lp->debugfs_root);
+
+ ieee802154_unregister_hw(lp->hw);
+
+ cancel_delayed_work_sync(&lp->work);
+ destroy_workqueue(lp->wqueue);
+
+ mutex_destroy(&lp->bmux);
+ ieee802154_free_hw(lp->hw);
+}
+
+static const struct of_device_id adf7242_of_match[] = {
+ { .compatible = "adi,adf7242", },
+ { .compatible = "adi,adf7241", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, adf7242_of_match);
+
+static const struct spi_device_id adf7242_device_id[] = {
+ { .name = "adf7242", },
+ { .name = "adf7241", },
+ { },
+};
+MODULE_DEVICE_TABLE(spi, adf7242_device_id);
+
+static struct spi_driver adf7242_driver = {
+ .id_table = adf7242_device_id,
+ .driver = {
+ .of_match_table = adf7242_of_match,
+ .name = "adf7242",
+ },
+ .probe = adf7242_probe,
+ .remove = adf7242_remove,
+};
+
+module_spi_driver(adf7242_driver);
+
+MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>");
+MODULE_DESCRIPTION("ADF7242 IEEE802.15.4 Transceiver Driver");
+MODULE_LICENSE("GPL");
+
+MODULE_FIRMWARE(FIRMWARE);
diff --git a/drivers/net/ieee802154/at86rf230.c b/drivers/net/ieee802154/at86rf230.c
new file mode 100644
index 0000000000..164c7f605a
--- /dev/null
+++ b/drivers/net/ieee802154/at86rf230.c
@@ -0,0 +1,1675 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * AT86RF230/RF231 driver
+ *
+ * Copyright (C) 2009-2012 Siemens AG
+ *
+ * Written by:
+ * Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
+ * Alexander Smirnov <alex.bluesman.smirnov@gmail.com>
+ * Alexander Aring <aar@pengutronix.de>
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/hrtimer.h>
+#include <linux/jiffies.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/property.h>
+#include <linux/spi/spi.h>
+#include <linux/regmap.h>
+#include <linux/skbuff.h>
+#include <linux/of_gpio.h>
+#include <linux/ieee802154.h>
+
+#include <net/mac802154.h>
+#include <net/cfg802154.h>
+
+#include "at86rf230.h"
+
+struct at86rf230_local;
+/* at86rf2xx chip depend data.
+ * All timings are in us.
+ */
+struct at86rf2xx_chip_data {
+ u16 t_sleep_cycle;
+ u16 t_channel_switch;
+ u16 t_reset_to_off;
+ u16 t_off_to_aack;
+ u16 t_off_to_tx_on;
+ u16 t_off_to_sleep;
+ u16 t_sleep_to_off;
+ u16 t_frame;
+ u16 t_p_ack;
+ int rssi_base_val;
+
+ int (*set_channel)(struct at86rf230_local *, u8, u8);
+ int (*set_txpower)(struct at86rf230_local *, s32);
+};
+
+#define AT86RF2XX_MAX_BUF (127 + 3)
+/* tx retries to access the TX_ON state
+ * if it's above then force change will be started.
+ *
+ * We assume the max_frame_retries (7) value of 802.15.4 here.
+ */
+#define AT86RF2XX_MAX_TX_RETRIES 7
+/* We use the recommended 5 minutes timeout to recalibrate */
+#define AT86RF2XX_CAL_LOOP_TIMEOUT (5 * 60 * HZ)
+
+struct at86rf230_state_change {
+ struct at86rf230_local *lp;
+ int irq;
+
+ struct hrtimer timer;
+ struct spi_message msg;
+ struct spi_transfer trx;
+ u8 buf[AT86RF2XX_MAX_BUF];
+
+ void (*complete)(void *context);
+ u8 from_state;
+ u8 to_state;
+ int trac;
+
+ bool free;
+};
+
+struct at86rf230_local {
+ struct spi_device *spi;
+
+ struct ieee802154_hw *hw;
+ struct at86rf2xx_chip_data *data;
+ struct regmap *regmap;
+ struct gpio_desc *slp_tr;
+ bool sleep;
+
+ struct completion state_complete;
+ struct at86rf230_state_change state;
+
+ unsigned long cal_timeout;
+ bool is_tx;
+ bool is_tx_from_off;
+ bool was_tx;
+ u8 tx_retry;
+ struct sk_buff *tx_skb;
+ struct at86rf230_state_change tx;
+};
+
+#define AT86RF2XX_NUMREGS 0x3F
+
+static void
+at86rf230_async_state_change(struct at86rf230_local *lp,
+ struct at86rf230_state_change *ctx,
+ const u8 state, void (*complete)(void *context));
+
+static inline void
+at86rf230_sleep(struct at86rf230_local *lp)
+{
+ if (lp->slp_tr) {
+ gpiod_set_value(lp->slp_tr, 1);
+ usleep_range(lp->data->t_off_to_sleep,
+ lp->data->t_off_to_sleep + 10);
+ lp->sleep = true;
+ }
+}
+
+static inline void
+at86rf230_awake(struct at86rf230_local *lp)
+{
+ if (lp->slp_tr) {
+ gpiod_set_value(lp->slp_tr, 0);
+ usleep_range(lp->data->t_sleep_to_off,
+ lp->data->t_sleep_to_off + 100);
+ lp->sleep = false;
+ }
+}
+
+static inline int
+__at86rf230_write(struct at86rf230_local *lp,
+ unsigned int addr, unsigned int data)
+{
+ bool sleep = lp->sleep;
+ int ret;
+
+ /* awake for register setting if sleep */
+ if (sleep)
+ at86rf230_awake(lp);
+
+ ret = regmap_write(lp->regmap, addr, data);
+
+ /* sleep again if was sleeping */
+ if (sleep)
+ at86rf230_sleep(lp);
+
+ return ret;
+}
+
+static inline int
+__at86rf230_read(struct at86rf230_local *lp,
+ unsigned int addr, unsigned int *data)
+{
+ bool sleep = lp->sleep;
+ int ret;
+
+ /* awake for register setting if sleep */
+ if (sleep)
+ at86rf230_awake(lp);
+
+ ret = regmap_read(lp->regmap, addr, data);
+
+ /* sleep again if was sleeping */
+ if (sleep)
+ at86rf230_sleep(lp);
+
+ return ret;
+}
+
+static inline int
+at86rf230_read_subreg(struct at86rf230_local *lp,
+ unsigned int addr, unsigned int mask,
+ unsigned int shift, unsigned int *data)
+{
+ int rc;
+
+ rc = __at86rf230_read(lp, addr, data);
+ if (!rc)
+ *data = (*data & mask) >> shift;
+
+ return rc;
+}
+
+static inline int
+at86rf230_write_subreg(struct at86rf230_local *lp,
+ unsigned int addr, unsigned int mask,
+ unsigned int shift, unsigned int data)
+{
+ bool sleep = lp->sleep;
+ int ret;
+
+ /* awake for register setting if sleep */
+ if (sleep)
+ at86rf230_awake(lp);
+
+ ret = regmap_update_bits(lp->regmap, addr, mask, data << shift);
+
+ /* sleep again if was sleeping */
+ if (sleep)
+ at86rf230_sleep(lp);
+
+ return ret;
+}
+
+static inline void
+at86rf230_slp_tr_rising_edge(struct at86rf230_local *lp)
+{
+ gpiod_set_value(lp->slp_tr, 1);
+ udelay(1);
+ gpiod_set_value(lp->slp_tr, 0);
+}
+
+static bool
+at86rf230_reg_writeable(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case RG_TRX_STATE:
+ case RG_TRX_CTRL_0:
+ case RG_TRX_CTRL_1:
+ case RG_PHY_TX_PWR:
+ case RG_PHY_ED_LEVEL:
+ case RG_PHY_CC_CCA:
+ case RG_CCA_THRES:
+ case RG_RX_CTRL:
+ case RG_SFD_VALUE:
+ case RG_TRX_CTRL_2:
+ case RG_ANT_DIV:
+ case RG_IRQ_MASK:
+ case RG_VREG_CTRL:
+ case RG_BATMON:
+ case RG_XOSC_CTRL:
+ case RG_RX_SYN:
+ case RG_XAH_CTRL_1:
+ case RG_FTN_CTRL:
+ case RG_PLL_CF:
+ case RG_PLL_DCU:
+ case RG_SHORT_ADDR_0:
+ case RG_SHORT_ADDR_1:
+ case RG_PAN_ID_0:
+ case RG_PAN_ID_1:
+ case RG_IEEE_ADDR_0:
+ case RG_IEEE_ADDR_1:
+ case RG_IEEE_ADDR_2:
+ case RG_IEEE_ADDR_3:
+ case RG_IEEE_ADDR_4:
+ case RG_IEEE_ADDR_5:
+ case RG_IEEE_ADDR_6:
+ case RG_IEEE_ADDR_7:
+ case RG_XAH_CTRL_0:
+ case RG_CSMA_SEED_0:
+ case RG_CSMA_SEED_1:
+ case RG_CSMA_BE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool
+at86rf230_reg_readable(struct device *dev, unsigned int reg)
+{
+ bool rc;
+
+ /* all writeable are also readable */
+ rc = at86rf230_reg_writeable(dev, reg);
+ if (rc)
+ return rc;
+
+ /* readonly regs */
+ switch (reg) {
+ case RG_TRX_STATUS:
+ case RG_PHY_RSSI:
+ case RG_IRQ_STATUS:
+ case RG_PART_NUM:
+ case RG_VERSION_NUM:
+ case RG_MAN_ID_1:
+ case RG_MAN_ID_0:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool
+at86rf230_reg_volatile(struct device *dev, unsigned int reg)
+{
+ /* can be changed during runtime */
+ switch (reg) {
+ case RG_TRX_STATUS:
+ case RG_TRX_STATE:
+ case RG_PHY_RSSI:
+ case RG_PHY_ED_LEVEL:
+ case RG_IRQ_STATUS:
+ case RG_VREG_CTRL:
+ case RG_PLL_CF:
+ case RG_PLL_DCU:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool
+at86rf230_reg_precious(struct device *dev, unsigned int reg)
+{
+ /* don't clear irq line on read */
+ switch (reg) {
+ case RG_IRQ_STATUS:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config at86rf230_regmap_spi_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .write_flag_mask = CMD_REG | CMD_WRITE,
+ .read_flag_mask = CMD_REG,
+ .cache_type = REGCACHE_RBTREE,
+ .max_register = AT86RF2XX_NUMREGS,
+ .writeable_reg = at86rf230_reg_writeable,
+ .readable_reg = at86rf230_reg_readable,
+ .volatile_reg = at86rf230_reg_volatile,
+ .precious_reg = at86rf230_reg_precious,
+};
+
+static void
+at86rf230_async_error_recover_complete(void *context)
+{
+ struct at86rf230_state_change *ctx = context;
+ struct at86rf230_local *lp = ctx->lp;
+
+ if (ctx->free)
+ kfree(ctx);
+
+ if (lp->was_tx) {
+ lp->was_tx = 0;
+ ieee802154_xmit_hw_error(lp->hw, lp->tx_skb);
+ }
+}
+
+static void
+at86rf230_async_error_recover(void *context)
+{
+ struct at86rf230_state_change *ctx = context;
+ struct at86rf230_local *lp = ctx->lp;
+
+ if (lp->is_tx) {
+ lp->was_tx = 1;
+ lp->is_tx = 0;
+ }
+
+ at86rf230_async_state_change(lp, ctx, STATE_RX_AACK_ON,
+ at86rf230_async_error_recover_complete);
+}
+
+static inline void
+at86rf230_async_error(struct at86rf230_local *lp,
+ struct at86rf230_state_change *ctx, int rc)
+{
+ dev_err(&lp->spi->dev, "spi_async error %d\n", rc);
+
+ at86rf230_async_state_change(lp, ctx, STATE_FORCE_TRX_OFF,
+ at86rf230_async_error_recover);
+}
+
+/* Generic function to get some register value in async mode */
+static void
+at86rf230_async_read_reg(struct at86rf230_local *lp, u8 reg,
+ struct at86rf230_state_change *ctx,
+ void (*complete)(void *context))
+{
+ int rc;
+
+ u8 *tx_buf = ctx->buf;
+
+ tx_buf[0] = (reg & CMD_REG_MASK) | CMD_REG;
+ ctx->msg.complete = complete;
+ rc = spi_async(lp->spi, &ctx->msg);
+ if (rc)
+ at86rf230_async_error(lp, ctx, rc);
+}
+
+static void
+at86rf230_async_write_reg(struct at86rf230_local *lp, u8 reg, u8 val,
+ struct at86rf230_state_change *ctx,
+ void (*complete)(void *context))
+{
+ int rc;
+
+ ctx->buf[0] = (reg & CMD_REG_MASK) | CMD_REG | CMD_WRITE;
+ ctx->buf[1] = val;
+ ctx->msg.complete = complete;
+ rc = spi_async(lp->spi, &ctx->msg);
+ if (rc)
+ at86rf230_async_error(lp, ctx, rc);
+}
+
+static void
+at86rf230_async_state_assert(void *context)
+{
+ struct at86rf230_state_change *ctx = context;
+ struct at86rf230_local *lp = ctx->lp;
+ const u8 *buf = ctx->buf;
+ const u8 trx_state = buf[1] & TRX_STATE_MASK;
+
+ /* Assert state change */
+ if (trx_state != ctx->to_state) {
+ /* Special handling if transceiver state is in
+ * STATE_BUSY_RX_AACK and a SHR was detected.
+ */
+ if (trx_state == STATE_BUSY_RX_AACK) {
+ /* Undocumented race condition. If we send a state
+ * change to STATE_RX_AACK_ON the transceiver could
+ * change his state automatically to STATE_BUSY_RX_AACK
+ * if a SHR was detected. This is not an error, but we
+ * can't assert this.
+ */
+ if (ctx->to_state == STATE_RX_AACK_ON)
+ goto done;
+
+ /* If we change to STATE_TX_ON without forcing and
+ * transceiver state is STATE_BUSY_RX_AACK, we wait
+ * 'tFrame + tPAck' receiving time. In this time the
+ * PDU should be received. If the transceiver is still
+ * in STATE_BUSY_RX_AACK, we run a force state change
+ * to STATE_TX_ON. This is a timeout handling, if the
+ * transceiver stucks in STATE_BUSY_RX_AACK.
+ *
+ * Additional we do several retries to try to get into
+ * TX_ON state without forcing. If the retries are
+ * higher or equal than AT86RF2XX_MAX_TX_RETRIES we
+ * will do a force change.
+ */
+ if (ctx->to_state == STATE_TX_ON ||
+ ctx->to_state == STATE_TRX_OFF) {
+ u8 state = ctx->to_state;
+
+ if (lp->tx_retry >= AT86RF2XX_MAX_TX_RETRIES)
+ state = STATE_FORCE_TRX_OFF;
+ lp->tx_retry++;
+
+ at86rf230_async_state_change(lp, ctx, state,
+ ctx->complete);
+ return;
+ }
+ }
+
+ dev_warn(&lp->spi->dev, "unexcept state change from 0x%02x to 0x%02x. Actual state: 0x%02x\n",
+ ctx->from_state, ctx->to_state, trx_state);
+ }
+
+done:
+ if (ctx->complete)
+ ctx->complete(context);
+}
+
+static enum hrtimer_restart at86rf230_async_state_timer(struct hrtimer *timer)
+{
+ struct at86rf230_state_change *ctx =
+ container_of(timer, struct at86rf230_state_change, timer);
+ struct at86rf230_local *lp = ctx->lp;
+
+ at86rf230_async_read_reg(lp, RG_TRX_STATUS, ctx,
+ at86rf230_async_state_assert);
+
+ return HRTIMER_NORESTART;
+}
+
+/* Do state change timing delay. */
+static void
+at86rf230_async_state_delay(void *context)
+{
+ struct at86rf230_state_change *ctx = context;
+ struct at86rf230_local *lp = ctx->lp;
+ struct at86rf2xx_chip_data *c = lp->data;
+ bool force = false;
+ ktime_t tim;
+
+ /* The force state changes are will show as normal states in the
+ * state status subregister. We change the to_state to the
+ * corresponding one and remember if it was a force change, this
+ * differs if we do a state change from STATE_BUSY_RX_AACK.
+ */
+ switch (ctx->to_state) {
+ case STATE_FORCE_TX_ON:
+ ctx->to_state = STATE_TX_ON;
+ force = true;
+ break;
+ case STATE_FORCE_TRX_OFF:
+ ctx->to_state = STATE_TRX_OFF;
+ force = true;
+ break;
+ default:
+ break;
+ }
+
+ switch (ctx->from_state) {
+ case STATE_TRX_OFF:
+ switch (ctx->to_state) {
+ case STATE_RX_AACK_ON:
+ tim = c->t_off_to_aack * NSEC_PER_USEC;
+ /* state change from TRX_OFF to RX_AACK_ON to do a
+ * calibration, we need to reset the timeout for the
+ * next one.
+ */
+ lp->cal_timeout = jiffies + AT86RF2XX_CAL_LOOP_TIMEOUT;
+ goto change;
+ case STATE_TX_ARET_ON:
+ case STATE_TX_ON:
+ tim = c->t_off_to_tx_on * NSEC_PER_USEC;
+ /* state change from TRX_OFF to TX_ON or ARET_ON to do
+ * a calibration, we need to reset the timeout for the
+ * next one.
+ */
+ lp->cal_timeout = jiffies + AT86RF2XX_CAL_LOOP_TIMEOUT;
+ goto change;
+ default:
+ break;
+ }
+ break;
+ case STATE_BUSY_RX_AACK:
+ switch (ctx->to_state) {
+ case STATE_TRX_OFF:
+ case STATE_TX_ON:
+ /* Wait for worst case receiving time if we
+ * didn't make a force change from BUSY_RX_AACK
+ * to TX_ON or TRX_OFF.
+ */
+ if (!force) {
+ tim = (c->t_frame + c->t_p_ack) * NSEC_PER_USEC;
+ goto change;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ /* Default value, means RESET state */
+ case STATE_P_ON:
+ switch (ctx->to_state) {
+ case STATE_TRX_OFF:
+ tim = c->t_reset_to_off * NSEC_PER_USEC;
+ goto change;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ /* Default delay is 1us in the most cases */
+ udelay(1);
+ at86rf230_async_state_timer(&ctx->timer);
+ return;
+
+change:
+ hrtimer_start(&ctx->timer, tim, HRTIMER_MODE_REL);
+}
+
+static void
+at86rf230_async_state_change_start(void *context)
+{
+ struct at86rf230_state_change *ctx = context;
+ struct at86rf230_local *lp = ctx->lp;
+ u8 *buf = ctx->buf;
+ const u8 trx_state = buf[1] & TRX_STATE_MASK;
+
+ /* Check for "possible" STATE_TRANSITION_IN_PROGRESS */
+ if (trx_state == STATE_TRANSITION_IN_PROGRESS) {
+ udelay(1);
+ at86rf230_async_read_reg(lp, RG_TRX_STATUS, ctx,
+ at86rf230_async_state_change_start);
+ return;
+ }
+
+ /* Check if we already are in the state which we change in */
+ if (trx_state == ctx->to_state) {
+ if (ctx->complete)
+ ctx->complete(context);
+ return;
+ }
+
+ /* Set current state to the context of state change */
+ ctx->from_state = trx_state;
+
+ /* Going into the next step for a state change which do a timing
+ * relevant delay.
+ */
+ at86rf230_async_write_reg(lp, RG_TRX_STATE, ctx->to_state, ctx,
+ at86rf230_async_state_delay);
+}
+
+static void
+at86rf230_async_state_change(struct at86rf230_local *lp,
+ struct at86rf230_state_change *ctx,
+ const u8 state, void (*complete)(void *context))
+{
+ /* Initialization for the state change context */
+ ctx->to_state = state;
+ ctx->complete = complete;
+ at86rf230_async_read_reg(lp, RG_TRX_STATUS, ctx,
+ at86rf230_async_state_change_start);
+}
+
+static void
+at86rf230_sync_state_change_complete(void *context)
+{
+ struct at86rf230_state_change *ctx = context;
+ struct at86rf230_local *lp = ctx->lp;
+
+ complete(&lp->state_complete);
+}
+
+/* This function do a sync framework above the async state change.
+ * Some callbacks of the IEEE 802.15.4 driver interface need to be
+ * handled synchronously.
+ */
+static int
+at86rf230_sync_state_change(struct at86rf230_local *lp, unsigned int state)
+{
+ unsigned long rc;
+
+ at86rf230_async_state_change(lp, &lp->state, state,
+ at86rf230_sync_state_change_complete);
+
+ rc = wait_for_completion_timeout(&lp->state_complete,
+ msecs_to_jiffies(100));
+ if (!rc) {
+ at86rf230_async_error(lp, &lp->state, -ETIMEDOUT);
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static void
+at86rf230_tx_complete(void *context)
+{
+ struct at86rf230_state_change *ctx = context;
+ struct at86rf230_local *lp = ctx->lp;
+
+ if (ctx->trac == IEEE802154_SUCCESS)
+ ieee802154_xmit_complete(lp->hw, lp->tx_skb, false);
+ else
+ ieee802154_xmit_error(lp->hw, lp->tx_skb, ctx->trac);
+
+ kfree(ctx);
+}
+
+static void
+at86rf230_tx_on(void *context)
+{
+ struct at86rf230_state_change *ctx = context;
+ struct at86rf230_local *lp = ctx->lp;
+
+ at86rf230_async_state_change(lp, ctx, STATE_RX_AACK_ON,
+ at86rf230_tx_complete);
+}
+
+static void
+at86rf230_tx_trac_check(void *context)
+{
+ struct at86rf230_state_change *ctx = context;
+ struct at86rf230_local *lp = ctx->lp;
+ u8 trac = TRAC_MASK(ctx->buf[1]);
+
+ switch (trac) {
+ case TRAC_SUCCESS:
+ case TRAC_SUCCESS_DATA_PENDING:
+ ctx->trac = IEEE802154_SUCCESS;
+ break;
+ case TRAC_CHANNEL_ACCESS_FAILURE:
+ ctx->trac = IEEE802154_CHANNEL_ACCESS_FAILURE;
+ break;
+ case TRAC_NO_ACK:
+ ctx->trac = IEEE802154_NO_ACK;
+ break;
+ default:
+ ctx->trac = IEEE802154_SYSTEM_ERROR;
+ }
+
+ at86rf230_async_state_change(lp, ctx, STATE_TX_ON, at86rf230_tx_on);
+}
+
+static void
+at86rf230_rx_read_frame_complete(void *context)
+{
+ struct at86rf230_state_change *ctx = context;
+ struct at86rf230_local *lp = ctx->lp;
+ const u8 *buf = ctx->buf;
+ struct sk_buff *skb;
+ u8 len, lqi;
+
+ len = buf[1];
+ if (!ieee802154_is_valid_psdu_len(len)) {
+ dev_vdbg(&lp->spi->dev, "corrupted frame received\n");
+ len = IEEE802154_MTU;
+ }
+ lqi = buf[2 + len];
+
+ skb = dev_alloc_skb(IEEE802154_MTU);
+ if (!skb) {
+ dev_vdbg(&lp->spi->dev, "failed to allocate sk_buff\n");
+ kfree(ctx);
+ return;
+ }
+
+ skb_put_data(skb, buf + 2, len);
+ ieee802154_rx_irqsafe(lp->hw, skb, lqi);
+ kfree(ctx);
+}
+
+static void
+at86rf230_rx_trac_check(void *context)
+{
+ struct at86rf230_state_change *ctx = context;
+ struct at86rf230_local *lp = ctx->lp;
+ u8 *buf = ctx->buf;
+ int rc;
+
+ buf[0] = CMD_FB;
+ ctx->trx.len = AT86RF2XX_MAX_BUF;
+ ctx->msg.complete = at86rf230_rx_read_frame_complete;
+ rc = spi_async(lp->spi, &ctx->msg);
+ if (rc) {
+ ctx->trx.len = 2;
+ at86rf230_async_error(lp, ctx, rc);
+ }
+}
+
+static void
+at86rf230_irq_trx_end(void *context)
+{
+ struct at86rf230_state_change *ctx = context;
+ struct at86rf230_local *lp = ctx->lp;
+
+ if (lp->is_tx) {
+ lp->is_tx = 0;
+ at86rf230_async_read_reg(lp, RG_TRX_STATE, ctx,
+ at86rf230_tx_trac_check);
+ } else {
+ at86rf230_async_read_reg(lp, RG_TRX_STATE, ctx,
+ at86rf230_rx_trac_check);
+ }
+}
+
+static void
+at86rf230_irq_status(void *context)
+{
+ struct at86rf230_state_change *ctx = context;
+ struct at86rf230_local *lp = ctx->lp;
+ const u8 *buf = ctx->buf;
+ u8 irq = buf[1];
+
+ enable_irq(lp->spi->irq);
+
+ if (irq & IRQ_TRX_END) {
+ at86rf230_irq_trx_end(ctx);
+ } else {
+ dev_err(&lp->spi->dev, "not supported irq %02x received\n",
+ irq);
+ kfree(ctx);
+ }
+}
+
+static void
+at86rf230_setup_spi_messages(struct at86rf230_local *lp,
+ struct at86rf230_state_change *state)
+{
+ state->lp = lp;
+ state->irq = lp->spi->irq;
+ spi_message_init(&state->msg);
+ state->msg.context = state;
+ state->trx.len = 2;
+ state->trx.tx_buf = state->buf;
+ state->trx.rx_buf = state->buf;
+ spi_message_add_tail(&state->trx, &state->msg);
+ hrtimer_init(&state->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ state->timer.function = at86rf230_async_state_timer;
+}
+
+static irqreturn_t at86rf230_isr(int irq, void *data)
+{
+ struct at86rf230_local *lp = data;
+ struct at86rf230_state_change *ctx;
+ int rc;
+
+ disable_irq_nosync(irq);
+
+ ctx = kzalloc(sizeof(*ctx), GFP_ATOMIC);
+ if (!ctx) {
+ enable_irq(irq);
+ return IRQ_NONE;
+ }
+
+ at86rf230_setup_spi_messages(lp, ctx);
+ /* tell on error handling to free ctx */
+ ctx->free = true;
+
+ ctx->buf[0] = (RG_IRQ_STATUS & CMD_REG_MASK) | CMD_REG;
+ ctx->msg.complete = at86rf230_irq_status;
+ rc = spi_async(lp->spi, &ctx->msg);
+ if (rc) {
+ at86rf230_async_error(lp, ctx, rc);
+ enable_irq(irq);
+ return IRQ_NONE;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void
+at86rf230_write_frame_complete(void *context)
+{
+ struct at86rf230_state_change *ctx = context;
+ struct at86rf230_local *lp = ctx->lp;
+
+ ctx->trx.len = 2;
+
+ if (lp->slp_tr)
+ at86rf230_slp_tr_rising_edge(lp);
+ else
+ at86rf230_async_write_reg(lp, RG_TRX_STATE, STATE_BUSY_TX, ctx,
+ NULL);
+}
+
+static void
+at86rf230_write_frame(void *context)
+{
+ struct at86rf230_state_change *ctx = context;
+ struct at86rf230_local *lp = ctx->lp;
+ struct sk_buff *skb = lp->tx_skb;
+ u8 *buf = ctx->buf;
+ int rc;
+
+ lp->is_tx = 1;
+
+ buf[0] = CMD_FB | CMD_WRITE;
+ buf[1] = skb->len + 2;
+ memcpy(buf + 2, skb->data, skb->len);
+ ctx->trx.len = skb->len + 2;
+ ctx->msg.complete = at86rf230_write_frame_complete;
+ rc = spi_async(lp->spi, &ctx->msg);
+ if (rc) {
+ ctx->trx.len = 2;
+ at86rf230_async_error(lp, ctx, rc);
+ }
+}
+
+static void
+at86rf230_xmit_tx_on(void *context)
+{
+ struct at86rf230_state_change *ctx = context;
+ struct at86rf230_local *lp = ctx->lp;
+
+ at86rf230_async_state_change(lp, ctx, STATE_TX_ARET_ON,
+ at86rf230_write_frame);
+}
+
+static void
+at86rf230_xmit_start(void *context)
+{
+ struct at86rf230_state_change *ctx = context;
+ struct at86rf230_local *lp = ctx->lp;
+
+ /* check if we change from off state */
+ if (lp->is_tx_from_off)
+ at86rf230_async_state_change(lp, ctx, STATE_TX_ARET_ON,
+ at86rf230_write_frame);
+ else
+ at86rf230_async_state_change(lp, ctx, STATE_TX_ON,
+ at86rf230_xmit_tx_on);
+}
+
+static int
+at86rf230_xmit(struct ieee802154_hw *hw, struct sk_buff *skb)
+{
+ struct at86rf230_local *lp = hw->priv;
+ struct at86rf230_state_change *ctx = &lp->tx;
+
+ lp->tx_skb = skb;
+ lp->tx_retry = 0;
+
+ /* After 5 minutes in PLL and the same frequency we run again the
+ * calibration loops which is recommended by at86rf2xx datasheets.
+ *
+ * The calibration is initiate by a state change from TRX_OFF
+ * to TX_ON, the lp->cal_timeout should be reinit by state_delay
+ * function then to start in the next 5 minutes.
+ */
+ if (time_is_before_jiffies(lp->cal_timeout)) {
+ lp->is_tx_from_off = true;
+ at86rf230_async_state_change(lp, ctx, STATE_TRX_OFF,
+ at86rf230_xmit_start);
+ } else {
+ lp->is_tx_from_off = false;
+ at86rf230_xmit_start(ctx);
+ }
+
+ return 0;
+}
+
+static int
+at86rf230_ed(struct ieee802154_hw *hw, u8 *level)
+{
+ WARN_ON(!level);
+ *level = 0xbe;
+ return 0;
+}
+
+static int
+at86rf230_start(struct ieee802154_hw *hw)
+{
+ struct at86rf230_local *lp = hw->priv;
+
+ at86rf230_awake(lp);
+ enable_irq(lp->spi->irq);
+
+ return at86rf230_sync_state_change(lp, STATE_RX_AACK_ON);
+}
+
+static void
+at86rf230_stop(struct ieee802154_hw *hw)
+{
+ struct at86rf230_local *lp = hw->priv;
+ u8 csma_seed[2];
+
+ at86rf230_sync_state_change(lp, STATE_FORCE_TRX_OFF);
+
+ disable_irq(lp->spi->irq);
+
+ /* It's recommended to set random new csma_seeds before sleep state.
+ * Makes only sense in the stop callback, not doing this inside of
+ * at86rf230_sleep, this is also used when we don't transmit afterwards
+ * when calling start callback again.
+ */
+ get_random_bytes(csma_seed, ARRAY_SIZE(csma_seed));
+ at86rf230_write_subreg(lp, SR_CSMA_SEED_0, csma_seed[0]);
+ at86rf230_write_subreg(lp, SR_CSMA_SEED_1, csma_seed[1]);
+
+ at86rf230_sleep(lp);
+}
+
+static int
+at86rf23x_set_channel(struct at86rf230_local *lp, u8 page, u8 channel)
+{
+ return at86rf230_write_subreg(lp, SR_CHANNEL, channel);
+}
+
+#define AT86RF2XX_MAX_ED_LEVELS 0xF
+static const s32 at86rf233_ed_levels[AT86RF2XX_MAX_ED_LEVELS + 1] = {
+ -9400, -9200, -9000, -8800, -8600, -8400, -8200, -8000, -7800, -7600,
+ -7400, -7200, -7000, -6800, -6600, -6400,
+};
+
+static const s32 at86rf231_ed_levels[AT86RF2XX_MAX_ED_LEVELS + 1] = {
+ -9100, -8900, -8700, -8500, -8300, -8100, -7900, -7700, -7500, -7300,
+ -7100, -6900, -6700, -6500, -6300, -6100,
+};
+
+static const s32 at86rf212_ed_levels_100[AT86RF2XX_MAX_ED_LEVELS + 1] = {
+ -10000, -9800, -9600, -9400, -9200, -9000, -8800, -8600, -8400, -8200,
+ -8000, -7800, -7600, -7400, -7200, -7000,
+};
+
+static const s32 at86rf212_ed_levels_98[AT86RF2XX_MAX_ED_LEVELS + 1] = {
+ -9800, -9600, -9400, -9200, -9000, -8800, -8600, -8400, -8200, -8000,
+ -7800, -7600, -7400, -7200, -7000, -6800,
+};
+
+static inline int
+at86rf212_update_cca_ed_level(struct at86rf230_local *lp, int rssi_base_val)
+{
+ unsigned int cca_ed_thres;
+ int rc;
+
+ rc = at86rf230_read_subreg(lp, SR_CCA_ED_THRES, &cca_ed_thres);
+ if (rc < 0)
+ return rc;
+
+ switch (rssi_base_val) {
+ case -98:
+ lp->hw->phy->supported.cca_ed_levels = at86rf212_ed_levels_98;
+ lp->hw->phy->supported.cca_ed_levels_size = ARRAY_SIZE(at86rf212_ed_levels_98);
+ lp->hw->phy->cca_ed_level = at86rf212_ed_levels_98[cca_ed_thres];
+ break;
+ case -100:
+ lp->hw->phy->supported.cca_ed_levels = at86rf212_ed_levels_100;
+ lp->hw->phy->supported.cca_ed_levels_size = ARRAY_SIZE(at86rf212_ed_levels_100);
+ lp->hw->phy->cca_ed_level = at86rf212_ed_levels_100[cca_ed_thres];
+ break;
+ default:
+ WARN_ON(1);
+ }
+
+ return 0;
+}
+
+static int
+at86rf212_set_channel(struct at86rf230_local *lp, u8 page, u8 channel)
+{
+ int rc;
+
+ if (channel == 0)
+ rc = at86rf230_write_subreg(lp, SR_SUB_MODE, 0);
+ else
+ rc = at86rf230_write_subreg(lp, SR_SUB_MODE, 1);
+ if (rc < 0)
+ return rc;
+
+ if (page == 0) {
+ rc = at86rf230_write_subreg(lp, SR_BPSK_QPSK, 0);
+ lp->data->rssi_base_val = -100;
+ } else {
+ rc = at86rf230_write_subreg(lp, SR_BPSK_QPSK, 1);
+ lp->data->rssi_base_val = -98;
+ }
+ if (rc < 0)
+ return rc;
+
+ rc = at86rf212_update_cca_ed_level(lp, lp->data->rssi_base_val);
+ if (rc < 0)
+ return rc;
+
+ return at86rf230_write_subreg(lp, SR_CHANNEL, channel);
+}
+
+static int
+at86rf230_channel(struct ieee802154_hw *hw, u8 page, u8 channel)
+{
+ struct at86rf230_local *lp = hw->priv;
+ int rc;
+
+ rc = lp->data->set_channel(lp, page, channel);
+ /* Wait for PLL */
+ usleep_range(lp->data->t_channel_switch,
+ lp->data->t_channel_switch + 10);
+
+ lp->cal_timeout = jiffies + AT86RF2XX_CAL_LOOP_TIMEOUT;
+ return rc;
+}
+
+static int
+at86rf230_set_hw_addr_filt(struct ieee802154_hw *hw,
+ struct ieee802154_hw_addr_filt *filt,
+ unsigned long changed)
+{
+ struct at86rf230_local *lp = hw->priv;
+
+ if (changed & IEEE802154_AFILT_SADDR_CHANGED) {
+ u16 addr = le16_to_cpu(filt->short_addr);
+
+ dev_vdbg(&lp->spi->dev, "%s called for saddr\n", __func__);
+ __at86rf230_write(lp, RG_SHORT_ADDR_0, addr);
+ __at86rf230_write(lp, RG_SHORT_ADDR_1, addr >> 8);
+ }
+
+ if (changed & IEEE802154_AFILT_PANID_CHANGED) {
+ u16 pan = le16_to_cpu(filt->pan_id);
+
+ dev_vdbg(&lp->spi->dev, "%s called for pan id\n", __func__);
+ __at86rf230_write(lp, RG_PAN_ID_0, pan);
+ __at86rf230_write(lp, RG_PAN_ID_1, pan >> 8);
+ }
+
+ if (changed & IEEE802154_AFILT_IEEEADDR_CHANGED) {
+ u8 i, addr[8];
+
+ memcpy(addr, &filt->ieee_addr, 8);
+ dev_vdbg(&lp->spi->dev, "%s called for IEEE addr\n", __func__);
+ for (i = 0; i < 8; i++)
+ __at86rf230_write(lp, RG_IEEE_ADDR_0 + i, addr[i]);
+ }
+
+ if (changed & IEEE802154_AFILT_PANC_CHANGED) {
+ dev_vdbg(&lp->spi->dev, "%s called for panc change\n", __func__);
+ if (filt->pan_coord)
+ at86rf230_write_subreg(lp, SR_AACK_I_AM_COORD, 1);
+ else
+ at86rf230_write_subreg(lp, SR_AACK_I_AM_COORD, 0);
+ }
+
+ return 0;
+}
+
+#define AT86RF23X_MAX_TX_POWERS 0xF
+static const s32 at86rf233_powers[AT86RF23X_MAX_TX_POWERS + 1] = {
+ 400, 370, 340, 300, 250, 200, 100, 0, -100, -200, -300, -400, -600,
+ -800, -1200, -1700,
+};
+
+static const s32 at86rf231_powers[AT86RF23X_MAX_TX_POWERS + 1] = {
+ 300, 280, 230, 180, 130, 70, 0, -100, -200, -300, -400, -500, -700,
+ -900, -1200, -1700,
+};
+
+#define AT86RF212_MAX_TX_POWERS 0x1F
+static const s32 at86rf212_powers[AT86RF212_MAX_TX_POWERS + 1] = {
+ 500, 400, 300, 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, -2400, -2500, -2600,
+};
+
+static int
+at86rf23x_set_txpower(struct at86rf230_local *lp, s32 mbm)
+{
+ u32 i;
+
+ for (i = 0; i < lp->hw->phy->supported.tx_powers_size; i++) {
+ if (lp->hw->phy->supported.tx_powers[i] == mbm)
+ return at86rf230_write_subreg(lp, SR_TX_PWR_23X, i);
+ }
+
+ return -EINVAL;
+}
+
+static int
+at86rf212_set_txpower(struct at86rf230_local *lp, s32 mbm)
+{
+ u32 i;
+
+ for (i = 0; i < lp->hw->phy->supported.tx_powers_size; i++) {
+ if (lp->hw->phy->supported.tx_powers[i] == mbm)
+ return at86rf230_write_subreg(lp, SR_TX_PWR_212, i);
+ }
+
+ return -EINVAL;
+}
+
+static int
+at86rf230_set_txpower(struct ieee802154_hw *hw, s32 mbm)
+{
+ struct at86rf230_local *lp = hw->priv;
+
+ return lp->data->set_txpower(lp, mbm);
+}
+
+static int
+at86rf230_set_lbt(struct ieee802154_hw *hw, bool on)
+{
+ struct at86rf230_local *lp = hw->priv;
+
+ return at86rf230_write_subreg(lp, SR_CSMA_LBT_MODE, on);
+}
+
+static int
+at86rf230_set_cca_mode(struct ieee802154_hw *hw,
+ const struct wpan_phy_cca *cca)
+{
+ struct at86rf230_local *lp = hw->priv;
+ u8 val;
+
+ /* mapping 802.15.4 to driver spec */
+ switch (cca->mode) {
+ case NL802154_CCA_ENERGY:
+ val = 1;
+ break;
+ case NL802154_CCA_CARRIER:
+ val = 2;
+ break;
+ case NL802154_CCA_ENERGY_CARRIER:
+ switch (cca->opt) {
+ case NL802154_CCA_OPT_ENERGY_CARRIER_AND:
+ val = 3;
+ break;
+ case NL802154_CCA_OPT_ENERGY_CARRIER_OR:
+ val = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return at86rf230_write_subreg(lp, SR_CCA_MODE, val);
+}
+
+static int
+at86rf230_set_cca_ed_level(struct ieee802154_hw *hw, s32 mbm)
+{
+ struct at86rf230_local *lp = hw->priv;
+ u32 i;
+
+ for (i = 0; i < hw->phy->supported.cca_ed_levels_size; i++) {
+ if (hw->phy->supported.cca_ed_levels[i] == mbm)
+ return at86rf230_write_subreg(lp, SR_CCA_ED_THRES, i);
+ }
+
+ return -EINVAL;
+}
+
+static int
+at86rf230_set_csma_params(struct ieee802154_hw *hw, u8 min_be, u8 max_be,
+ u8 retries)
+{
+ struct at86rf230_local *lp = hw->priv;
+ int rc;
+
+ rc = at86rf230_write_subreg(lp, SR_MIN_BE, min_be);
+ if (rc)
+ return rc;
+
+ rc = at86rf230_write_subreg(lp, SR_MAX_BE, max_be);
+ if (rc)
+ return rc;
+
+ return at86rf230_write_subreg(lp, SR_MAX_CSMA_RETRIES, retries);
+}
+
+static int
+at86rf230_set_frame_retries(struct ieee802154_hw *hw, s8 retries)
+{
+ struct at86rf230_local *lp = hw->priv;
+
+ return at86rf230_write_subreg(lp, SR_MAX_FRAME_RETRIES, retries);
+}
+
+static int
+at86rf230_set_promiscuous_mode(struct ieee802154_hw *hw, const bool on)
+{
+ struct at86rf230_local *lp = hw->priv;
+ int rc;
+
+ if (on) {
+ rc = at86rf230_write_subreg(lp, SR_AACK_DIS_ACK, 1);
+ if (rc < 0)
+ return rc;
+
+ rc = at86rf230_write_subreg(lp, SR_AACK_PROM_MODE, 1);
+ if (rc < 0)
+ return rc;
+ } else {
+ rc = at86rf230_write_subreg(lp, SR_AACK_PROM_MODE, 0);
+ if (rc < 0)
+ return rc;
+
+ rc = at86rf230_write_subreg(lp, SR_AACK_DIS_ACK, 0);
+ if (rc < 0)
+ return rc;
+ }
+
+ return 0;
+}
+
+static const struct ieee802154_ops at86rf230_ops = {
+ .owner = THIS_MODULE,
+ .xmit_async = at86rf230_xmit,
+ .ed = at86rf230_ed,
+ .set_channel = at86rf230_channel,
+ .start = at86rf230_start,
+ .stop = at86rf230_stop,
+ .set_hw_addr_filt = at86rf230_set_hw_addr_filt,
+ .set_txpower = at86rf230_set_txpower,
+ .set_lbt = at86rf230_set_lbt,
+ .set_cca_mode = at86rf230_set_cca_mode,
+ .set_cca_ed_level = at86rf230_set_cca_ed_level,
+ .set_csma_params = at86rf230_set_csma_params,
+ .set_frame_retries = at86rf230_set_frame_retries,
+ .set_promiscuous_mode = at86rf230_set_promiscuous_mode,
+};
+
+static struct at86rf2xx_chip_data at86rf233_data = {
+ .t_sleep_cycle = 330,
+ .t_channel_switch = 11,
+ .t_reset_to_off = 26,
+ .t_off_to_aack = 80,
+ .t_off_to_tx_on = 80,
+ .t_off_to_sleep = 35,
+ .t_sleep_to_off = 1000,
+ .t_frame = 4096,
+ .t_p_ack = 545,
+ .rssi_base_val = -94,
+ .set_channel = at86rf23x_set_channel,
+ .set_txpower = at86rf23x_set_txpower,
+};
+
+static struct at86rf2xx_chip_data at86rf231_data = {
+ .t_sleep_cycle = 330,
+ .t_channel_switch = 24,
+ .t_reset_to_off = 37,
+ .t_off_to_aack = 110,
+ .t_off_to_tx_on = 110,
+ .t_off_to_sleep = 35,
+ .t_sleep_to_off = 1000,
+ .t_frame = 4096,
+ .t_p_ack = 545,
+ .rssi_base_val = -91,
+ .set_channel = at86rf23x_set_channel,
+ .set_txpower = at86rf23x_set_txpower,
+};
+
+static struct at86rf2xx_chip_data at86rf212_data = {
+ .t_sleep_cycle = 330,
+ .t_channel_switch = 11,
+ .t_reset_to_off = 26,
+ .t_off_to_aack = 200,
+ .t_off_to_tx_on = 200,
+ .t_off_to_sleep = 35,
+ .t_sleep_to_off = 1000,
+ .t_frame = 4096,
+ .t_p_ack = 545,
+ .rssi_base_val = -100,
+ .set_channel = at86rf212_set_channel,
+ .set_txpower = at86rf212_set_txpower,
+};
+
+static int at86rf230_hw_init(struct at86rf230_local *lp, u8 xtal_trim)
+{
+ int rc, irq_type, irq_pol = IRQ_ACTIVE_HIGH;
+ unsigned int dvdd;
+ u8 csma_seed[2];
+
+ rc = at86rf230_sync_state_change(lp, STATE_FORCE_TRX_OFF);
+ if (rc)
+ return rc;
+
+ irq_type = irq_get_trigger_type(lp->spi->irq);
+ if (irq_type == IRQ_TYPE_EDGE_FALLING ||
+ irq_type == IRQ_TYPE_LEVEL_LOW)
+ irq_pol = IRQ_ACTIVE_LOW;
+
+ rc = at86rf230_write_subreg(lp, SR_IRQ_POLARITY, irq_pol);
+ if (rc)
+ return rc;
+
+ rc = at86rf230_write_subreg(lp, SR_RX_SAFE_MODE, 1);
+ if (rc)
+ return rc;
+
+ rc = at86rf230_write_subreg(lp, SR_IRQ_MASK, IRQ_TRX_END);
+ if (rc)
+ return rc;
+
+ /* reset values differs in at86rf231 and at86rf233 */
+ rc = at86rf230_write_subreg(lp, SR_IRQ_MASK_MODE, 0);
+ if (rc)
+ return rc;
+
+ get_random_bytes(csma_seed, ARRAY_SIZE(csma_seed));
+ rc = at86rf230_write_subreg(lp, SR_CSMA_SEED_0, csma_seed[0]);
+ if (rc)
+ return rc;
+ rc = at86rf230_write_subreg(lp, SR_CSMA_SEED_1, csma_seed[1]);
+ if (rc)
+ return rc;
+
+ /* CLKM changes are applied immediately */
+ rc = at86rf230_write_subreg(lp, SR_CLKM_SHA_SEL, 0x00);
+ if (rc)
+ return rc;
+
+ /* Turn CLKM Off */
+ rc = at86rf230_write_subreg(lp, SR_CLKM_CTRL, 0x00);
+ if (rc)
+ return rc;
+ /* Wait the next SLEEP cycle */
+ usleep_range(lp->data->t_sleep_cycle,
+ lp->data->t_sleep_cycle + 100);
+
+ /* xtal_trim value is calculated by:
+ * CL = 0.5 * (CX + CTRIM + CPAR)
+ *
+ * whereas:
+ * CL = capacitor of used crystal
+ * CX = connected capacitors at xtal pins
+ * CPAR = in all at86rf2xx datasheets this is a constant value 3 pF,
+ * but this is different on each board setup. You need to fine
+ * tuning this value via CTRIM.
+ * CTRIM = variable capacitor setting. Resolution is 0.3 pF range is
+ * 0 pF upto 4.5 pF.
+ *
+ * Examples:
+ * atben transceiver:
+ *
+ * CL = 8 pF
+ * CX = 12 pF
+ * CPAR = 3 pF (We assume the magic constant from datasheet)
+ * CTRIM = 0.9 pF
+ *
+ * (12+0.9+3)/2 = 7.95 which is nearly at 8 pF
+ *
+ * xtal_trim = 0x3
+ *
+ * openlabs transceiver:
+ *
+ * CL = 16 pF
+ * CX = 22 pF
+ * CPAR = 3 pF (We assume the magic constant from datasheet)
+ * CTRIM = 4.5 pF
+ *
+ * (22+4.5+3)/2 = 14.75 which is the nearest value to 16 pF
+ *
+ * xtal_trim = 0xf
+ */
+ rc = at86rf230_write_subreg(lp, SR_XTAL_TRIM, xtal_trim);
+ if (rc)
+ return rc;
+
+ rc = at86rf230_read_subreg(lp, SR_DVDD_OK, &dvdd);
+ if (rc)
+ return rc;
+ if (!dvdd) {
+ dev_err(&lp->spi->dev, "DVDD error\n");
+ return -EINVAL;
+ }
+
+ /* Force setting slotted operation bit to 0. Sometimes the atben
+ * sets this bit and I don't know why. We set this always force
+ * to zero while probing.
+ */
+ return at86rf230_write_subreg(lp, SR_SLOTTED_OPERATION, 0);
+}
+
+static int
+at86rf230_detect_device(struct at86rf230_local *lp)
+{
+ unsigned int part, version, val;
+ u16 man_id = 0;
+ const char *chip;
+ int rc;
+
+ rc = __at86rf230_read(lp, RG_MAN_ID_0, &val);
+ if (rc)
+ return rc;
+ man_id |= val;
+
+ rc = __at86rf230_read(lp, RG_MAN_ID_1, &val);
+ if (rc)
+ return rc;
+ man_id |= (val << 8);
+
+ rc = __at86rf230_read(lp, RG_PART_NUM, &part);
+ if (rc)
+ return rc;
+
+ rc = __at86rf230_read(lp, RG_VERSION_NUM, &version);
+ if (rc)
+ return rc;
+
+ if (man_id != 0x001f) {
+ dev_err(&lp->spi->dev, "Non-Atmel dev found (MAN_ID %02x %02x)\n",
+ man_id >> 8, man_id & 0xFF);
+ return -EINVAL;
+ }
+
+ lp->hw->flags = IEEE802154_HW_TX_OMIT_CKSUM |
+ IEEE802154_HW_CSMA_PARAMS |
+ IEEE802154_HW_FRAME_RETRIES | IEEE802154_HW_AFILT |
+ IEEE802154_HW_PROMISCUOUS;
+
+ lp->hw->phy->flags = WPAN_PHY_FLAG_TXPOWER |
+ WPAN_PHY_FLAG_CCA_ED_LEVEL |
+ WPAN_PHY_FLAG_CCA_MODE;
+
+ lp->hw->phy->supported.cca_modes = BIT(NL802154_CCA_ENERGY) |
+ BIT(NL802154_CCA_CARRIER) | BIT(NL802154_CCA_ENERGY_CARRIER);
+ lp->hw->phy->supported.cca_opts = BIT(NL802154_CCA_OPT_ENERGY_CARRIER_AND) |
+ BIT(NL802154_CCA_OPT_ENERGY_CARRIER_OR);
+
+ lp->hw->phy->cca.mode = NL802154_CCA_ENERGY;
+
+ switch (part) {
+ case 2:
+ chip = "at86rf230";
+ rc = -ENOTSUPP;
+ goto not_supp;
+ case 3:
+ chip = "at86rf231";
+ lp->data = &at86rf231_data;
+ lp->hw->phy->supported.channels[0] = 0x7FFF800;
+ lp->hw->phy->current_channel = 11;
+ lp->hw->phy->supported.tx_powers = at86rf231_powers;
+ lp->hw->phy->supported.tx_powers_size = ARRAY_SIZE(at86rf231_powers);
+ lp->hw->phy->supported.cca_ed_levels = at86rf231_ed_levels;
+ lp->hw->phy->supported.cca_ed_levels_size = ARRAY_SIZE(at86rf231_ed_levels);
+ break;
+ case 7:
+ chip = "at86rf212";
+ lp->data = &at86rf212_data;
+ lp->hw->flags |= IEEE802154_HW_LBT;
+ lp->hw->phy->supported.channels[0] = 0x00007FF;
+ lp->hw->phy->supported.channels[2] = 0x00007FF;
+ lp->hw->phy->current_channel = 5;
+ lp->hw->phy->supported.lbt = NL802154_SUPPORTED_BOOL_BOTH;
+ lp->hw->phy->supported.tx_powers = at86rf212_powers;
+ lp->hw->phy->supported.tx_powers_size = ARRAY_SIZE(at86rf212_powers);
+ lp->hw->phy->supported.cca_ed_levels = at86rf212_ed_levels_100;
+ lp->hw->phy->supported.cca_ed_levels_size = ARRAY_SIZE(at86rf212_ed_levels_100);
+ break;
+ case 11:
+ chip = "at86rf233";
+ lp->data = &at86rf233_data;
+ lp->hw->phy->supported.channels[0] = 0x7FFF800;
+ lp->hw->phy->current_channel = 13;
+ lp->hw->phy->supported.tx_powers = at86rf233_powers;
+ lp->hw->phy->supported.tx_powers_size = ARRAY_SIZE(at86rf233_powers);
+ lp->hw->phy->supported.cca_ed_levels = at86rf233_ed_levels;
+ lp->hw->phy->supported.cca_ed_levels_size = ARRAY_SIZE(at86rf233_ed_levels);
+ break;
+ default:
+ chip = "unknown";
+ rc = -ENOTSUPP;
+ goto not_supp;
+ }
+
+ lp->hw->phy->cca_ed_level = lp->hw->phy->supported.cca_ed_levels[7];
+ lp->hw->phy->transmit_power = lp->hw->phy->supported.tx_powers[0];
+
+not_supp:
+ dev_info(&lp->spi->dev, "Detected %s chip version %d\n", chip, version);
+
+ return rc;
+}
+
+static int at86rf230_probe(struct spi_device *spi)
+{
+ struct ieee802154_hw *hw;
+ struct at86rf230_local *lp;
+ struct gpio_desc *slp_tr;
+ struct gpio_desc *rstn;
+ unsigned int status;
+ int rc, irq_type;
+ u8 xtal_trim;
+
+ if (!spi->irq) {
+ dev_err(&spi->dev, "no IRQ specified\n");
+ return -EINVAL;
+ }
+
+ rc = device_property_read_u8(&spi->dev, "xtal-trim", &xtal_trim);
+ if (rc < 0) {
+ if (rc != -EINVAL) {
+ dev_err(&spi->dev,
+ "failed to parse xtal-trim: %d\n", rc);
+ return rc;
+ }
+ xtal_trim = 0;
+ }
+
+ rstn = devm_gpiod_get_optional(&spi->dev, "reset", GPIOD_OUT_LOW);
+ rc = PTR_ERR_OR_ZERO(rstn);
+ if (rc)
+ return rc;
+
+ gpiod_set_consumer_name(rstn, "rstn");
+
+ slp_tr = devm_gpiod_get_optional(&spi->dev, "sleep", GPIOD_OUT_LOW);
+ rc = PTR_ERR_OR_ZERO(slp_tr);
+ if (rc)
+ return rc;
+
+ gpiod_set_consumer_name(slp_tr, "slp_tr");
+
+ /* Reset */
+ if (rstn) {
+ udelay(1);
+ gpiod_set_value_cansleep(rstn, 1);
+ udelay(1);
+ gpiod_set_value_cansleep(rstn, 0);
+ usleep_range(120, 240);
+ }
+
+ hw = ieee802154_alloc_hw(sizeof(*lp), &at86rf230_ops);
+ if (!hw)
+ return -ENOMEM;
+
+ lp = hw->priv;
+ lp->hw = hw;
+ lp->spi = spi;
+ lp->slp_tr = slp_tr;
+ hw->parent = &spi->dev;
+ ieee802154_random_extended_addr(&hw->phy->perm_extended_addr);
+
+ lp->regmap = devm_regmap_init_spi(spi, &at86rf230_regmap_spi_config);
+ if (IS_ERR(lp->regmap)) {
+ rc = PTR_ERR(lp->regmap);
+ dev_err(&spi->dev, "Failed to allocate register map: %d\n",
+ rc);
+ goto free_dev;
+ }
+
+ at86rf230_setup_spi_messages(lp, &lp->state);
+ at86rf230_setup_spi_messages(lp, &lp->tx);
+
+ rc = at86rf230_detect_device(lp);
+ if (rc < 0)
+ goto free_dev;
+
+ init_completion(&lp->state_complete);
+
+ spi_set_drvdata(spi, lp);
+
+ rc = at86rf230_hw_init(lp, xtal_trim);
+ if (rc)
+ goto free_dev;
+
+ /* Read irq status register to reset irq line */
+ rc = at86rf230_read_subreg(lp, RG_IRQ_STATUS, 0xff, 0, &status);
+ if (rc)
+ goto free_dev;
+
+ irq_type = irq_get_trigger_type(spi->irq);
+ if (!irq_type)
+ irq_type = IRQF_TRIGGER_HIGH;
+
+ rc = devm_request_irq(&spi->dev, spi->irq, at86rf230_isr,
+ IRQF_SHARED | irq_type, dev_name(&spi->dev), lp);
+ if (rc)
+ goto free_dev;
+
+ /* disable_irq by default and wait for starting hardware */
+ disable_irq(spi->irq);
+
+ /* going into sleep by default */
+ at86rf230_sleep(lp);
+
+ rc = ieee802154_register_hw(lp->hw);
+ if (rc)
+ goto free_dev;
+
+ return rc;
+
+free_dev:
+ ieee802154_free_hw(lp->hw);
+
+ return rc;
+}
+
+static void at86rf230_remove(struct spi_device *spi)
+{
+ struct at86rf230_local *lp = spi_get_drvdata(spi);
+
+ /* mask all at86rf230 irq's */
+ at86rf230_write_subreg(lp, SR_IRQ_MASK, 0);
+ ieee802154_unregister_hw(lp->hw);
+ ieee802154_free_hw(lp->hw);
+ dev_dbg(&spi->dev, "unregistered at86rf230\n");
+}
+
+static const struct of_device_id at86rf230_of_match[] = {
+ { .compatible = "atmel,at86rf230", },
+ { .compatible = "atmel,at86rf231", },
+ { .compatible = "atmel,at86rf233", },
+ { .compatible = "atmel,at86rf212", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, at86rf230_of_match);
+
+static const struct spi_device_id at86rf230_device_id[] = {
+ { .name = "at86rf230", },
+ { .name = "at86rf231", },
+ { .name = "at86rf233", },
+ { .name = "at86rf212", },
+ { },
+};
+MODULE_DEVICE_TABLE(spi, at86rf230_device_id);
+
+static struct spi_driver at86rf230_driver = {
+ .id_table = at86rf230_device_id,
+ .driver = {
+ .of_match_table = at86rf230_of_match,
+ .name = "at86rf230",
+ },
+ .probe = at86rf230_probe,
+ .remove = at86rf230_remove,
+};
+
+module_spi_driver(at86rf230_driver);
+
+MODULE_DESCRIPTION("AT86RF230 Transceiver Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/ieee802154/at86rf230.h b/drivers/net/ieee802154/at86rf230.h
new file mode 100644
index 0000000000..042bb27287
--- /dev/null
+++ b/drivers/net/ieee802154/at86rf230.h
@@ -0,0 +1,220 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * AT86RF230/RF231 driver
+ *
+ * Copyright (C) 2009-2012 Siemens AG
+ *
+ * Written by:
+ * Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
+ * Alexander Smirnov <alex.bluesman.smirnov@gmail.com>
+ */
+
+#ifndef _AT86RF230_H
+#define _AT86RF230_H
+
+#define RG_TRX_STATUS (0x01)
+#define SR_TRX_STATUS 0x01, 0x1f, 0
+#define SR_RESERVED_01_3 0x01, 0x20, 5
+#define SR_CCA_STATUS 0x01, 0x40, 6
+#define SR_CCA_DONE 0x01, 0x80, 7
+#define RG_TRX_STATE (0x02)
+#define SR_TRX_CMD 0x02, 0x1f, 0
+#define SR_TRAC_STATUS 0x02, 0xe0, 5
+#define RG_TRX_CTRL_0 (0x03)
+#define SR_CLKM_CTRL 0x03, 0x07, 0
+#define SR_CLKM_SHA_SEL 0x03, 0x08, 3
+#define SR_PAD_IO_CLKM 0x03, 0x30, 4
+#define SR_PAD_IO 0x03, 0xc0, 6
+#define RG_TRX_CTRL_1 (0x04)
+#define SR_IRQ_POLARITY 0x04, 0x01, 0
+#define SR_IRQ_MASK_MODE 0x04, 0x02, 1
+#define SR_SPI_CMD_MODE 0x04, 0x0c, 2
+#define SR_RX_BL_CTRL 0x04, 0x10, 4
+#define SR_TX_AUTO_CRC_ON 0x04, 0x20, 5
+#define SR_IRQ_2_EXT_EN 0x04, 0x40, 6
+#define SR_PA_EXT_EN 0x04, 0x80, 7
+#define RG_PHY_TX_PWR (0x05)
+#define SR_TX_PWR_23X 0x05, 0x0f, 0
+#define SR_PA_LT_230 0x05, 0x30, 4
+#define SR_PA_BUF_LT_230 0x05, 0xc0, 6
+#define SR_TX_PWR_212 0x05, 0x1f, 0
+#define SR_GC_PA_212 0x05, 0x60, 5
+#define SR_PA_BOOST_LT_212 0x05, 0x80, 7
+#define RG_PHY_RSSI (0x06)
+#define SR_RSSI 0x06, 0x1f, 0
+#define SR_RND_VALUE 0x06, 0x60, 5
+#define SR_RX_CRC_VALID 0x06, 0x80, 7
+#define RG_PHY_ED_LEVEL (0x07)
+#define SR_ED_LEVEL 0x07, 0xff, 0
+#define RG_PHY_CC_CCA (0x08)
+#define SR_CHANNEL 0x08, 0x1f, 0
+#define SR_CCA_MODE 0x08, 0x60, 5
+#define SR_CCA_REQUEST 0x08, 0x80, 7
+#define RG_CCA_THRES (0x09)
+#define SR_CCA_ED_THRES 0x09, 0x0f, 0
+#define SR_RESERVED_09_1 0x09, 0xf0, 4
+#define RG_RX_CTRL (0x0a)
+#define SR_PDT_THRES 0x0a, 0x0f, 0
+#define SR_RESERVED_0a_1 0x0a, 0xf0, 4
+#define RG_SFD_VALUE (0x0b)
+#define SR_SFD_VALUE 0x0b, 0xff, 0
+#define RG_TRX_CTRL_2 (0x0c)
+#define SR_OQPSK_DATA_RATE 0x0c, 0x03, 0
+#define SR_SUB_MODE 0x0c, 0x04, 2
+#define SR_BPSK_QPSK 0x0c, 0x08, 3
+#define SR_OQPSK_SUB1_RC_EN 0x0c, 0x10, 4
+#define SR_RESERVED_0c_5 0x0c, 0x60, 5
+#define SR_RX_SAFE_MODE 0x0c, 0x80, 7
+#define RG_ANT_DIV (0x0d)
+#define SR_ANT_CTRL 0x0d, 0x03, 0
+#define SR_ANT_EXT_SW_EN 0x0d, 0x04, 2
+#define SR_ANT_DIV_EN 0x0d, 0x08, 3
+#define SR_RESERVED_0d_2 0x0d, 0x70, 4
+#define SR_ANT_SEL 0x0d, 0x80, 7
+#define RG_IRQ_MASK (0x0e)
+#define SR_IRQ_MASK 0x0e, 0xff, 0
+#define RG_IRQ_STATUS (0x0f)
+#define SR_IRQ_0_PLL_LOCK 0x0f, 0x01, 0
+#define SR_IRQ_1_PLL_UNLOCK 0x0f, 0x02, 1
+#define SR_IRQ_2_RX_START 0x0f, 0x04, 2
+#define SR_IRQ_3_TRX_END 0x0f, 0x08, 3
+#define SR_IRQ_4_CCA_ED_DONE 0x0f, 0x10, 4
+#define SR_IRQ_5_AMI 0x0f, 0x20, 5
+#define SR_IRQ_6_TRX_UR 0x0f, 0x40, 6
+#define SR_IRQ_7_BAT_LOW 0x0f, 0x80, 7
+#define RG_VREG_CTRL (0x10)
+#define SR_RESERVED_10_6 0x10, 0x03, 0
+#define SR_DVDD_OK 0x10, 0x04, 2
+#define SR_DVREG_EXT 0x10, 0x08, 3
+#define SR_RESERVED_10_3 0x10, 0x30, 4
+#define SR_AVDD_OK 0x10, 0x40, 6
+#define SR_AVREG_EXT 0x10, 0x80, 7
+#define RG_BATMON (0x11)
+#define SR_BATMON_VTH 0x11, 0x0f, 0
+#define SR_BATMON_HR 0x11, 0x10, 4
+#define SR_BATMON_OK 0x11, 0x20, 5
+#define SR_RESERVED_11_1 0x11, 0xc0, 6
+#define RG_XOSC_CTRL (0x12)
+#define SR_XTAL_TRIM 0x12, 0x0f, 0
+#define SR_XTAL_MODE 0x12, 0xf0, 4
+#define RG_RX_SYN (0x15)
+#define SR_RX_PDT_LEVEL 0x15, 0x0f, 0
+#define SR_RESERVED_15_2 0x15, 0x70, 4
+#define SR_RX_PDT_DIS 0x15, 0x80, 7
+#define RG_XAH_CTRL_1 (0x17)
+#define SR_RESERVED_17_8 0x17, 0x01, 0
+#define SR_AACK_PROM_MODE 0x17, 0x02, 1
+#define SR_AACK_ACK_TIME 0x17, 0x04, 2
+#define SR_RESERVED_17_5 0x17, 0x08, 3
+#define SR_AACK_UPLD_RES_FT 0x17, 0x10, 4
+#define SR_AACK_FLTR_RES_FT 0x17, 0x20, 5
+#define SR_CSMA_LBT_MODE 0x17, 0x40, 6
+#define SR_RESERVED_17_1 0x17, 0x80, 7
+#define RG_FTN_CTRL (0x18)
+#define SR_RESERVED_18_2 0x18, 0x7f, 0
+#define SR_FTN_START 0x18, 0x80, 7
+#define RG_PLL_CF (0x1a)
+#define SR_RESERVED_1a_2 0x1a, 0x7f, 0
+#define SR_PLL_CF_START 0x1a, 0x80, 7
+#define RG_PLL_DCU (0x1b)
+#define SR_RESERVED_1b_3 0x1b, 0x3f, 0
+#define SR_RESERVED_1b_2 0x1b, 0x40, 6
+#define SR_PLL_DCU_START 0x1b, 0x80, 7
+#define RG_PART_NUM (0x1c)
+#define SR_PART_NUM 0x1c, 0xff, 0
+#define RG_VERSION_NUM (0x1d)
+#define SR_VERSION_NUM 0x1d, 0xff, 0
+#define RG_MAN_ID_0 (0x1e)
+#define SR_MAN_ID_0 0x1e, 0xff, 0
+#define RG_MAN_ID_1 (0x1f)
+#define SR_MAN_ID_1 0x1f, 0xff, 0
+#define RG_SHORT_ADDR_0 (0x20)
+#define SR_SHORT_ADDR_0 0x20, 0xff, 0
+#define RG_SHORT_ADDR_1 (0x21)
+#define SR_SHORT_ADDR_1 0x21, 0xff, 0
+#define RG_PAN_ID_0 (0x22)
+#define SR_PAN_ID_0 0x22, 0xff, 0
+#define RG_PAN_ID_1 (0x23)
+#define SR_PAN_ID_1 0x23, 0xff, 0
+#define RG_IEEE_ADDR_0 (0x24)
+#define SR_IEEE_ADDR_0 0x24, 0xff, 0
+#define RG_IEEE_ADDR_1 (0x25)
+#define SR_IEEE_ADDR_1 0x25, 0xff, 0
+#define RG_IEEE_ADDR_2 (0x26)
+#define SR_IEEE_ADDR_2 0x26, 0xff, 0
+#define RG_IEEE_ADDR_3 (0x27)
+#define SR_IEEE_ADDR_3 0x27, 0xff, 0
+#define RG_IEEE_ADDR_4 (0x28)
+#define SR_IEEE_ADDR_4 0x28, 0xff, 0
+#define RG_IEEE_ADDR_5 (0x29)
+#define SR_IEEE_ADDR_5 0x29, 0xff, 0
+#define RG_IEEE_ADDR_6 (0x2a)
+#define SR_IEEE_ADDR_6 0x2a, 0xff, 0
+#define RG_IEEE_ADDR_7 (0x2b)
+#define SR_IEEE_ADDR_7 0x2b, 0xff, 0
+#define RG_XAH_CTRL_0 (0x2c)
+#define SR_SLOTTED_OPERATION 0x2c, 0x01, 0
+#define SR_MAX_CSMA_RETRIES 0x2c, 0x0e, 1
+#define SR_MAX_FRAME_RETRIES 0x2c, 0xf0, 4
+#define RG_CSMA_SEED_0 (0x2d)
+#define SR_CSMA_SEED_0 0x2d, 0xff, 0
+#define RG_CSMA_SEED_1 (0x2e)
+#define SR_CSMA_SEED_1 0x2e, 0x07, 0
+#define SR_AACK_I_AM_COORD 0x2e, 0x08, 3
+#define SR_AACK_DIS_ACK 0x2e, 0x10, 4
+#define SR_AACK_SET_PD 0x2e, 0x20, 5
+#define SR_AACK_FVN_MODE 0x2e, 0xc0, 6
+#define RG_CSMA_BE (0x2f)
+#define SR_MIN_BE 0x2f, 0x0f, 0
+#define SR_MAX_BE 0x2f, 0xf0, 4
+
+#define CMD_REG 0x80
+#define CMD_REG_MASK 0x3f
+#define CMD_WRITE 0x40
+#define CMD_FB 0x20
+
+#define IRQ_BAT_LOW BIT(7)
+#define IRQ_TRX_UR BIT(6)
+#define IRQ_AMI BIT(5)
+#define IRQ_CCA_ED BIT(4)
+#define IRQ_TRX_END BIT(3)
+#define IRQ_RX_START BIT(2)
+#define IRQ_PLL_UNL BIT(1)
+#define IRQ_PLL_LOCK BIT(0)
+
+#define IRQ_ACTIVE_HIGH 0
+#define IRQ_ACTIVE_LOW 1
+
+#define STATE_P_ON 0x00 /* BUSY */
+#define STATE_BUSY_RX 0x01
+#define STATE_BUSY_TX 0x02
+#define STATE_FORCE_TRX_OFF 0x03
+#define STATE_FORCE_TX_ON 0x04 /* IDLE */
+/* 0x05 */ /* INVALID_PARAMETER */
+#define STATE_RX_ON 0x06
+/* 0x07 */ /* SUCCESS */
+#define STATE_TRX_OFF 0x08
+#define STATE_TX_ON 0x09
+/* 0x0a - 0x0e */ /* 0x0a - UNSUPPORTED_ATTRIBUTE */
+#define STATE_SLEEP 0x0F
+#define STATE_PREP_DEEP_SLEEP 0x10
+#define STATE_BUSY_RX_AACK 0x11
+#define STATE_BUSY_TX_ARET 0x12
+#define STATE_RX_AACK_ON 0x16
+#define STATE_TX_ARET_ON 0x19
+#define STATE_RX_ON_NOCLK 0x1C
+#define STATE_RX_AACK_ON_NOCLK 0x1D
+#define STATE_BUSY_RX_AACK_NOCLK 0x1E
+#define STATE_TRANSITION_IN_PROGRESS 0x1F
+
+#define TRX_STATE_MASK (0x1F)
+#define TRAC_MASK(x) ((x & 0xe0) >> 5)
+
+#define TRAC_SUCCESS 0
+#define TRAC_SUCCESS_DATA_PENDING 1
+#define TRAC_SUCCESS_WAIT_FOR_ACK 2
+#define TRAC_CHANNEL_ACCESS_FAILURE 3
+#define TRAC_NO_ACK 5
+#define TRAC_INVALID 7
+
+#endif /* !_AT86RF230_H */
diff --git a/drivers/net/ieee802154/atusb.c b/drivers/net/ieee802154/atusb.c
new file mode 100644
index 0000000000..95a4a3cdc8
--- /dev/null
+++ b/drivers/net/ieee802154/atusb.c
@@ -0,0 +1,1116 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * atusb.c - Driver for the ATUSB IEEE 802.15.4 dongle
+ *
+ * Written 2013 by Werner Almesberger <werner@almesberger.net>
+ *
+ * Copyright (c) 2015 - 2016 Stefan Schmidt <stefan@datenfreihafen.org>
+ *
+ * Based on at86rf230.c and spi_atusb.c.
+ * at86rf230.c is
+ * Copyright (C) 2009 Siemens AG
+ * Written by: Dmitry Eremin-Solenikov <dmitry.baryshkov@siemens.com>
+ *
+ * spi_atusb.c is
+ * Copyright (c) 2011 Richard Sharpe <realrichardsharpe@gmail.com>
+ * Copyright (c) 2011 Stefan Schmidt <stefan@datenfreihafen.org>
+ * Copyright (c) 2011 Werner Almesberger <werner@almesberger.net>
+ *
+ * USB initialization is
+ * Copyright (c) 2013 Alexander Aring <alex.aring@gmail.com>
+ *
+ * Busware HUL support is
+ * Copyright (c) 2017 Josef Filzmaier <j.filzmaier@gmx.at>
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/jiffies.h>
+#include <linux/usb.h>
+#include <linux/skbuff.h>
+
+#include <net/cfg802154.h>
+#include <net/mac802154.h>
+
+#include "at86rf230.h"
+#include "atusb.h"
+
+#define ATUSB_JEDEC_ATMEL 0x1f /* JEDEC manufacturer ID */
+
+#define ATUSB_NUM_RX_URBS 4 /* allow for a bit of local latency */
+#define ATUSB_ALLOC_DELAY_MS 100 /* delay after failed allocation */
+#define ATUSB_TX_TIMEOUT_MS 200 /* on the air timeout */
+
+struct atusb {
+ struct ieee802154_hw *hw;
+ struct usb_device *usb_dev;
+ struct atusb_chip_data *data;
+ int shutdown; /* non-zero if shutting down */
+ int err; /* set by first error */
+
+ /* RX variables */
+ struct delayed_work work; /* memory allocations */
+ struct usb_anchor idle_urbs; /* URBs waiting to be submitted */
+ struct usb_anchor rx_urbs; /* URBs waiting for reception */
+
+ /* TX variables */
+ struct usb_ctrlrequest tx_dr;
+ struct urb *tx_urb;
+ struct sk_buff *tx_skb;
+ u8 tx_ack_seq; /* current TX ACK sequence number */
+
+ /* Firmware variable */
+ unsigned char fw_ver_maj; /* Firmware major version number */
+ unsigned char fw_ver_min; /* Firmware minor version number */
+ unsigned char fw_hw_type; /* Firmware hardware type */
+};
+
+struct atusb_chip_data {
+ u16 t_channel_switch;
+ int rssi_base_val;
+
+ int (*set_channel)(struct ieee802154_hw*, u8, u8);
+ int (*set_txpower)(struct ieee802154_hw*, s32);
+};
+
+static int atusb_write_subreg(struct atusb *atusb, u8 reg, u8 mask,
+ u8 shift, u8 value)
+{
+ struct usb_device *usb_dev = atusb->usb_dev;
+ u8 orig, tmp;
+ int ret = 0;
+
+ dev_dbg(&usb_dev->dev, "%s: 0x%02x <- 0x%02x\n", __func__, reg, value);
+
+ ret = usb_control_msg_recv(usb_dev, 0, ATUSB_REG_READ, ATUSB_REQ_FROM_DEV,
+ 0, reg, &orig, 1, 1000, GFP_KERNEL);
+ if (ret < 0)
+ return ret;
+
+ /* Write the value only into that part of the register which is allowed
+ * by the mask. All other bits stay as before.
+ */
+ tmp = orig & ~mask;
+ tmp |= (value << shift) & mask;
+
+ if (tmp != orig)
+ ret = usb_control_msg_send(usb_dev, 0, ATUSB_REG_WRITE, ATUSB_REQ_TO_DEV,
+ tmp, reg, NULL, 0, 1000, GFP_KERNEL);
+
+ return ret;
+}
+
+static int atusb_read_subreg(struct atusb *lp,
+ unsigned int addr, unsigned int mask,
+ unsigned int shift)
+{
+ int reg, ret;
+
+ ret = usb_control_msg_recv(lp->usb_dev, 0, ATUSB_REG_READ, ATUSB_REQ_FROM_DEV,
+ 0, addr, &reg, 1, 1000, GFP_KERNEL);
+ if (ret < 0)
+ return ret;
+
+ reg = (reg & mask) >> shift;
+
+ return reg;
+}
+
+static int atusb_get_and_clear_error(struct atusb *atusb)
+{
+ int err = atusb->err;
+
+ atusb->err = 0;
+ return err;
+}
+
+/* ----- skb allocation ---------------------------------------------------- */
+
+#define MAX_PSDU 127
+#define MAX_RX_XFER (1 + MAX_PSDU + 2 + 1) /* PHR+PSDU+CRC+LQI */
+
+#define SKB_ATUSB(skb) (*(struct atusb **)(skb)->cb)
+
+static void atusb_in(struct urb *urb);
+
+static int atusb_submit_rx_urb(struct atusb *atusb, struct urb *urb)
+{
+ struct usb_device *usb_dev = atusb->usb_dev;
+ struct sk_buff *skb = urb->context;
+ int ret;
+
+ if (!skb) {
+ skb = alloc_skb(MAX_RX_XFER, GFP_KERNEL);
+ if (!skb) {
+ dev_warn_ratelimited(&usb_dev->dev,
+ "atusb_in: can't allocate skb\n");
+ return -ENOMEM;
+ }
+ skb_put(skb, MAX_RX_XFER);
+ SKB_ATUSB(skb) = atusb;
+ }
+
+ usb_fill_bulk_urb(urb, usb_dev, usb_rcvbulkpipe(usb_dev, 1),
+ skb->data, MAX_RX_XFER, atusb_in, skb);
+ usb_anchor_urb(urb, &atusb->rx_urbs);
+
+ ret = usb_submit_urb(urb, GFP_KERNEL);
+ if (ret) {
+ usb_unanchor_urb(urb);
+ kfree_skb(skb);
+ urb->context = NULL;
+ }
+ return ret;
+}
+
+static void atusb_work_urbs(struct work_struct *work)
+{
+ struct atusb *atusb =
+ container_of(to_delayed_work(work), struct atusb, work);
+ struct usb_device *usb_dev = atusb->usb_dev;
+ struct urb *urb;
+ int ret;
+
+ if (atusb->shutdown)
+ return;
+
+ do {
+ urb = usb_get_from_anchor(&atusb->idle_urbs);
+ if (!urb)
+ return;
+ ret = atusb_submit_rx_urb(atusb, urb);
+ } while (!ret);
+
+ usb_anchor_urb(urb, &atusb->idle_urbs);
+ dev_warn_ratelimited(&usb_dev->dev,
+ "atusb_in: can't allocate/submit URB (%d)\n", ret);
+ schedule_delayed_work(&atusb->work,
+ msecs_to_jiffies(ATUSB_ALLOC_DELAY_MS) + 1);
+}
+
+/* ----- Asynchronous USB -------------------------------------------------- */
+
+static void atusb_tx_done(struct atusb *atusb, u8 seq, int reason)
+{
+ struct usb_device *usb_dev = atusb->usb_dev;
+ u8 expect = atusb->tx_ack_seq;
+
+ dev_dbg(&usb_dev->dev, "%s (0x%02x/0x%02x)\n", __func__, seq, expect);
+ if (seq == expect) {
+ /* TODO check for ifs handling in firmware */
+ if (reason == IEEE802154_SUCCESS)
+ ieee802154_xmit_complete(atusb->hw, atusb->tx_skb, false);
+ else
+ ieee802154_xmit_error(atusb->hw, atusb->tx_skb, reason);
+ } else {
+ /* TODO I experience this case when atusb has a tx complete
+ * irq before probing, we should fix the firmware it's an
+ * unlikely case now that seq == expect is then true, but can
+ * happen and fail with a tx_skb = NULL;
+ */
+ ieee802154_xmit_hw_error(atusb->hw, atusb->tx_skb);
+ }
+}
+
+static void atusb_in_good(struct urb *urb)
+{
+ struct usb_device *usb_dev = urb->dev;
+ struct sk_buff *skb = urb->context;
+ struct atusb *atusb = SKB_ATUSB(skb);
+ int result = IEEE802154_SUCCESS;
+ u8 len, lqi, trac;
+
+ if (!urb->actual_length) {
+ dev_dbg(&usb_dev->dev, "atusb_in: zero-sized URB ?\n");
+ return;
+ }
+
+ len = *skb->data;
+
+ switch (urb->actual_length) {
+ case 2:
+ trac = TRAC_MASK(*(skb->data + 1));
+ switch (trac) {
+ case TRAC_SUCCESS:
+ case TRAC_SUCCESS_DATA_PENDING:
+ /* already IEEE802154_SUCCESS */
+ break;
+ case TRAC_CHANNEL_ACCESS_FAILURE:
+ result = IEEE802154_CHANNEL_ACCESS_FAILURE;
+ break;
+ case TRAC_NO_ACK:
+ result = IEEE802154_NO_ACK;
+ break;
+ default:
+ result = IEEE802154_SYSTEM_ERROR;
+ }
+
+ fallthrough;
+ case 1:
+ atusb_tx_done(atusb, len, result);
+ return;
+ }
+
+ if (len + 1 > urb->actual_length - 1) {
+ dev_dbg(&usb_dev->dev, "atusb_in: frame len %d+1 > URB %u-1\n",
+ len, urb->actual_length);
+ return;
+ }
+
+ if (!ieee802154_is_valid_psdu_len(len)) {
+ dev_dbg(&usb_dev->dev, "atusb_in: frame corrupted\n");
+ return;
+ }
+
+ lqi = skb->data[len + 1];
+ dev_dbg(&usb_dev->dev, "atusb_in: rx len %d lqi 0x%02x\n", len, lqi);
+ skb_pull(skb, 1); /* remove PHR */
+ skb_trim(skb, len); /* get payload only */
+ ieee802154_rx_irqsafe(atusb->hw, skb, lqi);
+ urb->context = NULL; /* skb is gone */
+}
+
+static void atusb_in(struct urb *urb)
+{
+ struct usb_device *usb_dev = urb->dev;
+ struct sk_buff *skb = urb->context;
+ struct atusb *atusb = SKB_ATUSB(skb);
+
+ dev_dbg(&usb_dev->dev, "%s: status %d len %d\n", __func__,
+ urb->status, urb->actual_length);
+ if (urb->status) {
+ if (urb->status == -ENOENT) { /* being killed */
+ kfree_skb(skb);
+ urb->context = NULL;
+ return;
+ }
+ dev_dbg(&usb_dev->dev, "%s: URB error %d\n", __func__, urb->status);
+ } else {
+ atusb_in_good(urb);
+ }
+
+ usb_anchor_urb(urb, &atusb->idle_urbs);
+ if (!atusb->shutdown)
+ schedule_delayed_work(&atusb->work, 0);
+}
+
+/* ----- URB allocation/deallocation --------------------------------------- */
+
+static void atusb_free_urbs(struct atusb *atusb)
+{
+ struct urb *urb;
+
+ while (1) {
+ urb = usb_get_from_anchor(&atusb->idle_urbs);
+ if (!urb)
+ break;
+ kfree_skb(urb->context);
+ usb_free_urb(urb);
+ }
+}
+
+static int atusb_alloc_urbs(struct atusb *atusb, int n)
+{
+ struct urb *urb;
+
+ while (n) {
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb) {
+ atusb_free_urbs(atusb);
+ return -ENOMEM;
+ }
+ usb_anchor_urb(urb, &atusb->idle_urbs);
+ usb_free_urb(urb);
+ n--;
+ }
+ return 0;
+}
+
+/* ----- IEEE 802.15.4 interface operations -------------------------------- */
+
+static void atusb_xmit_complete(struct urb *urb)
+{
+ dev_dbg(&urb->dev->dev, "atusb_xmit urb completed");
+}
+
+static int atusb_xmit(struct ieee802154_hw *hw, struct sk_buff *skb)
+{
+ struct atusb *atusb = hw->priv;
+ struct usb_device *usb_dev = atusb->usb_dev;
+ int ret;
+
+ dev_dbg(&usb_dev->dev, "%s (%d)\n", __func__, skb->len);
+ atusb->tx_skb = skb;
+ atusb->tx_ack_seq++;
+ atusb->tx_dr.wIndex = cpu_to_le16(atusb->tx_ack_seq);
+ atusb->tx_dr.wLength = cpu_to_le16(skb->len);
+
+ usb_fill_control_urb(atusb->tx_urb, usb_dev,
+ usb_sndctrlpipe(usb_dev, 0),
+ (unsigned char *)&atusb->tx_dr, skb->data,
+ skb->len, atusb_xmit_complete, NULL);
+ ret = usb_submit_urb(atusb->tx_urb, GFP_ATOMIC);
+ dev_dbg(&usb_dev->dev, "%s done (%d)\n", __func__, ret);
+ return ret;
+}
+
+static int atusb_ed(struct ieee802154_hw *hw, u8 *level)
+{
+ WARN_ON(!level);
+ *level = 0xbe;
+ return 0;
+}
+
+static int atusb_set_hw_addr_filt(struct ieee802154_hw *hw,
+ struct ieee802154_hw_addr_filt *filt,
+ unsigned long changed)
+{
+ struct atusb *atusb = hw->priv;
+ struct device *dev = &atusb->usb_dev->dev;
+
+ if (changed & IEEE802154_AFILT_SADDR_CHANGED) {
+ u16 addr = le16_to_cpu(filt->short_addr);
+
+ dev_vdbg(dev, "%s called for saddr\n", __func__);
+ usb_control_msg_send(atusb->usb_dev, 0, ATUSB_REG_WRITE, ATUSB_REQ_TO_DEV,
+ addr, RG_SHORT_ADDR_0, NULL, 0, 1000, GFP_KERNEL);
+
+ usb_control_msg_send(atusb->usb_dev, 0, ATUSB_REG_WRITE, ATUSB_REQ_TO_DEV,
+ addr >> 8, RG_SHORT_ADDR_1, NULL, 0, 1000, GFP_KERNEL);
+ }
+
+ if (changed & IEEE802154_AFILT_PANID_CHANGED) {
+ u16 pan = le16_to_cpu(filt->pan_id);
+
+ dev_vdbg(dev, "%s called for pan id\n", __func__);
+ usb_control_msg_send(atusb->usb_dev, 0, ATUSB_REG_WRITE, ATUSB_REQ_TO_DEV,
+ pan, RG_PAN_ID_0, NULL, 0, 1000, GFP_KERNEL);
+
+ usb_control_msg_send(atusb->usb_dev, 0, ATUSB_REG_WRITE, ATUSB_REQ_TO_DEV,
+ pan >> 8, RG_PAN_ID_1, NULL, 0, 1000, GFP_KERNEL);
+ }
+
+ if (changed & IEEE802154_AFILT_IEEEADDR_CHANGED) {
+ u8 i, addr[IEEE802154_EXTENDED_ADDR_LEN];
+
+ memcpy(addr, &filt->ieee_addr, IEEE802154_EXTENDED_ADDR_LEN);
+ dev_vdbg(dev, "%s called for IEEE addr\n", __func__);
+ for (i = 0; i < 8; i++)
+ usb_control_msg_send(atusb->usb_dev, 0, ATUSB_REG_WRITE, ATUSB_REQ_TO_DEV,
+ addr[i], RG_IEEE_ADDR_0 + i, NULL, 0,
+ 1000, GFP_KERNEL);
+ }
+
+ if (changed & IEEE802154_AFILT_PANC_CHANGED) {
+ dev_vdbg(dev, "%s called for panc change\n", __func__);
+ if (filt->pan_coord)
+ atusb_write_subreg(atusb, SR_AACK_I_AM_COORD, 1);
+ else
+ atusb_write_subreg(atusb, SR_AACK_I_AM_COORD, 0);
+ }
+
+ return atusb_get_and_clear_error(atusb);
+}
+
+static int atusb_start(struct ieee802154_hw *hw)
+{
+ struct atusb *atusb = hw->priv;
+ struct usb_device *usb_dev = atusb->usb_dev;
+ int ret;
+
+ dev_dbg(&usb_dev->dev, "%s\n", __func__);
+ schedule_delayed_work(&atusb->work, 0);
+ usb_control_msg_send(atusb->usb_dev, 0, ATUSB_RX_MODE, ATUSB_REQ_TO_DEV, 1, 0,
+ NULL, 0, 1000, GFP_KERNEL);
+ ret = atusb_get_and_clear_error(atusb);
+ if (ret < 0)
+ usb_kill_anchored_urbs(&atusb->idle_urbs);
+ return ret;
+}
+
+static void atusb_stop(struct ieee802154_hw *hw)
+{
+ struct atusb *atusb = hw->priv;
+ struct usb_device *usb_dev = atusb->usb_dev;
+
+ dev_dbg(&usb_dev->dev, "%s\n", __func__);
+ usb_kill_anchored_urbs(&atusb->idle_urbs);
+ usb_control_msg_send(atusb->usb_dev, 0, ATUSB_RX_MODE, ATUSB_REQ_TO_DEV, 0, 0,
+ NULL, 0, 1000, GFP_KERNEL);
+ atusb_get_and_clear_error(atusb);
+}
+
+#define ATUSB_MAX_TX_POWERS 0xF
+static const s32 atusb_powers[ATUSB_MAX_TX_POWERS + 1] = {
+ 300, 280, 230, 180, 130, 70, 0, -100, -200, -300, -400, -500, -700,
+ -900, -1200, -1700,
+};
+
+static int
+atusb_txpower(struct ieee802154_hw *hw, s32 mbm)
+{
+ struct atusb *atusb = hw->priv;
+
+ if (atusb->data)
+ return atusb->data->set_txpower(hw, mbm);
+ else
+ return -ENOTSUPP;
+}
+
+static int
+atusb_set_txpower(struct ieee802154_hw *hw, s32 mbm)
+{
+ struct atusb *atusb = hw->priv;
+ u32 i;
+
+ for (i = 0; i < hw->phy->supported.tx_powers_size; i++) {
+ if (hw->phy->supported.tx_powers[i] == mbm)
+ return atusb_write_subreg(atusb, SR_TX_PWR_23X, i);
+ }
+
+ return -EINVAL;
+}
+
+static int
+hulusb_set_txpower(struct ieee802154_hw *hw, s32 mbm)
+{
+ u32 i;
+
+ for (i = 0; i < hw->phy->supported.tx_powers_size; i++) {
+ if (hw->phy->supported.tx_powers[i] == mbm)
+ return atusb_write_subreg(hw->priv, SR_TX_PWR_212, i);
+ }
+
+ return -EINVAL;
+}
+
+#define ATUSB_MAX_ED_LEVELS 0xF
+static const s32 atusb_ed_levels[ATUSB_MAX_ED_LEVELS + 1] = {
+ -9100, -8900, -8700, -8500, -8300, -8100, -7900, -7700, -7500, -7300,
+ -7100, -6900, -6700, -6500, -6300, -6100,
+};
+
+#define AT86RF212_MAX_TX_POWERS 0x1F
+static const s32 at86rf212_powers[AT86RF212_MAX_TX_POWERS + 1] = {
+ 500, 400, 300, 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, -2400, -2500, -2600,
+};
+
+#define AT86RF2XX_MAX_ED_LEVELS 0xF
+static const s32 at86rf212_ed_levels_100[AT86RF2XX_MAX_ED_LEVELS + 1] = {
+ -10000, -9800, -9600, -9400, -9200, -9000, -8800, -8600, -8400, -8200,
+ -8000, -7800, -7600, -7400, -7200, -7000,
+};
+
+static const s32 at86rf212_ed_levels_98[AT86RF2XX_MAX_ED_LEVELS + 1] = {
+ -9800, -9600, -9400, -9200, -9000, -8800, -8600, -8400, -8200, -8000,
+ -7800, -7600, -7400, -7200, -7000, -6800,
+};
+
+static int
+atusb_set_cca_mode(struct ieee802154_hw *hw, const struct wpan_phy_cca *cca)
+{
+ struct atusb *atusb = hw->priv;
+ u8 val;
+
+ /* mapping 802.15.4 to driver spec */
+ switch (cca->mode) {
+ case NL802154_CCA_ENERGY:
+ val = 1;
+ break;
+ case NL802154_CCA_CARRIER:
+ val = 2;
+ break;
+ case NL802154_CCA_ENERGY_CARRIER:
+ switch (cca->opt) {
+ case NL802154_CCA_OPT_ENERGY_CARRIER_AND:
+ val = 3;
+ break;
+ case NL802154_CCA_OPT_ENERGY_CARRIER_OR:
+ val = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return atusb_write_subreg(atusb, SR_CCA_MODE, val);
+}
+
+static int hulusb_set_cca_ed_level(struct atusb *lp, int rssi_base_val)
+{
+ int cca_ed_thres;
+
+ cca_ed_thres = atusb_read_subreg(lp, SR_CCA_ED_THRES);
+ if (cca_ed_thres < 0)
+ return cca_ed_thres;
+
+ switch (rssi_base_val) {
+ case -98:
+ lp->hw->phy->supported.cca_ed_levels = at86rf212_ed_levels_98;
+ lp->hw->phy->supported.cca_ed_levels_size = ARRAY_SIZE(at86rf212_ed_levels_98);
+ lp->hw->phy->cca_ed_level = at86rf212_ed_levels_98[cca_ed_thres];
+ break;
+ case -100:
+ lp->hw->phy->supported.cca_ed_levels = at86rf212_ed_levels_100;
+ lp->hw->phy->supported.cca_ed_levels_size = ARRAY_SIZE(at86rf212_ed_levels_100);
+ lp->hw->phy->cca_ed_level = at86rf212_ed_levels_100[cca_ed_thres];
+ break;
+ default:
+ WARN_ON(1);
+ }
+
+ return 0;
+}
+
+static int
+atusb_set_cca_ed_level(struct ieee802154_hw *hw, s32 mbm)
+{
+ struct atusb *atusb = hw->priv;
+ u32 i;
+
+ for (i = 0; i < hw->phy->supported.cca_ed_levels_size; i++) {
+ if (hw->phy->supported.cca_ed_levels[i] == mbm)
+ return atusb_write_subreg(atusb, SR_CCA_ED_THRES, i);
+ }
+
+ return -EINVAL;
+}
+
+static int atusb_channel(struct ieee802154_hw *hw, u8 page, u8 channel)
+{
+ struct atusb *atusb = hw->priv;
+ int ret = -ENOTSUPP;
+
+ if (atusb->data) {
+ ret = atusb->data->set_channel(hw, page, channel);
+ /* @@@ ugly synchronization */
+ msleep(atusb->data->t_channel_switch);
+ }
+
+ return ret;
+}
+
+static int atusb_set_channel(struct ieee802154_hw *hw, u8 page, u8 channel)
+{
+ struct atusb *atusb = hw->priv;
+ int ret;
+
+ ret = atusb_write_subreg(atusb, SR_CHANNEL, channel);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+static int hulusb_set_channel(struct ieee802154_hw *hw, u8 page, u8 channel)
+{
+ int rc;
+ int rssi_base_val;
+
+ struct atusb *lp = hw->priv;
+
+ if (channel == 0)
+ rc = atusb_write_subreg(lp, SR_SUB_MODE, 0);
+ else
+ rc = atusb_write_subreg(lp, SR_SUB_MODE, 1);
+ if (rc < 0)
+ return rc;
+
+ if (page == 0) {
+ rc = atusb_write_subreg(lp, SR_BPSK_QPSK, 0);
+ rssi_base_val = -100;
+ } else {
+ rc = atusb_write_subreg(lp, SR_BPSK_QPSK, 1);
+ rssi_base_val = -98;
+ }
+ if (rc < 0)
+ return rc;
+
+ rc = hulusb_set_cca_ed_level(lp, rssi_base_val);
+ if (rc < 0)
+ return rc;
+
+ return atusb_write_subreg(lp, SR_CHANNEL, channel);
+}
+
+static int
+atusb_set_csma_params(struct ieee802154_hw *hw, u8 min_be, u8 max_be, u8 retries)
+{
+ struct atusb *atusb = hw->priv;
+ int ret;
+
+ ret = atusb_write_subreg(atusb, SR_MIN_BE, min_be);
+ if (ret)
+ return ret;
+
+ ret = atusb_write_subreg(atusb, SR_MAX_BE, max_be);
+ if (ret)
+ return ret;
+
+ return atusb_write_subreg(atusb, SR_MAX_CSMA_RETRIES, retries);
+}
+
+static int
+hulusb_set_lbt(struct ieee802154_hw *hw, bool on)
+{
+ struct atusb *atusb = hw->priv;
+
+ return atusb_write_subreg(atusb, SR_CSMA_LBT_MODE, on);
+}
+
+static int
+atusb_set_frame_retries(struct ieee802154_hw *hw, s8 retries)
+{
+ struct atusb *atusb = hw->priv;
+
+ return atusb_write_subreg(atusb, SR_MAX_FRAME_RETRIES, retries);
+}
+
+static int
+atusb_set_promiscuous_mode(struct ieee802154_hw *hw, const bool on)
+{
+ struct atusb *atusb = hw->priv;
+ int ret;
+
+ if (on) {
+ ret = atusb_write_subreg(atusb, SR_AACK_DIS_ACK, 1);
+ if (ret < 0)
+ return ret;
+
+ ret = atusb_write_subreg(atusb, SR_AACK_PROM_MODE, 1);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = atusb_write_subreg(atusb, SR_AACK_PROM_MODE, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = atusb_write_subreg(atusb, SR_AACK_DIS_ACK, 0);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct atusb_chip_data atusb_chip_data = {
+ .t_channel_switch = 1,
+ .rssi_base_val = -91,
+ .set_txpower = atusb_set_txpower,
+ .set_channel = atusb_set_channel,
+};
+
+static struct atusb_chip_data hulusb_chip_data = {
+ .t_channel_switch = 11,
+ .rssi_base_val = -100,
+ .set_txpower = hulusb_set_txpower,
+ .set_channel = hulusb_set_channel,
+};
+
+static const struct ieee802154_ops atusb_ops = {
+ .owner = THIS_MODULE,
+ .xmit_async = atusb_xmit,
+ .ed = atusb_ed,
+ .set_channel = atusb_channel,
+ .start = atusb_start,
+ .stop = atusb_stop,
+ .set_hw_addr_filt = atusb_set_hw_addr_filt,
+ .set_txpower = atusb_txpower,
+ .set_lbt = hulusb_set_lbt,
+ .set_cca_mode = atusb_set_cca_mode,
+ .set_cca_ed_level = atusb_set_cca_ed_level,
+ .set_csma_params = atusb_set_csma_params,
+ .set_frame_retries = atusb_set_frame_retries,
+ .set_promiscuous_mode = atusb_set_promiscuous_mode,
+};
+
+/* ----- Firmware and chip version information ----------------------------- */
+
+static int atusb_get_and_show_revision(struct atusb *atusb)
+{
+ struct usb_device *usb_dev = atusb->usb_dev;
+ char *hw_name;
+ unsigned char buffer[3];
+ int ret;
+
+ /* Get a couple of the ATMega Firmware values */
+ ret = usb_control_msg_recv(atusb->usb_dev, 0, ATUSB_ID, ATUSB_REQ_FROM_DEV, 0, 0,
+ buffer, 3, 1000, GFP_KERNEL);
+ if (!ret) {
+ atusb->fw_ver_maj = buffer[0];
+ atusb->fw_ver_min = buffer[1];
+ atusb->fw_hw_type = buffer[2];
+
+ switch (atusb->fw_hw_type) {
+ case ATUSB_HW_TYPE_100813:
+ case ATUSB_HW_TYPE_101216:
+ case ATUSB_HW_TYPE_110131:
+ hw_name = "ATUSB";
+ atusb->data = &atusb_chip_data;
+ break;
+ case ATUSB_HW_TYPE_RZUSB:
+ hw_name = "RZUSB";
+ atusb->data = &atusb_chip_data;
+ break;
+ case ATUSB_HW_TYPE_HULUSB:
+ hw_name = "HULUSB";
+ atusb->data = &hulusb_chip_data;
+ break;
+ default:
+ hw_name = "UNKNOWN";
+ atusb->err = -ENOTSUPP;
+ ret = -ENOTSUPP;
+ break;
+ }
+
+ dev_info(&usb_dev->dev,
+ "Firmware: major: %u, minor: %u, hardware type: %s (%d)\n",
+ atusb->fw_ver_maj, atusb->fw_ver_min, hw_name,
+ atusb->fw_hw_type);
+ }
+ if (atusb->fw_ver_maj == 0 && atusb->fw_ver_min < 2) {
+ dev_info(&usb_dev->dev,
+ "Firmware version (%u.%u) predates our first public release.",
+ atusb->fw_ver_maj, atusb->fw_ver_min);
+ dev_info(&usb_dev->dev, "Please update to version 0.2 or newer");
+ }
+
+ return ret;
+}
+
+static int atusb_get_and_show_build(struct atusb *atusb)
+{
+ struct usb_device *usb_dev = atusb->usb_dev;
+ char *build;
+ int ret;
+
+ build = kmalloc(ATUSB_BUILD_SIZE + 1, GFP_KERNEL);
+ if (!build)
+ return -ENOMEM;
+
+ ret = usb_control_msg(atusb->usb_dev, usb_rcvctrlpipe(usb_dev, 0), ATUSB_BUILD,
+ ATUSB_REQ_FROM_DEV, 0, 0, build, ATUSB_BUILD_SIZE, 1000);
+ if (ret >= 0) {
+ build[ret] = 0;
+ dev_info(&usb_dev->dev, "Firmware: build %s\n", build);
+ }
+
+ kfree(build);
+ return ret;
+}
+
+static int atusb_get_and_conf_chip(struct atusb *atusb)
+{
+ struct usb_device *usb_dev = atusb->usb_dev;
+ u8 man_id_0, man_id_1, part_num, version_num;
+ const char *chip;
+ struct ieee802154_hw *hw = atusb->hw;
+ int ret;
+
+ ret = usb_control_msg_recv(usb_dev, 0, ATUSB_REG_READ, ATUSB_REQ_FROM_DEV,
+ 0, RG_MAN_ID_0, &man_id_0, 1, 1000, GFP_KERNEL);
+ if (ret < 0)
+ return ret;
+
+ ret = usb_control_msg_recv(usb_dev, 0, ATUSB_REG_READ, ATUSB_REQ_FROM_DEV,
+ 0, RG_MAN_ID_1, &man_id_1, 1, 1000, GFP_KERNEL);
+ if (ret < 0)
+ return ret;
+
+ ret = usb_control_msg_recv(usb_dev, 0, ATUSB_REG_READ, ATUSB_REQ_FROM_DEV,
+ 0, RG_PART_NUM, &part_num, 1, 1000, GFP_KERNEL);
+ if (ret < 0)
+ return ret;
+
+ ret = usb_control_msg_recv(usb_dev, 0, ATUSB_REG_READ, ATUSB_REQ_FROM_DEV,
+ 0, RG_VERSION_NUM, &version_num, 1, 1000, GFP_KERNEL);
+ if (ret < 0)
+ return ret;
+
+ hw->flags = IEEE802154_HW_TX_OMIT_CKSUM | IEEE802154_HW_AFILT |
+ IEEE802154_HW_PROMISCUOUS | IEEE802154_HW_CSMA_PARAMS;
+
+ hw->phy->flags = WPAN_PHY_FLAG_TXPOWER | WPAN_PHY_FLAG_CCA_ED_LEVEL |
+ WPAN_PHY_FLAG_CCA_MODE;
+
+ hw->phy->supported.cca_modes = BIT(NL802154_CCA_ENERGY) |
+ BIT(NL802154_CCA_CARRIER) |
+ BIT(NL802154_CCA_ENERGY_CARRIER);
+ hw->phy->supported.cca_opts = BIT(NL802154_CCA_OPT_ENERGY_CARRIER_AND) |
+ BIT(NL802154_CCA_OPT_ENERGY_CARRIER_OR);
+
+ hw->phy->cca.mode = NL802154_CCA_ENERGY;
+
+ hw->phy->current_page = 0;
+
+ if ((man_id_1 << 8 | man_id_0) != ATUSB_JEDEC_ATMEL) {
+ dev_err(&usb_dev->dev,
+ "non-Atmel transceiver xxxx%02x%02x\n",
+ man_id_1, man_id_0);
+ goto fail;
+ }
+
+ switch (part_num) {
+ case 2:
+ chip = "AT86RF230";
+ atusb->hw->phy->supported.channels[0] = 0x7FFF800;
+ atusb->hw->phy->current_channel = 11; /* reset default */
+ atusb->hw->phy->supported.tx_powers = atusb_powers;
+ atusb->hw->phy->supported.tx_powers_size = ARRAY_SIZE(atusb_powers);
+ hw->phy->supported.cca_ed_levels = atusb_ed_levels;
+ hw->phy->supported.cca_ed_levels_size = ARRAY_SIZE(atusb_ed_levels);
+ break;
+ case 3:
+ chip = "AT86RF231";
+ atusb->hw->phy->supported.channels[0] = 0x7FFF800;
+ atusb->hw->phy->current_channel = 11; /* reset default */
+ atusb->hw->phy->supported.tx_powers = atusb_powers;
+ atusb->hw->phy->supported.tx_powers_size = ARRAY_SIZE(atusb_powers);
+ hw->phy->supported.cca_ed_levels = atusb_ed_levels;
+ hw->phy->supported.cca_ed_levels_size = ARRAY_SIZE(atusb_ed_levels);
+ break;
+ case 7:
+ chip = "AT86RF212";
+ atusb->hw->flags |= IEEE802154_HW_LBT;
+ atusb->hw->phy->supported.channels[0] = 0x00007FF;
+ atusb->hw->phy->supported.channels[2] = 0x00007FF;
+ atusb->hw->phy->current_channel = 5;
+ atusb->hw->phy->supported.lbt = NL802154_SUPPORTED_BOOL_BOTH;
+ atusb->hw->phy->supported.tx_powers = at86rf212_powers;
+ atusb->hw->phy->supported.tx_powers_size = ARRAY_SIZE(at86rf212_powers);
+ atusb->hw->phy->supported.cca_ed_levels = at86rf212_ed_levels_100;
+ atusb->hw->phy->supported.cca_ed_levels_size = ARRAY_SIZE(at86rf212_ed_levels_100);
+ break;
+ default:
+ dev_err(&usb_dev->dev,
+ "unexpected transceiver, part 0x%02x version 0x%02x\n",
+ part_num, version_num);
+ goto fail;
+ }
+
+ hw->phy->transmit_power = hw->phy->supported.tx_powers[0];
+ hw->phy->cca_ed_level = hw->phy->supported.cca_ed_levels[7];
+
+ dev_info(&usb_dev->dev, "ATUSB: %s version %d\n", chip, version_num);
+
+ return 0;
+
+fail:
+ atusb->err = -ENODEV;
+ return -ENODEV;
+}
+
+static int atusb_set_extended_addr(struct atusb *atusb)
+{
+ struct usb_device *usb_dev = atusb->usb_dev;
+ unsigned char buffer[IEEE802154_EXTENDED_ADDR_LEN];
+ __le64 extended_addr;
+ u64 addr;
+ int ret;
+
+ /* Firmware versions before 0.3 do not support the EUI64_READ command.
+ * Just use a random address and be done.
+ */
+ if (atusb->fw_ver_maj == 0 && atusb->fw_ver_min < 3) {
+ ieee802154_random_extended_addr(&atusb->hw->phy->perm_extended_addr);
+ return 0;
+ }
+
+ /* Firmware is new enough so we fetch the address from EEPROM */
+ ret = usb_control_msg_recv(atusb->usb_dev, 0, ATUSB_EUI64_READ, ATUSB_REQ_FROM_DEV, 0, 0,
+ buffer, IEEE802154_EXTENDED_ADDR_LEN, 1000, GFP_KERNEL);
+ if (ret < 0) {
+ dev_err(&usb_dev->dev, "failed to fetch extended address, random address set\n");
+ ieee802154_random_extended_addr(&atusb->hw->phy->perm_extended_addr);
+ return ret;
+ }
+
+ memcpy(&extended_addr, buffer, IEEE802154_EXTENDED_ADDR_LEN);
+ /* Check if read address is not empty and the unicast bit is set correctly */
+ if (!ieee802154_is_valid_extended_unicast_addr(extended_addr)) {
+ dev_info(&usb_dev->dev, "no permanent extended address found, random address set\n");
+ ieee802154_random_extended_addr(&atusb->hw->phy->perm_extended_addr);
+ } else {
+ atusb->hw->phy->perm_extended_addr = extended_addr;
+ addr = swab64((__force u64)atusb->hw->phy->perm_extended_addr);
+ dev_info(&usb_dev->dev, "Read permanent extended address %8phC from device\n",
+ &addr);
+ }
+
+ return ret;
+}
+
+/* ----- Setup ------------------------------------------------------------- */
+
+static int atusb_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ struct usb_device *usb_dev = interface_to_usbdev(interface);
+ struct ieee802154_hw *hw;
+ struct atusb *atusb = NULL;
+ int ret = -ENOMEM;
+
+ hw = ieee802154_alloc_hw(sizeof(struct atusb), &atusb_ops);
+ if (!hw)
+ return -ENOMEM;
+
+ atusb = hw->priv;
+ atusb->hw = hw;
+ atusb->usb_dev = usb_get_dev(usb_dev);
+ usb_set_intfdata(interface, atusb);
+
+ atusb->shutdown = 0;
+ atusb->err = 0;
+ INIT_DELAYED_WORK(&atusb->work, atusb_work_urbs);
+ init_usb_anchor(&atusb->idle_urbs);
+ init_usb_anchor(&atusb->rx_urbs);
+
+ if (atusb_alloc_urbs(atusb, ATUSB_NUM_RX_URBS))
+ goto fail;
+
+ atusb->tx_dr.bRequestType = ATUSB_REQ_TO_DEV;
+ atusb->tx_dr.bRequest = ATUSB_TX;
+ atusb->tx_dr.wValue = cpu_to_le16(0);
+
+ atusb->tx_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!atusb->tx_urb)
+ goto fail;
+
+ hw->parent = &usb_dev->dev;
+
+ usb_control_msg_send(atusb->usb_dev, 0, ATUSB_RF_RESET, ATUSB_REQ_TO_DEV, 0, 0,
+ NULL, 0, 1000, GFP_KERNEL);
+ atusb_get_and_conf_chip(atusb);
+ atusb_get_and_show_revision(atusb);
+ atusb_get_and_show_build(atusb);
+ atusb_set_extended_addr(atusb);
+
+ if ((atusb->fw_ver_maj == 0 && atusb->fw_ver_min >= 3) || atusb->fw_ver_maj > 0)
+ hw->flags |= IEEE802154_HW_FRAME_RETRIES;
+
+ ret = atusb_get_and_clear_error(atusb);
+ if (ret) {
+ dev_err(&atusb->usb_dev->dev,
+ "%s: initialization failed, error = %d\n",
+ __func__, ret);
+ goto fail;
+ }
+
+ ret = ieee802154_register_hw(hw);
+ if (ret)
+ goto fail;
+
+ /* If we just powered on, we're now in P_ON and need to enter TRX_OFF
+ * explicitly. Any resets after that will send us straight to TRX_OFF,
+ * making the command below redundant.
+ */
+ usb_control_msg_send(atusb->usb_dev, 0, ATUSB_REG_WRITE, ATUSB_REQ_TO_DEV,
+ STATE_FORCE_TRX_OFF, RG_TRX_STATE, NULL, 0, 1000, GFP_KERNEL);
+
+ msleep(1); /* reset => TRX_OFF, tTR13 = 37 us */
+
+#if 0
+ /* Calculating the maximum time available to empty the frame buffer
+ * on reception:
+ *
+ * According to [1], the inter-frame gap is
+ * R * 20 * 16 us + 128 us
+ * where R is a random number from 0 to 7. Furthermore, we have 20 bit
+ * times (80 us at 250 kbps) of SHR of the next frame before the
+ * transceiver begins storing data in the frame buffer.
+ *
+ * This yields a minimum time of 208 us between the last data of a
+ * frame and the first data of the next frame. This time is further
+ * reduced by interrupt latency in the atusb firmware.
+ *
+ * atusb currently needs about 500 us to retrieve a maximum-sized
+ * frame. We therefore have to allow reception of a new frame to begin
+ * while we retrieve the previous frame.
+ *
+ * [1] "JN-AN-1035 Calculating data rates in an IEEE 802.15.4-based
+ * network", Jennic 2006.
+ * http://www.jennic.com/download_file.php?supportFile=JN-AN-1035%20Calculating%20802-15-4%20Data%20Rates-1v0.pdf
+ */
+
+ atusb_write_subreg(atusb, SR_RX_SAFE_MODE, 1);
+#endif
+ usb_control_msg_send(atusb->usb_dev, 0, ATUSB_REG_WRITE, ATUSB_REQ_TO_DEV,
+ 0xff, RG_IRQ_MASK, NULL, 0, 1000, GFP_KERNEL);
+
+ ret = atusb_get_and_clear_error(atusb);
+ if (!ret)
+ return 0;
+
+ dev_err(&atusb->usb_dev->dev,
+ "%s: setup failed, error = %d\n",
+ __func__, ret);
+
+ ieee802154_unregister_hw(hw);
+fail:
+ atusb_free_urbs(atusb);
+ usb_kill_urb(atusb->tx_urb);
+ usb_free_urb(atusb->tx_urb);
+ usb_put_dev(usb_dev);
+ ieee802154_free_hw(hw);
+ return ret;
+}
+
+static void atusb_disconnect(struct usb_interface *interface)
+{
+ struct atusb *atusb = usb_get_intfdata(interface);
+
+ dev_dbg(&atusb->usb_dev->dev, "%s\n", __func__);
+
+ atusb->shutdown = 1;
+ cancel_delayed_work_sync(&atusb->work);
+
+ usb_kill_anchored_urbs(&atusb->rx_urbs);
+ atusb_free_urbs(atusb);
+ usb_kill_urb(atusb->tx_urb);
+ usb_free_urb(atusb->tx_urb);
+
+ ieee802154_unregister_hw(atusb->hw);
+
+ usb_put_dev(atusb->usb_dev);
+
+ ieee802154_free_hw(atusb->hw);
+
+ usb_set_intfdata(interface, NULL);
+
+ pr_debug("%s done\n", __func__);
+}
+
+/* The devices we work with */
+static const struct usb_device_id atusb_device_table[] = {
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+ USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = ATUSB_VENDOR_ID,
+ .idProduct = ATUSB_PRODUCT_ID,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC
+ },
+ /* end with null element */
+ {}
+};
+MODULE_DEVICE_TABLE(usb, atusb_device_table);
+
+static struct usb_driver atusb_driver = {
+ .name = "atusb",
+ .probe = atusb_probe,
+ .disconnect = atusb_disconnect,
+ .id_table = atusb_device_table,
+};
+module_usb_driver(atusb_driver);
+
+MODULE_AUTHOR("Alexander Aring <alex.aring@gmail.com>");
+MODULE_AUTHOR("Richard Sharpe <realrichardsharpe@gmail.com>");
+MODULE_AUTHOR("Stefan Schmidt <stefan@datenfreihafen.org>");
+MODULE_AUTHOR("Werner Almesberger <werner@almesberger.net>");
+MODULE_AUTHOR("Josef Filzmaier <j.filzmaier@gmx.at>");
+MODULE_DESCRIPTION("ATUSB IEEE 802.15.4 Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ieee802154/atusb.h b/drivers/net/ieee802154/atusb.h
new file mode 100644
index 0000000000..411a611ecc
--- /dev/null
+++ b/drivers/net/ieee802154/atusb.h
@@ -0,0 +1,95 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * atusb.h - Definitions shared between kernel and ATUSB firmware
+ *
+ * Written 2013 by Werner Almesberger <werner@almesberger.net>
+ *
+ * (at your option) any later version.
+ *
+ * This file should be identical for kernel and firmware.
+ * Kernel: drivers/net/ieee802154/atusb.h
+ * Firmware: ben-wpan/atusb/fw/include/atusb/atusb.h
+ */
+
+#ifndef _ATUSB_H
+#define _ATUSB_H
+
+#define ATUSB_VENDOR_ID 0x20b7 /* Qi Hardware*/
+#define ATUSB_PRODUCT_ID 0x1540 /* 802.15.4, device 0 */
+ /* -- - - */
+
+#define ATUSB_BUILD_SIZE 256 /* maximum build version/date message length */
+
+/* Commands to our device. Make sure this is synced with the firmware */
+enum atusb_requests {
+ ATUSB_ID = 0x00, /* system status/control grp */
+ ATUSB_BUILD,
+ ATUSB_RESET,
+ ATUSB_RF_RESET = 0x10, /* debug/test group */
+ ATUSB_POLL_INT,
+ ATUSB_TEST, /* atusb-sil only */
+ ATUSB_TIMER,
+ ATUSB_GPIO,
+ ATUSB_SLP_TR,
+ ATUSB_GPIO_CLEANUP,
+ ATUSB_REG_WRITE = 0x20, /* transceiver group */
+ ATUSB_REG_READ,
+ ATUSB_BUF_WRITE,
+ ATUSB_BUF_READ,
+ ATUSB_SRAM_WRITE,
+ ATUSB_SRAM_READ,
+ ATUSB_SPI_WRITE = 0x30, /* SPI group */
+ ATUSB_SPI_READ1,
+ ATUSB_SPI_READ2,
+ ATUSB_SPI_WRITE2_SYNC,
+ ATUSB_RX_MODE = 0x40, /* HardMAC group */
+ ATUSB_TX,
+ ATUSB_EUI64_WRITE = 0x50, /* Parameter in EEPROM grp */
+ ATUSB_EUI64_READ,
+};
+
+enum {
+ ATUSB_HW_TYPE_100813, /* 2010-08-13 */
+ ATUSB_HW_TYPE_101216, /* 2010-12-16 */
+ ATUSB_HW_TYPE_110131, /* 2011-01-31, ATmega32U2-based */
+ ATUSB_HW_TYPE_RZUSB, /* Atmel Raven USB dongle with at86rf230 */
+ ATUSB_HW_TYPE_HULUSB, /* Busware HUL USB dongle with at86rf212 */
+};
+
+/*
+ * Direction bRequest wValue wIndex wLength
+ *
+ * ->host ATUSB_ID - - 3
+ * ->host ATUSB_BUILD - - #bytes
+ * host-> ATUSB_RESET - - 0
+ *
+ * host-> ATUSB_RF_RESET - - 0
+ * ->host ATUSB_POLL_INT - - 1
+ * host-> ATUSB_TEST - - 0
+ * ->host ATUSB_TIMER - - #bytes (6)
+ * ->host ATUSB_GPIO dir+data mask+p# 3
+ * host-> ATUSB_SLP_TR - - 0
+ * host-> ATUSB_GPIO_CLEANUP - - 0
+ *
+ * host-> ATUSB_REG_WRITE value addr 0
+ * ->host ATUSB_REG_READ - addr 1
+ * host-> ATUSB_BUF_WRITE - - #bytes
+ * ->host ATUSB_BUF_READ - - #bytes
+ * host-> ATUSB_SRAM_WRITE - addr #bytes
+ * ->host ATUSB_SRAM_READ - addr #bytes
+ *
+ * host-> ATUSB_SPI_WRITE byte0 byte1 #bytes
+ * ->host ATUSB_SPI_READ1 byte0 - #bytes
+ * ->host ATUSB_SPI_READ2 byte0 byte1 #bytes
+ * ->host ATUSB_SPI_WRITE2_SYNC byte0 byte1 0/1
+ *
+ * host-> ATUSB_RX_MODE on - 0
+ * host-> ATUSB_TX flags ack_seq #bytes
+ * host-> ATUSB_EUI64_WRITE - - #bytes (8)
+ * ->host ATUSB_EUI64_READ - - #bytes (8)
+ */
+
+#define ATUSB_REQ_FROM_DEV (USB_TYPE_VENDOR | USB_DIR_IN)
+#define ATUSB_REQ_TO_DEV (USB_TYPE_VENDOR | USB_DIR_OUT)
+
+#endif /* !_ATUSB_H */
diff --git a/drivers/net/ieee802154/ca8210.c b/drivers/net/ieee802154/ca8210.c
new file mode 100644
index 0000000000..4ec0dab388
--- /dev/null
+++ b/drivers/net/ieee802154/ca8210.c
@@ -0,0 +1,3181 @@
+/*
+ * http://www.cascoda.com/products/ca-821x/
+ * Copyright (c) 2016, Cascoda, Ltd.
+ * All rights reserved.
+ *
+ * This code is dual-licensed under both GPLv2 and 3-clause BSD. What follows is
+ * the license notice for both respectively.
+ *
+ *******************************************************************************
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ *******************************************************************************
+ *
+ * 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.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * 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 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 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 DAMAGE.
+ */
+
+#include <linux/cdev.h>
+#include <linux/clk-provider.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio.h>
+#include <linux/ieee802154.h>
+#include <linux/io.h>
+#include <linux/kfifo.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/poll.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+
+#include <net/ieee802154_netdev.h>
+#include <net/mac802154.h>
+
+#define DRIVER_NAME "ca8210"
+
+/* external clock frequencies */
+#define ONE_MHZ 1000000
+#define TWO_MHZ (2 * ONE_MHZ)
+#define FOUR_MHZ (4 * ONE_MHZ)
+#define EIGHT_MHZ (8 * ONE_MHZ)
+#define SIXTEEN_MHZ (16 * ONE_MHZ)
+
+/* spi constants */
+#define CA8210_SPI_BUF_SIZE 256
+#define CA8210_SYNC_TIMEOUT 1000 /* Timeout for synchronous commands [ms] */
+
+/* test interface constants */
+#define CA8210_TEST_INT_FILE_NAME "ca8210_test"
+#define CA8210_TEST_INT_FIFO_SIZE 256
+
+/* HWME attribute IDs */
+#define HWME_EDTHRESHOLD (0x04)
+#define HWME_EDVALUE (0x06)
+#define HWME_SYSCLKOUT (0x0F)
+#define HWME_LQILIMIT (0x11)
+
+/* TDME attribute IDs */
+#define TDME_CHANNEL (0x00)
+#define TDME_ATM_CONFIG (0x06)
+
+#define MAX_HWME_ATTRIBUTE_SIZE 16
+#define MAX_TDME_ATTRIBUTE_SIZE 2
+
+/* PHY/MAC PIB Attribute Enumerations */
+#define PHY_CURRENT_CHANNEL (0x00)
+#define PHY_TRANSMIT_POWER (0x02)
+#define PHY_CCA_MODE (0x03)
+#define MAC_ASSOCIATION_PERMIT (0x41)
+#define MAC_AUTO_REQUEST (0x42)
+#define MAC_BATT_LIFE_EXT (0x43)
+#define MAC_BATT_LIFE_EXT_PERIODS (0x44)
+#define MAC_BEACON_PAYLOAD (0x45)
+#define MAC_BEACON_PAYLOAD_LENGTH (0x46)
+#define MAC_BEACON_ORDER (0x47)
+#define MAC_GTS_PERMIT (0x4d)
+#define MAC_MAX_CSMA_BACKOFFS (0x4e)
+#define MAC_MIN_BE (0x4f)
+#define MAC_PAN_ID (0x50)
+#define MAC_PROMISCUOUS_MODE (0x51)
+#define MAC_RX_ON_WHEN_IDLE (0x52)
+#define MAC_SHORT_ADDRESS (0x53)
+#define MAC_SUPERFRAME_ORDER (0x54)
+#define MAC_ASSOCIATED_PAN_COORD (0x56)
+#define MAC_MAX_BE (0x57)
+#define MAC_MAX_FRAME_RETRIES (0x59)
+#define MAC_RESPONSE_WAIT_TIME (0x5A)
+#define MAC_SECURITY_ENABLED (0x5D)
+
+#define MAC_AUTO_REQUEST_SECURITY_LEVEL (0x78)
+#define MAC_AUTO_REQUEST_KEY_ID_MODE (0x79)
+
+#define NS_IEEE_ADDRESS (0xFF) /* Non-standard IEEE address */
+
+/* MAC Address Mode Definitions */
+#define MAC_MODE_NO_ADDR (0x00)
+#define MAC_MODE_SHORT_ADDR (0x02)
+#define MAC_MODE_LONG_ADDR (0x03)
+
+/* MAC constants */
+#define MAX_BEACON_OVERHEAD (75)
+#define MAX_BEACON_PAYLOAD_LENGTH (IEEE802154_MTU - MAX_BEACON_OVERHEAD)
+
+#define MAX_ATTRIBUTE_SIZE (122)
+#define MAX_DATA_SIZE (114)
+
+#define CA8210_VALID_CHANNELS (0x07FFF800)
+
+/* MAC workarounds for V1.1 and MPW silicon (V0.x) */
+#define CA8210_MAC_WORKAROUNDS (0)
+#define CA8210_MAC_MPW (0)
+
+/* memory manipulation macros */
+#define LS_BYTE(x) ((u8)((x) & 0xFF))
+#define MS_BYTE(x) ((u8)(((x) >> 8) & 0xFF))
+
+/* message ID codes in SPI commands */
+/* downstream */
+#define MCPS_DATA_REQUEST (0x00)
+#define MLME_ASSOCIATE_REQUEST (0x02)
+#define MLME_ASSOCIATE_RESPONSE (0x03)
+#define MLME_DISASSOCIATE_REQUEST (0x04)
+#define MLME_GET_REQUEST (0x05)
+#define MLME_ORPHAN_RESPONSE (0x06)
+#define MLME_RESET_REQUEST (0x07)
+#define MLME_RX_ENABLE_REQUEST (0x08)
+#define MLME_SCAN_REQUEST (0x09)
+#define MLME_SET_REQUEST (0x0A)
+#define MLME_START_REQUEST (0x0B)
+#define MLME_POLL_REQUEST (0x0D)
+#define HWME_SET_REQUEST (0x0E)
+#define HWME_GET_REQUEST (0x0F)
+#define TDME_SETSFR_REQUEST (0x11)
+#define TDME_GETSFR_REQUEST (0x12)
+#define TDME_SET_REQUEST (0x14)
+/* upstream */
+#define MCPS_DATA_INDICATION (0x00)
+#define MCPS_DATA_CONFIRM (0x01)
+#define MLME_RESET_CONFIRM (0x0A)
+#define MLME_SET_CONFIRM (0x0E)
+#define MLME_START_CONFIRM (0x0F)
+#define HWME_SET_CONFIRM (0x12)
+#define HWME_GET_CONFIRM (0x13)
+#define HWME_WAKEUP_INDICATION (0x15)
+#define TDME_SETSFR_CONFIRM (0x17)
+
+/* SPI command IDs */
+/* bit indicating a confirm or indication from slave to master */
+#define SPI_S2M (0x20)
+/* bit indicating a synchronous message */
+#define SPI_SYN (0x40)
+
+/* SPI command definitions */
+#define SPI_IDLE (0xFF)
+#define SPI_NACK (0xF0)
+
+#define SPI_MCPS_DATA_REQUEST (MCPS_DATA_REQUEST)
+#define SPI_MCPS_DATA_INDICATION (MCPS_DATA_INDICATION + SPI_S2M)
+#define SPI_MCPS_DATA_CONFIRM (MCPS_DATA_CONFIRM + SPI_S2M)
+
+#define SPI_MLME_ASSOCIATE_REQUEST (MLME_ASSOCIATE_REQUEST)
+#define SPI_MLME_RESET_REQUEST (MLME_RESET_REQUEST + SPI_SYN)
+#define SPI_MLME_SET_REQUEST (MLME_SET_REQUEST + SPI_SYN)
+#define SPI_MLME_START_REQUEST (MLME_START_REQUEST + SPI_SYN)
+#define SPI_MLME_RESET_CONFIRM (MLME_RESET_CONFIRM + SPI_S2M + SPI_SYN)
+#define SPI_MLME_SET_CONFIRM (MLME_SET_CONFIRM + SPI_S2M + SPI_SYN)
+#define SPI_MLME_START_CONFIRM (MLME_START_CONFIRM + SPI_S2M + SPI_SYN)
+
+#define SPI_HWME_SET_REQUEST (HWME_SET_REQUEST + SPI_SYN)
+#define SPI_HWME_GET_REQUEST (HWME_GET_REQUEST + SPI_SYN)
+#define SPI_HWME_SET_CONFIRM (HWME_SET_CONFIRM + SPI_S2M + SPI_SYN)
+#define SPI_HWME_GET_CONFIRM (HWME_GET_CONFIRM + SPI_S2M + SPI_SYN)
+#define SPI_HWME_WAKEUP_INDICATION (HWME_WAKEUP_INDICATION + SPI_S2M)
+
+#define SPI_TDME_SETSFR_REQUEST (TDME_SETSFR_REQUEST + SPI_SYN)
+#define SPI_TDME_SET_REQUEST (TDME_SET_REQUEST + SPI_SYN)
+#define SPI_TDME_SETSFR_CONFIRM (TDME_SETSFR_CONFIRM + SPI_S2M + SPI_SYN)
+
+/* TDME SFR addresses */
+/* Page 0 */
+#define CA8210_SFR_PACFG (0xB1)
+#define CA8210_SFR_MACCON (0xD8)
+#define CA8210_SFR_PACFGIB (0xFE)
+/* Page 1 */
+#define CA8210_SFR_LOTXCAL (0xBF)
+#define CA8210_SFR_PTHRH (0xD1)
+#define CA8210_SFR_PRECFG (0xD3)
+#define CA8210_SFR_LNAGX40 (0xE1)
+#define CA8210_SFR_LNAGX41 (0xE2)
+#define CA8210_SFR_LNAGX42 (0xE3)
+#define CA8210_SFR_LNAGX43 (0xE4)
+#define CA8210_SFR_LNAGX44 (0xE5)
+#define CA8210_SFR_LNAGX45 (0xE6)
+#define CA8210_SFR_LNAGX46 (0xE7)
+#define CA8210_SFR_LNAGX47 (0xE9)
+
+#define PACFGIB_DEFAULT_CURRENT (0x3F)
+#define PTHRH_DEFAULT_THRESHOLD (0x5A)
+#define LNAGX40_DEFAULT_GAIN (0x29) /* 10dB */
+#define LNAGX41_DEFAULT_GAIN (0x54) /* 21dB */
+#define LNAGX42_DEFAULT_GAIN (0x6C) /* 27dB */
+#define LNAGX43_DEFAULT_GAIN (0x7A) /* 30dB */
+#define LNAGX44_DEFAULT_GAIN (0x84) /* 33dB */
+#define LNAGX45_DEFAULT_GAIN (0x8B) /* 34dB */
+#define LNAGX46_DEFAULT_GAIN (0x92) /* 36dB */
+#define LNAGX47_DEFAULT_GAIN (0x96) /* 37dB */
+
+#define CA8210_IOCTL_HARD_RESET (0x00)
+
+/* Structs/Enums */
+
+/**
+ * struct cas_control - spi transfer structure
+ * @msg: spi_message for each exchange
+ * @transfer: spi_transfer for each exchange
+ * @tx_buf: source array for transmission
+ * @tx_in_buf: array storing bytes received during transmission
+ * @priv: pointer to private data
+ *
+ * This structure stores all the necessary data passed around during a single
+ * spi exchange.
+ */
+struct cas_control {
+ struct spi_message msg;
+ struct spi_transfer transfer;
+
+ u8 tx_buf[CA8210_SPI_BUF_SIZE];
+ u8 tx_in_buf[CA8210_SPI_BUF_SIZE];
+
+ struct ca8210_priv *priv;
+};
+
+/**
+ * struct ca8210_test - ca8210 test interface structure
+ * @ca8210_dfs_spi_int: pointer to the entry in the debug fs for this device
+ * @up_fifo: fifo for upstream messages
+ * @readq: read wait queue
+ *
+ * This structure stores all the data pertaining to the debug interface
+ */
+struct ca8210_test {
+ struct dentry *ca8210_dfs_spi_int;
+ struct kfifo up_fifo;
+ wait_queue_head_t readq;
+};
+
+/**
+ * struct ca8210_priv - ca8210 private data structure
+ * @spi: pointer to the ca8210 spi device object
+ * @hw: pointer to the ca8210 ieee802154_hw object
+ * @hw_registered: true if hw has been registered with ieee802154
+ * @lock: spinlock protecting the private data area
+ * @mlme_workqueue: workqueue for triggering MLME Reset
+ * @irq_workqueue: workqueue for irq processing
+ * @tx_skb: current socket buffer to transmit
+ * @nextmsduhandle: msdu handle to pass to the 15.4 MAC layer for the
+ * next transmission
+ * @clk: external clock provided by the ca8210
+ * @last_dsn: sequence number of last data packet received, for
+ * resend detection
+ * @test: test interface data section for this instance
+ * @async_tx_pending: true if an asynchronous transmission was started and
+ * is not complete
+ * @sync_command_response: pointer to buffer to fill with sync response
+ * @ca8210_is_awake: nonzero if ca8210 is initialised, ready for comms
+ * @sync_down: counts number of downstream synchronous commands
+ * @sync_up: counts number of upstream synchronous commands
+ * @spi_transfer_complete: completion object for a single spi_transfer
+ * @sync_exchange_complete: completion object for a complete synchronous API
+ * exchange
+ * @promiscuous: whether the ca8210 is in promiscuous mode or not
+ * @retries: records how many times the current pending spi
+ * transfer has been retried
+ */
+struct ca8210_priv {
+ struct spi_device *spi;
+ struct ieee802154_hw *hw;
+ bool hw_registered;
+ spinlock_t lock;
+ struct workqueue_struct *mlme_workqueue;
+ struct workqueue_struct *irq_workqueue;
+ struct sk_buff *tx_skb;
+ u8 nextmsduhandle;
+ struct clk *clk;
+ int last_dsn;
+ struct ca8210_test test;
+ bool async_tx_pending;
+ u8 *sync_command_response;
+ struct completion ca8210_is_awake;
+ int sync_down, sync_up;
+ struct completion spi_transfer_complete, sync_exchange_complete;
+ bool promiscuous;
+ int retries;
+};
+
+/**
+ * struct work_priv_container - link between a work object and the relevant
+ * device's private data
+ * @work: work object being executed
+ * @priv: device's private data section
+ *
+ */
+struct work_priv_container {
+ struct work_struct work;
+ struct ca8210_priv *priv;
+};
+
+/**
+ * struct ca8210_platform_data - ca8210 platform data structure
+ * @extclockenable: true if the external clock is to be enabled
+ * @extclockfreq: frequency of the external clock
+ * @extclockgpio: ca8210 output gpio of the external clock
+ * @gpio_reset: gpio number of ca8210 reset line
+ * @gpio_irq: gpio number of ca8210 interrupt line
+ * @irq_id: identifier for the ca8210 irq
+ *
+ */
+struct ca8210_platform_data {
+ bool extclockenable;
+ unsigned int extclockfreq;
+ unsigned int extclockgpio;
+ int gpio_reset;
+ int gpio_irq;
+ int irq_id;
+};
+
+/**
+ * struct fulladdr - full MAC addressing information structure
+ * @mode: address mode (none, short, extended)
+ * @pan_id: 16-bit LE pan id
+ * @address: LE address, variable length as specified by mode
+ *
+ */
+struct fulladdr {
+ u8 mode;
+ u8 pan_id[2];
+ u8 address[8];
+};
+
+/**
+ * union macaddr: generic MAC address container
+ * @short_address: 16-bit short address
+ * @ieee_address: 64-bit extended address as LE byte array
+ *
+ */
+union macaddr {
+ u16 short_address;
+ u8 ieee_address[8];
+};
+
+/**
+ * struct secspec: security specification for SAP commands
+ * @security_level: 0-7, controls level of authentication & encryption
+ * @key_id_mode: 0-3, specifies how to obtain key
+ * @key_source: extended key retrieval data
+ * @key_index: single-byte key identifier
+ *
+ */
+struct secspec {
+ u8 security_level;
+ u8 key_id_mode;
+ u8 key_source[8];
+ u8 key_index;
+};
+
+/* downlink functions parameter set definitions */
+struct mcps_data_request_pset {
+ u8 src_addr_mode;
+ struct fulladdr dst;
+ u8 msdu_length;
+ u8 msdu_handle;
+ u8 tx_options;
+ u8 msdu[MAX_DATA_SIZE];
+};
+
+struct mlme_set_request_pset {
+ u8 pib_attribute;
+ u8 pib_attribute_index;
+ u8 pib_attribute_length;
+ u8 pib_attribute_value[MAX_ATTRIBUTE_SIZE];
+};
+
+struct hwme_set_request_pset {
+ u8 hw_attribute;
+ u8 hw_attribute_length;
+ u8 hw_attribute_value[MAX_HWME_ATTRIBUTE_SIZE];
+};
+
+struct hwme_get_request_pset {
+ u8 hw_attribute;
+};
+
+struct tdme_setsfr_request_pset {
+ u8 sfr_page;
+ u8 sfr_address;
+ u8 sfr_value;
+};
+
+/* uplink functions parameter set definitions */
+struct hwme_set_confirm_pset {
+ u8 status;
+ u8 hw_attribute;
+};
+
+struct hwme_get_confirm_pset {
+ u8 status;
+ u8 hw_attribute;
+ u8 hw_attribute_length;
+ u8 hw_attribute_value[MAX_HWME_ATTRIBUTE_SIZE];
+};
+
+struct tdme_setsfr_confirm_pset {
+ u8 status;
+ u8 sfr_page;
+ u8 sfr_address;
+};
+
+struct mac_message {
+ u8 command_id;
+ u8 length;
+ union {
+ struct mcps_data_request_pset data_req;
+ struct mlme_set_request_pset set_req;
+ struct hwme_set_request_pset hwme_set_req;
+ struct hwme_get_request_pset hwme_get_req;
+ struct tdme_setsfr_request_pset tdme_set_sfr_req;
+ struct hwme_set_confirm_pset hwme_set_cnf;
+ struct hwme_get_confirm_pset hwme_get_cnf;
+ struct tdme_setsfr_confirm_pset tdme_set_sfr_cnf;
+ u8 u8param;
+ u8 status;
+ u8 payload[148];
+ } pdata;
+};
+
+union pa_cfg_sfr {
+ struct {
+ u8 bias_current_trim : 3;
+ u8 /* reserved */ : 1;
+ u8 buffer_capacitor_trim : 3;
+ u8 boost : 1;
+ };
+ u8 paib;
+};
+
+struct preamble_cfg_sfr {
+ u8 timeout_symbols : 3;
+ u8 acquisition_symbols : 3;
+ u8 search_symbols : 2;
+};
+
+static int (*cascoda_api_upstream)(
+ const u8 *buf,
+ size_t len,
+ void *device_ref
+);
+
+/**
+ * link_to_linux_err() - Translates an 802.15.4 return code into the closest
+ * linux error
+ * @link_status: 802.15.4 status code
+ *
+ * Return: 0 or Linux error code
+ */
+static int link_to_linux_err(int link_status)
+{
+ if (link_status < 0) {
+ /* status is already a Linux code */
+ return link_status;
+ }
+ switch (link_status) {
+ case IEEE802154_SUCCESS:
+ case IEEE802154_REALIGNMENT:
+ return 0;
+ case IEEE802154_IMPROPER_KEY_TYPE:
+ return -EKEYREJECTED;
+ case IEEE802154_IMPROPER_SECURITY_LEVEL:
+ case IEEE802154_UNSUPPORTED_LEGACY:
+ case IEEE802154_DENIED:
+ return -EACCES;
+ case IEEE802154_BEACON_LOST:
+ case IEEE802154_NO_ACK:
+ case IEEE802154_NO_BEACON:
+ return -ENETUNREACH;
+ case IEEE802154_CHANNEL_ACCESS_FAILURE:
+ case IEEE802154_TX_ACTIVE:
+ case IEEE802154_SCAN_IN_PROGRESS:
+ return -EBUSY;
+ case IEEE802154_DISABLE_TRX_FAILURE:
+ case IEEE802154_OUT_OF_CAP:
+ return -EAGAIN;
+ case IEEE802154_FRAME_TOO_LONG:
+ return -EMSGSIZE;
+ case IEEE802154_INVALID_GTS:
+ case IEEE802154_PAST_TIME:
+ return -EBADSLT;
+ case IEEE802154_INVALID_HANDLE:
+ return -EBADMSG;
+ case IEEE802154_INVALID_PARAMETER:
+ case IEEE802154_UNSUPPORTED_ATTRIBUTE:
+ case IEEE802154_ON_TIME_TOO_LONG:
+ case IEEE802154_INVALID_INDEX:
+ return -EINVAL;
+ case IEEE802154_NO_DATA:
+ return -ENODATA;
+ case IEEE802154_NO_SHORT_ADDRESS:
+ return -EFAULT;
+ case IEEE802154_PAN_ID_CONFLICT:
+ return -EADDRINUSE;
+ case IEEE802154_TRANSACTION_EXPIRED:
+ return -ETIME;
+ case IEEE802154_TRANSACTION_OVERFLOW:
+ return -ENOBUFS;
+ case IEEE802154_UNAVAILABLE_KEY:
+ return -ENOKEY;
+ case IEEE802154_INVALID_ADDRESS:
+ return -ENXIO;
+ case IEEE802154_TRACKING_OFF:
+ case IEEE802154_SUPERFRAME_OVERLAP:
+ return -EREMOTEIO;
+ case IEEE802154_LIMIT_REACHED:
+ return -EDQUOT;
+ case IEEE802154_READ_ONLY:
+ return -EROFS;
+ default:
+ return -EPROTO;
+ }
+}
+
+/**
+ * ca8210_test_int_driver_write() - Writes a message to the test interface to be
+ * read by the userspace
+ * @buf: Buffer containing upstream message
+ * @len: length of message to write
+ * @spi: SPI device of message originator
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_test_int_driver_write(
+ const u8 *buf,
+ size_t len,
+ void *spi
+)
+{
+ struct ca8210_priv *priv = spi_get_drvdata(spi);
+ struct ca8210_test *test = &priv->test;
+ char *fifo_buffer;
+ int i;
+
+ dev_dbg(
+ &priv->spi->dev,
+ "test_interface: Buffering upstream message:\n"
+ );
+ for (i = 0; i < len; i++)
+ dev_dbg(&priv->spi->dev, "%#03x\n", buf[i]);
+
+ fifo_buffer = kmemdup(buf, len, GFP_KERNEL);
+ if (!fifo_buffer)
+ return -ENOMEM;
+ kfifo_in(&test->up_fifo, &fifo_buffer, 4);
+ wake_up_interruptible(&priv->test.readq);
+
+ return 0;
+}
+
+/* SPI Operation */
+
+static int ca8210_net_rx(
+ struct ieee802154_hw *hw,
+ u8 *command,
+ size_t len
+);
+static u8 mlme_reset_request_sync(
+ u8 set_default_pib,
+ void *device_ref
+);
+static int ca8210_spi_transfer(
+ struct spi_device *spi,
+ const u8 *buf,
+ size_t len
+);
+
+/**
+ * ca8210_reset_send() - Hard resets the ca8210 for a given time
+ * @spi: Pointer to target ca8210 spi device
+ * @ms: Milliseconds to hold the reset line low for
+ */
+static void ca8210_reset_send(struct spi_device *spi, unsigned int ms)
+{
+ struct ca8210_platform_data *pdata = spi->dev.platform_data;
+ struct ca8210_priv *priv = spi_get_drvdata(spi);
+ long status;
+
+ gpio_set_value(pdata->gpio_reset, 0);
+ reinit_completion(&priv->ca8210_is_awake);
+ msleep(ms);
+ gpio_set_value(pdata->gpio_reset, 1);
+ priv->promiscuous = false;
+
+ /* Wait until wakeup indication seen */
+ status = wait_for_completion_interruptible_timeout(
+ &priv->ca8210_is_awake,
+ msecs_to_jiffies(CA8210_SYNC_TIMEOUT)
+ );
+ if (status == 0) {
+ dev_crit(
+ &spi->dev,
+ "Fatal: No wakeup from ca8210 after reset!\n"
+ );
+ }
+
+ dev_dbg(&spi->dev, "Reset the device\n");
+}
+
+/**
+ * ca8210_mlme_reset_worker() - Resets the MLME, Called when the MAC OVERFLOW
+ * condition happens.
+ * @work: Pointer to work being executed
+ */
+static void ca8210_mlme_reset_worker(struct work_struct *work)
+{
+ struct work_priv_container *wpc = container_of(
+ work,
+ struct work_priv_container,
+ work
+ );
+ struct ca8210_priv *priv = wpc->priv;
+
+ mlme_reset_request_sync(0, priv->spi);
+ kfree(wpc);
+}
+
+/**
+ * ca8210_rx_done() - Calls various message dispatches responding to a received
+ * command
+ * @cas_ctl: Pointer to the cas_control object for the relevant spi transfer
+ *
+ * Presents a received SAP command from the ca8210 to the Cascoda EVBME, test
+ * interface and network driver.
+ */
+static void ca8210_rx_done(struct cas_control *cas_ctl)
+{
+ u8 *buf;
+ unsigned int len;
+ struct work_priv_container *mlme_reset_wpc;
+ struct ca8210_priv *priv = cas_ctl->priv;
+
+ buf = cas_ctl->tx_in_buf;
+ len = buf[1] + 2;
+ if (len > CA8210_SPI_BUF_SIZE) {
+ dev_crit(
+ &priv->spi->dev,
+ "Received packet len (%u) erroneously long\n",
+ len
+ );
+ goto finish;
+ }
+
+ if (buf[0] & SPI_SYN) {
+ if (priv->sync_command_response) {
+ memcpy(priv->sync_command_response, buf, len);
+ complete(&priv->sync_exchange_complete);
+ } else {
+ if (cascoda_api_upstream)
+ cascoda_api_upstream(buf, len, priv->spi);
+ priv->sync_up++;
+ }
+ } else {
+ if (cascoda_api_upstream)
+ cascoda_api_upstream(buf, len, priv->spi);
+ }
+
+ ca8210_net_rx(priv->hw, buf, len);
+ if (buf[0] == SPI_MCPS_DATA_CONFIRM) {
+ if (buf[3] == IEEE802154_TRANSACTION_OVERFLOW) {
+ dev_info(
+ &priv->spi->dev,
+ "Waiting for transaction overflow to stabilise...\n");
+ msleep(2000);
+ dev_info(
+ &priv->spi->dev,
+ "Resetting MAC...\n");
+
+ mlme_reset_wpc = kmalloc(sizeof(*mlme_reset_wpc),
+ GFP_KERNEL);
+ if (!mlme_reset_wpc)
+ goto finish;
+ INIT_WORK(
+ &mlme_reset_wpc->work,
+ ca8210_mlme_reset_worker
+ );
+ mlme_reset_wpc->priv = priv;
+ queue_work(priv->mlme_workqueue, &mlme_reset_wpc->work);
+ }
+ } else if (buf[0] == SPI_HWME_WAKEUP_INDICATION) {
+ dev_notice(
+ &priv->spi->dev,
+ "Wakeup indication received, reason:\n"
+ );
+ switch (buf[2]) {
+ case 0:
+ dev_notice(
+ &priv->spi->dev,
+ "Transceiver woken up from Power Up / System Reset\n"
+ );
+ break;
+ case 1:
+ dev_notice(
+ &priv->spi->dev,
+ "Watchdog Timer Time-Out\n"
+ );
+ break;
+ case 2:
+ dev_notice(
+ &priv->spi->dev,
+ "Transceiver woken up from Power-Off by Sleep Timer Time-Out\n");
+ break;
+ case 3:
+ dev_notice(
+ &priv->spi->dev,
+ "Transceiver woken up from Power-Off by GPIO Activity\n"
+ );
+ break;
+ case 4:
+ dev_notice(
+ &priv->spi->dev,
+ "Transceiver woken up from Standby by Sleep Timer Time-Out\n"
+ );
+ break;
+ case 5:
+ dev_notice(
+ &priv->spi->dev,
+ "Transceiver woken up from Standby by GPIO Activity\n"
+ );
+ break;
+ case 6:
+ dev_notice(
+ &priv->spi->dev,
+ "Sleep-Timer Time-Out in Active Mode\n"
+ );
+ break;
+ default:
+ dev_warn(&priv->spi->dev, "Wakeup reason unknown\n");
+ break;
+ }
+ complete(&priv->ca8210_is_awake);
+ }
+
+finish:;
+}
+
+static void ca8210_remove(struct spi_device *spi_device);
+
+/**
+ * ca8210_spi_transfer_complete() - Called when a single spi transfer has
+ * completed
+ * @context: Pointer to the cas_control object for the finished transfer
+ */
+static void ca8210_spi_transfer_complete(void *context)
+{
+ struct cas_control *cas_ctl = context;
+ struct ca8210_priv *priv = cas_ctl->priv;
+ bool duplex_rx = false;
+ int i;
+ u8 retry_buffer[CA8210_SPI_BUF_SIZE];
+
+ if (
+ cas_ctl->tx_in_buf[0] == SPI_NACK ||
+ (cas_ctl->tx_in_buf[0] == SPI_IDLE &&
+ cas_ctl->tx_in_buf[1] == SPI_NACK)
+ ) {
+ /* ca8210 is busy */
+ dev_info(&priv->spi->dev, "ca8210 was busy during attempted write\n");
+ if (cas_ctl->tx_buf[0] == SPI_IDLE) {
+ dev_warn(
+ &priv->spi->dev,
+ "IRQ servicing NACKd, dropping transfer\n"
+ );
+ kfree(cas_ctl);
+ return;
+ }
+ if (priv->retries > 3) {
+ dev_err(&priv->spi->dev, "too many retries!\n");
+ kfree(cas_ctl);
+ ca8210_remove(priv->spi);
+ return;
+ }
+ memcpy(retry_buffer, cas_ctl->tx_buf, CA8210_SPI_BUF_SIZE);
+ kfree(cas_ctl);
+ ca8210_spi_transfer(
+ priv->spi,
+ retry_buffer,
+ CA8210_SPI_BUF_SIZE
+ );
+ priv->retries++;
+ dev_info(&priv->spi->dev, "retried spi write\n");
+ return;
+ } else if (
+ cas_ctl->tx_in_buf[0] != SPI_IDLE &&
+ cas_ctl->tx_in_buf[0] != SPI_NACK
+ ) {
+ duplex_rx = true;
+ }
+
+ if (duplex_rx) {
+ dev_dbg(&priv->spi->dev, "READ CMD DURING TX\n");
+ for (i = 0; i < cas_ctl->tx_in_buf[1] + 2; i++)
+ dev_dbg(
+ &priv->spi->dev,
+ "%#03x\n",
+ cas_ctl->tx_in_buf[i]
+ );
+ ca8210_rx_done(cas_ctl);
+ }
+ complete(&priv->spi_transfer_complete);
+ kfree(cas_ctl);
+ priv->retries = 0;
+}
+
+/**
+ * ca8210_spi_transfer() - Initiate duplex spi transfer with ca8210
+ * @spi: Pointer to spi device for transfer
+ * @buf: Octet array to send
+ * @len: length of the buffer being sent
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_spi_transfer(
+ struct spi_device *spi,
+ const u8 *buf,
+ size_t len
+)
+{
+ int i, status = 0;
+ struct ca8210_priv *priv;
+ struct cas_control *cas_ctl;
+
+ if (!spi) {
+ pr_crit("NULL spi device passed to %s\n", __func__);
+ return -ENODEV;
+ }
+
+ priv = spi_get_drvdata(spi);
+ reinit_completion(&priv->spi_transfer_complete);
+
+ dev_dbg(&spi->dev, "%s called\n", __func__);
+
+ cas_ctl = kzalloc(sizeof(*cas_ctl), GFP_ATOMIC);
+ if (!cas_ctl)
+ return -ENOMEM;
+
+ cas_ctl->priv = priv;
+ memset(cas_ctl->tx_buf, SPI_IDLE, CA8210_SPI_BUF_SIZE);
+ memset(cas_ctl->tx_in_buf, SPI_IDLE, CA8210_SPI_BUF_SIZE);
+ memcpy(cas_ctl->tx_buf, buf, len);
+
+ for (i = 0; i < len; i++)
+ dev_dbg(&spi->dev, "%#03x\n", cas_ctl->tx_buf[i]);
+
+ spi_message_init(&cas_ctl->msg);
+
+ cas_ctl->transfer.tx_nbits = 1; /* 1 MOSI line */
+ cas_ctl->transfer.rx_nbits = 1; /* 1 MISO line */
+ cas_ctl->transfer.speed_hz = 0; /* Use device setting */
+ cas_ctl->transfer.bits_per_word = 0; /* Use device setting */
+ cas_ctl->transfer.tx_buf = cas_ctl->tx_buf;
+ cas_ctl->transfer.rx_buf = cas_ctl->tx_in_buf;
+ cas_ctl->transfer.delay.value = 0;
+ cas_ctl->transfer.delay.unit = SPI_DELAY_UNIT_USECS;
+ cas_ctl->transfer.cs_change = 0;
+ cas_ctl->transfer.len = sizeof(struct mac_message);
+ cas_ctl->msg.complete = ca8210_spi_transfer_complete;
+ cas_ctl->msg.context = cas_ctl;
+
+ spi_message_add_tail(
+ &cas_ctl->transfer,
+ &cas_ctl->msg
+ );
+
+ status = spi_async(spi, &cas_ctl->msg);
+ if (status < 0) {
+ dev_crit(
+ &spi->dev,
+ "status %d from spi_sync in write\n",
+ status
+ );
+ }
+
+ return status;
+}
+
+/**
+ * ca8210_spi_exchange() - Exchange API/SAP commands with the radio
+ * @buf: Octet array of command being sent downstream
+ * @len: length of buf
+ * @response: buffer for storing synchronous response
+ * @device_ref: spi_device pointer for ca8210
+ *
+ * Effectively calls ca8210_spi_transfer to write buf[] to the spi, then for
+ * synchronous commands waits for the corresponding response to be read from
+ * the spi before returning. The response is written to the response parameter.
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_spi_exchange(
+ const u8 *buf,
+ size_t len,
+ u8 *response,
+ void *device_ref
+)
+{
+ int status = 0;
+ struct spi_device *spi = device_ref;
+ struct ca8210_priv *priv = spi->dev.driver_data;
+ long wait_remaining;
+
+ if ((buf[0] & SPI_SYN) && response) { /* if sync wait for confirm */
+ reinit_completion(&priv->sync_exchange_complete);
+ priv->sync_command_response = response;
+ }
+
+ do {
+ reinit_completion(&priv->spi_transfer_complete);
+ status = ca8210_spi_transfer(priv->spi, buf, len);
+ if (status) {
+ dev_warn(
+ &spi->dev,
+ "spi write failed, returned %d\n",
+ status
+ );
+ if (status == -EBUSY)
+ continue;
+ if (((buf[0] & SPI_SYN) && response))
+ complete(&priv->sync_exchange_complete);
+ goto cleanup;
+ }
+
+ wait_remaining = wait_for_completion_interruptible_timeout(
+ &priv->spi_transfer_complete,
+ msecs_to_jiffies(1000)
+ );
+ if (wait_remaining == -ERESTARTSYS) {
+ status = -ERESTARTSYS;
+ } else if (wait_remaining == 0) {
+ dev_err(
+ &spi->dev,
+ "SPI downstream transfer timed out!\n"
+ );
+ status = -ETIME;
+ goto cleanup;
+ }
+ } while (status < 0);
+
+ if (!((buf[0] & SPI_SYN) && response))
+ goto cleanup;
+
+ wait_remaining = wait_for_completion_interruptible_timeout(
+ &priv->sync_exchange_complete,
+ msecs_to_jiffies(CA8210_SYNC_TIMEOUT)
+ );
+ if (wait_remaining == -ERESTARTSYS) {
+ status = -ERESTARTSYS;
+ } else if (wait_remaining == 0) {
+ dev_err(
+ &spi->dev,
+ "Synchronous confirm timeout\n"
+ );
+ status = -ETIME;
+ }
+
+cleanup:
+ priv->sync_command_response = NULL;
+ return status;
+}
+
+/**
+ * ca8210_interrupt_handler() - Called when an irq is received from the ca8210
+ * @irq: Id of the irq being handled
+ * @dev_id: Pointer passed by the system, pointing to the ca8210's private data
+ *
+ * This function is called when the irq line from the ca8210 is asserted,
+ * signifying that the ca8210 has a message to send upstream to us. Starts the
+ * asynchronous spi read.
+ *
+ * Return: irq return code
+ */
+static irqreturn_t ca8210_interrupt_handler(int irq, void *dev_id)
+{
+ struct ca8210_priv *priv = dev_id;
+ int status;
+
+ dev_dbg(&priv->spi->dev, "irq: Interrupt occurred\n");
+ do {
+ status = ca8210_spi_transfer(priv->spi, NULL, 0);
+ if (status && (status != -EBUSY)) {
+ dev_warn(
+ &priv->spi->dev,
+ "spi read failed, returned %d\n",
+ status
+ );
+ }
+ } while (status == -EBUSY);
+ return IRQ_HANDLED;
+}
+
+static int (*cascoda_api_downstream)(
+ const u8 *buf,
+ size_t len,
+ u8 *response,
+ void *device_ref
+) = ca8210_spi_exchange;
+
+/* Cascoda API / 15.4 SAP Primitives */
+
+/**
+ * tdme_setsfr_request_sync() - TDME_SETSFR_request/confirm according to API
+ * @sfr_page: SFR Page
+ * @sfr_address: SFR Address
+ * @sfr_value: SFR Value
+ * @device_ref: Nondescript pointer to target device
+ *
+ * Return: 802.15.4 status code of TDME-SETSFR.confirm
+ */
+static u8 tdme_setsfr_request_sync(
+ u8 sfr_page,
+ u8 sfr_address,
+ u8 sfr_value,
+ void *device_ref
+)
+{
+ int ret;
+ struct mac_message command, response;
+ struct spi_device *spi = device_ref;
+
+ command.command_id = SPI_TDME_SETSFR_REQUEST;
+ command.length = 3;
+ command.pdata.tdme_set_sfr_req.sfr_page = sfr_page;
+ command.pdata.tdme_set_sfr_req.sfr_address = sfr_address;
+ command.pdata.tdme_set_sfr_req.sfr_value = sfr_value;
+ response.command_id = SPI_IDLE;
+ ret = cascoda_api_downstream(
+ &command.command_id,
+ command.length + 2,
+ &response.command_id,
+ device_ref
+ );
+ if (ret) {
+ dev_crit(&spi->dev, "cascoda_api_downstream returned %d", ret);
+ return IEEE802154_SYSTEM_ERROR;
+ }
+
+ if (response.command_id != SPI_TDME_SETSFR_CONFIRM) {
+ dev_crit(
+ &spi->dev,
+ "sync response to SPI_TDME_SETSFR_REQUEST was not SPI_TDME_SETSFR_CONFIRM, it was %d\n",
+ response.command_id
+ );
+ return IEEE802154_SYSTEM_ERROR;
+ }
+
+ return response.pdata.tdme_set_sfr_cnf.status;
+}
+
+/**
+ * tdme_chipinit() - TDME Chip Register Default Initialisation Macro
+ * @device_ref: Nondescript pointer to target device
+ *
+ * Return: 802.15.4 status code of API calls
+ */
+static u8 tdme_chipinit(void *device_ref)
+{
+ u8 status = IEEE802154_SUCCESS;
+ u8 sfr_address;
+ struct spi_device *spi = device_ref;
+ struct preamble_cfg_sfr pre_cfg_value = {
+ .timeout_symbols = 3,
+ .acquisition_symbols = 3,
+ .search_symbols = 1,
+ };
+ /* LNA Gain Settings */
+ status = tdme_setsfr_request_sync(
+ 1, (sfr_address = CA8210_SFR_LNAGX40),
+ LNAGX40_DEFAULT_GAIN, device_ref);
+ if (status)
+ goto finish;
+ status = tdme_setsfr_request_sync(
+ 1, (sfr_address = CA8210_SFR_LNAGX41),
+ LNAGX41_DEFAULT_GAIN, device_ref);
+ if (status)
+ goto finish;
+ status = tdme_setsfr_request_sync(
+ 1, (sfr_address = CA8210_SFR_LNAGX42),
+ LNAGX42_DEFAULT_GAIN, device_ref);
+ if (status)
+ goto finish;
+ status = tdme_setsfr_request_sync(
+ 1, (sfr_address = CA8210_SFR_LNAGX43),
+ LNAGX43_DEFAULT_GAIN, device_ref);
+ if (status)
+ goto finish;
+ status = tdme_setsfr_request_sync(
+ 1, (sfr_address = CA8210_SFR_LNAGX44),
+ LNAGX44_DEFAULT_GAIN, device_ref);
+ if (status)
+ goto finish;
+ status = tdme_setsfr_request_sync(
+ 1, (sfr_address = CA8210_SFR_LNAGX45),
+ LNAGX45_DEFAULT_GAIN, device_ref);
+ if (status)
+ goto finish;
+ status = tdme_setsfr_request_sync(
+ 1, (sfr_address = CA8210_SFR_LNAGX46),
+ LNAGX46_DEFAULT_GAIN, device_ref);
+ if (status)
+ goto finish;
+ status = tdme_setsfr_request_sync(
+ 1, (sfr_address = CA8210_SFR_LNAGX47),
+ LNAGX47_DEFAULT_GAIN, device_ref);
+ if (status)
+ goto finish;
+ /* Preamble Timing Config */
+ status = tdme_setsfr_request_sync(
+ 1, (sfr_address = CA8210_SFR_PRECFG),
+ *((u8 *)&pre_cfg_value), device_ref);
+ if (status)
+ goto finish;
+ /* Preamble Threshold High */
+ status = tdme_setsfr_request_sync(
+ 1, (sfr_address = CA8210_SFR_PTHRH),
+ PTHRH_DEFAULT_THRESHOLD, device_ref);
+ if (status)
+ goto finish;
+ /* Tx Output Power 8 dBm */
+ status = tdme_setsfr_request_sync(
+ 0, (sfr_address = CA8210_SFR_PACFGIB),
+ PACFGIB_DEFAULT_CURRENT, device_ref);
+ if (status)
+ goto finish;
+
+finish:
+ if (status != IEEE802154_SUCCESS) {
+ dev_err(
+ &spi->dev,
+ "failed to set sfr at %#03x, status = %#03x\n",
+ sfr_address,
+ status
+ );
+ }
+ return status;
+}
+
+/**
+ * tdme_channelinit() - TDME Channel Register Default Initialisation Macro (Tx)
+ * @channel: 802.15.4 channel to initialise chip for
+ * @device_ref: Nondescript pointer to target device
+ *
+ * Return: 802.15.4 status code of API calls
+ */
+static u8 tdme_channelinit(u8 channel, void *device_ref)
+{
+ /* Transceiver front-end local oscillator tx two-point calibration
+ * value. Tuned for the hardware.
+ */
+ u8 txcalval;
+
+ if (channel >= 25)
+ txcalval = 0xA7;
+ else if (channel >= 23)
+ txcalval = 0xA8;
+ else if (channel >= 22)
+ txcalval = 0xA9;
+ else if (channel >= 20)
+ txcalval = 0xAA;
+ else if (channel >= 17)
+ txcalval = 0xAB;
+ else if (channel >= 16)
+ txcalval = 0xAC;
+ else if (channel >= 14)
+ txcalval = 0xAD;
+ else if (channel >= 12)
+ txcalval = 0xAE;
+ else
+ txcalval = 0xAF;
+
+ return tdme_setsfr_request_sync(
+ 1,
+ CA8210_SFR_LOTXCAL,
+ txcalval,
+ device_ref
+ ); /* LO Tx Cal */
+}
+
+/**
+ * tdme_checkpibattribute() - Checks Attribute Values that are not checked in
+ * MAC
+ * @pib_attribute: Attribute Number
+ * @pib_attribute_length: Attribute length
+ * @pib_attribute_value: Pointer to Attribute Value
+ *
+ * Return: 802.15.4 status code of checks
+ */
+static u8 tdme_checkpibattribute(
+ u8 pib_attribute,
+ u8 pib_attribute_length,
+ const void *pib_attribute_value
+)
+{
+ u8 status = IEEE802154_SUCCESS;
+ u8 value;
+
+ value = *((u8 *)pib_attribute_value);
+
+ switch (pib_attribute) {
+ /* PHY */
+ case PHY_TRANSMIT_POWER:
+ if (value > 0x3F)
+ status = IEEE802154_INVALID_PARAMETER;
+ break;
+ case PHY_CCA_MODE:
+ if (value > 0x03)
+ status = IEEE802154_INVALID_PARAMETER;
+ break;
+ /* MAC */
+ case MAC_BATT_LIFE_EXT_PERIODS:
+ if (value < 6 || value > 41)
+ status = IEEE802154_INVALID_PARAMETER;
+ break;
+ case MAC_BEACON_PAYLOAD:
+ if (pib_attribute_length > MAX_BEACON_PAYLOAD_LENGTH)
+ status = IEEE802154_INVALID_PARAMETER;
+ break;
+ case MAC_BEACON_PAYLOAD_LENGTH:
+ if (value > MAX_BEACON_PAYLOAD_LENGTH)
+ status = IEEE802154_INVALID_PARAMETER;
+ break;
+ case MAC_BEACON_ORDER:
+ if (value > 15)
+ status = IEEE802154_INVALID_PARAMETER;
+ break;
+ case MAC_MAX_BE:
+ if (value < 3 || value > 8)
+ status = IEEE802154_INVALID_PARAMETER;
+ break;
+ case MAC_MAX_CSMA_BACKOFFS:
+ if (value > 5)
+ status = IEEE802154_INVALID_PARAMETER;
+ break;
+ case MAC_MAX_FRAME_RETRIES:
+ if (value > 7)
+ status = IEEE802154_INVALID_PARAMETER;
+ break;
+ case MAC_MIN_BE:
+ if (value > 8)
+ status = IEEE802154_INVALID_PARAMETER;
+ break;
+ case MAC_RESPONSE_WAIT_TIME:
+ if (value < 2 || value > 64)
+ status = IEEE802154_INVALID_PARAMETER;
+ break;
+ case MAC_SUPERFRAME_ORDER:
+ if (value > 15)
+ status = IEEE802154_INVALID_PARAMETER;
+ break;
+ /* boolean */
+ case MAC_ASSOCIATED_PAN_COORD:
+ case MAC_ASSOCIATION_PERMIT:
+ case MAC_AUTO_REQUEST:
+ case MAC_BATT_LIFE_EXT:
+ case MAC_GTS_PERMIT:
+ case MAC_PROMISCUOUS_MODE:
+ case MAC_RX_ON_WHEN_IDLE:
+ case MAC_SECURITY_ENABLED:
+ if (value > 1)
+ status = IEEE802154_INVALID_PARAMETER;
+ break;
+ /* MAC SEC */
+ case MAC_AUTO_REQUEST_SECURITY_LEVEL:
+ if (value > 7)
+ status = IEEE802154_INVALID_PARAMETER;
+ break;
+ case MAC_AUTO_REQUEST_KEY_ID_MODE:
+ if (value > 3)
+ status = IEEE802154_INVALID_PARAMETER;
+ break;
+ default:
+ break;
+ }
+
+ return status;
+}
+
+/**
+ * tdme_settxpower() - Sets the tx power for MLME_SET phyTransmitPower
+ * @txp: Transmit Power
+ * @device_ref: Nondescript pointer to target device
+ *
+ * Normalised to 802.15.4 Definition (6-bit, signed):
+ * Bit 7-6: not used
+ * Bit 5-0: tx power (-32 - +31 dB)
+ *
+ * Return: 802.15.4 status code of api calls
+ */
+static u8 tdme_settxpower(u8 txp, void *device_ref)
+{
+ u8 status;
+ s8 txp_val;
+ u8 txp_ext;
+ union pa_cfg_sfr pa_cfg_val;
+
+ /* extend from 6 to 8 bit */
+ txp_ext = 0x3F & txp;
+ if (txp_ext & 0x20)
+ txp_ext += 0xC0;
+ txp_val = (s8)txp_ext;
+
+ if (CA8210_MAC_MPW) {
+ if (txp_val > 0) {
+ /* 8 dBm: ptrim = 5, itrim = +3 => +4 dBm */
+ pa_cfg_val.bias_current_trim = 3;
+ pa_cfg_val.buffer_capacitor_trim = 5;
+ pa_cfg_val.boost = 1;
+ } else {
+ /* 0 dBm: ptrim = 7, itrim = +3 => -6 dBm */
+ pa_cfg_val.bias_current_trim = 3;
+ pa_cfg_val.buffer_capacitor_trim = 7;
+ pa_cfg_val.boost = 0;
+ }
+ /* write PACFG */
+ status = tdme_setsfr_request_sync(
+ 0,
+ CA8210_SFR_PACFG,
+ pa_cfg_val.paib,
+ device_ref
+ );
+ } else {
+ /* Look-Up Table for Setting Current and Frequency Trim values
+ * for desired Output Power
+ */
+ if (txp_val > 8) {
+ pa_cfg_val.paib = 0x3F;
+ } else if (txp_val == 8) {
+ pa_cfg_val.paib = 0x32;
+ } else if (txp_val == 7) {
+ pa_cfg_val.paib = 0x22;
+ } else if (txp_val == 6) {
+ pa_cfg_val.paib = 0x18;
+ } else if (txp_val == 5) {
+ pa_cfg_val.paib = 0x10;
+ } else if (txp_val == 4) {
+ pa_cfg_val.paib = 0x0C;
+ } else if (txp_val == 3) {
+ pa_cfg_val.paib = 0x08;
+ } else if (txp_val == 2) {
+ pa_cfg_val.paib = 0x05;
+ } else if (txp_val == 1) {
+ pa_cfg_val.paib = 0x03;
+ } else if (txp_val == 0) {
+ pa_cfg_val.paib = 0x01;
+ } else { /* < 0 */
+ pa_cfg_val.paib = 0x00;
+ }
+ /* write PACFGIB */
+ status = tdme_setsfr_request_sync(
+ 0,
+ CA8210_SFR_PACFGIB,
+ pa_cfg_val.paib,
+ device_ref
+ );
+ }
+
+ return status;
+}
+
+/**
+ * mcps_data_request() - mcps_data_request (Send Data) according to API Spec
+ * @src_addr_mode: Source Addressing Mode
+ * @dst_address_mode: Destination Addressing Mode
+ * @dst_pan_id: Destination PAN ID
+ * @dst_addr: Pointer to Destination Address
+ * @msdu_length: length of Data
+ * @msdu: Pointer to Data
+ * @msdu_handle: Handle of Data
+ * @tx_options: Tx Options Bit Field
+ * @security: Pointer to Security Structure or NULL
+ * @device_ref: Nondescript pointer to target device
+ *
+ * Return: 802.15.4 status code of action
+ */
+static u8 mcps_data_request(
+ u8 src_addr_mode,
+ u8 dst_address_mode,
+ u16 dst_pan_id,
+ union macaddr *dst_addr,
+ u8 msdu_length,
+ u8 *msdu,
+ u8 msdu_handle,
+ u8 tx_options,
+ struct secspec *security,
+ void *device_ref
+)
+{
+ struct secspec *psec;
+ struct mac_message command;
+
+ command.command_id = SPI_MCPS_DATA_REQUEST;
+ command.pdata.data_req.src_addr_mode = src_addr_mode;
+ command.pdata.data_req.dst.mode = dst_address_mode;
+ if (dst_address_mode != MAC_MODE_NO_ADDR) {
+ command.pdata.data_req.dst.pan_id[0] = LS_BYTE(dst_pan_id);
+ command.pdata.data_req.dst.pan_id[1] = MS_BYTE(dst_pan_id);
+ if (dst_address_mode == MAC_MODE_SHORT_ADDR) {
+ command.pdata.data_req.dst.address[0] = LS_BYTE(
+ dst_addr->short_address
+ );
+ command.pdata.data_req.dst.address[1] = MS_BYTE(
+ dst_addr->short_address
+ );
+ } else { /* MAC_MODE_LONG_ADDR*/
+ memcpy(
+ command.pdata.data_req.dst.address,
+ dst_addr->ieee_address,
+ 8
+ );
+ }
+ }
+ command.pdata.data_req.msdu_length = msdu_length;
+ command.pdata.data_req.msdu_handle = msdu_handle;
+ command.pdata.data_req.tx_options = tx_options;
+ memcpy(command.pdata.data_req.msdu, msdu, msdu_length);
+ psec = (struct secspec *)(command.pdata.data_req.msdu + msdu_length);
+ command.length = sizeof(struct mcps_data_request_pset) -
+ MAX_DATA_SIZE + msdu_length;
+ if (!security || security->security_level == 0) {
+ psec->security_level = 0;
+ command.length += 1;
+ } else {
+ *psec = *security;
+ command.length += sizeof(struct secspec);
+ }
+
+ if (ca8210_spi_transfer(device_ref, &command.command_id,
+ command.length + 2))
+ return IEEE802154_SYSTEM_ERROR;
+
+ return IEEE802154_SUCCESS;
+}
+
+/**
+ * mlme_reset_request_sync() - MLME_RESET_request/confirm according to API Spec
+ * @set_default_pib: Set defaults in PIB
+ * @device_ref: Nondescript pointer to target device
+ *
+ * Return: 802.15.4 status code of MLME-RESET.confirm
+ */
+static u8 mlme_reset_request_sync(
+ u8 set_default_pib,
+ void *device_ref
+)
+{
+ u8 status;
+ struct mac_message command, response;
+ struct spi_device *spi = device_ref;
+
+ command.command_id = SPI_MLME_RESET_REQUEST;
+ command.length = 1;
+ command.pdata.u8param = set_default_pib;
+
+ if (cascoda_api_downstream(
+ &command.command_id,
+ command.length + 2,
+ &response.command_id,
+ device_ref)) {
+ dev_err(&spi->dev, "cascoda_api_downstream failed\n");
+ return IEEE802154_SYSTEM_ERROR;
+ }
+
+ if (response.command_id != SPI_MLME_RESET_CONFIRM)
+ return IEEE802154_SYSTEM_ERROR;
+
+ status = response.pdata.status;
+
+ /* reset COORD Bit for Channel Filtering as Coordinator */
+ if (CA8210_MAC_WORKAROUNDS && set_default_pib && !status) {
+ status = tdme_setsfr_request_sync(
+ 0,
+ CA8210_SFR_MACCON,
+ 0,
+ device_ref
+ );
+ }
+
+ return status;
+}
+
+/**
+ * mlme_set_request_sync() - MLME_SET_request/confirm according to API Spec
+ * @pib_attribute: Attribute Number
+ * @pib_attribute_index: Index within Attribute if an Array
+ * @pib_attribute_length: Attribute length
+ * @pib_attribute_value: Pointer to Attribute Value
+ * @device_ref: Nondescript pointer to target device
+ *
+ * Return: 802.15.4 status code of MLME-SET.confirm
+ */
+static u8 mlme_set_request_sync(
+ u8 pib_attribute,
+ u8 pib_attribute_index,
+ u8 pib_attribute_length,
+ const void *pib_attribute_value,
+ void *device_ref
+)
+{
+ u8 status;
+ struct mac_message command, response;
+
+ /* pre-check the validity of pib_attribute values that are not checked
+ * in MAC
+ */
+ if (tdme_checkpibattribute(
+ pib_attribute, pib_attribute_length, pib_attribute_value)) {
+ return IEEE802154_INVALID_PARAMETER;
+ }
+
+ if (pib_attribute == PHY_CURRENT_CHANNEL) {
+ status = tdme_channelinit(
+ *((u8 *)pib_attribute_value),
+ device_ref
+ );
+ if (status)
+ return status;
+ }
+
+ if (pib_attribute == PHY_TRANSMIT_POWER) {
+ return tdme_settxpower(
+ *((u8 *)pib_attribute_value),
+ device_ref
+ );
+ }
+
+ command.command_id = SPI_MLME_SET_REQUEST;
+ command.length = sizeof(struct mlme_set_request_pset) -
+ MAX_ATTRIBUTE_SIZE + pib_attribute_length;
+ command.pdata.set_req.pib_attribute = pib_attribute;
+ command.pdata.set_req.pib_attribute_index = pib_attribute_index;
+ command.pdata.set_req.pib_attribute_length = pib_attribute_length;
+ memcpy(
+ command.pdata.set_req.pib_attribute_value,
+ pib_attribute_value,
+ pib_attribute_length
+ );
+
+ if (cascoda_api_downstream(
+ &command.command_id,
+ command.length + 2,
+ &response.command_id,
+ device_ref)) {
+ return IEEE802154_SYSTEM_ERROR;
+ }
+
+ if (response.command_id != SPI_MLME_SET_CONFIRM)
+ return IEEE802154_SYSTEM_ERROR;
+
+ return response.pdata.status;
+}
+
+/**
+ * hwme_set_request_sync() - HWME_SET_request/confirm according to API Spec
+ * @hw_attribute: Attribute Number
+ * @hw_attribute_length: Attribute length
+ * @hw_attribute_value: Pointer to Attribute Value
+ * @device_ref: Nondescript pointer to target device
+ *
+ * Return: 802.15.4 status code of HWME-SET.confirm
+ */
+static u8 hwme_set_request_sync(
+ u8 hw_attribute,
+ u8 hw_attribute_length,
+ u8 *hw_attribute_value,
+ void *device_ref
+)
+{
+ struct mac_message command, response;
+
+ command.command_id = SPI_HWME_SET_REQUEST;
+ command.length = 2 + hw_attribute_length;
+ command.pdata.hwme_set_req.hw_attribute = hw_attribute;
+ command.pdata.hwme_set_req.hw_attribute_length = hw_attribute_length;
+ memcpy(
+ command.pdata.hwme_set_req.hw_attribute_value,
+ hw_attribute_value,
+ hw_attribute_length
+ );
+
+ if (cascoda_api_downstream(
+ &command.command_id,
+ command.length + 2,
+ &response.command_id,
+ device_ref)) {
+ return IEEE802154_SYSTEM_ERROR;
+ }
+
+ if (response.command_id != SPI_HWME_SET_CONFIRM)
+ return IEEE802154_SYSTEM_ERROR;
+
+ return response.pdata.hwme_set_cnf.status;
+}
+
+/**
+ * hwme_get_request_sync() - HWME_GET_request/confirm according to API Spec
+ * @hw_attribute: Attribute Number
+ * @hw_attribute_length: Attribute length
+ * @hw_attribute_value: Pointer to Attribute Value
+ * @device_ref: Nondescript pointer to target device
+ *
+ * Return: 802.15.4 status code of HWME-GET.confirm
+ */
+static u8 hwme_get_request_sync(
+ u8 hw_attribute,
+ u8 *hw_attribute_length,
+ u8 *hw_attribute_value,
+ void *device_ref
+)
+{
+ struct mac_message command, response;
+
+ command.command_id = SPI_HWME_GET_REQUEST;
+ command.length = 1;
+ command.pdata.hwme_get_req.hw_attribute = hw_attribute;
+
+ if (cascoda_api_downstream(
+ &command.command_id,
+ command.length + 2,
+ &response.command_id,
+ device_ref)) {
+ return IEEE802154_SYSTEM_ERROR;
+ }
+
+ if (response.command_id != SPI_HWME_GET_CONFIRM)
+ return IEEE802154_SYSTEM_ERROR;
+
+ if (response.pdata.hwme_get_cnf.status == IEEE802154_SUCCESS) {
+ *hw_attribute_length =
+ response.pdata.hwme_get_cnf.hw_attribute_length;
+ memcpy(
+ hw_attribute_value,
+ response.pdata.hwme_get_cnf.hw_attribute_value,
+ *hw_attribute_length
+ );
+ }
+
+ return response.pdata.hwme_get_cnf.status;
+}
+
+/* Network driver operation */
+
+/**
+ * ca8210_async_xmit_complete() - Called to announce that an asynchronous
+ * transmission has finished
+ * @hw: ieee802154_hw of ca8210 that has finished exchange
+ * @msduhandle: Identifier of transmission that has completed
+ * @status: Returned 802.15.4 status code of the transmission
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_async_xmit_complete(
+ struct ieee802154_hw *hw,
+ u8 msduhandle,
+ u8 status)
+{
+ struct ca8210_priv *priv = hw->priv;
+
+ if (priv->nextmsduhandle != msduhandle) {
+ dev_err(
+ &priv->spi->dev,
+ "Unexpected msdu_handle on data confirm, Expected %d, got %d\n",
+ priv->nextmsduhandle,
+ msduhandle
+ );
+ return -EIO;
+ }
+
+ priv->async_tx_pending = false;
+ priv->nextmsduhandle++;
+
+ if (status) {
+ dev_err(
+ &priv->spi->dev,
+ "Link transmission unsuccessful, status = %d\n",
+ status
+ );
+ if (status != IEEE802154_TRANSACTION_OVERFLOW) {
+ ieee802154_xmit_error(priv->hw, priv->tx_skb, status);
+ return 0;
+ }
+ }
+ ieee802154_xmit_complete(priv->hw, priv->tx_skb, true);
+
+ return 0;
+}
+
+/**
+ * ca8210_skb_rx() - Contructs a properly framed socket buffer from a received
+ * MCPS_DATA_indication
+ * @hw: ieee802154_hw that MCPS_DATA_indication was received by
+ * @len: length of MCPS_DATA_indication
+ * @data_ind: Octet array of MCPS_DATA_indication
+ *
+ * Called by the spi driver whenever a SAP command is received, this function
+ * will ascertain whether the command is of interest to the network driver and
+ * take necessary action.
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_skb_rx(
+ struct ieee802154_hw *hw,
+ size_t len,
+ u8 *data_ind
+)
+{
+ struct ieee802154_hdr hdr;
+ int msdulen;
+ int hlen;
+ u8 mpdulinkquality = data_ind[23];
+ struct sk_buff *skb;
+ struct ca8210_priv *priv = hw->priv;
+
+ /* Allocate mtu size buffer for every rx packet */
+ skb = dev_alloc_skb(IEEE802154_MTU + sizeof(hdr));
+ if (!skb)
+ return -ENOMEM;
+
+ skb_reserve(skb, sizeof(hdr));
+
+ msdulen = data_ind[22]; /* msdu_length */
+ if (msdulen > IEEE802154_MTU) {
+ dev_err(
+ &priv->spi->dev,
+ "received erroneously large msdu length!\n"
+ );
+ kfree_skb(skb);
+ return -EMSGSIZE;
+ }
+ dev_dbg(&priv->spi->dev, "skb buffer length = %d\n", msdulen);
+
+ if (priv->promiscuous)
+ goto copy_payload;
+
+ /* Populate hdr */
+ hdr.sec.level = data_ind[29 + msdulen];
+ dev_dbg(&priv->spi->dev, "security level: %#03x\n", hdr.sec.level);
+ if (hdr.sec.level > 0) {
+ hdr.sec.key_id_mode = data_ind[30 + msdulen];
+ memcpy(&hdr.sec.extended_src, &data_ind[31 + msdulen], 8);
+ hdr.sec.key_id = data_ind[39 + msdulen];
+ }
+ hdr.source.mode = data_ind[0];
+ dev_dbg(&priv->spi->dev, "srcAddrMode: %#03x\n", hdr.source.mode);
+ hdr.source.pan_id = *(u16 *)&data_ind[1];
+ dev_dbg(&priv->spi->dev, "srcPanId: %#06x\n", hdr.source.pan_id);
+ memcpy(&hdr.source.extended_addr, &data_ind[3], 8);
+ hdr.dest.mode = data_ind[11];
+ dev_dbg(&priv->spi->dev, "dstAddrMode: %#03x\n", hdr.dest.mode);
+ hdr.dest.pan_id = *(u16 *)&data_ind[12];
+ dev_dbg(&priv->spi->dev, "dstPanId: %#06x\n", hdr.dest.pan_id);
+ memcpy(&hdr.dest.extended_addr, &data_ind[14], 8);
+
+ /* Fill in FC implicitly */
+ hdr.fc.type = 1; /* Data frame */
+ if (hdr.sec.level)
+ hdr.fc.security_enabled = 1;
+ else
+ hdr.fc.security_enabled = 0;
+ if (data_ind[1] != data_ind[12] || data_ind[2] != data_ind[13])
+ hdr.fc.intra_pan = 1;
+ else
+ hdr.fc.intra_pan = 0;
+ hdr.fc.dest_addr_mode = hdr.dest.mode;
+ hdr.fc.source_addr_mode = hdr.source.mode;
+
+ /* Add hdr to front of buffer */
+ hlen = ieee802154_hdr_push(skb, &hdr);
+
+ if (hlen < 0) {
+ dev_crit(&priv->spi->dev, "failed to push mac hdr onto skb!\n");
+ kfree_skb(skb);
+ return hlen;
+ }
+
+ skb_reset_mac_header(skb);
+ skb->mac_len = hlen;
+
+copy_payload:
+ /* Add <msdulen> bytes of space to the back of the buffer */
+ /* Copy msdu to skb */
+ skb_put_data(skb, &data_ind[29], msdulen);
+
+ ieee802154_rx_irqsafe(hw, skb, mpdulinkquality);
+ return 0;
+}
+
+/**
+ * ca8210_net_rx() - Acts upon received SAP commands relevant to the network
+ * driver
+ * @hw: ieee802154_hw that command was received by
+ * @command: Octet array of received command
+ * @len: length of the received command
+ *
+ * Called by the spi driver whenever a SAP command is received, this function
+ * will ascertain whether the command is of interest to the network driver and
+ * take necessary action.
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_net_rx(struct ieee802154_hw *hw, u8 *command, size_t len)
+{
+ struct ca8210_priv *priv = hw->priv;
+ unsigned long flags;
+ u8 status;
+
+ dev_dbg(&priv->spi->dev, "%s: CmdID = %d\n", __func__, command[0]);
+
+ if (command[0] == SPI_MCPS_DATA_INDICATION) {
+ /* Received data */
+ spin_lock_irqsave(&priv->lock, flags);
+ if (command[26] == priv->last_dsn) {
+ dev_dbg(
+ &priv->spi->dev,
+ "DSN %d resend received, ignoring...\n",
+ command[26]
+ );
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return 0;
+ }
+ priv->last_dsn = command[26];
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return ca8210_skb_rx(hw, len - 2, command + 2);
+ } else if (command[0] == SPI_MCPS_DATA_CONFIRM) {
+ status = command[3];
+ if (priv->async_tx_pending) {
+ return ca8210_async_xmit_complete(
+ hw,
+ command[2],
+ status
+ );
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * ca8210_skb_tx() - Transmits a given socket buffer using the ca8210
+ * @skb: Socket buffer to transmit
+ * @msduhandle: Data identifier to pass to the 802.15.4 MAC
+ * @priv: Pointer to private data section of target ca8210
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_skb_tx(
+ struct sk_buff *skb,
+ u8 msduhandle,
+ struct ca8210_priv *priv
+)
+{
+ struct ieee802154_hdr header = { };
+ struct secspec secspec;
+ int mac_len, status;
+
+ dev_dbg(&priv->spi->dev, "%s called\n", __func__);
+
+ /* Get addressing info from skb - ieee802154 layer creates a full
+ * packet
+ */
+ mac_len = ieee802154_hdr_peek_addrs(skb, &header);
+ if (mac_len < 0)
+ return mac_len;
+
+ secspec.security_level = header.sec.level;
+ secspec.key_id_mode = header.sec.key_id_mode;
+ if (secspec.key_id_mode == 2)
+ memcpy(secspec.key_source, &header.sec.short_src, 4);
+ else if (secspec.key_id_mode == 3)
+ memcpy(secspec.key_source, &header.sec.extended_src, 8);
+ secspec.key_index = header.sec.key_id;
+
+ /* Pass to Cascoda API */
+ status = mcps_data_request(
+ header.source.mode,
+ header.dest.mode,
+ header.dest.pan_id,
+ (union macaddr *)&header.dest.extended_addr,
+ skb->len - mac_len,
+ &skb->data[mac_len],
+ msduhandle,
+ header.fc.ack_request,
+ &secspec,
+ priv->spi
+ );
+ return link_to_linux_err(status);
+}
+
+/**
+ * ca8210_start() - Starts the network driver
+ * @hw: ieee802154_hw of ca8210 being started
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_start(struct ieee802154_hw *hw)
+{
+ int status;
+ u8 rx_on_when_idle;
+ u8 lqi_threshold = 0;
+ struct ca8210_priv *priv = hw->priv;
+
+ priv->last_dsn = -1;
+ /* Turn receiver on when idle for now just to test rx */
+ rx_on_when_idle = 1;
+ status = mlme_set_request_sync(
+ MAC_RX_ON_WHEN_IDLE,
+ 0,
+ 1,
+ &rx_on_when_idle,
+ priv->spi
+ );
+ if (status) {
+ dev_crit(
+ &priv->spi->dev,
+ "Setting rx_on_when_idle failed, status = %d\n",
+ status
+ );
+ return link_to_linux_err(status);
+ }
+ status = hwme_set_request_sync(
+ HWME_LQILIMIT,
+ 1,
+ &lqi_threshold,
+ priv->spi
+ );
+ if (status) {
+ dev_crit(
+ &priv->spi->dev,
+ "Setting lqilimit failed, status = %d\n",
+ status
+ );
+ return link_to_linux_err(status);
+ }
+
+ return 0;
+}
+
+/**
+ * ca8210_stop() - Stops the network driver
+ * @hw: ieee802154_hw of ca8210 being stopped
+ *
+ * Return: 0 or linux error code
+ */
+static void ca8210_stop(struct ieee802154_hw *hw)
+{
+}
+
+/**
+ * ca8210_xmit_async() - Asynchronously transmits a given socket buffer using
+ * the ca8210
+ * @hw: ieee802154_hw of ca8210 to transmit from
+ * @skb: Socket buffer to transmit
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_xmit_async(struct ieee802154_hw *hw, struct sk_buff *skb)
+{
+ struct ca8210_priv *priv = hw->priv;
+ int status;
+
+ dev_dbg(&priv->spi->dev, "calling %s\n", __func__);
+
+ priv->tx_skb = skb;
+ priv->async_tx_pending = true;
+ status = ca8210_skb_tx(skb, priv->nextmsduhandle, priv);
+ return status;
+}
+
+/**
+ * ca8210_get_ed() - Returns the measured energy on the current channel at this
+ * instant in time
+ * @hw: ieee802154_hw of target ca8210
+ * @level: Measured Energy Detect level
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_get_ed(struct ieee802154_hw *hw, u8 *level)
+{
+ u8 lenvar;
+ struct ca8210_priv *priv = hw->priv;
+
+ return link_to_linux_err(
+ hwme_get_request_sync(HWME_EDVALUE, &lenvar, level, priv->spi)
+ );
+}
+
+/**
+ * ca8210_set_channel() - Sets the current operating 802.15.4 channel of the
+ * ca8210
+ * @hw: ieee802154_hw of target ca8210
+ * @page: Channel page to set
+ * @channel: Channel number to set
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_set_channel(
+ struct ieee802154_hw *hw,
+ u8 page,
+ u8 channel
+)
+{
+ u8 status;
+ struct ca8210_priv *priv = hw->priv;
+
+ status = mlme_set_request_sync(
+ PHY_CURRENT_CHANNEL,
+ 0,
+ 1,
+ &channel,
+ priv->spi
+ );
+ if (status) {
+ dev_err(
+ &priv->spi->dev,
+ "error setting channel, MLME-SET.confirm status = %d\n",
+ status
+ );
+ }
+ return link_to_linux_err(status);
+}
+
+/**
+ * ca8210_set_hw_addr_filt() - Sets the address filtering parameters of the
+ * ca8210
+ * @hw: ieee802154_hw of target ca8210
+ * @filt: Filtering parameters
+ * @changed: Bitmap representing which parameters to change
+ *
+ * Effectively just sets the actual addressing information identifying this node
+ * as all filtering is performed by the ca8210 as detailed in the IEEE 802.15.4
+ * 2006 specification.
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_set_hw_addr_filt(
+ struct ieee802154_hw *hw,
+ struct ieee802154_hw_addr_filt *filt,
+ unsigned long changed
+)
+{
+ u8 status = 0;
+ struct ca8210_priv *priv = hw->priv;
+
+ if (changed & IEEE802154_AFILT_PANID_CHANGED) {
+ status = mlme_set_request_sync(
+ MAC_PAN_ID,
+ 0,
+ 2,
+ &filt->pan_id, priv->spi
+ );
+ if (status) {
+ dev_err(
+ &priv->spi->dev,
+ "error setting pan id, MLME-SET.confirm status = %d",
+ status
+ );
+ return link_to_linux_err(status);
+ }
+ }
+ if (changed & IEEE802154_AFILT_SADDR_CHANGED) {
+ status = mlme_set_request_sync(
+ MAC_SHORT_ADDRESS,
+ 0,
+ 2,
+ &filt->short_addr, priv->spi
+ );
+ if (status) {
+ dev_err(
+ &priv->spi->dev,
+ "error setting short address, MLME-SET.confirm status = %d",
+ status
+ );
+ return link_to_linux_err(status);
+ }
+ }
+ if (changed & IEEE802154_AFILT_IEEEADDR_CHANGED) {
+ status = mlme_set_request_sync(
+ NS_IEEE_ADDRESS,
+ 0,
+ 8,
+ &filt->ieee_addr,
+ priv->spi
+ );
+ if (status) {
+ dev_err(
+ &priv->spi->dev,
+ "error setting ieee address, MLME-SET.confirm status = %d",
+ status
+ );
+ return link_to_linux_err(status);
+ }
+ }
+ /* TODO: Should use MLME_START to set coord bit? */
+ return 0;
+}
+
+/**
+ * ca8210_set_tx_power() - Sets the transmit power of the ca8210
+ * @hw: ieee802154_hw of target ca8210
+ * @mbm: Transmit power in mBm (dBm*100)
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_set_tx_power(struct ieee802154_hw *hw, s32 mbm)
+{
+ struct ca8210_priv *priv = hw->priv;
+
+ mbm /= 100;
+ return link_to_linux_err(
+ mlme_set_request_sync(PHY_TRANSMIT_POWER, 0, 1, &mbm, priv->spi)
+ );
+}
+
+/**
+ * ca8210_set_cca_mode() - Sets the clear channel assessment mode of the ca8210
+ * @hw: ieee802154_hw of target ca8210
+ * @cca: CCA mode to set
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_set_cca_mode(
+ struct ieee802154_hw *hw,
+ const struct wpan_phy_cca *cca
+)
+{
+ u8 status;
+ u8 cca_mode;
+ struct ca8210_priv *priv = hw->priv;
+
+ cca_mode = cca->mode & 3;
+ if (cca_mode == 3 && cca->opt == NL802154_CCA_OPT_ENERGY_CARRIER_OR) {
+ /* cca_mode 0 == CS OR ED, 3 == CS AND ED */
+ cca_mode = 0;
+ }
+ status = mlme_set_request_sync(
+ PHY_CCA_MODE,
+ 0,
+ 1,
+ &cca_mode,
+ priv->spi
+ );
+ if (status) {
+ dev_err(
+ &priv->spi->dev,
+ "error setting cca mode, MLME-SET.confirm status = %d",
+ status
+ );
+ }
+ return link_to_linux_err(status);
+}
+
+/**
+ * ca8210_set_cca_ed_level() - Sets the CCA ED level of the ca8210
+ * @hw: ieee802154_hw of target ca8210
+ * @level: ED level to set (in mbm)
+ *
+ * Sets the minimum threshold of measured energy above which the ca8210 will
+ * back off and retry a transmission.
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_set_cca_ed_level(struct ieee802154_hw *hw, s32 level)
+{
+ u8 status;
+ u8 ed_threshold = (level / 100) * 2 + 256;
+ struct ca8210_priv *priv = hw->priv;
+
+ status = hwme_set_request_sync(
+ HWME_EDTHRESHOLD,
+ 1,
+ &ed_threshold,
+ priv->spi
+ );
+ if (status) {
+ dev_err(
+ &priv->spi->dev,
+ "error setting ed threshold, HWME-SET.confirm status = %d",
+ status
+ );
+ }
+ return link_to_linux_err(status);
+}
+
+/**
+ * ca8210_set_csma_params() - Sets the CSMA parameters of the ca8210
+ * @hw: ieee802154_hw of target ca8210
+ * @min_be: Minimum backoff exponent when backing off a transmission
+ * @max_be: Maximum backoff exponent when backing off a transmission
+ * @retries: Number of times to retry after backing off
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_set_csma_params(
+ struct ieee802154_hw *hw,
+ u8 min_be,
+ u8 max_be,
+ u8 retries
+)
+{
+ u8 status;
+ struct ca8210_priv *priv = hw->priv;
+
+ status = mlme_set_request_sync(MAC_MIN_BE, 0, 1, &min_be, priv->spi);
+ if (status) {
+ dev_err(
+ &priv->spi->dev,
+ "error setting min be, MLME-SET.confirm status = %d",
+ status
+ );
+ return link_to_linux_err(status);
+ }
+ status = mlme_set_request_sync(MAC_MAX_BE, 0, 1, &max_be, priv->spi);
+ if (status) {
+ dev_err(
+ &priv->spi->dev,
+ "error setting max be, MLME-SET.confirm status = %d",
+ status
+ );
+ return link_to_linux_err(status);
+ }
+ status = mlme_set_request_sync(
+ MAC_MAX_CSMA_BACKOFFS,
+ 0,
+ 1,
+ &retries,
+ priv->spi
+ );
+ if (status) {
+ dev_err(
+ &priv->spi->dev,
+ "error setting max csma backoffs, MLME-SET.confirm status = %d",
+ status
+ );
+ }
+ return link_to_linux_err(status);
+}
+
+/**
+ * ca8210_set_frame_retries() - Sets the maximum frame retries of the ca8210
+ * @hw: ieee802154_hw of target ca8210
+ * @retries: Number of retries
+ *
+ * Sets the number of times to retry a transmission if no acknowledgment was
+ * received from the other end when one was requested.
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_set_frame_retries(struct ieee802154_hw *hw, s8 retries)
+{
+ u8 status;
+ struct ca8210_priv *priv = hw->priv;
+
+ status = mlme_set_request_sync(
+ MAC_MAX_FRAME_RETRIES,
+ 0,
+ 1,
+ &retries,
+ priv->spi
+ );
+ if (status) {
+ dev_err(
+ &priv->spi->dev,
+ "error setting frame retries, MLME-SET.confirm status = %d",
+ status
+ );
+ }
+ return link_to_linux_err(status);
+}
+
+static int ca8210_set_promiscuous_mode(struct ieee802154_hw *hw, const bool on)
+{
+ u8 status;
+ struct ca8210_priv *priv = hw->priv;
+
+ status = mlme_set_request_sync(
+ MAC_PROMISCUOUS_MODE,
+ 0,
+ 1,
+ (const void *)&on,
+ priv->spi
+ );
+ if (status) {
+ dev_err(
+ &priv->spi->dev,
+ "error setting promiscuous mode, MLME-SET.confirm status = %d",
+ status
+ );
+ } else {
+ priv->promiscuous = on;
+ }
+ return link_to_linux_err(status);
+}
+
+static const struct ieee802154_ops ca8210_phy_ops = {
+ .start = ca8210_start,
+ .stop = ca8210_stop,
+ .xmit_async = ca8210_xmit_async,
+ .ed = ca8210_get_ed,
+ .set_channel = ca8210_set_channel,
+ .set_hw_addr_filt = ca8210_set_hw_addr_filt,
+ .set_txpower = ca8210_set_tx_power,
+ .set_cca_mode = ca8210_set_cca_mode,
+ .set_cca_ed_level = ca8210_set_cca_ed_level,
+ .set_csma_params = ca8210_set_csma_params,
+ .set_frame_retries = ca8210_set_frame_retries,
+ .set_promiscuous_mode = ca8210_set_promiscuous_mode
+};
+
+/* Test/EVBME Interface */
+
+/**
+ * ca8210_test_int_open() - Opens the test interface to the userspace
+ * @inodp: inode representation of file interface
+ * @filp: file interface
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_test_int_open(struct inode *inodp, struct file *filp)
+{
+ struct ca8210_priv *priv = inodp->i_private;
+
+ filp->private_data = priv;
+ return 0;
+}
+
+/**
+ * ca8210_test_check_upstream() - Checks a command received from the upstream
+ * testing interface for required action
+ * @buf: Buffer containing command to check
+ * @device_ref: Nondescript pointer to target device
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_test_check_upstream(u8 *buf, void *device_ref)
+{
+ int ret;
+ u8 response[CA8210_SPI_BUF_SIZE];
+
+ if (buf[0] == SPI_MLME_SET_REQUEST) {
+ ret = tdme_checkpibattribute(buf[2], buf[4], buf + 5);
+ if (ret) {
+ response[0] = SPI_MLME_SET_CONFIRM;
+ response[1] = 3;
+ response[2] = IEEE802154_INVALID_PARAMETER;
+ response[3] = buf[2];
+ response[4] = buf[3];
+ if (cascoda_api_upstream)
+ cascoda_api_upstream(response, 5, device_ref);
+ return ret;
+ }
+ }
+ if (buf[0] == SPI_MLME_ASSOCIATE_REQUEST) {
+ return tdme_channelinit(buf[2], device_ref);
+ } else if (buf[0] == SPI_MLME_START_REQUEST) {
+ return tdme_channelinit(buf[4], device_ref);
+ } else if (
+ (buf[0] == SPI_MLME_SET_REQUEST) &&
+ (buf[2] == PHY_CURRENT_CHANNEL)
+ ) {
+ return tdme_channelinit(buf[5], device_ref);
+ } else if (
+ (buf[0] == SPI_TDME_SET_REQUEST) &&
+ (buf[2] == TDME_CHANNEL)
+ ) {
+ return tdme_channelinit(buf[4], device_ref);
+ } else if (
+ (CA8210_MAC_WORKAROUNDS) &&
+ (buf[0] == SPI_MLME_RESET_REQUEST) &&
+ (buf[2] == 1)
+ ) {
+ /* reset COORD Bit for Channel Filtering as Coordinator */
+ return tdme_setsfr_request_sync(
+ 0,
+ CA8210_SFR_MACCON,
+ 0,
+ device_ref
+ );
+ }
+ return 0;
+} /* End of EVBMECheckSerialCommand() */
+
+/**
+ * ca8210_test_int_user_write() - Called by a process in userspace to send a
+ * message to the ca8210 drivers
+ * @filp: file interface
+ * @in_buf: Buffer containing message to write
+ * @len: length of message
+ * @off: file offset
+ *
+ * Return: 0 or linux error code
+ */
+static ssize_t ca8210_test_int_user_write(
+ struct file *filp,
+ const char __user *in_buf,
+ size_t len,
+ loff_t *off
+)
+{
+ int ret;
+ struct ca8210_priv *priv = filp->private_data;
+ u8 command[CA8210_SPI_BUF_SIZE];
+
+ memset(command, SPI_IDLE, 6);
+ if (len > CA8210_SPI_BUF_SIZE || len < 2) {
+ dev_warn(
+ &priv->spi->dev,
+ "userspace requested erroneous write length (%zu)\n",
+ len
+ );
+ return -EBADE;
+ }
+
+ ret = copy_from_user(command, in_buf, len);
+ if (ret) {
+ dev_err(
+ &priv->spi->dev,
+ "%d bytes could not be copied from userspace\n",
+ ret
+ );
+ return -EIO;
+ }
+ if (len != command[1] + 2) {
+ dev_err(
+ &priv->spi->dev,
+ "write len does not match packet length field\n"
+ );
+ return -EBADE;
+ }
+
+ ret = ca8210_test_check_upstream(command, priv->spi);
+ if (ret == 0) {
+ ret = ca8210_spi_exchange(
+ command,
+ command[1] + 2,
+ NULL,
+ priv->spi
+ );
+ if (ret < 0) {
+ /* effectively 0 bytes were written successfully */
+ dev_err(
+ &priv->spi->dev,
+ "spi exchange failed\n"
+ );
+ return ret;
+ }
+ if (command[0] & SPI_SYN)
+ priv->sync_down++;
+ }
+
+ return len;
+}
+
+/**
+ * ca8210_test_int_user_read() - Called by a process in userspace to read a
+ * message from the ca8210 drivers
+ * @filp: file interface
+ * @buf: Buffer to write message to
+ * @len: length of message to read (ignored)
+ * @offp: file offset
+ *
+ * If the O_NONBLOCK flag was set when opening the file then this function will
+ * not block, i.e. it will return if the fifo is empty. Otherwise the function
+ * will block, i.e. wait until new data arrives.
+ *
+ * Return: number of bytes read
+ */
+static ssize_t ca8210_test_int_user_read(
+ struct file *filp,
+ char __user *buf,
+ size_t len,
+ loff_t *offp
+)
+{
+ int i, cmdlen;
+ struct ca8210_priv *priv = filp->private_data;
+ unsigned char *fifo_buffer;
+ unsigned long bytes_not_copied;
+
+ if (filp->f_flags & O_NONBLOCK) {
+ /* Non-blocking mode */
+ if (kfifo_is_empty(&priv->test.up_fifo))
+ return 0;
+ } else {
+ /* Blocking mode */
+ wait_event_interruptible(
+ priv->test.readq,
+ !kfifo_is_empty(&priv->test.up_fifo)
+ );
+ }
+
+ if (kfifo_out(&priv->test.up_fifo, &fifo_buffer, 4) != 4) {
+ dev_err(
+ &priv->spi->dev,
+ "test_interface: Wrong number of elements popped from upstream fifo\n"
+ );
+ return 0;
+ }
+ cmdlen = fifo_buffer[1];
+ bytes_not_copied = cmdlen + 2;
+
+ bytes_not_copied = copy_to_user(buf, fifo_buffer, bytes_not_copied);
+ if (bytes_not_copied > 0) {
+ dev_err(
+ &priv->spi->dev,
+ "%lu bytes could not be copied to user space!\n",
+ bytes_not_copied
+ );
+ }
+
+ dev_dbg(&priv->spi->dev, "test_interface: Cmd len = %d\n", cmdlen);
+
+ dev_dbg(&priv->spi->dev, "test_interface: Read\n");
+ for (i = 0; i < cmdlen + 2; i++)
+ dev_dbg(&priv->spi->dev, "%#03x\n", fifo_buffer[i]);
+
+ kfree(fifo_buffer);
+
+ return cmdlen + 2;
+}
+
+/**
+ * ca8210_test_int_ioctl() - Called by a process in userspace to enact an
+ * arbitrary action
+ * @filp: file interface
+ * @ioctl_num: which action to enact
+ * @ioctl_param: arbitrary parameter for the action
+ *
+ * Return: status
+ */
+static long ca8210_test_int_ioctl(
+ struct file *filp,
+ unsigned int ioctl_num,
+ unsigned long ioctl_param
+)
+{
+ struct ca8210_priv *priv = filp->private_data;
+
+ switch (ioctl_num) {
+ case CA8210_IOCTL_HARD_RESET:
+ ca8210_reset_send(priv->spi, ioctl_param);
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+/**
+ * ca8210_test_int_poll() - Called by a process in userspace to determine which
+ * actions are currently possible for the file
+ * @filp: file interface
+ * @ptable: poll table
+ *
+ * Return: set of poll return flags
+ */
+static __poll_t ca8210_test_int_poll(
+ struct file *filp,
+ struct poll_table_struct *ptable
+)
+{
+ __poll_t return_flags = 0;
+ struct ca8210_priv *priv = filp->private_data;
+
+ poll_wait(filp, &priv->test.readq, ptable);
+ if (!kfifo_is_empty(&priv->test.up_fifo))
+ return_flags |= (EPOLLIN | EPOLLRDNORM);
+ if (wait_event_interruptible(
+ priv->test.readq,
+ !kfifo_is_empty(&priv->test.up_fifo))) {
+ return EPOLLERR;
+ }
+ return return_flags;
+}
+
+static const struct file_operations test_int_fops = {
+ .read = ca8210_test_int_user_read,
+ .write = ca8210_test_int_user_write,
+ .open = ca8210_test_int_open,
+ .release = NULL,
+ .unlocked_ioctl = ca8210_test_int_ioctl,
+ .poll = ca8210_test_int_poll
+};
+
+/* Init/Deinit */
+
+/**
+ * ca8210_get_platform_data() - Populate a ca8210_platform_data object
+ * @spi_device: Pointer to ca8210 spi device object to get data for
+ * @pdata: Pointer to ca8210_platform_data object to populate
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_get_platform_data(
+ struct spi_device *spi_device,
+ struct ca8210_platform_data *pdata
+)
+{
+ int ret = 0;
+
+ if (!spi_device->dev.of_node)
+ return -EINVAL;
+
+ pdata->extclockenable = of_property_read_bool(
+ spi_device->dev.of_node,
+ "extclock-enable"
+ );
+ if (pdata->extclockenable) {
+ ret = of_property_read_u32(
+ spi_device->dev.of_node,
+ "extclock-freq",
+ &pdata->extclockfreq
+ );
+ if (ret < 0)
+ return ret;
+
+ ret = of_property_read_u32(
+ spi_device->dev.of_node,
+ "extclock-gpio",
+ &pdata->extclockgpio
+ );
+ }
+
+ return ret;
+}
+
+/**
+ * ca8210_config_extern_clk() - Configure the external clock provided by the
+ * ca8210
+ * @pdata: Pointer to ca8210_platform_data containing clock parameters
+ * @spi: Pointer to target ca8210 spi device
+ * @on: True to turn the clock on, false to turn off
+ *
+ * The external clock is configured with a frequency and output pin taken from
+ * the platform data.
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_config_extern_clk(
+ struct ca8210_platform_data *pdata,
+ struct spi_device *spi,
+ bool on
+)
+{
+ u8 clkparam[2];
+
+ if (on) {
+ dev_info(&spi->dev, "Switching external clock on\n");
+ switch (pdata->extclockfreq) {
+ case SIXTEEN_MHZ:
+ clkparam[0] = 1;
+ break;
+ case EIGHT_MHZ:
+ clkparam[0] = 2;
+ break;
+ case FOUR_MHZ:
+ clkparam[0] = 3;
+ break;
+ case TWO_MHZ:
+ clkparam[0] = 4;
+ break;
+ case ONE_MHZ:
+ clkparam[0] = 5;
+ break;
+ default:
+ dev_crit(&spi->dev, "Invalid extclock-freq\n");
+ return -EINVAL;
+ }
+ clkparam[1] = pdata->extclockgpio;
+ } else {
+ dev_info(&spi->dev, "Switching external clock off\n");
+ clkparam[0] = 0; /* off */
+ clkparam[1] = 0;
+ }
+ return link_to_linux_err(
+ hwme_set_request_sync(HWME_SYSCLKOUT, 2, clkparam, spi)
+ );
+}
+
+/**
+ * ca8210_register_ext_clock() - Register ca8210's external clock with kernel
+ * @spi: Pointer to target ca8210 spi device
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_register_ext_clock(struct spi_device *spi)
+{
+ struct device_node *np = spi->dev.of_node;
+ struct ca8210_priv *priv = spi_get_drvdata(spi);
+ struct ca8210_platform_data *pdata = spi->dev.platform_data;
+
+ if (!np)
+ return -EFAULT;
+
+ priv->clk = clk_register_fixed_rate(
+ &spi->dev,
+ np->name,
+ NULL,
+ 0,
+ pdata->extclockfreq
+ );
+
+ if (IS_ERR(priv->clk)) {
+ dev_crit(&spi->dev, "Failed to register external clk\n");
+ return PTR_ERR(priv->clk);
+ }
+
+ return of_clk_add_provider(np, of_clk_src_simple_get, priv->clk);
+}
+
+/**
+ * ca8210_unregister_ext_clock() - Unregister ca8210's external clock with
+ * kernel
+ * @spi: Pointer to target ca8210 spi device
+ */
+static void ca8210_unregister_ext_clock(struct spi_device *spi)
+{
+ struct ca8210_priv *priv = spi_get_drvdata(spi);
+
+ if (IS_ERR_OR_NULL(priv->clk))
+ return;
+
+ of_clk_del_provider(spi->dev.of_node);
+ clk_unregister(priv->clk);
+ dev_info(&spi->dev, "External clock unregistered\n");
+}
+
+/**
+ * ca8210_reset_init() - Initialise the reset input to the ca8210
+ * @spi: Pointer to target ca8210 spi device
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_reset_init(struct spi_device *spi)
+{
+ int ret;
+ struct ca8210_platform_data *pdata = spi->dev.platform_data;
+
+ pdata->gpio_reset = of_get_named_gpio(
+ spi->dev.of_node,
+ "reset-gpio",
+ 0
+ );
+
+ ret = gpio_direction_output(pdata->gpio_reset, 1);
+ if (ret < 0) {
+ dev_crit(
+ &spi->dev,
+ "Reset GPIO %d did not set to output mode\n",
+ pdata->gpio_reset
+ );
+ }
+
+ return ret;
+}
+
+/**
+ * ca8210_interrupt_init() - Initialise the irq output from the ca8210
+ * @spi: Pointer to target ca8210 spi device
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_interrupt_init(struct spi_device *spi)
+{
+ int ret;
+ struct ca8210_platform_data *pdata = spi->dev.platform_data;
+
+ pdata->gpio_irq = of_get_named_gpio(
+ spi->dev.of_node,
+ "irq-gpio",
+ 0
+ );
+
+ pdata->irq_id = gpio_to_irq(pdata->gpio_irq);
+ if (pdata->irq_id < 0) {
+ dev_crit(
+ &spi->dev,
+ "Could not get irq for gpio pin %d\n",
+ pdata->gpio_irq
+ );
+ gpio_free(pdata->gpio_irq);
+ return pdata->irq_id;
+ }
+
+ ret = request_irq(
+ pdata->irq_id,
+ ca8210_interrupt_handler,
+ IRQF_TRIGGER_FALLING,
+ "ca8210-irq",
+ spi_get_drvdata(spi)
+ );
+ if (ret) {
+ dev_crit(&spi->dev, "request_irq %d failed\n", pdata->irq_id);
+ gpio_free(pdata->gpio_irq);
+ }
+
+ return ret;
+}
+
+/**
+ * ca8210_dev_com_init() - Initialise the spi communication component
+ * @priv: Pointer to private data structure
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_dev_com_init(struct ca8210_priv *priv)
+{
+ priv->mlme_workqueue = alloc_ordered_workqueue(
+ "MLME work queue",
+ WQ_UNBOUND
+ );
+ if (!priv->mlme_workqueue) {
+ dev_crit(&priv->spi->dev, "alloc of mlme_workqueue failed!\n");
+ return -ENOMEM;
+ }
+
+ priv->irq_workqueue = alloc_ordered_workqueue(
+ "ca8210 irq worker",
+ WQ_UNBOUND
+ );
+ if (!priv->irq_workqueue) {
+ dev_crit(&priv->spi->dev, "alloc of irq_workqueue failed!\n");
+ destroy_workqueue(priv->mlme_workqueue);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+/**
+ * ca8210_dev_com_clear() - Deinitialise the spi communication component
+ * @priv: Pointer to private data structure
+ */
+static void ca8210_dev_com_clear(struct ca8210_priv *priv)
+{
+ destroy_workqueue(priv->mlme_workqueue);
+ destroy_workqueue(priv->irq_workqueue);
+}
+
+#define CA8210_MAX_TX_POWERS (9)
+static const s32 ca8210_tx_powers[CA8210_MAX_TX_POWERS] = {
+ 800, 700, 600, 500, 400, 300, 200, 100, 0
+};
+
+#define CA8210_MAX_ED_LEVELS (21)
+static const s32 ca8210_ed_levels[CA8210_MAX_ED_LEVELS] = {
+ -10300, -10250, -10200, -10150, -10100, -10050, -10000, -9950, -9900,
+ -9850, -9800, -9750, -9700, -9650, -9600, -9550, -9500, -9450, -9400,
+ -9350, -9300
+};
+
+/**
+ * ca8210_hw_setup() - Populate the ieee802154_hw phy attributes with the
+ * ca8210's defaults
+ * @ca8210_hw: Pointer to ieee802154_hw to populate
+ */
+static void ca8210_hw_setup(struct ieee802154_hw *ca8210_hw)
+{
+ /* Support channels 11-26 */
+ ca8210_hw->phy->supported.channels[0] = CA8210_VALID_CHANNELS;
+ ca8210_hw->phy->supported.tx_powers_size = CA8210_MAX_TX_POWERS;
+ ca8210_hw->phy->supported.tx_powers = ca8210_tx_powers;
+ ca8210_hw->phy->supported.cca_ed_levels_size = CA8210_MAX_ED_LEVELS;
+ ca8210_hw->phy->supported.cca_ed_levels = ca8210_ed_levels;
+ ca8210_hw->phy->current_channel = 18;
+ ca8210_hw->phy->current_page = 0;
+ ca8210_hw->phy->transmit_power = 800;
+ ca8210_hw->phy->cca.mode = NL802154_CCA_ENERGY_CARRIER;
+ ca8210_hw->phy->cca.opt = NL802154_CCA_OPT_ENERGY_CARRIER_AND;
+ ca8210_hw->phy->cca_ed_level = -9800;
+ ca8210_hw->phy->symbol_duration = 16;
+ ca8210_hw->phy->lifs_period = 40 * ca8210_hw->phy->symbol_duration;
+ ca8210_hw->phy->sifs_period = 12 * ca8210_hw->phy->symbol_duration;
+ ca8210_hw->flags =
+ IEEE802154_HW_AFILT |
+ IEEE802154_HW_OMIT_CKSUM |
+ IEEE802154_HW_FRAME_RETRIES |
+ IEEE802154_HW_PROMISCUOUS |
+ IEEE802154_HW_CSMA_PARAMS;
+ ca8210_hw->phy->flags =
+ WPAN_PHY_FLAG_TXPOWER |
+ WPAN_PHY_FLAG_CCA_ED_LEVEL |
+ WPAN_PHY_FLAG_CCA_MODE |
+ WPAN_PHY_FLAG_DATAGRAMS_ONLY;
+}
+
+/**
+ * ca8210_test_interface_init() - Initialise the test file interface
+ * @priv: Pointer to private data structure
+ *
+ * Provided as an alternative to the standard linux network interface, the test
+ * interface exposes a file in the filesystem (ca8210_test) that allows
+ * 802.15.4 SAP Commands and Cascoda EVBME commands to be sent directly to
+ * the stack.
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_test_interface_init(struct ca8210_priv *priv)
+{
+ struct ca8210_test *test = &priv->test;
+ char node_name[32];
+
+ snprintf(
+ node_name,
+ sizeof(node_name),
+ "ca8210@%d_%d",
+ priv->spi->master->bus_num,
+ spi_get_chipselect(priv->spi, 0)
+ );
+
+ test->ca8210_dfs_spi_int = debugfs_create_file(
+ node_name,
+ 0600, /* S_IRUSR | S_IWUSR */
+ NULL,
+ priv,
+ &test_int_fops
+ );
+
+ debugfs_create_symlink("ca8210", NULL, node_name);
+ init_waitqueue_head(&test->readq);
+ return kfifo_alloc(
+ &test->up_fifo,
+ CA8210_TEST_INT_FIFO_SIZE,
+ GFP_KERNEL
+ );
+}
+
+/**
+ * ca8210_test_interface_clear() - Deinitialise the test file interface
+ * @priv: Pointer to private data structure
+ */
+static void ca8210_test_interface_clear(struct ca8210_priv *priv)
+{
+ struct ca8210_test *test = &priv->test;
+
+ debugfs_remove(test->ca8210_dfs_spi_int);
+ kfifo_free(&test->up_fifo);
+ dev_info(&priv->spi->dev, "Test interface removed\n");
+}
+
+/**
+ * ca8210_remove() - Shut down a ca8210 upon being disconnected
+ * @spi_device: Pointer to spi device data structure
+ *
+ * Return: 0 or linux error code
+ */
+static void ca8210_remove(struct spi_device *spi_device)
+{
+ struct ca8210_priv *priv;
+ struct ca8210_platform_data *pdata;
+
+ dev_info(&spi_device->dev, "Removing ca8210\n");
+
+ pdata = spi_device->dev.platform_data;
+ if (pdata) {
+ if (pdata->extclockenable) {
+ ca8210_unregister_ext_clock(spi_device);
+ ca8210_config_extern_clk(pdata, spi_device, 0);
+ }
+ free_irq(pdata->irq_id, spi_device->dev.driver_data);
+ kfree(pdata);
+ spi_device->dev.platform_data = NULL;
+ }
+ /* get spi_device private data */
+ priv = spi_get_drvdata(spi_device);
+ if (priv) {
+ dev_info(
+ &spi_device->dev,
+ "sync_down = %d, sync_up = %d\n",
+ priv->sync_down,
+ priv->sync_up
+ );
+ ca8210_dev_com_clear(spi_device->dev.driver_data);
+ if (priv->hw) {
+ if (priv->hw_registered)
+ ieee802154_unregister_hw(priv->hw);
+ ieee802154_free_hw(priv->hw);
+ priv->hw = NULL;
+ dev_info(
+ &spi_device->dev,
+ "Unregistered & freed ieee802154_hw.\n"
+ );
+ }
+ if (IS_ENABLED(CONFIG_IEEE802154_CA8210_DEBUGFS))
+ ca8210_test_interface_clear(priv);
+ }
+}
+
+/**
+ * ca8210_probe() - Set up a connected ca8210 upon being detected by the system
+ * @spi_device: Pointer to spi device data structure
+ *
+ * Return: 0 or linux error code
+ */
+static int ca8210_probe(struct spi_device *spi_device)
+{
+ struct ca8210_priv *priv;
+ struct ieee802154_hw *hw;
+ struct ca8210_platform_data *pdata;
+ int ret;
+
+ dev_info(&spi_device->dev, "Inserting ca8210\n");
+
+ /* allocate ieee802154_hw and private data */
+ hw = ieee802154_alloc_hw(sizeof(struct ca8210_priv), &ca8210_phy_ops);
+ if (!hw) {
+ dev_crit(&spi_device->dev, "ieee802154_alloc_hw failed\n");
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ priv = hw->priv;
+ priv->hw = hw;
+ priv->spi = spi_device;
+ hw->parent = &spi_device->dev;
+ spin_lock_init(&priv->lock);
+ priv->async_tx_pending = false;
+ priv->hw_registered = false;
+ priv->sync_up = 0;
+ priv->sync_down = 0;
+ priv->promiscuous = false;
+ priv->retries = 0;
+ init_completion(&priv->ca8210_is_awake);
+ init_completion(&priv->spi_transfer_complete);
+ init_completion(&priv->sync_exchange_complete);
+ spi_set_drvdata(priv->spi, priv);
+ if (IS_ENABLED(CONFIG_IEEE802154_CA8210_DEBUGFS)) {
+ cascoda_api_upstream = ca8210_test_int_driver_write;
+ ca8210_test_interface_init(priv);
+ } else {
+ cascoda_api_upstream = NULL;
+ }
+ ca8210_hw_setup(hw);
+ ieee802154_random_extended_addr(&hw->phy->perm_extended_addr);
+
+ pdata = kmalloc(sizeof(*pdata), GFP_KERNEL);
+ if (!pdata) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ priv->spi->dev.platform_data = pdata;
+ ret = ca8210_get_platform_data(priv->spi, pdata);
+ if (ret) {
+ dev_crit(&spi_device->dev, "ca8210_get_platform_data failed\n");
+ goto error;
+ }
+
+ ret = ca8210_dev_com_init(priv);
+ if (ret) {
+ dev_crit(&spi_device->dev, "ca8210_dev_com_init failed\n");
+ goto error;
+ }
+ ret = ca8210_reset_init(priv->spi);
+ if (ret) {
+ dev_crit(&spi_device->dev, "ca8210_reset_init failed\n");
+ goto error;
+ }
+
+ ret = ca8210_interrupt_init(priv->spi);
+ if (ret) {
+ dev_crit(&spi_device->dev, "ca8210_interrupt_init failed\n");
+ goto error;
+ }
+
+ msleep(100);
+
+ ca8210_reset_send(priv->spi, 1);
+
+ ret = tdme_chipinit(priv->spi);
+ if (ret) {
+ dev_crit(&spi_device->dev, "tdme_chipinit failed\n");
+ goto error;
+ }
+
+ if (pdata->extclockenable) {
+ ret = ca8210_config_extern_clk(pdata, priv->spi, 1);
+ if (ret) {
+ dev_crit(
+ &spi_device->dev,
+ "ca8210_config_extern_clk failed\n"
+ );
+ goto error;
+ }
+ ret = ca8210_register_ext_clock(priv->spi);
+ if (ret) {
+ dev_crit(
+ &spi_device->dev,
+ "ca8210_register_ext_clock failed\n"
+ );
+ goto error;
+ }
+ }
+
+ ret = ieee802154_register_hw(hw);
+ if (ret) {
+ dev_crit(&spi_device->dev, "ieee802154_register_hw failed\n");
+ goto error;
+ }
+ priv->hw_registered = true;
+
+ return 0;
+error:
+ msleep(100); /* wait for pending spi transfers to complete */
+ ca8210_remove(spi_device);
+ return link_to_linux_err(ret);
+}
+
+static const struct of_device_id ca8210_of_ids[] = {
+ {.compatible = "cascoda,ca8210", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, ca8210_of_ids);
+
+static struct spi_driver ca8210_spi_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = ca8210_of_ids,
+ },
+ .probe = ca8210_probe,
+ .remove = ca8210_remove
+};
+
+module_spi_driver(ca8210_spi_driver);
+
+MODULE_AUTHOR("Harry Morris <h.morris@cascoda.com>");
+MODULE_DESCRIPTION("CA-8210 SoftMAC driver");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_VERSION("1.0");
diff --git a/drivers/net/ieee802154/cc2520.c b/drivers/net/ieee802154/cc2520.c
new file mode 100644
index 0000000000..a94d8dd71a
--- /dev/null
+++ b/drivers/net/ieee802154/cc2520.c
@@ -0,0 +1,1192 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Driver for TI CC2520 802.15.4 Wireless-PAN Networking controller
+ *
+ * Copyright (C) 2014 Varka Bhadram <varkab@cdac.in>
+ * Md.Jamal Mohiuddin <mjmohiuddin@cdac.in>
+ * P Sowjanya <sowjanyap@cdac.in>
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <linux/delay.h>
+#include <linux/spi/spi.h>
+#include <linux/property.h>
+#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+#include <linux/skbuff.h>
+#include <linux/ieee802154.h>
+#include <linux/crc-ccitt.h>
+#include <asm/unaligned.h>
+
+#include <net/mac802154.h>
+#include <net/cfg802154.h>
+
+#define SPI_COMMAND_BUFFER 3
+#define HIGH 1
+#define LOW 0
+#define STATE_IDLE 0
+#define RSSI_VALID 0
+#define RSSI_OFFSET 78
+
+#define CC2520_RAM_SIZE 640
+#define CC2520_FIFO_SIZE 128
+
+#define CC2520RAM_TXFIFO 0x100
+#define CC2520RAM_RXFIFO 0x180
+#define CC2520RAM_IEEEADDR 0x3EA
+#define CC2520RAM_PANID 0x3F2
+#define CC2520RAM_SHORTADDR 0x3F4
+
+#define CC2520_FREG_MASK 0x3F
+
+/* status byte values */
+#define CC2520_STATUS_XOSC32M_STABLE BIT(7)
+#define CC2520_STATUS_RSSI_VALID BIT(6)
+#define CC2520_STATUS_TX_UNDERFLOW BIT(3)
+
+/* IEEE-802.15.4 defined constants (2.4 GHz logical channels) */
+#define CC2520_MINCHANNEL 11
+#define CC2520_MAXCHANNEL 26
+#define CC2520_CHANNEL_SPACING 5
+
+/* command strobes */
+#define CC2520_CMD_SNOP 0x00
+#define CC2520_CMD_IBUFLD 0x02
+#define CC2520_CMD_SIBUFEX 0x03
+#define CC2520_CMD_SSAMPLECCA 0x04
+#define CC2520_CMD_SRES 0x0f
+#define CC2520_CMD_MEMORY_MASK 0x0f
+#define CC2520_CMD_MEMORY_READ 0x10
+#define CC2520_CMD_MEMORY_WRITE 0x20
+#define CC2520_CMD_RXBUF 0x30
+#define CC2520_CMD_RXBUFCP 0x38
+#define CC2520_CMD_RXBUFMOV 0x32
+#define CC2520_CMD_TXBUF 0x3A
+#define CC2520_CMD_TXBUFCP 0x3E
+#define CC2520_CMD_RANDOM 0x3C
+#define CC2520_CMD_SXOSCON 0x40
+#define CC2520_CMD_STXCAL 0x41
+#define CC2520_CMD_SRXON 0x42
+#define CC2520_CMD_STXON 0x43
+#define CC2520_CMD_STXONCCA 0x44
+#define CC2520_CMD_SRFOFF 0x45
+#define CC2520_CMD_SXOSCOFF 0x46
+#define CC2520_CMD_SFLUSHRX 0x47
+#define CC2520_CMD_SFLUSHTX 0x48
+#define CC2520_CMD_SACK 0x49
+#define CC2520_CMD_SACKPEND 0x4A
+#define CC2520_CMD_SNACK 0x4B
+#define CC2520_CMD_SRXMASKBITSET 0x4C
+#define CC2520_CMD_SRXMASKBITCLR 0x4D
+#define CC2520_CMD_RXMASKAND 0x4E
+#define CC2520_CMD_RXMASKOR 0x4F
+#define CC2520_CMD_MEMCP 0x50
+#define CC2520_CMD_MEMCPR 0x52
+#define CC2520_CMD_MEMXCP 0x54
+#define CC2520_CMD_MEMXWR 0x56
+#define CC2520_CMD_BCLR 0x58
+#define CC2520_CMD_BSET 0x59
+#define CC2520_CMD_CTR_UCTR 0x60
+#define CC2520_CMD_CBCMAC 0x64
+#define CC2520_CMD_UCBCMAC 0x66
+#define CC2520_CMD_CCM 0x68
+#define CC2520_CMD_UCCM 0x6A
+#define CC2520_CMD_ECB 0x70
+#define CC2520_CMD_ECBO 0x72
+#define CC2520_CMD_ECBX 0x74
+#define CC2520_CMD_INC 0x78
+#define CC2520_CMD_ABORT 0x7F
+#define CC2520_CMD_REGISTER_READ 0x80
+#define CC2520_CMD_REGISTER_WRITE 0xC0
+
+/* status registers */
+#define CC2520_CHIPID 0x40
+#define CC2520_VERSION 0x42
+#define CC2520_EXTCLOCK 0x44
+#define CC2520_MDMCTRL0 0x46
+#define CC2520_MDMCTRL1 0x47
+#define CC2520_FREQEST 0x48
+#define CC2520_RXCTRL 0x4A
+#define CC2520_FSCTRL 0x4C
+#define CC2520_FSCAL0 0x4E
+#define CC2520_FSCAL1 0x4F
+#define CC2520_FSCAL2 0x50
+#define CC2520_FSCAL3 0x51
+#define CC2520_AGCCTRL0 0x52
+#define CC2520_AGCCTRL1 0x53
+#define CC2520_AGCCTRL2 0x54
+#define CC2520_AGCCTRL3 0x55
+#define CC2520_ADCTEST0 0x56
+#define CC2520_ADCTEST1 0x57
+#define CC2520_ADCTEST2 0x58
+#define CC2520_MDMTEST0 0x5A
+#define CC2520_MDMTEST1 0x5B
+#define CC2520_DACTEST0 0x5C
+#define CC2520_DACTEST1 0x5D
+#define CC2520_ATEST 0x5E
+#define CC2520_DACTEST2 0x5F
+#define CC2520_PTEST0 0x60
+#define CC2520_PTEST1 0x61
+#define CC2520_RESERVED 0x62
+#define CC2520_DPUBIST 0x7A
+#define CC2520_ACTBIST 0x7C
+#define CC2520_RAMBIST 0x7E
+
+/* frame registers */
+#define CC2520_FRMFILT0 0x00
+#define CC2520_FRMFILT1 0x01
+#define CC2520_SRCMATCH 0x02
+#define CC2520_SRCSHORTEN0 0x04
+#define CC2520_SRCSHORTEN1 0x05
+#define CC2520_SRCSHORTEN2 0x06
+#define CC2520_SRCEXTEN0 0x08
+#define CC2520_SRCEXTEN1 0x09
+#define CC2520_SRCEXTEN2 0x0A
+#define CC2520_FRMCTRL0 0x0C
+#define CC2520_FRMCTRL1 0x0D
+#define CC2520_RXENABLE0 0x0E
+#define CC2520_RXENABLE1 0x0F
+#define CC2520_EXCFLAG0 0x10
+#define CC2520_EXCFLAG1 0x11
+#define CC2520_EXCFLAG2 0x12
+#define CC2520_EXCMASKA0 0x14
+#define CC2520_EXCMASKA1 0x15
+#define CC2520_EXCMASKA2 0x16
+#define CC2520_EXCMASKB0 0x18
+#define CC2520_EXCMASKB1 0x19
+#define CC2520_EXCMASKB2 0x1A
+#define CC2520_EXCBINDX0 0x1C
+#define CC2520_EXCBINDX1 0x1D
+#define CC2520_EXCBINDY0 0x1E
+#define CC2520_EXCBINDY1 0x1F
+#define CC2520_GPIOCTRL0 0x20
+#define CC2520_GPIOCTRL1 0x21
+#define CC2520_GPIOCTRL2 0x22
+#define CC2520_GPIOCTRL3 0x23
+#define CC2520_GPIOCTRL4 0x24
+#define CC2520_GPIOCTRL5 0x25
+#define CC2520_GPIOPOLARITY 0x26
+#define CC2520_GPIOCTRL 0x28
+#define CC2520_DPUCON 0x2A
+#define CC2520_DPUSTAT 0x2C
+#define CC2520_FREQCTRL 0x2E
+#define CC2520_FREQTUNE 0x2F
+#define CC2520_TXPOWER 0x30
+#define CC2520_TXCTRL 0x31
+#define CC2520_FSMSTAT0 0x32
+#define CC2520_FSMSTAT1 0x33
+#define CC2520_FIFOPCTRL 0x34
+#define CC2520_FSMCTRL 0x35
+#define CC2520_CCACTRL0 0x36
+#define CC2520_CCACTRL1 0x37
+#define CC2520_RSSI 0x38
+#define CC2520_RSSISTAT 0x39
+#define CC2520_RXFIRST 0x3C
+#define CC2520_RXFIFOCNT 0x3E
+#define CC2520_TXFIFOCNT 0x3F
+
+/* CC2520_FRMFILT0 */
+#define FRMFILT0_FRAME_FILTER_EN BIT(0)
+#define FRMFILT0_PAN_COORDINATOR BIT(1)
+
+/* CC2520_FRMCTRL0 */
+#define FRMCTRL0_AUTOACK BIT(5)
+#define FRMCTRL0_AUTOCRC BIT(6)
+
+/* CC2520_FRMCTRL1 */
+#define FRMCTRL1_SET_RXENMASK_ON_TX BIT(0)
+#define FRMCTRL1_IGNORE_TX_UNDERF BIT(1)
+
+/* Driver private information */
+struct cc2520_private {
+ struct spi_device *spi; /* SPI device structure */
+ struct ieee802154_hw *hw; /* IEEE-802.15.4 device */
+ u8 *buf; /* SPI TX/Rx data buffer */
+ struct mutex buffer_mutex; /* SPI buffer mutex */
+ bool is_tx; /* Flag for sync b/w Tx and Rx */
+ bool amplified; /* Flag for CC2591 */
+ struct gpio_desc *fifo_pin; /* FIFO GPIO pin number */
+ struct work_struct fifop_irqwork;/* Workqueue for FIFOP */
+ spinlock_t lock; /* Lock for is_tx*/
+ struct completion tx_complete; /* Work completion for Tx */
+ bool promiscuous; /* Flag for promiscuous mode */
+};
+
+/* Generic Functions */
+static int
+cc2520_cmd_strobe(struct cc2520_private *priv, u8 cmd)
+{
+ int ret;
+ struct spi_message msg;
+ struct spi_transfer xfer = {
+ .len = 0,
+ .tx_buf = priv->buf,
+ .rx_buf = priv->buf,
+ };
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&xfer, &msg);
+
+ mutex_lock(&priv->buffer_mutex);
+ priv->buf[xfer.len++] = cmd;
+ dev_vdbg(&priv->spi->dev,
+ "command strobe buf[0] = %02x\n",
+ priv->buf[0]);
+
+ ret = spi_sync(priv->spi, &msg);
+ dev_vdbg(&priv->spi->dev,
+ "buf[0] = %02x\n", priv->buf[0]);
+ mutex_unlock(&priv->buffer_mutex);
+
+ return ret;
+}
+
+static int
+cc2520_get_status(struct cc2520_private *priv, u8 *status)
+{
+ int ret;
+ struct spi_message msg;
+ struct spi_transfer xfer = {
+ .len = 0,
+ .tx_buf = priv->buf,
+ .rx_buf = priv->buf,
+ };
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&xfer, &msg);
+
+ mutex_lock(&priv->buffer_mutex);
+ priv->buf[xfer.len++] = CC2520_CMD_SNOP;
+ dev_vdbg(&priv->spi->dev,
+ "get status command buf[0] = %02x\n", priv->buf[0]);
+
+ ret = spi_sync(priv->spi, &msg);
+ if (!ret)
+ *status = priv->buf[0];
+ dev_vdbg(&priv->spi->dev,
+ "buf[0] = %02x\n", priv->buf[0]);
+ mutex_unlock(&priv->buffer_mutex);
+
+ return ret;
+}
+
+static int
+cc2520_write_register(struct cc2520_private *priv, u8 reg, u8 value)
+{
+ int status;
+ struct spi_message msg;
+ struct spi_transfer xfer = {
+ .len = 0,
+ .tx_buf = priv->buf,
+ .rx_buf = priv->buf,
+ };
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&xfer, &msg);
+
+ mutex_lock(&priv->buffer_mutex);
+
+ if (reg <= CC2520_FREG_MASK) {
+ priv->buf[xfer.len++] = CC2520_CMD_REGISTER_WRITE | reg;
+ priv->buf[xfer.len++] = value;
+ } else {
+ priv->buf[xfer.len++] = CC2520_CMD_MEMORY_WRITE;
+ priv->buf[xfer.len++] = reg;
+ priv->buf[xfer.len++] = value;
+ }
+ status = spi_sync(priv->spi, &msg);
+ if (msg.status)
+ status = msg.status;
+
+ mutex_unlock(&priv->buffer_mutex);
+
+ return status;
+}
+
+static int
+cc2520_write_ram(struct cc2520_private *priv, u16 reg, u8 len, u8 *data)
+{
+ int status;
+ struct spi_message msg;
+ struct spi_transfer xfer_head = {
+ .len = 0,
+ .tx_buf = priv->buf,
+ .rx_buf = priv->buf,
+ };
+
+ struct spi_transfer xfer_buf = {
+ .len = len,
+ .tx_buf = data,
+ };
+
+ mutex_lock(&priv->buffer_mutex);
+ priv->buf[xfer_head.len++] = (CC2520_CMD_MEMORY_WRITE |
+ ((reg >> 8) & 0xff));
+ priv->buf[xfer_head.len++] = reg & 0xff;
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&xfer_head, &msg);
+ spi_message_add_tail(&xfer_buf, &msg);
+
+ status = spi_sync(priv->spi, &msg);
+ dev_dbg(&priv->spi->dev, "spi status = %d\n", status);
+ if (msg.status)
+ status = msg.status;
+
+ mutex_unlock(&priv->buffer_mutex);
+ return status;
+}
+
+static int
+cc2520_read_register(struct cc2520_private *priv, u8 reg, u8 *data)
+{
+ int status;
+ struct spi_message msg;
+ struct spi_transfer xfer1 = {
+ .len = 0,
+ .tx_buf = priv->buf,
+ .rx_buf = priv->buf,
+ };
+
+ struct spi_transfer xfer2 = {
+ .len = 1,
+ .rx_buf = data,
+ };
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&xfer1, &msg);
+ spi_message_add_tail(&xfer2, &msg);
+
+ mutex_lock(&priv->buffer_mutex);
+ priv->buf[xfer1.len++] = CC2520_CMD_MEMORY_READ;
+ priv->buf[xfer1.len++] = reg;
+
+ status = spi_sync(priv->spi, &msg);
+ dev_dbg(&priv->spi->dev,
+ "spi status = %d\n", status);
+ if (msg.status)
+ status = msg.status;
+
+ mutex_unlock(&priv->buffer_mutex);
+
+ return status;
+}
+
+static int
+cc2520_write_txfifo(struct cc2520_private *priv, u8 pkt_len, u8 *data, u8 len)
+{
+ int status;
+
+ /* length byte must include FCS even
+ * if it is calculated in the hardware
+ */
+ int len_byte = pkt_len;
+
+ struct spi_message msg;
+
+ struct spi_transfer xfer_head = {
+ .len = 0,
+ .tx_buf = priv->buf,
+ .rx_buf = priv->buf,
+ };
+ struct spi_transfer xfer_len = {
+ .len = 1,
+ .tx_buf = &len_byte,
+ };
+ struct spi_transfer xfer_buf = {
+ .len = len,
+ .tx_buf = data,
+ };
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&xfer_head, &msg);
+ spi_message_add_tail(&xfer_len, &msg);
+ spi_message_add_tail(&xfer_buf, &msg);
+
+ mutex_lock(&priv->buffer_mutex);
+ priv->buf[xfer_head.len++] = CC2520_CMD_TXBUF;
+ dev_vdbg(&priv->spi->dev,
+ "TX_FIFO cmd buf[0] = %02x\n", priv->buf[0]);
+
+ status = spi_sync(priv->spi, &msg);
+ dev_vdbg(&priv->spi->dev, "status = %d\n", status);
+ if (msg.status)
+ status = msg.status;
+ dev_vdbg(&priv->spi->dev, "status = %d\n", status);
+ dev_vdbg(&priv->spi->dev, "buf[0] = %02x\n", priv->buf[0]);
+ mutex_unlock(&priv->buffer_mutex);
+
+ return status;
+}
+
+static int
+cc2520_read_rxfifo(struct cc2520_private *priv, u8 *data, u8 len)
+{
+ int status;
+ struct spi_message msg;
+
+ struct spi_transfer xfer_head = {
+ .len = 0,
+ .tx_buf = priv->buf,
+ .rx_buf = priv->buf,
+ };
+ struct spi_transfer xfer_buf = {
+ .len = len,
+ .rx_buf = data,
+ };
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&xfer_head, &msg);
+ spi_message_add_tail(&xfer_buf, &msg);
+
+ mutex_lock(&priv->buffer_mutex);
+ priv->buf[xfer_head.len++] = CC2520_CMD_RXBUF;
+
+ dev_vdbg(&priv->spi->dev, "read rxfifo buf[0] = %02x\n", priv->buf[0]);
+ dev_vdbg(&priv->spi->dev, "buf[1] = %02x\n", priv->buf[1]);
+
+ status = spi_sync(priv->spi, &msg);
+ dev_vdbg(&priv->spi->dev, "status = %d\n", status);
+ if (msg.status)
+ status = msg.status;
+ dev_vdbg(&priv->spi->dev, "status = %d\n", status);
+ dev_vdbg(&priv->spi->dev,
+ "return status buf[0] = %02x\n", priv->buf[0]);
+ dev_vdbg(&priv->spi->dev, "length buf[1] = %02x\n", priv->buf[1]);
+
+ mutex_unlock(&priv->buffer_mutex);
+
+ return status;
+}
+
+static int cc2520_start(struct ieee802154_hw *hw)
+{
+ return cc2520_cmd_strobe(hw->priv, CC2520_CMD_SRXON);
+}
+
+static void cc2520_stop(struct ieee802154_hw *hw)
+{
+ cc2520_cmd_strobe(hw->priv, CC2520_CMD_SRFOFF);
+}
+
+static int
+cc2520_tx(struct ieee802154_hw *hw, struct sk_buff *skb)
+{
+ struct cc2520_private *priv = hw->priv;
+ unsigned long flags;
+ int rc;
+ u8 status = 0;
+ u8 pkt_len;
+
+ /* In promiscuous mode we disable AUTOCRC so we can get the raw CRC
+ * values on RX. This means we need to manually add the CRC on TX.
+ */
+ if (priv->promiscuous) {
+ u16 crc = crc_ccitt(0, skb->data, skb->len);
+
+ put_unaligned_le16(crc, skb_put(skb, 2));
+ pkt_len = skb->len;
+ } else {
+ pkt_len = skb->len + 2;
+ }
+
+ rc = cc2520_cmd_strobe(priv, CC2520_CMD_SFLUSHTX);
+ if (rc)
+ goto err_tx;
+
+ rc = cc2520_write_txfifo(priv, pkt_len, skb->data, skb->len);
+ if (rc)
+ goto err_tx;
+
+ rc = cc2520_get_status(priv, &status);
+ if (rc)
+ goto err_tx;
+
+ if (status & CC2520_STATUS_TX_UNDERFLOW) {
+ rc = -EINVAL;
+ dev_err(&priv->spi->dev, "cc2520 tx underflow exception\n");
+ goto err_tx;
+ }
+
+ spin_lock_irqsave(&priv->lock, flags);
+ WARN_ON(priv->is_tx);
+ priv->is_tx = 1;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ rc = cc2520_cmd_strobe(priv, CC2520_CMD_STXONCCA);
+ if (rc)
+ goto err;
+
+ rc = wait_for_completion_interruptible(&priv->tx_complete);
+ if (rc < 0)
+ goto err;
+
+ cc2520_cmd_strobe(priv, CC2520_CMD_SFLUSHTX);
+ cc2520_cmd_strobe(priv, CC2520_CMD_SRXON);
+
+ return rc;
+err:
+ spin_lock_irqsave(&priv->lock, flags);
+ priv->is_tx = 0;
+ spin_unlock_irqrestore(&priv->lock, flags);
+err_tx:
+ return rc;
+}
+
+static int cc2520_rx(struct cc2520_private *priv)
+{
+ u8 len = 0, lqi = 0, bytes = 1;
+ struct sk_buff *skb;
+
+ /* Read single length byte from the radio. */
+ cc2520_read_rxfifo(priv, &len, bytes);
+
+ if (!ieee802154_is_valid_psdu_len(len)) {
+ /* Corrupted frame received, clear frame buffer by
+ * reading entire buffer.
+ */
+ dev_dbg(&priv->spi->dev, "corrupted frame received\n");
+ len = IEEE802154_MTU;
+ }
+
+ skb = dev_alloc_skb(len);
+ if (!skb)
+ return -ENOMEM;
+
+ if (cc2520_read_rxfifo(priv, skb_put(skb, len), len)) {
+ dev_dbg(&priv->spi->dev, "frame reception failed\n");
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ /* In promiscuous mode, we configure the radio to include the
+ * CRC (AUTOCRC==0) and we pass on the packet unconditionally. If not
+ * in promiscuous mode, we check the CRC here, but leave the
+ * RSSI/LQI/CRC_OK bytes as they will get removed in the mac layer.
+ */
+ if (!priv->promiscuous) {
+ bool crc_ok;
+
+ /* Check if the CRC is valid. With AUTOCRC set, the most
+ * significant bit of the last byte returned from the CC2520
+ * is CRC_OK flag. See section 20.3.4 of the datasheet.
+ */
+ crc_ok = skb->data[len - 1] & BIT(7);
+
+ /* If we failed CRC drop the packet in the driver layer. */
+ if (!crc_ok) {
+ dev_dbg(&priv->spi->dev, "CRC check failed\n");
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ /* To calculate LQI, the lower 7 bits of the last byte (the
+ * correlation value provided by the radio) must be scaled to
+ * the range 0-255. According to section 20.6, the correlation
+ * value ranges from 50-110. Ideally this would be calibrated
+ * per hardware design, but we use roughly the datasheet values
+ * to get close enough while avoiding floating point.
+ */
+ lqi = skb->data[len - 1] & 0x7f;
+ if (lqi < 50)
+ lqi = 50;
+ else if (lqi > 113)
+ lqi = 113;
+ lqi = (lqi - 50) * 4;
+ }
+
+ ieee802154_rx_irqsafe(priv->hw, skb, lqi);
+
+ dev_vdbg(&priv->spi->dev, "RXFIFO: %x %x\n", len, lqi);
+
+ return 0;
+}
+
+static int
+cc2520_ed(struct ieee802154_hw *hw, u8 *level)
+{
+ struct cc2520_private *priv = hw->priv;
+ u8 status = 0xff;
+ u8 rssi;
+ int ret;
+
+ ret = cc2520_read_register(priv, CC2520_RSSISTAT, &status);
+ if (ret)
+ return ret;
+
+ if (status != RSSI_VALID)
+ return -EINVAL;
+
+ ret = cc2520_read_register(priv, CC2520_RSSI, &rssi);
+ if (ret)
+ return ret;
+
+ /* level = RSSI(rssi) - OFFSET [dBm] : offset is 76dBm */
+ *level = rssi - RSSI_OFFSET;
+
+ return 0;
+}
+
+static int
+cc2520_set_channel(struct ieee802154_hw *hw, u8 page, u8 channel)
+{
+ struct cc2520_private *priv = hw->priv;
+ int ret;
+
+ dev_dbg(&priv->spi->dev, "trying to set channel\n");
+
+ WARN_ON(page != 0);
+ WARN_ON(channel < CC2520_MINCHANNEL);
+ WARN_ON(channel > CC2520_MAXCHANNEL);
+
+ ret = cc2520_write_register(priv, CC2520_FREQCTRL,
+ 11 + 5 * (channel - 11));
+
+ return ret;
+}
+
+static int
+cc2520_filter(struct ieee802154_hw *hw,
+ struct ieee802154_hw_addr_filt *filt, unsigned long changed)
+{
+ struct cc2520_private *priv = hw->priv;
+ int ret = 0;
+
+ if (changed & IEEE802154_AFILT_PANID_CHANGED) {
+ u16 panid = le16_to_cpu(filt->pan_id);
+
+ dev_vdbg(&priv->spi->dev, "%s called for pan id\n", __func__);
+ ret = cc2520_write_ram(priv, CC2520RAM_PANID,
+ sizeof(panid), (u8 *)&panid);
+ }
+
+ if (changed & IEEE802154_AFILT_IEEEADDR_CHANGED) {
+ dev_vdbg(&priv->spi->dev,
+ "%s called for IEEE addr\n", __func__);
+ ret = cc2520_write_ram(priv, CC2520RAM_IEEEADDR,
+ sizeof(filt->ieee_addr),
+ (u8 *)&filt->ieee_addr);
+ }
+
+ if (changed & IEEE802154_AFILT_SADDR_CHANGED) {
+ u16 addr = le16_to_cpu(filt->short_addr);
+
+ dev_vdbg(&priv->spi->dev, "%s called for saddr\n", __func__);
+ ret = cc2520_write_ram(priv, CC2520RAM_SHORTADDR,
+ sizeof(addr), (u8 *)&addr);
+ }
+
+ if (changed & IEEE802154_AFILT_PANC_CHANGED) {
+ u8 frmfilt0;
+
+ dev_vdbg(&priv->spi->dev,
+ "%s called for panc change\n", __func__);
+
+ cc2520_read_register(priv, CC2520_FRMFILT0, &frmfilt0);
+
+ if (filt->pan_coord)
+ frmfilt0 |= FRMFILT0_PAN_COORDINATOR;
+ else
+ frmfilt0 &= ~FRMFILT0_PAN_COORDINATOR;
+
+ ret = cc2520_write_register(priv, CC2520_FRMFILT0, frmfilt0);
+ }
+
+ return ret;
+}
+
+static inline int cc2520_set_tx_power(struct cc2520_private *priv, s32 mbm)
+{
+ u8 power;
+
+ switch (mbm) {
+ case 500:
+ power = 0xF7;
+ break;
+ case 300:
+ power = 0xF2;
+ break;
+ case 200:
+ power = 0xAB;
+ break;
+ case 100:
+ power = 0x13;
+ break;
+ case 0:
+ power = 0x32;
+ break;
+ case -200:
+ power = 0x81;
+ break;
+ case -400:
+ power = 0x88;
+ break;
+ case -700:
+ power = 0x2C;
+ break;
+ case -1800:
+ power = 0x03;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return cc2520_write_register(priv, CC2520_TXPOWER, power);
+}
+
+static inline int cc2520_cc2591_set_tx_power(struct cc2520_private *priv,
+ s32 mbm)
+{
+ u8 power;
+
+ switch (mbm) {
+ case 1700:
+ power = 0xF9;
+ break;
+ case 1600:
+ power = 0xF0;
+ break;
+ case 1400:
+ power = 0xA0;
+ break;
+ case 1100:
+ power = 0x2C;
+ break;
+ case -100:
+ power = 0x03;
+ break;
+ case -800:
+ power = 0x01;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return cc2520_write_register(priv, CC2520_TXPOWER, power);
+}
+
+#define CC2520_MAX_TX_POWERS 0x8
+static const s32 cc2520_powers[CC2520_MAX_TX_POWERS + 1] = {
+ 500, 300, 200, 100, 0, -200, -400, -700, -1800,
+};
+
+#define CC2520_CC2591_MAX_TX_POWERS 0x5
+static const s32 cc2520_cc2591_powers[CC2520_CC2591_MAX_TX_POWERS + 1] = {
+ 1700, 1600, 1400, 1100, -100, -800,
+};
+
+static int
+cc2520_set_txpower(struct ieee802154_hw *hw, s32 mbm)
+{
+ struct cc2520_private *priv = hw->priv;
+
+ if (!priv->amplified)
+ return cc2520_set_tx_power(priv, mbm);
+
+ return cc2520_cc2591_set_tx_power(priv, mbm);
+}
+
+static int
+cc2520_set_promiscuous_mode(struct ieee802154_hw *hw, bool on)
+{
+ struct cc2520_private *priv = hw->priv;
+ u8 frmfilt0;
+
+ dev_dbg(&priv->spi->dev, "%s : mode %d\n", __func__, on);
+
+ priv->promiscuous = on;
+
+ cc2520_read_register(priv, CC2520_FRMFILT0, &frmfilt0);
+
+ if (on) {
+ /* Disable automatic ACK, automatic CRC, and frame filtering. */
+ cc2520_write_register(priv, CC2520_FRMCTRL0, 0);
+ frmfilt0 &= ~FRMFILT0_FRAME_FILTER_EN;
+ } else {
+ cc2520_write_register(priv, CC2520_FRMCTRL0, FRMCTRL0_AUTOACK |
+ FRMCTRL0_AUTOCRC);
+ frmfilt0 |= FRMFILT0_FRAME_FILTER_EN;
+ }
+ return cc2520_write_register(priv, CC2520_FRMFILT0, frmfilt0);
+}
+
+static const struct ieee802154_ops cc2520_ops = {
+ .owner = THIS_MODULE,
+ .start = cc2520_start,
+ .stop = cc2520_stop,
+ .xmit_sync = cc2520_tx,
+ .ed = cc2520_ed,
+ .set_channel = cc2520_set_channel,
+ .set_hw_addr_filt = cc2520_filter,
+ .set_txpower = cc2520_set_txpower,
+ .set_promiscuous_mode = cc2520_set_promiscuous_mode,
+};
+
+static int cc2520_register(struct cc2520_private *priv)
+{
+ int ret = -ENOMEM;
+
+ priv->hw = ieee802154_alloc_hw(sizeof(*priv), &cc2520_ops);
+ if (!priv->hw)
+ goto err_ret;
+
+ priv->hw->priv = priv;
+ priv->hw->parent = &priv->spi->dev;
+ priv->hw->extra_tx_headroom = 0;
+ ieee802154_random_extended_addr(&priv->hw->phy->perm_extended_addr);
+
+ /* We do support only 2.4 Ghz */
+ priv->hw->phy->supported.channels[0] = 0x7FFF800;
+ priv->hw->flags = IEEE802154_HW_TX_OMIT_CKSUM | IEEE802154_HW_AFILT |
+ IEEE802154_HW_PROMISCUOUS;
+
+ priv->hw->phy->flags = WPAN_PHY_FLAG_TXPOWER;
+
+ if (!priv->amplified) {
+ priv->hw->phy->supported.tx_powers = cc2520_powers;
+ priv->hw->phy->supported.tx_powers_size = ARRAY_SIZE(cc2520_powers);
+ priv->hw->phy->transmit_power = priv->hw->phy->supported.tx_powers[4];
+ } else {
+ priv->hw->phy->supported.tx_powers = cc2520_cc2591_powers;
+ priv->hw->phy->supported.tx_powers_size = ARRAY_SIZE(cc2520_cc2591_powers);
+ priv->hw->phy->transmit_power = priv->hw->phy->supported.tx_powers[0];
+ }
+
+ priv->hw->phy->current_channel = 11;
+
+ dev_vdbg(&priv->spi->dev, "registered cc2520\n");
+ ret = ieee802154_register_hw(priv->hw);
+ if (ret)
+ goto err_free_device;
+
+ return 0;
+
+err_free_device:
+ ieee802154_free_hw(priv->hw);
+err_ret:
+ return ret;
+}
+
+static void cc2520_fifop_irqwork(struct work_struct *work)
+{
+ struct cc2520_private *priv
+ = container_of(work, struct cc2520_private, fifop_irqwork);
+
+ dev_dbg(&priv->spi->dev, "fifop interrupt received\n");
+
+ if (gpiod_get_value(priv->fifo_pin))
+ cc2520_rx(priv);
+ else
+ dev_dbg(&priv->spi->dev, "rxfifo overflow\n");
+
+ cc2520_cmd_strobe(priv, CC2520_CMD_SFLUSHRX);
+ cc2520_cmd_strobe(priv, CC2520_CMD_SFLUSHRX);
+}
+
+static irqreturn_t cc2520_fifop_isr(int irq, void *data)
+{
+ struct cc2520_private *priv = data;
+
+ schedule_work(&priv->fifop_irqwork);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t cc2520_sfd_isr(int irq, void *data)
+{
+ struct cc2520_private *priv = data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (priv->is_tx) {
+ priv->is_tx = 0;
+ spin_unlock_irqrestore(&priv->lock, flags);
+ dev_dbg(&priv->spi->dev, "SFD for TX\n");
+ complete(&priv->tx_complete);
+ } else {
+ spin_unlock_irqrestore(&priv->lock, flags);
+ dev_dbg(&priv->spi->dev, "SFD for RX\n");
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int cc2520_hw_init(struct cc2520_private *priv)
+{
+ u8 status = 0, state = 0xff;
+ int ret;
+ int timeout = 100;
+
+ ret = cc2520_read_register(priv, CC2520_FSMSTAT1, &state);
+ if (ret)
+ goto err_ret;
+
+ if (state != STATE_IDLE)
+ return -EINVAL;
+
+ do {
+ ret = cc2520_get_status(priv, &status);
+ if (ret)
+ goto err_ret;
+
+ if (timeout-- <= 0) {
+ dev_err(&priv->spi->dev, "oscillator start failed!\n");
+ return -ETIMEDOUT;
+ }
+ udelay(1);
+ } while (!(status & CC2520_STATUS_XOSC32M_STABLE));
+
+ dev_vdbg(&priv->spi->dev, "oscillator brought up\n");
+
+ /* If the CC2520 is connected to a CC2591 amplifier, we must both
+ * configure GPIOs on the CC2520 to correctly configure the CC2591
+ * and change a couple settings of the CC2520 to work with the
+ * amplifier. See section 8 page 17 of TI application note AN065.
+ * http://www.ti.com/lit/an/swra229a/swra229a.pdf
+ */
+ if (priv->amplified) {
+ ret = cc2520_write_register(priv, CC2520_AGCCTRL1, 0x16);
+ if (ret)
+ goto err_ret;
+
+ ret = cc2520_write_register(priv, CC2520_GPIOCTRL0, 0x46);
+ if (ret)
+ goto err_ret;
+
+ ret = cc2520_write_register(priv, CC2520_GPIOCTRL5, 0x47);
+ if (ret)
+ goto err_ret;
+
+ ret = cc2520_write_register(priv, CC2520_GPIOPOLARITY, 0x1e);
+ if (ret)
+ goto err_ret;
+
+ ret = cc2520_write_register(priv, CC2520_TXCTRL, 0xc1);
+ if (ret)
+ goto err_ret;
+ } else {
+ ret = cc2520_write_register(priv, CC2520_AGCCTRL1, 0x11);
+ if (ret)
+ goto err_ret;
+ }
+
+ /* Registers default value: section 28.1 in Datasheet */
+
+ /* Set the CCA threshold to -50 dBm. This seems to have been copied
+ * from the TinyOS CC2520 driver and is much higher than the -84 dBm
+ * threshold suggested in the datasheet.
+ */
+ ret = cc2520_write_register(priv, CC2520_CCACTRL0, 0x1A);
+ if (ret)
+ goto err_ret;
+
+ ret = cc2520_write_register(priv, CC2520_MDMCTRL0, 0x85);
+ if (ret)
+ goto err_ret;
+
+ ret = cc2520_write_register(priv, CC2520_MDMCTRL1, 0x14);
+ if (ret)
+ goto err_ret;
+
+ ret = cc2520_write_register(priv, CC2520_RXCTRL, 0x3f);
+ if (ret)
+ goto err_ret;
+
+ ret = cc2520_write_register(priv, CC2520_FSCTRL, 0x5a);
+ if (ret)
+ goto err_ret;
+
+ ret = cc2520_write_register(priv, CC2520_FSCAL1, 0x2b);
+ if (ret)
+ goto err_ret;
+
+ ret = cc2520_write_register(priv, CC2520_ADCTEST0, 0x10);
+ if (ret)
+ goto err_ret;
+
+ ret = cc2520_write_register(priv, CC2520_ADCTEST1, 0x0e);
+ if (ret)
+ goto err_ret;
+
+ ret = cc2520_write_register(priv, CC2520_ADCTEST2, 0x03);
+ if (ret)
+ goto err_ret;
+
+ /* Configure registers correctly for this driver. */
+ ret = cc2520_write_register(priv, CC2520_FRMCTRL1,
+ FRMCTRL1_SET_RXENMASK_ON_TX |
+ FRMCTRL1_IGNORE_TX_UNDERF);
+ if (ret)
+ goto err_ret;
+
+ ret = cc2520_write_register(priv, CC2520_FIFOPCTRL, 127);
+ if (ret)
+ goto err_ret;
+
+ return 0;
+
+err_ret:
+ return ret;
+}
+
+static int cc2520_probe(struct spi_device *spi)
+{
+ struct cc2520_private *priv;
+ struct gpio_desc *fifop;
+ struct gpio_desc *cca;
+ struct gpio_desc *sfd;
+ struct gpio_desc *reset;
+ struct gpio_desc *vreg;
+ int ret;
+
+ priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ spi_set_drvdata(spi, priv);
+
+ /* CC2591 front end for CC2520 */
+ /* Assumption that CC2591 is not connected */
+ priv->amplified = false;
+ if (device_property_read_bool(&spi->dev, "amplified"))
+ priv->amplified = true;
+
+ priv->spi = spi;
+
+ priv->buf = devm_kzalloc(&spi->dev,
+ SPI_COMMAND_BUFFER, GFP_KERNEL);
+ if (!priv->buf)
+ return -ENOMEM;
+
+ mutex_init(&priv->buffer_mutex);
+ INIT_WORK(&priv->fifop_irqwork, cc2520_fifop_irqwork);
+ spin_lock_init(&priv->lock);
+ init_completion(&priv->tx_complete);
+
+ /* Request all the gpio's */
+ priv->fifo_pin = devm_gpiod_get(&spi->dev, "fifo", GPIOD_IN);
+ if (IS_ERR(priv->fifo_pin)) {
+ dev_err(&spi->dev, "fifo gpio is not valid\n");
+ ret = PTR_ERR(priv->fifo_pin);
+ goto err_hw_init;
+ }
+
+ cca = devm_gpiod_get(&spi->dev, "cca", GPIOD_IN);
+ if (IS_ERR(cca)) {
+ dev_err(&spi->dev, "cca gpio is not valid\n");
+ ret = PTR_ERR(cca);
+ goto err_hw_init;
+ }
+
+ fifop = devm_gpiod_get(&spi->dev, "fifop", GPIOD_IN);
+ if (IS_ERR(fifop)) {
+ dev_err(&spi->dev, "fifop gpio is not valid\n");
+ ret = PTR_ERR(fifop);
+ goto err_hw_init;
+ }
+
+ sfd = devm_gpiod_get(&spi->dev, "sfd", GPIOD_IN);
+ if (IS_ERR(sfd)) {
+ dev_err(&spi->dev, "sfd gpio is not valid\n");
+ ret = PTR_ERR(sfd);
+ goto err_hw_init;
+ }
+
+ reset = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(reset)) {
+ dev_err(&spi->dev, "reset gpio is not valid\n");
+ ret = PTR_ERR(reset);
+ goto err_hw_init;
+ }
+
+ vreg = devm_gpiod_get(&spi->dev, "vreg", GPIOD_OUT_LOW);
+ if (IS_ERR(vreg)) {
+ dev_err(&spi->dev, "vreg gpio is not valid\n");
+ ret = PTR_ERR(vreg);
+ goto err_hw_init;
+ }
+
+ gpiod_set_value(vreg, HIGH);
+ usleep_range(100, 150);
+
+ gpiod_set_value(reset, HIGH);
+ usleep_range(200, 250);
+
+ ret = cc2520_hw_init(priv);
+ if (ret)
+ goto err_hw_init;
+
+ /* Set up fifop interrupt */
+ ret = devm_request_irq(&spi->dev,
+ gpiod_to_irq(fifop),
+ cc2520_fifop_isr,
+ IRQF_TRIGGER_RISING,
+ dev_name(&spi->dev),
+ priv);
+ if (ret) {
+ dev_err(&spi->dev, "could not get fifop irq\n");
+ goto err_hw_init;
+ }
+
+ /* Set up sfd interrupt */
+ ret = devm_request_irq(&spi->dev,
+ gpiod_to_irq(sfd),
+ cc2520_sfd_isr,
+ IRQF_TRIGGER_FALLING,
+ dev_name(&spi->dev),
+ priv);
+ if (ret) {
+ dev_err(&spi->dev, "could not get sfd irq\n");
+ goto err_hw_init;
+ }
+
+ ret = cc2520_register(priv);
+ if (ret)
+ goto err_hw_init;
+
+ return 0;
+
+err_hw_init:
+ mutex_destroy(&priv->buffer_mutex);
+ flush_work(&priv->fifop_irqwork);
+ return ret;
+}
+
+static void cc2520_remove(struct spi_device *spi)
+{
+ struct cc2520_private *priv = spi_get_drvdata(spi);
+
+ mutex_destroy(&priv->buffer_mutex);
+ flush_work(&priv->fifop_irqwork);
+
+ ieee802154_unregister_hw(priv->hw);
+ ieee802154_free_hw(priv->hw);
+}
+
+static const struct spi_device_id cc2520_ids[] = {
+ {"cc2520", },
+ {},
+};
+MODULE_DEVICE_TABLE(spi, cc2520_ids);
+
+static const struct of_device_id cc2520_of_ids[] = {
+ {.compatible = "ti,cc2520", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, cc2520_of_ids);
+
+/* SPI driver structure */
+static struct spi_driver cc2520_driver = {
+ .driver = {
+ .name = "cc2520",
+ .of_match_table = cc2520_of_ids,
+ },
+ .id_table = cc2520_ids,
+ .probe = cc2520_probe,
+ .remove = cc2520_remove,
+};
+module_spi_driver(cc2520_driver);
+
+MODULE_AUTHOR("Varka Bhadram <varkab@cdac.in>");
+MODULE_DESCRIPTION("CC2520 Transceiver Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/ieee802154/fakelb.c b/drivers/net/ieee802154/fakelb.c
new file mode 100644
index 0000000000..523d13ee02
--- /dev/null
+++ b/drivers/net/ieee802154/fakelb.c
@@ -0,0 +1,263 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Loopback IEEE 802.15.4 interface
+ *
+ * Copyright 2007-2012 Siemens AG
+ *
+ * Written by:
+ * Sergey Lapin <slapin@ossfans.org>
+ * Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
+ * Alexander Smirnov <alex.bluesman.smirnov@gmail.com>
+ */
+
+#include <linux/module.h>
+#include <linux/timer.h>
+#include <linux/platform_device.h>
+#include <linux/netdevice.h>
+#include <linux/device.h>
+#include <linux/spinlock.h>
+#include <net/mac802154.h>
+#include <net/cfg802154.h>
+
+static int numlbs = 2;
+
+static LIST_HEAD(fakelb_phys);
+static DEFINE_MUTEX(fakelb_phys_lock);
+
+static LIST_HEAD(fakelb_ifup_phys);
+static DEFINE_RWLOCK(fakelb_ifup_phys_lock);
+
+struct fakelb_phy {
+ struct ieee802154_hw *hw;
+
+ u8 page;
+ u8 channel;
+
+ bool suspended;
+
+ struct list_head list;
+ struct list_head list_ifup;
+};
+
+static int fakelb_hw_ed(struct ieee802154_hw *hw, u8 *level)
+{
+ WARN_ON(!level);
+ *level = 0xbe;
+
+ return 0;
+}
+
+static int fakelb_hw_channel(struct ieee802154_hw *hw, u8 page, u8 channel)
+{
+ struct fakelb_phy *phy = hw->priv;
+
+ write_lock_bh(&fakelb_ifup_phys_lock);
+ phy->page = page;
+ phy->channel = channel;
+ write_unlock_bh(&fakelb_ifup_phys_lock);
+ return 0;
+}
+
+static int fakelb_hw_xmit(struct ieee802154_hw *hw, struct sk_buff *skb)
+{
+ struct fakelb_phy *current_phy = hw->priv, *phy;
+
+ read_lock_bh(&fakelb_ifup_phys_lock);
+ WARN_ON(current_phy->suspended);
+ list_for_each_entry(phy, &fakelb_ifup_phys, list_ifup) {
+ if (current_phy == phy)
+ continue;
+
+ if (current_phy->page == phy->page &&
+ current_phy->channel == phy->channel) {
+ struct sk_buff *newskb = pskb_copy(skb, GFP_ATOMIC);
+
+ if (newskb)
+ ieee802154_rx_irqsafe(phy->hw, newskb, 0xcc);
+ }
+ }
+ read_unlock_bh(&fakelb_ifup_phys_lock);
+
+ ieee802154_xmit_complete(hw, skb, false);
+ return 0;
+}
+
+static int fakelb_hw_start(struct ieee802154_hw *hw)
+{
+ struct fakelb_phy *phy = hw->priv;
+
+ write_lock_bh(&fakelb_ifup_phys_lock);
+ phy->suspended = false;
+ list_add(&phy->list_ifup, &fakelb_ifup_phys);
+ write_unlock_bh(&fakelb_ifup_phys_lock);
+
+ return 0;
+}
+
+static void fakelb_hw_stop(struct ieee802154_hw *hw)
+{
+ struct fakelb_phy *phy = hw->priv;
+
+ write_lock_bh(&fakelb_ifup_phys_lock);
+ phy->suspended = true;
+ list_del(&phy->list_ifup);
+ write_unlock_bh(&fakelb_ifup_phys_lock);
+}
+
+static int
+fakelb_set_promiscuous_mode(struct ieee802154_hw *hw, const bool on)
+{
+ return 0;
+}
+
+static const struct ieee802154_ops fakelb_ops = {
+ .owner = THIS_MODULE,
+ .xmit_async = fakelb_hw_xmit,
+ .ed = fakelb_hw_ed,
+ .set_channel = fakelb_hw_channel,
+ .start = fakelb_hw_start,
+ .stop = fakelb_hw_stop,
+ .set_promiscuous_mode = fakelb_set_promiscuous_mode,
+};
+
+/* Number of dummy devices to be set up by this module. */
+module_param(numlbs, int, 0);
+MODULE_PARM_DESC(numlbs, " number of pseudo devices");
+
+static int fakelb_add_one(struct device *dev)
+{
+ struct ieee802154_hw *hw;
+ struct fakelb_phy *phy;
+ int err;
+
+ hw = ieee802154_alloc_hw(sizeof(*phy), &fakelb_ops);
+ if (!hw)
+ return -ENOMEM;
+
+ phy = hw->priv;
+ phy->hw = hw;
+
+ /* 868 MHz BPSK 802.15.4-2003 */
+ hw->phy->supported.channels[0] |= 1;
+ /* 915 MHz BPSK 802.15.4-2003 */
+ hw->phy->supported.channels[0] |= 0x7fe;
+ /* 2.4 GHz O-QPSK 802.15.4-2003 */
+ hw->phy->supported.channels[0] |= 0x7FFF800;
+ /* 868 MHz ASK 802.15.4-2006 */
+ hw->phy->supported.channels[1] |= 1;
+ /* 915 MHz ASK 802.15.4-2006 */
+ hw->phy->supported.channels[1] |= 0x7fe;
+ /* 868 MHz O-QPSK 802.15.4-2006 */
+ hw->phy->supported.channels[2] |= 1;
+ /* 915 MHz O-QPSK 802.15.4-2006 */
+ hw->phy->supported.channels[2] |= 0x7fe;
+ /* 2.4 GHz CSS 802.15.4a-2007 */
+ hw->phy->supported.channels[3] |= 0x3fff;
+ /* UWB Sub-gigahertz 802.15.4a-2007 */
+ hw->phy->supported.channels[4] |= 1;
+ /* UWB Low band 802.15.4a-2007 */
+ hw->phy->supported.channels[4] |= 0x1e;
+ /* UWB High band 802.15.4a-2007 */
+ hw->phy->supported.channels[4] |= 0xffe0;
+ /* 750 MHz O-QPSK 802.15.4c-2009 */
+ hw->phy->supported.channels[5] |= 0xf;
+ /* 750 MHz MPSK 802.15.4c-2009 */
+ hw->phy->supported.channels[5] |= 0xf0;
+ /* 950 MHz BPSK 802.15.4d-2009 */
+ hw->phy->supported.channels[6] |= 0x3ff;
+ /* 950 MHz GFSK 802.15.4d-2009 */
+ hw->phy->supported.channels[6] |= 0x3ffc00;
+
+ ieee802154_random_extended_addr(&hw->phy->perm_extended_addr);
+ /* fake phy channel 13 as default */
+ hw->phy->current_channel = 13;
+ phy->channel = hw->phy->current_channel;
+
+ hw->flags = IEEE802154_HW_PROMISCUOUS;
+ hw->parent = dev;
+
+ err = ieee802154_register_hw(hw);
+ if (err)
+ goto err_reg;
+
+ mutex_lock(&fakelb_phys_lock);
+ list_add_tail(&phy->list, &fakelb_phys);
+ mutex_unlock(&fakelb_phys_lock);
+
+ return 0;
+
+err_reg:
+ ieee802154_free_hw(phy->hw);
+ return err;
+}
+
+static void fakelb_del(struct fakelb_phy *phy)
+{
+ list_del(&phy->list);
+
+ ieee802154_unregister_hw(phy->hw);
+ ieee802154_free_hw(phy->hw);
+}
+
+static int fakelb_probe(struct platform_device *pdev)
+{
+ struct fakelb_phy *phy, *tmp;
+ int err, i;
+
+ for (i = 0; i < numlbs; i++) {
+ err = fakelb_add_one(&pdev->dev);
+ if (err < 0)
+ goto err_slave;
+ }
+
+ dev_info(&pdev->dev, "added %i fake ieee802154 hardware devices\n", numlbs);
+ return 0;
+
+err_slave:
+ mutex_lock(&fakelb_phys_lock);
+ list_for_each_entry_safe(phy, tmp, &fakelb_phys, list)
+ fakelb_del(phy);
+ mutex_unlock(&fakelb_phys_lock);
+ return err;
+}
+
+static int fakelb_remove(struct platform_device *pdev)
+{
+ struct fakelb_phy *phy, *tmp;
+
+ mutex_lock(&fakelb_phys_lock);
+ list_for_each_entry_safe(phy, tmp, &fakelb_phys, list)
+ fakelb_del(phy);
+ mutex_unlock(&fakelb_phys_lock);
+ return 0;
+}
+
+static struct platform_device *ieee802154fake_dev;
+
+static struct platform_driver ieee802154fake_driver = {
+ .probe = fakelb_probe,
+ .remove = fakelb_remove,
+ .driver = {
+ .name = "ieee802154fakelb",
+ },
+};
+
+static __init int fakelb_init_module(void)
+{
+ ieee802154fake_dev = platform_device_register_simple(
+ "ieee802154fakelb", -1, NULL, 0);
+
+ pr_warn("fakelb driver is marked as deprecated, please use mac802154_hwsim!\n");
+
+ return platform_driver_register(&ieee802154fake_driver);
+}
+
+static __exit void fake_remove_module(void)
+{
+ platform_driver_unregister(&ieee802154fake_driver);
+ platform_device_unregister(ieee802154fake_dev);
+}
+
+module_init(fakelb_init_module);
+module_exit(fake_remove_module);
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ieee802154/mac802154_hwsim.c b/drivers/net/ieee802154/mac802154_hwsim.c
new file mode 100644
index 0000000000..31cba9aa76
--- /dev/null
+++ b/drivers/net/ieee802154/mac802154_hwsim.c
@@ -0,0 +1,1094 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HWSIM IEEE 802.15.4 interface
+ *
+ * (C) 2018 Mojatau, Alexander Aring <aring@mojatau.com>
+ * Copyright 2007-2012 Siemens AG
+ *
+ * Based on fakelb, original Written by:
+ * Sergey Lapin <slapin@ossfans.org>
+ * Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
+ * Alexander Smirnov <alex.bluesman.smirnov@gmail.com>
+ */
+
+#include <linux/module.h>
+#include <linux/timer.h>
+#include <linux/platform_device.h>
+#include <linux/rtnetlink.h>
+#include <linux/netdevice.h>
+#include <linux/device.h>
+#include <linux/spinlock.h>
+#include <net/ieee802154_netdev.h>
+#include <net/mac802154.h>
+#include <net/cfg802154.h>
+#include <net/genetlink.h>
+#include "mac802154_hwsim.h"
+
+MODULE_DESCRIPTION("Software simulator of IEEE 802.15.4 radio(s) for mac802154");
+MODULE_LICENSE("GPL");
+
+static LIST_HEAD(hwsim_phys);
+static DEFINE_MUTEX(hwsim_phys_lock);
+
+static struct platform_device *mac802154hwsim_dev;
+
+/* MAC802154_HWSIM netlink family */
+static struct genl_family hwsim_genl_family;
+
+static int hwsim_radio_idx;
+
+enum hwsim_multicast_groups {
+ HWSIM_MCGRP_CONFIG,
+};
+
+static const struct genl_multicast_group hwsim_mcgrps[] = {
+ [HWSIM_MCGRP_CONFIG] = { .name = "config", },
+};
+
+struct hwsim_pib {
+ u8 page;
+ u8 channel;
+ struct ieee802154_hw_addr_filt filt;
+ enum ieee802154_filtering_level filt_level;
+
+ struct rcu_head rcu;
+};
+
+struct hwsim_edge_info {
+ u8 lqi;
+
+ struct rcu_head rcu;
+};
+
+struct hwsim_edge {
+ struct hwsim_phy *endpoint;
+ struct hwsim_edge_info __rcu *info;
+
+ struct list_head list;
+ struct rcu_head rcu;
+};
+
+struct hwsim_phy {
+ struct ieee802154_hw *hw;
+ u32 idx;
+
+ struct hwsim_pib __rcu *pib;
+
+ bool suspended;
+ struct list_head edges;
+
+ struct list_head list;
+};
+
+static int hwsim_add_one(struct genl_info *info, struct device *dev,
+ bool init);
+static void hwsim_del(struct hwsim_phy *phy);
+
+static int hwsim_hw_ed(struct ieee802154_hw *hw, u8 *level)
+{
+ *level = 0xbe;
+
+ return 0;
+}
+
+static int hwsim_update_pib(struct ieee802154_hw *hw, u8 page, u8 channel,
+ struct ieee802154_hw_addr_filt *filt,
+ enum ieee802154_filtering_level filt_level)
+{
+ struct hwsim_phy *phy = hw->priv;
+ struct hwsim_pib *pib, *pib_old;
+
+ pib = kzalloc(sizeof(*pib), GFP_ATOMIC);
+ if (!pib)
+ return -ENOMEM;
+
+ pib_old = rtnl_dereference(phy->pib);
+
+ pib->page = page;
+ pib->channel = channel;
+ pib->filt.short_addr = filt->short_addr;
+ pib->filt.pan_id = filt->pan_id;
+ pib->filt.ieee_addr = filt->ieee_addr;
+ pib->filt.pan_coord = filt->pan_coord;
+ pib->filt_level = filt_level;
+
+ rcu_assign_pointer(phy->pib, pib);
+ kfree_rcu(pib_old, rcu);
+ return 0;
+}
+
+static int hwsim_hw_channel(struct ieee802154_hw *hw, u8 page, u8 channel)
+{
+ struct hwsim_phy *phy = hw->priv;
+ struct hwsim_pib *pib;
+ int ret;
+
+ rcu_read_lock();
+ pib = rcu_dereference(phy->pib);
+ ret = hwsim_update_pib(hw, page, channel, &pib->filt, pib->filt_level);
+ rcu_read_unlock();
+
+ return ret;
+}
+
+static int hwsim_hw_addr_filt(struct ieee802154_hw *hw,
+ struct ieee802154_hw_addr_filt *filt,
+ unsigned long changed)
+{
+ struct hwsim_phy *phy = hw->priv;
+ struct hwsim_pib *pib;
+ int ret;
+
+ rcu_read_lock();
+ pib = rcu_dereference(phy->pib);
+ ret = hwsim_update_pib(hw, pib->page, pib->channel, filt, pib->filt_level);
+ rcu_read_unlock();
+
+ return ret;
+}
+
+static void hwsim_hw_receive(struct ieee802154_hw *hw, struct sk_buff *skb,
+ u8 lqi)
+{
+ struct ieee802154_hdr hdr;
+ struct hwsim_phy *phy = hw->priv;
+ struct hwsim_pib *pib;
+
+ rcu_read_lock();
+ pib = rcu_dereference(phy->pib);
+
+ if (!pskb_may_pull(skb, 3)) {
+ dev_dbg(hw->parent, "invalid frame\n");
+ goto drop;
+ }
+
+ memcpy(&hdr, skb->data, 3);
+
+ /* Level 4 filtering: Frame fields validity */
+ if (pib->filt_level == IEEE802154_FILTERING_4_FRAME_FIELDS) {
+ /* a) Drop reserved frame types */
+ switch (mac_cb(skb)->type) {
+ case IEEE802154_FC_TYPE_BEACON:
+ case IEEE802154_FC_TYPE_DATA:
+ case IEEE802154_FC_TYPE_ACK:
+ case IEEE802154_FC_TYPE_MAC_CMD:
+ break;
+ default:
+ dev_dbg(hw->parent, "unrecognized frame type 0x%x\n",
+ mac_cb(skb)->type);
+ goto drop;
+ }
+
+ /* b) Drop reserved frame versions */
+ switch (hdr.fc.version) {
+ case IEEE802154_2003_STD:
+ case IEEE802154_2006_STD:
+ case IEEE802154_STD:
+ break;
+ default:
+ dev_dbg(hw->parent,
+ "unrecognized frame version 0x%x\n",
+ hdr.fc.version);
+ goto drop;
+ }
+
+ /* c) PAN ID constraints */
+ if ((mac_cb(skb)->dest.mode == IEEE802154_ADDR_LONG ||
+ mac_cb(skb)->dest.mode == IEEE802154_ADDR_SHORT) &&
+ mac_cb(skb)->dest.pan_id != pib->filt.pan_id &&
+ mac_cb(skb)->dest.pan_id != cpu_to_le16(IEEE802154_PANID_BROADCAST)) {
+ dev_dbg(hw->parent,
+ "unrecognized PAN ID %04x\n",
+ le16_to_cpu(mac_cb(skb)->dest.pan_id));
+ goto drop;
+ }
+
+ /* d1) Short address constraints */
+ if (mac_cb(skb)->dest.mode == IEEE802154_ADDR_SHORT &&
+ mac_cb(skb)->dest.short_addr != pib->filt.short_addr &&
+ mac_cb(skb)->dest.short_addr != cpu_to_le16(IEEE802154_ADDR_BROADCAST)) {
+ dev_dbg(hw->parent,
+ "unrecognized short address %04x\n",
+ le16_to_cpu(mac_cb(skb)->dest.short_addr));
+ goto drop;
+ }
+
+ /* d2) Extended address constraints */
+ if (mac_cb(skb)->dest.mode == IEEE802154_ADDR_LONG &&
+ mac_cb(skb)->dest.extended_addr != pib->filt.ieee_addr) {
+ dev_dbg(hw->parent,
+ "unrecognized long address 0x%016llx\n",
+ mac_cb(skb)->dest.extended_addr);
+ goto drop;
+ }
+
+ /* d4) Specific PAN coordinator case (no parent) */
+ if ((mac_cb(skb)->type == IEEE802154_FC_TYPE_DATA ||
+ mac_cb(skb)->type == IEEE802154_FC_TYPE_MAC_CMD) &&
+ mac_cb(skb)->dest.mode == IEEE802154_ADDR_NONE) {
+ dev_dbg(hw->parent,
+ "relaying is not supported\n");
+ goto drop;
+ }
+
+ /* e) Beacon frames follow specific PAN ID rules */
+ if (mac_cb(skb)->type == IEEE802154_FC_TYPE_BEACON &&
+ pib->filt.pan_id != cpu_to_le16(IEEE802154_PANID_BROADCAST) &&
+ mac_cb(skb)->dest.pan_id != pib->filt.pan_id) {
+ dev_dbg(hw->parent,
+ "invalid beacon PAN ID %04x\n",
+ le16_to_cpu(mac_cb(skb)->dest.pan_id));
+ goto drop;
+ }
+ }
+
+ rcu_read_unlock();
+
+ ieee802154_rx_irqsafe(hw, skb, lqi);
+
+ return;
+
+drop:
+ rcu_read_unlock();
+ kfree_skb(skb);
+}
+
+static int hwsim_hw_xmit(struct ieee802154_hw *hw, struct sk_buff *skb)
+{
+ struct hwsim_phy *current_phy = hw->priv;
+ struct hwsim_pib *current_pib, *endpoint_pib;
+ struct hwsim_edge_info *einfo;
+ struct hwsim_edge *e;
+
+ WARN_ON(current_phy->suspended);
+
+ rcu_read_lock();
+ current_pib = rcu_dereference(current_phy->pib);
+ list_for_each_entry_rcu(e, &current_phy->edges, list) {
+ /* Can be changed later in rx_irqsafe, but this is only a
+ * performance tweak. Received radio should drop the frame
+ * in mac802154 stack anyway... so we don't need to be
+ * 100% of locking here to check on suspended
+ */
+ if (e->endpoint->suspended)
+ continue;
+
+ endpoint_pib = rcu_dereference(e->endpoint->pib);
+ if (current_pib->page == endpoint_pib->page &&
+ current_pib->channel == endpoint_pib->channel) {
+ struct sk_buff *newskb = pskb_copy(skb, GFP_ATOMIC);
+
+ einfo = rcu_dereference(e->info);
+ if (newskb)
+ hwsim_hw_receive(e->endpoint->hw, newskb, einfo->lqi);
+ }
+ }
+ rcu_read_unlock();
+
+ ieee802154_xmit_complete(hw, skb, false);
+ return 0;
+}
+
+static int hwsim_hw_start(struct ieee802154_hw *hw)
+{
+ struct hwsim_phy *phy = hw->priv;
+
+ phy->suspended = false;
+
+ return 0;
+}
+
+static void hwsim_hw_stop(struct ieee802154_hw *hw)
+{
+ struct hwsim_phy *phy = hw->priv;
+
+ phy->suspended = true;
+}
+
+static int
+hwsim_set_promiscuous_mode(struct ieee802154_hw *hw, const bool on)
+{
+ enum ieee802154_filtering_level filt_level;
+ struct hwsim_phy *phy = hw->priv;
+ struct hwsim_pib *pib;
+ int ret;
+
+ if (on)
+ filt_level = IEEE802154_FILTERING_NONE;
+ else
+ filt_level = IEEE802154_FILTERING_4_FRAME_FIELDS;
+
+ rcu_read_lock();
+ pib = rcu_dereference(phy->pib);
+ ret = hwsim_update_pib(hw, pib->page, pib->channel, &pib->filt, filt_level);
+ rcu_read_unlock();
+
+ return ret;
+}
+
+static const struct ieee802154_ops hwsim_ops = {
+ .owner = THIS_MODULE,
+ .xmit_async = hwsim_hw_xmit,
+ .ed = hwsim_hw_ed,
+ .set_channel = hwsim_hw_channel,
+ .start = hwsim_hw_start,
+ .stop = hwsim_hw_stop,
+ .set_promiscuous_mode = hwsim_set_promiscuous_mode,
+ .set_hw_addr_filt = hwsim_hw_addr_filt,
+};
+
+static int hwsim_new_radio_nl(struct sk_buff *msg, struct genl_info *info)
+{
+ return hwsim_add_one(info, &mac802154hwsim_dev->dev, false);
+}
+
+static int hwsim_del_radio_nl(struct sk_buff *msg, struct genl_info *info)
+{
+ struct hwsim_phy *phy, *tmp;
+ s64 idx = -1;
+
+ if (!info->attrs[MAC802154_HWSIM_ATTR_RADIO_ID])
+ return -EINVAL;
+
+ idx = nla_get_u32(info->attrs[MAC802154_HWSIM_ATTR_RADIO_ID]);
+
+ mutex_lock(&hwsim_phys_lock);
+ list_for_each_entry_safe(phy, tmp, &hwsim_phys, list) {
+ if (idx == phy->idx) {
+ hwsim_del(phy);
+ mutex_unlock(&hwsim_phys_lock);
+ return 0;
+ }
+ }
+ mutex_unlock(&hwsim_phys_lock);
+
+ return -ENODEV;
+}
+
+static int append_radio_msg(struct sk_buff *skb, struct hwsim_phy *phy)
+{
+ struct nlattr *nl_edges, *nl_edge;
+ struct hwsim_edge_info *einfo;
+ struct hwsim_edge *e;
+ int ret;
+
+ ret = nla_put_u32(skb, MAC802154_HWSIM_ATTR_RADIO_ID, phy->idx);
+ if (ret < 0)
+ return ret;
+
+ rcu_read_lock();
+ if (list_empty(&phy->edges)) {
+ rcu_read_unlock();
+ return 0;
+ }
+
+ nl_edges = nla_nest_start_noflag(skb,
+ MAC802154_HWSIM_ATTR_RADIO_EDGES);
+ if (!nl_edges) {
+ rcu_read_unlock();
+ return -ENOBUFS;
+ }
+
+ list_for_each_entry_rcu(e, &phy->edges, list) {
+ nl_edge = nla_nest_start_noflag(skb,
+ MAC802154_HWSIM_ATTR_RADIO_EDGE);
+ if (!nl_edge) {
+ rcu_read_unlock();
+ nla_nest_cancel(skb, nl_edges);
+ return -ENOBUFS;
+ }
+
+ ret = nla_put_u32(skb, MAC802154_HWSIM_EDGE_ATTR_ENDPOINT_ID,
+ e->endpoint->idx);
+ if (ret < 0) {
+ rcu_read_unlock();
+ nla_nest_cancel(skb, nl_edge);
+ nla_nest_cancel(skb, nl_edges);
+ return ret;
+ }
+
+ einfo = rcu_dereference(e->info);
+ ret = nla_put_u8(skb, MAC802154_HWSIM_EDGE_ATTR_LQI,
+ einfo->lqi);
+ if (ret < 0) {
+ rcu_read_unlock();
+ nla_nest_cancel(skb, nl_edge);
+ nla_nest_cancel(skb, nl_edges);
+ return ret;
+ }
+
+ nla_nest_end(skb, nl_edge);
+ }
+ rcu_read_unlock();
+
+ nla_nest_end(skb, nl_edges);
+
+ return 0;
+}
+
+static int hwsim_get_radio(struct sk_buff *skb, struct hwsim_phy *phy,
+ u32 portid, u32 seq,
+ struct netlink_callback *cb, int flags)
+{
+ void *hdr;
+ int res;
+
+ hdr = genlmsg_put(skb, portid, seq, &hwsim_genl_family, flags,
+ MAC802154_HWSIM_CMD_GET_RADIO);
+ if (!hdr)
+ return -EMSGSIZE;
+
+ if (cb)
+ genl_dump_check_consistent(cb, hdr);
+
+ res = append_radio_msg(skb, phy);
+ if (res < 0)
+ goto out_err;
+
+ genlmsg_end(skb, hdr);
+ return 0;
+
+out_err:
+ genlmsg_cancel(skb, hdr);
+ return res;
+}
+
+static int hwsim_get_radio_nl(struct sk_buff *msg, struct genl_info *info)
+{
+ struct hwsim_phy *phy;
+ struct sk_buff *skb;
+ int idx, res = -ENODEV;
+
+ if (!info->attrs[MAC802154_HWSIM_ATTR_RADIO_ID])
+ return -EINVAL;
+ idx = nla_get_u32(info->attrs[MAC802154_HWSIM_ATTR_RADIO_ID]);
+
+ mutex_lock(&hwsim_phys_lock);
+ list_for_each_entry(phy, &hwsim_phys, list) {
+ if (phy->idx != idx)
+ continue;
+
+ skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
+ if (!skb) {
+ res = -ENOMEM;
+ goto out_err;
+ }
+
+ res = hwsim_get_radio(skb, phy, info->snd_portid,
+ info->snd_seq, NULL, 0);
+ if (res < 0) {
+ nlmsg_free(skb);
+ goto out_err;
+ }
+
+ res = genlmsg_reply(skb, info);
+ break;
+ }
+
+out_err:
+ mutex_unlock(&hwsim_phys_lock);
+
+ return res;
+}
+
+static int hwsim_dump_radio_nl(struct sk_buff *skb,
+ struct netlink_callback *cb)
+{
+ int idx = cb->args[0];
+ struct hwsim_phy *phy;
+ int res;
+
+ mutex_lock(&hwsim_phys_lock);
+
+ if (idx == hwsim_radio_idx)
+ goto done;
+
+ list_for_each_entry(phy, &hwsim_phys, list) {
+ if (phy->idx < idx)
+ continue;
+
+ res = hwsim_get_radio(skb, phy, NETLINK_CB(cb->skb).portid,
+ cb->nlh->nlmsg_seq, cb, NLM_F_MULTI);
+ if (res < 0)
+ break;
+
+ idx = phy->idx + 1;
+ }
+
+ cb->args[0] = idx;
+
+done:
+ mutex_unlock(&hwsim_phys_lock);
+ return skb->len;
+}
+
+/* caller need to held hwsim_phys_lock */
+static struct hwsim_phy *hwsim_get_radio_by_id(uint32_t idx)
+{
+ struct hwsim_phy *phy;
+
+ list_for_each_entry(phy, &hwsim_phys, list) {
+ if (phy->idx == idx)
+ return phy;
+ }
+
+ return NULL;
+}
+
+static const struct nla_policy hwsim_edge_policy[MAC802154_HWSIM_EDGE_ATTR_MAX + 1] = {
+ [MAC802154_HWSIM_EDGE_ATTR_ENDPOINT_ID] = { .type = NLA_U32 },
+ [MAC802154_HWSIM_EDGE_ATTR_LQI] = { .type = NLA_U8 },
+};
+
+static struct hwsim_edge *hwsim_alloc_edge(struct hwsim_phy *endpoint, u8 lqi)
+{
+ struct hwsim_edge_info *einfo;
+ struct hwsim_edge *e;
+
+ e = kzalloc(sizeof(*e), GFP_KERNEL);
+ if (!e)
+ return NULL;
+
+ einfo = kzalloc(sizeof(*einfo), GFP_KERNEL);
+ if (!einfo) {
+ kfree(e);
+ return NULL;
+ }
+
+ einfo->lqi = 0xff;
+ rcu_assign_pointer(e->info, einfo);
+ e->endpoint = endpoint;
+
+ return e;
+}
+
+static void hwsim_free_edge(struct hwsim_edge *e)
+{
+ struct hwsim_edge_info *einfo;
+
+ rcu_read_lock();
+ einfo = rcu_dereference(e->info);
+ rcu_read_unlock();
+
+ kfree_rcu(einfo, rcu);
+ kfree_rcu(e, rcu);
+}
+
+static int hwsim_new_edge_nl(struct sk_buff *msg, struct genl_info *info)
+{
+ struct nlattr *edge_attrs[MAC802154_HWSIM_EDGE_ATTR_MAX + 1];
+ struct hwsim_phy *phy_v0, *phy_v1;
+ struct hwsim_edge *e;
+ u32 v0, v1;
+
+ if (!info->attrs[MAC802154_HWSIM_ATTR_RADIO_ID] ||
+ !info->attrs[MAC802154_HWSIM_ATTR_RADIO_EDGE])
+ return -EINVAL;
+
+ if (nla_parse_nested_deprecated(edge_attrs, MAC802154_HWSIM_EDGE_ATTR_MAX, info->attrs[MAC802154_HWSIM_ATTR_RADIO_EDGE], hwsim_edge_policy, NULL))
+ return -EINVAL;
+
+ if (!edge_attrs[MAC802154_HWSIM_EDGE_ATTR_ENDPOINT_ID])
+ return -EINVAL;
+
+ v0 = nla_get_u32(info->attrs[MAC802154_HWSIM_ATTR_RADIO_ID]);
+ v1 = nla_get_u32(edge_attrs[MAC802154_HWSIM_EDGE_ATTR_ENDPOINT_ID]);
+
+ if (v0 == v1)
+ return -EINVAL;
+
+ mutex_lock(&hwsim_phys_lock);
+ phy_v0 = hwsim_get_radio_by_id(v0);
+ if (!phy_v0) {
+ mutex_unlock(&hwsim_phys_lock);
+ return -ENOENT;
+ }
+
+ phy_v1 = hwsim_get_radio_by_id(v1);
+ if (!phy_v1) {
+ mutex_unlock(&hwsim_phys_lock);
+ return -ENOENT;
+ }
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(e, &phy_v0->edges, list) {
+ if (e->endpoint->idx == v1) {
+ mutex_unlock(&hwsim_phys_lock);
+ rcu_read_unlock();
+ return -EEXIST;
+ }
+ }
+ rcu_read_unlock();
+
+ e = hwsim_alloc_edge(phy_v1, 0xff);
+ if (!e) {
+ mutex_unlock(&hwsim_phys_lock);
+ return -ENOMEM;
+ }
+ list_add_rcu(&e->list, &phy_v0->edges);
+ /* wait until changes are done under hwsim_phys_lock lock
+ * should prevent of calling this function twice while
+ * edges list has not the changes yet.
+ */
+ synchronize_rcu();
+ mutex_unlock(&hwsim_phys_lock);
+
+ return 0;
+}
+
+static int hwsim_del_edge_nl(struct sk_buff *msg, struct genl_info *info)
+{
+ struct nlattr *edge_attrs[MAC802154_HWSIM_EDGE_ATTR_MAX + 1];
+ struct hwsim_phy *phy_v0;
+ struct hwsim_edge *e;
+ u32 v0, v1;
+
+ if (!info->attrs[MAC802154_HWSIM_ATTR_RADIO_ID] ||
+ !info->attrs[MAC802154_HWSIM_ATTR_RADIO_EDGE])
+ return -EINVAL;
+
+ if (nla_parse_nested_deprecated(edge_attrs, MAC802154_HWSIM_EDGE_ATTR_MAX, info->attrs[MAC802154_HWSIM_ATTR_RADIO_EDGE], hwsim_edge_policy, NULL))
+ return -EINVAL;
+
+ if (!edge_attrs[MAC802154_HWSIM_EDGE_ATTR_ENDPOINT_ID])
+ return -EINVAL;
+
+ v0 = nla_get_u32(info->attrs[MAC802154_HWSIM_ATTR_RADIO_ID]);
+ v1 = nla_get_u32(edge_attrs[MAC802154_HWSIM_EDGE_ATTR_ENDPOINT_ID]);
+
+ mutex_lock(&hwsim_phys_lock);
+ phy_v0 = hwsim_get_radio_by_id(v0);
+ if (!phy_v0) {
+ mutex_unlock(&hwsim_phys_lock);
+ return -ENOENT;
+ }
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(e, &phy_v0->edges, list) {
+ if (e->endpoint->idx == v1) {
+ rcu_read_unlock();
+ list_del_rcu(&e->list);
+ hwsim_free_edge(e);
+ /* same again - wait until list changes are done */
+ synchronize_rcu();
+ mutex_unlock(&hwsim_phys_lock);
+ return 0;
+ }
+ }
+ rcu_read_unlock();
+
+ mutex_unlock(&hwsim_phys_lock);
+
+ return -ENOENT;
+}
+
+static int hwsim_set_edge_lqi(struct sk_buff *msg, struct genl_info *info)
+{
+ struct nlattr *edge_attrs[MAC802154_HWSIM_EDGE_ATTR_MAX + 1];
+ struct hwsim_edge_info *einfo, *einfo_old;
+ struct hwsim_phy *phy_v0;
+ struct hwsim_edge *e;
+ u32 v0, v1;
+ u8 lqi;
+
+ if (!info->attrs[MAC802154_HWSIM_ATTR_RADIO_ID] ||
+ !info->attrs[MAC802154_HWSIM_ATTR_RADIO_EDGE])
+ return -EINVAL;
+
+ if (nla_parse_nested_deprecated(edge_attrs, MAC802154_HWSIM_EDGE_ATTR_MAX, info->attrs[MAC802154_HWSIM_ATTR_RADIO_EDGE], hwsim_edge_policy, NULL))
+ return -EINVAL;
+
+ if (!edge_attrs[MAC802154_HWSIM_EDGE_ATTR_ENDPOINT_ID] ||
+ !edge_attrs[MAC802154_HWSIM_EDGE_ATTR_LQI])
+ return -EINVAL;
+
+ v0 = nla_get_u32(info->attrs[MAC802154_HWSIM_ATTR_RADIO_ID]);
+ v1 = nla_get_u32(edge_attrs[MAC802154_HWSIM_EDGE_ATTR_ENDPOINT_ID]);
+ lqi = nla_get_u8(edge_attrs[MAC802154_HWSIM_EDGE_ATTR_LQI]);
+
+ mutex_lock(&hwsim_phys_lock);
+ phy_v0 = hwsim_get_radio_by_id(v0);
+ if (!phy_v0) {
+ mutex_unlock(&hwsim_phys_lock);
+ return -ENOENT;
+ }
+
+ einfo = kzalloc(sizeof(*einfo), GFP_KERNEL);
+ if (!einfo) {
+ mutex_unlock(&hwsim_phys_lock);
+ return -ENOMEM;
+ }
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(e, &phy_v0->edges, list) {
+ if (e->endpoint->idx == v1) {
+ einfo->lqi = lqi;
+ einfo_old = rcu_replace_pointer(e->info, einfo,
+ lockdep_is_held(&hwsim_phys_lock));
+ rcu_read_unlock();
+ kfree_rcu(einfo_old, rcu);
+ mutex_unlock(&hwsim_phys_lock);
+ return 0;
+ }
+ }
+ rcu_read_unlock();
+
+ kfree(einfo);
+ mutex_unlock(&hwsim_phys_lock);
+
+ return -ENOENT;
+}
+
+/* MAC802154_HWSIM netlink policy */
+
+static const struct nla_policy hwsim_genl_policy[MAC802154_HWSIM_ATTR_MAX + 1] = {
+ [MAC802154_HWSIM_ATTR_RADIO_ID] = { .type = NLA_U32 },
+ [MAC802154_HWSIM_ATTR_RADIO_EDGE] = { .type = NLA_NESTED },
+ [MAC802154_HWSIM_ATTR_RADIO_EDGES] = { .type = NLA_NESTED },
+};
+
+/* Generic Netlink operations array */
+static const struct genl_small_ops hwsim_nl_ops[] = {
+ {
+ .cmd = MAC802154_HWSIM_CMD_NEW_RADIO,
+ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+ .doit = hwsim_new_radio_nl,
+ .flags = GENL_UNS_ADMIN_PERM,
+ },
+ {
+ .cmd = MAC802154_HWSIM_CMD_DEL_RADIO,
+ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+ .doit = hwsim_del_radio_nl,
+ .flags = GENL_UNS_ADMIN_PERM,
+ },
+ {
+ .cmd = MAC802154_HWSIM_CMD_GET_RADIO,
+ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+ .doit = hwsim_get_radio_nl,
+ .dumpit = hwsim_dump_radio_nl,
+ },
+ {
+ .cmd = MAC802154_HWSIM_CMD_NEW_EDGE,
+ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+ .doit = hwsim_new_edge_nl,
+ .flags = GENL_UNS_ADMIN_PERM,
+ },
+ {
+ .cmd = MAC802154_HWSIM_CMD_DEL_EDGE,
+ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+ .doit = hwsim_del_edge_nl,
+ .flags = GENL_UNS_ADMIN_PERM,
+ },
+ {
+ .cmd = MAC802154_HWSIM_CMD_SET_EDGE,
+ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+ .doit = hwsim_set_edge_lqi,
+ .flags = GENL_UNS_ADMIN_PERM,
+ },
+};
+
+static struct genl_family hwsim_genl_family __ro_after_init = {
+ .name = "MAC802154_HWSIM",
+ .version = 1,
+ .maxattr = MAC802154_HWSIM_ATTR_MAX,
+ .policy = hwsim_genl_policy,
+ .module = THIS_MODULE,
+ .small_ops = hwsim_nl_ops,
+ .n_small_ops = ARRAY_SIZE(hwsim_nl_ops),
+ .resv_start_op = MAC802154_HWSIM_CMD_NEW_EDGE + 1,
+ .mcgrps = hwsim_mcgrps,
+ .n_mcgrps = ARRAY_SIZE(hwsim_mcgrps),
+};
+
+static void hwsim_mcast_config_msg(struct sk_buff *mcast_skb,
+ struct genl_info *info)
+{
+ if (info)
+ genl_notify(&hwsim_genl_family, mcast_skb, info,
+ HWSIM_MCGRP_CONFIG, GFP_KERNEL);
+ else
+ genlmsg_multicast(&hwsim_genl_family, mcast_skb, 0,
+ HWSIM_MCGRP_CONFIG, GFP_KERNEL);
+}
+
+static void hwsim_mcast_new_radio(struct genl_info *info, struct hwsim_phy *phy)
+{
+ struct sk_buff *mcast_skb;
+ void *data;
+
+ mcast_skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!mcast_skb)
+ return;
+
+ data = genlmsg_put(mcast_skb, 0, 0, &hwsim_genl_family, 0,
+ MAC802154_HWSIM_CMD_NEW_RADIO);
+ if (!data)
+ goto out_err;
+
+ if (append_radio_msg(mcast_skb, phy) < 0)
+ goto out_err;
+
+ genlmsg_end(mcast_skb, data);
+
+ hwsim_mcast_config_msg(mcast_skb, info);
+ return;
+
+out_err:
+ genlmsg_cancel(mcast_skb, data);
+ nlmsg_free(mcast_skb);
+}
+
+static void hwsim_edge_unsubscribe_me(struct hwsim_phy *phy)
+{
+ struct hwsim_phy *tmp;
+ struct hwsim_edge *e;
+
+ rcu_read_lock();
+ /* going to all phy edges and remove phy from it */
+ list_for_each_entry(tmp, &hwsim_phys, list) {
+ list_for_each_entry_rcu(e, &tmp->edges, list) {
+ if (e->endpoint->idx == phy->idx) {
+ list_del_rcu(&e->list);
+ hwsim_free_edge(e);
+ }
+ }
+ }
+ rcu_read_unlock();
+
+ synchronize_rcu();
+}
+
+static int hwsim_subscribe_all_others(struct hwsim_phy *phy)
+{
+ struct hwsim_phy *sub;
+ struct hwsim_edge *e;
+
+ list_for_each_entry(sub, &hwsim_phys, list) {
+ e = hwsim_alloc_edge(sub, 0xff);
+ if (!e)
+ goto me_fail;
+
+ list_add_rcu(&e->list, &phy->edges);
+ }
+
+ list_for_each_entry(sub, &hwsim_phys, list) {
+ e = hwsim_alloc_edge(phy, 0xff);
+ if (!e)
+ goto sub_fail;
+
+ list_add_rcu(&e->list, &sub->edges);
+ }
+
+ return 0;
+
+sub_fail:
+ hwsim_edge_unsubscribe_me(phy);
+me_fail:
+ rcu_read_lock();
+ list_for_each_entry_rcu(e, &phy->edges, list) {
+ list_del_rcu(&e->list);
+ hwsim_free_edge(e);
+ }
+ rcu_read_unlock();
+ return -ENOMEM;
+}
+
+static int hwsim_add_one(struct genl_info *info, struct device *dev,
+ bool init)
+{
+ struct ieee802154_hw *hw;
+ struct hwsim_phy *phy;
+ struct hwsim_pib *pib;
+ int idx;
+ int err;
+
+ idx = hwsim_radio_idx++;
+
+ hw = ieee802154_alloc_hw(sizeof(*phy), &hwsim_ops);
+ if (!hw)
+ return -ENOMEM;
+
+ phy = hw->priv;
+ phy->hw = hw;
+
+ /* 868 MHz BPSK 802.15.4-2003 */
+ hw->phy->supported.channels[0] |= 1;
+ /* 915 MHz BPSK 802.15.4-2003 */
+ hw->phy->supported.channels[0] |= 0x7fe;
+ /* 2.4 GHz O-QPSK 802.15.4-2003 */
+ hw->phy->supported.channels[0] |= 0x7FFF800;
+ /* 868 MHz ASK 802.15.4-2006 */
+ hw->phy->supported.channels[1] |= 1;
+ /* 915 MHz ASK 802.15.4-2006 */
+ hw->phy->supported.channels[1] |= 0x7fe;
+ /* 868 MHz O-QPSK 802.15.4-2006 */
+ hw->phy->supported.channels[2] |= 1;
+ /* 915 MHz O-QPSK 802.15.4-2006 */
+ hw->phy->supported.channels[2] |= 0x7fe;
+ /* 2.4 GHz CSS 802.15.4a-2007 */
+ hw->phy->supported.channels[3] |= 0x3fff;
+ /* UWB Sub-gigahertz 802.15.4a-2007 */
+ hw->phy->supported.channels[4] |= 1;
+ /* UWB Low band 802.15.4a-2007 */
+ hw->phy->supported.channels[4] |= 0x1e;
+ /* UWB High band 802.15.4a-2007 */
+ hw->phy->supported.channels[4] |= 0xffe0;
+ /* 750 MHz O-QPSK 802.15.4c-2009 */
+ hw->phy->supported.channels[5] |= 0xf;
+ /* 750 MHz MPSK 802.15.4c-2009 */
+ hw->phy->supported.channels[5] |= 0xf0;
+ /* 950 MHz BPSK 802.15.4d-2009 */
+ hw->phy->supported.channels[6] |= 0x3ff;
+ /* 950 MHz GFSK 802.15.4d-2009 */
+ hw->phy->supported.channels[6] |= 0x3ffc00;
+
+ ieee802154_random_extended_addr(&hw->phy->perm_extended_addr);
+
+ /* hwsim phy channel 13 as default */
+ hw->phy->current_channel = 13;
+ pib = kzalloc(sizeof(*pib), GFP_KERNEL);
+ if (!pib) {
+ err = -ENOMEM;
+ goto err_pib;
+ }
+
+ pib->channel = 13;
+ pib->filt.short_addr = cpu_to_le16(IEEE802154_ADDR_BROADCAST);
+ pib->filt.pan_id = cpu_to_le16(IEEE802154_PANID_BROADCAST);
+ rcu_assign_pointer(phy->pib, pib);
+ phy->idx = idx;
+ INIT_LIST_HEAD(&phy->edges);
+
+ hw->flags = IEEE802154_HW_PROMISCUOUS;
+ hw->parent = dev;
+
+ err = ieee802154_register_hw(hw);
+ if (err)
+ goto err_reg;
+
+ mutex_lock(&hwsim_phys_lock);
+ if (init) {
+ err = hwsim_subscribe_all_others(phy);
+ if (err < 0) {
+ mutex_unlock(&hwsim_phys_lock);
+ goto err_subscribe;
+ }
+ }
+ list_add_tail(&phy->list, &hwsim_phys);
+ mutex_unlock(&hwsim_phys_lock);
+
+ hwsim_mcast_new_radio(info, phy);
+
+ return idx;
+
+err_subscribe:
+ ieee802154_unregister_hw(phy->hw);
+err_reg:
+ kfree(pib);
+err_pib:
+ ieee802154_free_hw(phy->hw);
+ return err;
+}
+
+static void hwsim_del(struct hwsim_phy *phy)
+{
+ struct hwsim_pib *pib;
+ struct hwsim_edge *e;
+
+ hwsim_edge_unsubscribe_me(phy);
+
+ list_del(&phy->list);
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(e, &phy->edges, list) {
+ list_del_rcu(&e->list);
+ hwsim_free_edge(e);
+ }
+ pib = rcu_dereference(phy->pib);
+ rcu_read_unlock();
+
+ kfree_rcu(pib, rcu);
+
+ ieee802154_unregister_hw(phy->hw);
+ ieee802154_free_hw(phy->hw);
+}
+
+static int hwsim_probe(struct platform_device *pdev)
+{
+ struct hwsim_phy *phy, *tmp;
+ int err, i;
+
+ for (i = 0; i < 2; i++) {
+ err = hwsim_add_one(NULL, &pdev->dev, true);
+ if (err < 0)
+ goto err_slave;
+ }
+
+ dev_info(&pdev->dev, "Added 2 mac802154 hwsim hardware radios\n");
+ return 0;
+
+err_slave:
+ mutex_lock(&hwsim_phys_lock);
+ list_for_each_entry_safe(phy, tmp, &hwsim_phys, list)
+ hwsim_del(phy);
+ mutex_unlock(&hwsim_phys_lock);
+ return err;
+}
+
+static int hwsim_remove(struct platform_device *pdev)
+{
+ struct hwsim_phy *phy, *tmp;
+
+ mutex_lock(&hwsim_phys_lock);
+ list_for_each_entry_safe(phy, tmp, &hwsim_phys, list)
+ hwsim_del(phy);
+ mutex_unlock(&hwsim_phys_lock);
+
+ return 0;
+}
+
+static struct platform_driver mac802154hwsim_driver = {
+ .probe = hwsim_probe,
+ .remove = hwsim_remove,
+ .driver = {
+ .name = "mac802154_hwsim",
+ },
+};
+
+static __init int hwsim_init_module(void)
+{
+ int rc;
+
+ rc = genl_register_family(&hwsim_genl_family);
+ if (rc)
+ return rc;
+
+ mac802154hwsim_dev = platform_device_register_simple("mac802154_hwsim",
+ -1, NULL, 0);
+ if (IS_ERR(mac802154hwsim_dev)) {
+ rc = PTR_ERR(mac802154hwsim_dev);
+ goto platform_dev;
+ }
+
+ rc = platform_driver_register(&mac802154hwsim_driver);
+ if (rc < 0)
+ goto platform_drv;
+
+ return 0;
+
+platform_drv:
+ platform_device_unregister(mac802154hwsim_dev);
+platform_dev:
+ genl_unregister_family(&hwsim_genl_family);
+ return rc;
+}
+
+static __exit void hwsim_remove_module(void)
+{
+ genl_unregister_family(&hwsim_genl_family);
+ platform_driver_unregister(&mac802154hwsim_driver);
+ platform_device_unregister(mac802154hwsim_dev);
+}
+
+module_init(hwsim_init_module);
+module_exit(hwsim_remove_module);
diff --git a/drivers/net/ieee802154/mac802154_hwsim.h b/drivers/net/ieee802154/mac802154_hwsim.h
new file mode 100644
index 0000000000..6c6e30e387
--- /dev/null
+++ b/drivers/net/ieee802154/mac802154_hwsim.h
@@ -0,0 +1,73 @@
+#ifndef __MAC802154_HWSIM_H
+#define __MAC802154_HWSIM_H
+
+/* mac802154 hwsim netlink commands
+ *
+ * @MAC802154_HWSIM_CMD_UNSPEC: unspecified command to catch error
+ * @MAC802154_HWSIM_CMD_GET_RADIO: fetch information about existing radios
+ * @MAC802154_HWSIM_CMD_SET_RADIO: change radio parameters during runtime
+ * @MAC802154_HWSIM_CMD_NEW_RADIO: create a new radio with the given parameters
+ * returns the radio ID (>= 0) or negative on errors, if successful
+ * then multicast the result
+ * @MAC802154_HWSIM_CMD_DEL_RADIO: destroy a radio, reply is multicasted
+ * @MAC802154_HWSIM_CMD_GET_EDGE: fetch information about existing edges
+ * @MAC802154_HWSIM_CMD_SET_EDGE: change edge parameters during runtime
+ * @MAC802154_HWSIM_CMD_DEL_EDGE: delete edges between radios
+ * @MAC802154_HWSIM_CMD_NEW_EDGE: create a new edge between two radios
+ * @__MAC802154_HWSIM_CMD_MAX: enum limit
+ */
+enum {
+ MAC802154_HWSIM_CMD_UNSPEC,
+
+ MAC802154_HWSIM_CMD_GET_RADIO,
+ MAC802154_HWSIM_CMD_SET_RADIO,
+ MAC802154_HWSIM_CMD_NEW_RADIO,
+ MAC802154_HWSIM_CMD_DEL_RADIO,
+
+ MAC802154_HWSIM_CMD_GET_EDGE,
+ MAC802154_HWSIM_CMD_SET_EDGE,
+ MAC802154_HWSIM_CMD_DEL_EDGE,
+ MAC802154_HWSIM_CMD_NEW_EDGE,
+
+ __MAC802154_HWSIM_CMD_MAX,
+};
+
+#define MAC802154_HWSIM_CMD_MAX (__MAC802154_HWSIM_MAX - 1)
+
+/* mac802154 hwsim netlink attributes
+ *
+ * @MAC802154_HWSIM_ATTR_UNSPEC: unspecified attribute to catch error
+ * @MAC802154_HWSIM_ATTR_RADIO_ID: u32 attribute to identify the radio
+ * @MAC802154_HWSIM_ATTR_EDGE: nested attribute of edges
+ * @MAC802154_HWSIM_ATTR_EDGES: list if nested attributes which contains the
+ * edge information according the radio id
+ * @__MAC802154_HWSIM_ATTR_MAX: enum limit
+ */
+enum {
+ MAC802154_HWSIM_ATTR_UNSPEC,
+ MAC802154_HWSIM_ATTR_RADIO_ID,
+ MAC802154_HWSIM_ATTR_RADIO_EDGE,
+ MAC802154_HWSIM_ATTR_RADIO_EDGES,
+ __MAC802154_HWSIM_ATTR_MAX,
+};
+
+#define MAC802154_HWSIM_ATTR_MAX (__MAC802154_HWSIM_ATTR_MAX - 1)
+
+/* mac802154 hwsim edge netlink attributes
+ *
+ * @MAC802154_HWSIM_EDGE_ATTR_UNSPEC: unspecified attribute to catch error
+ * @MAC802154_HWSIM_EDGE_ATTR_ENDPOINT_ID: radio id where the edge points to
+ * @MAC802154_HWSIM_EDGE_ATTR_LQI: LQI value which the endpoint radio will
+ * receive for this edge
+ * @__MAC802154_HWSIM_ATTR_MAX: enum limit
+ */
+enum {
+ MAC802154_HWSIM_EDGE_ATTR_UNSPEC,
+ MAC802154_HWSIM_EDGE_ATTR_ENDPOINT_ID,
+ MAC802154_HWSIM_EDGE_ATTR_LQI,
+ __MAC802154_HWSIM_EDGE_ATTR_MAX,
+};
+
+#define MAC802154_HWSIM_EDGE_ATTR_MAX (__MAC802154_HWSIM_EDGE_ATTR_MAX - 1)
+
+#endif /* __MAC802154_HWSIM_H */
diff --git a/drivers/net/ieee802154/mcr20a.c b/drivers/net/ieee802154/mcr20a.c
new file mode 100644
index 0000000000..87abe3b463
--- /dev/null
+++ b/drivers/net/ieee802154/mcr20a.c
@@ -0,0 +1,1366 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for NXP MCR20A 802.15.4 Wireless-PAN Networking controller
+ *
+ * Copyright (C) 2018 Xue Liu <liuxuenetmail@gmail.com>
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/skbuff.h>
+#include <linux/of_gpio.h>
+#include <linux/regmap.h>
+#include <linux/ieee802154.h>
+#include <linux/debugfs.h>
+
+#include <net/mac802154.h>
+#include <net/cfg802154.h>
+
+#include <linux/device.h>
+
+#include "mcr20a.h"
+
+#define SPI_COMMAND_BUFFER 3
+
+#define REGISTER_READ BIT(7)
+#define REGISTER_WRITE (0 << 7)
+#define REGISTER_ACCESS (0 << 6)
+#define PACKET_BUFF_BURST_ACCESS BIT(6)
+#define PACKET_BUFF_BYTE_ACCESS BIT(5)
+
+#define MCR20A_WRITE_REG(x) (x)
+#define MCR20A_READ_REG(x) (REGISTER_READ | (x))
+#define MCR20A_BURST_READ_PACKET_BUF (0xC0)
+#define MCR20A_BURST_WRITE_PACKET_BUF (0x40)
+
+#define MCR20A_CMD_REG 0x80
+#define MCR20A_CMD_REG_MASK 0x3f
+#define MCR20A_CMD_WRITE 0x40
+#define MCR20A_CMD_FB 0x20
+
+/* Number of Interrupt Request Status Register */
+#define MCR20A_IRQSTS_NUM 2 /* only IRQ_STS1 and IRQ_STS2 */
+
+/* MCR20A CCA Type */
+enum {
+ MCR20A_CCA_ED, // energy detect - CCA bit not active,
+ // not to be used for T and CCCA sequences
+ MCR20A_CCA_MODE1, // energy detect - CCA bit ACTIVE
+ MCR20A_CCA_MODE2, // 802.15.4 compliant signal detect - CCA bit ACTIVE
+ MCR20A_CCA_MODE3
+};
+
+enum {
+ MCR20A_XCVSEQ_IDLE = 0x00,
+ MCR20A_XCVSEQ_RX = 0x01,
+ MCR20A_XCVSEQ_TX = 0x02,
+ MCR20A_XCVSEQ_CCA = 0x03,
+ MCR20A_XCVSEQ_TR = 0x04,
+ MCR20A_XCVSEQ_CCCA = 0x05,
+};
+
+/* IEEE-802.15.4 defined constants (2.4 GHz logical channels) */
+#define MCR20A_MIN_CHANNEL (11)
+#define MCR20A_MAX_CHANNEL (26)
+#define MCR20A_CHANNEL_SPACING (5)
+
+/* MCR20A CCA Threshold constans */
+#define MCR20A_MIN_CCA_THRESHOLD (0x6EU)
+#define MCR20A_MAX_CCA_THRESHOLD (0x00U)
+
+/* version 0C */
+#define MCR20A_OVERWRITE_VERSION (0x0C)
+
+/* MCR20A PLL configurations */
+static const u8 PLL_INT[16] = {
+ /* 2405 */ 0x0B, /* 2410 */ 0x0B, /* 2415 */ 0x0B,
+ /* 2420 */ 0x0B, /* 2425 */ 0x0B, /* 2430 */ 0x0B,
+ /* 2435 */ 0x0C, /* 2440 */ 0x0C, /* 2445 */ 0x0C,
+ /* 2450 */ 0x0C, /* 2455 */ 0x0C, /* 2460 */ 0x0C,
+ /* 2465 */ 0x0D, /* 2470 */ 0x0D, /* 2475 */ 0x0D,
+ /* 2480 */ 0x0D
+};
+
+static const u8 PLL_FRAC[16] = {
+ /* 2405 */ 0x28, /* 2410 */ 0x50, /* 2415 */ 0x78,
+ /* 2420 */ 0xA0, /* 2425 */ 0xC8, /* 2430 */ 0xF0,
+ /* 2435 */ 0x18, /* 2440 */ 0x40, /* 2445 */ 0x68,
+ /* 2450 */ 0x90, /* 2455 */ 0xB8, /* 2460 */ 0xE0,
+ /* 2465 */ 0x08, /* 2470 */ 0x30, /* 2475 */ 0x58,
+ /* 2480 */ 0x80
+};
+
+static const struct reg_sequence mar20a_iar_overwrites[] = {
+ { IAR_MISC_PAD_CTRL, 0x02 },
+ { IAR_VCO_CTRL1, 0xB3 },
+ { IAR_VCO_CTRL2, 0x07 },
+ { IAR_PA_TUNING, 0x71 },
+ { IAR_CHF_IBUF, 0x2F },
+ { IAR_CHF_QBUF, 0x2F },
+ { IAR_CHF_IRIN, 0x24 },
+ { IAR_CHF_QRIN, 0x24 },
+ { IAR_CHF_IL, 0x24 },
+ { IAR_CHF_QL, 0x24 },
+ { IAR_CHF_CC1, 0x32 },
+ { IAR_CHF_CCL, 0x1D },
+ { IAR_CHF_CC2, 0x2D },
+ { IAR_CHF_IROUT, 0x24 },
+ { IAR_CHF_QROUT, 0x24 },
+ { IAR_PA_CAL, 0x28 },
+ { IAR_AGC_THR1, 0x55 },
+ { IAR_AGC_THR2, 0x2D },
+ { IAR_ATT_RSSI1, 0x5F },
+ { IAR_ATT_RSSI2, 0x8F },
+ { IAR_RSSI_OFFSET, 0x61 },
+ { IAR_CHF_PMA_GAIN, 0x03 },
+ { IAR_CCA1_THRESH, 0x50 },
+ { IAR_CORR_NVAL, 0x13 },
+ { IAR_ACKDELAY, 0x3D },
+};
+
+#define MCR20A_VALID_CHANNELS (0x07FFF800)
+#define MCR20A_MAX_BUF (127)
+
+#define printdev(X) (&X->spi->dev)
+
+/* regmap information for Direct Access Register (DAR) access */
+#define MCR20A_DAR_WRITE 0x01
+#define MCR20A_DAR_READ 0x00
+#define MCR20A_DAR_NUMREGS 0x3F
+
+/* regmap information for Indirect Access Register (IAR) access */
+#define MCR20A_IAR_ACCESS 0x80
+#define MCR20A_IAR_NUMREGS 0xBEFF
+
+/* Read/Write SPI Commands for DAR and IAR registers. */
+#define MCR20A_READSHORT(reg) ((reg) << 1)
+#define MCR20A_WRITESHORT(reg) ((reg) << 1 | 1)
+#define MCR20A_READLONG(reg) (1 << 15 | (reg) << 5)
+#define MCR20A_WRITELONG(reg) (1 << 15 | (reg) << 5 | 1 << 4)
+
+/* Type definitions for link configuration of instantiable layers */
+#define MCR20A_PHY_INDIRECT_QUEUE_SIZE (12)
+
+static bool
+mcr20a_dar_writeable(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case DAR_IRQ_STS1:
+ case DAR_IRQ_STS2:
+ case DAR_IRQ_STS3:
+ case DAR_PHY_CTRL1:
+ case DAR_PHY_CTRL2:
+ case DAR_PHY_CTRL3:
+ case DAR_PHY_CTRL4:
+ case DAR_SRC_CTRL:
+ case DAR_SRC_ADDRS_SUM_LSB:
+ case DAR_SRC_ADDRS_SUM_MSB:
+ case DAR_T3CMP_LSB:
+ case DAR_T3CMP_MSB:
+ case DAR_T3CMP_USB:
+ case DAR_T2PRIMECMP_LSB:
+ case DAR_T2PRIMECMP_MSB:
+ case DAR_T1CMP_LSB:
+ case DAR_T1CMP_MSB:
+ case DAR_T1CMP_USB:
+ case DAR_T2CMP_LSB:
+ case DAR_T2CMP_MSB:
+ case DAR_T2CMP_USB:
+ case DAR_T4CMP_LSB:
+ case DAR_T4CMP_MSB:
+ case DAR_T4CMP_USB:
+ case DAR_PLL_INT0:
+ case DAR_PLL_FRAC0_LSB:
+ case DAR_PLL_FRAC0_MSB:
+ case DAR_PA_PWR:
+ /* no DAR_ACM */
+ case DAR_OVERWRITE_VER:
+ case DAR_CLK_OUT_CTRL:
+ case DAR_PWR_MODES:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool
+mcr20a_dar_readable(struct device *dev, unsigned int reg)
+{
+ bool rc;
+
+ /* all writeable are also readable */
+ rc = mcr20a_dar_writeable(dev, reg);
+ if (rc)
+ return rc;
+
+ /* readonly regs */
+ switch (reg) {
+ case DAR_RX_FRM_LEN:
+ case DAR_CCA1_ED_FNL:
+ case DAR_EVENT_TMR_LSB:
+ case DAR_EVENT_TMR_MSB:
+ case DAR_EVENT_TMR_USB:
+ case DAR_TIMESTAMP_LSB:
+ case DAR_TIMESTAMP_MSB:
+ case DAR_TIMESTAMP_USB:
+ case DAR_SEQ_STATE:
+ case DAR_LQI_VALUE:
+ case DAR_RSSI_CCA_CONT:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool
+mcr20a_dar_volatile(struct device *dev, unsigned int reg)
+{
+ /* can be changed during runtime */
+ switch (reg) {
+ case DAR_IRQ_STS1:
+ case DAR_IRQ_STS2:
+ case DAR_IRQ_STS3:
+ /* use them in spi_async and regmap so it's volatile */
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool
+mcr20a_dar_precious(struct device *dev, unsigned int reg)
+{
+ /* don't clear irq line on read */
+ switch (reg) {
+ case DAR_IRQ_STS1:
+ case DAR_IRQ_STS2:
+ case DAR_IRQ_STS3:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config mcr20a_dar_regmap = {
+ .name = "mcr20a_dar",
+ .reg_bits = 8,
+ .val_bits = 8,
+ .write_flag_mask = REGISTER_ACCESS | REGISTER_WRITE,
+ .read_flag_mask = REGISTER_ACCESS | REGISTER_READ,
+ .cache_type = REGCACHE_RBTREE,
+ .writeable_reg = mcr20a_dar_writeable,
+ .readable_reg = mcr20a_dar_readable,
+ .volatile_reg = mcr20a_dar_volatile,
+ .precious_reg = mcr20a_dar_precious,
+ .fast_io = true,
+ .can_multi_write = true,
+};
+
+static bool
+mcr20a_iar_writeable(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case IAR_XTAL_TRIM:
+ case IAR_PMC_LP_TRIM:
+ case IAR_MACPANID0_LSB:
+ case IAR_MACPANID0_MSB:
+ case IAR_MACSHORTADDRS0_LSB:
+ case IAR_MACSHORTADDRS0_MSB:
+ case IAR_MACLONGADDRS0_0:
+ case IAR_MACLONGADDRS0_8:
+ case IAR_MACLONGADDRS0_16:
+ case IAR_MACLONGADDRS0_24:
+ case IAR_MACLONGADDRS0_32:
+ case IAR_MACLONGADDRS0_40:
+ case IAR_MACLONGADDRS0_48:
+ case IAR_MACLONGADDRS0_56:
+ case IAR_RX_FRAME_FILTER:
+ case IAR_PLL_INT1:
+ case IAR_PLL_FRAC1_LSB:
+ case IAR_PLL_FRAC1_MSB:
+ case IAR_MACPANID1_LSB:
+ case IAR_MACPANID1_MSB:
+ case IAR_MACSHORTADDRS1_LSB:
+ case IAR_MACSHORTADDRS1_MSB:
+ case IAR_MACLONGADDRS1_0:
+ case IAR_MACLONGADDRS1_8:
+ case IAR_MACLONGADDRS1_16:
+ case IAR_MACLONGADDRS1_24:
+ case IAR_MACLONGADDRS1_32:
+ case IAR_MACLONGADDRS1_40:
+ case IAR_MACLONGADDRS1_48:
+ case IAR_MACLONGADDRS1_56:
+ case IAR_DUAL_PAN_CTRL:
+ case IAR_DUAL_PAN_DWELL:
+ case IAR_CCA1_THRESH:
+ case IAR_CCA1_ED_OFFSET_COMP:
+ case IAR_LQI_OFFSET_COMP:
+ case IAR_CCA_CTRL:
+ case IAR_CCA2_CORR_PEAKS:
+ case IAR_CCA2_CORR_THRESH:
+ case IAR_TMR_PRESCALE:
+ case IAR_ANT_PAD_CTRL:
+ case IAR_MISC_PAD_CTRL:
+ case IAR_BSM_CTRL:
+ case IAR_RNG:
+ case IAR_RX_WTR_MARK:
+ case IAR_SOFT_RESET:
+ case IAR_TXDELAY:
+ case IAR_ACKDELAY:
+ case IAR_CORR_NVAL:
+ case IAR_ANT_AGC_CTRL:
+ case IAR_AGC_THR1:
+ case IAR_AGC_THR2:
+ case IAR_PA_CAL:
+ case IAR_ATT_RSSI1:
+ case IAR_ATT_RSSI2:
+ case IAR_RSSI_OFFSET:
+ case IAR_XTAL_CTRL:
+ case IAR_CHF_PMA_GAIN:
+ case IAR_CHF_IBUF:
+ case IAR_CHF_QBUF:
+ case IAR_CHF_IRIN:
+ case IAR_CHF_QRIN:
+ case IAR_CHF_IL:
+ case IAR_CHF_QL:
+ case IAR_CHF_CC1:
+ case IAR_CHF_CCL:
+ case IAR_CHF_CC2:
+ case IAR_CHF_IROUT:
+ case IAR_CHF_QROUT:
+ case IAR_PA_TUNING:
+ case IAR_VCO_CTRL1:
+ case IAR_VCO_CTRL2:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool
+mcr20a_iar_readable(struct device *dev, unsigned int reg)
+{
+ bool rc;
+
+ /* all writeable are also readable */
+ rc = mcr20a_iar_writeable(dev, reg);
+ if (rc)
+ return rc;
+
+ /* readonly regs */
+ switch (reg) {
+ case IAR_PART_ID:
+ case IAR_DUAL_PAN_STS:
+ case IAR_RX_BYTE_COUNT:
+ case IAR_FILTERFAIL_CODE1:
+ case IAR_FILTERFAIL_CODE2:
+ case IAR_RSSI:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool
+mcr20a_iar_volatile(struct device *dev, unsigned int reg)
+{
+/* can be changed during runtime */
+ switch (reg) {
+ case IAR_DUAL_PAN_STS:
+ case IAR_RX_BYTE_COUNT:
+ case IAR_FILTERFAIL_CODE1:
+ case IAR_FILTERFAIL_CODE2:
+ case IAR_RSSI:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config mcr20a_iar_regmap = {
+ .name = "mcr20a_iar",
+ .reg_bits = 16,
+ .val_bits = 8,
+ .write_flag_mask = REGISTER_ACCESS | REGISTER_WRITE | IAR_INDEX,
+ .read_flag_mask = REGISTER_ACCESS | REGISTER_READ | IAR_INDEX,
+ .cache_type = REGCACHE_RBTREE,
+ .writeable_reg = mcr20a_iar_writeable,
+ .readable_reg = mcr20a_iar_readable,
+ .volatile_reg = mcr20a_iar_volatile,
+ .fast_io = true,
+};
+
+struct mcr20a_local {
+ struct spi_device *spi;
+
+ struct ieee802154_hw *hw;
+ struct regmap *regmap_dar;
+ struct regmap *regmap_iar;
+
+ u8 *buf;
+
+ bool is_tx;
+
+ /* for writing tx buffer */
+ struct spi_message tx_buf_msg;
+ u8 tx_header[1];
+ /* burst buffer write command */
+ struct spi_transfer tx_xfer_header;
+ u8 tx_len[1];
+ /* len of tx packet */
+ struct spi_transfer tx_xfer_len;
+ /* data of tx packet */
+ struct spi_transfer tx_xfer_buf;
+ struct sk_buff *tx_skb;
+
+ /* for read length rxfifo */
+ struct spi_message reg_msg;
+ u8 reg_cmd[1];
+ u8 reg_data[MCR20A_IRQSTS_NUM];
+ struct spi_transfer reg_xfer_cmd;
+ struct spi_transfer reg_xfer_data;
+
+ /* receive handling */
+ struct spi_message rx_buf_msg;
+ u8 rx_header[1];
+ struct spi_transfer rx_xfer_header;
+ u8 rx_lqi[1];
+ struct spi_transfer rx_xfer_lqi;
+ u8 rx_buf[MCR20A_MAX_BUF];
+ struct spi_transfer rx_xfer_buf;
+
+ /* isr handling for reading intstat */
+ struct spi_message irq_msg;
+ u8 irq_header[1];
+ u8 irq_data[MCR20A_IRQSTS_NUM];
+ struct spi_transfer irq_xfer_data;
+ struct spi_transfer irq_xfer_header;
+};
+
+static void
+mcr20a_write_tx_buf_complete(void *context)
+{
+ struct mcr20a_local *lp = context;
+ int ret;
+
+ dev_dbg(printdev(lp), "%s\n", __func__);
+
+ lp->reg_msg.complete = NULL;
+ lp->reg_cmd[0] = MCR20A_WRITE_REG(DAR_PHY_CTRL1);
+ lp->reg_data[0] = MCR20A_XCVSEQ_TX;
+ lp->reg_xfer_data.len = 1;
+
+ ret = spi_async(lp->spi, &lp->reg_msg);
+ if (ret)
+ dev_err(printdev(lp), "failed to set SEQ TX\n");
+}
+
+static int
+mcr20a_xmit(struct ieee802154_hw *hw, struct sk_buff *skb)
+{
+ struct mcr20a_local *lp = hw->priv;
+
+ dev_dbg(printdev(lp), "%s\n", __func__);
+
+ lp->tx_skb = skb;
+
+ print_hex_dump_debug("mcr20a tx: ", DUMP_PREFIX_OFFSET, 16, 1,
+ skb->data, skb->len, 0);
+
+ lp->is_tx = 1;
+
+ lp->reg_msg.complete = NULL;
+ lp->reg_cmd[0] = MCR20A_WRITE_REG(DAR_PHY_CTRL1);
+ lp->reg_data[0] = MCR20A_XCVSEQ_IDLE;
+ lp->reg_xfer_data.len = 1;
+
+ return spi_async(lp->spi, &lp->reg_msg);
+}
+
+static int
+mcr20a_ed(struct ieee802154_hw *hw, u8 *level)
+{
+ WARN_ON(!level);
+ *level = 0xbe;
+ return 0;
+}
+
+static int
+mcr20a_set_channel(struct ieee802154_hw *hw, u8 page, u8 channel)
+{
+ struct mcr20a_local *lp = hw->priv;
+ int ret;
+
+ dev_dbg(printdev(lp), "%s\n", __func__);
+
+ /* freqency = ((PLL_INT+64) + (PLL_FRAC/65536)) * 32 MHz */
+ ret = regmap_write(lp->regmap_dar, DAR_PLL_INT0, PLL_INT[channel - 11]);
+ if (ret)
+ return ret;
+ ret = regmap_write(lp->regmap_dar, DAR_PLL_FRAC0_LSB, 0x00);
+ if (ret)
+ return ret;
+ ret = regmap_write(lp->regmap_dar, DAR_PLL_FRAC0_MSB,
+ PLL_FRAC[channel - 11]);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int
+mcr20a_start(struct ieee802154_hw *hw)
+{
+ struct mcr20a_local *lp = hw->priv;
+ int ret;
+
+ dev_dbg(printdev(lp), "%s\n", __func__);
+
+ /* No slotted operation */
+ dev_dbg(printdev(lp), "no slotted operation\n");
+ ret = regmap_update_bits(lp->regmap_dar, DAR_PHY_CTRL1,
+ DAR_PHY_CTRL1_SLOTTED, 0x0);
+ if (ret < 0)
+ return ret;
+
+ /* enable irq */
+ enable_irq(lp->spi->irq);
+
+ /* Unmask SEQ interrupt */
+ ret = regmap_update_bits(lp->regmap_dar, DAR_PHY_CTRL2,
+ DAR_PHY_CTRL2_SEQMSK, 0x0);
+ if (ret < 0)
+ return ret;
+
+ /* Start the RX sequence */
+ dev_dbg(printdev(lp), "start the RX sequence\n");
+ ret = regmap_update_bits(lp->regmap_dar, DAR_PHY_CTRL1,
+ DAR_PHY_CTRL1_XCVSEQ_MASK, MCR20A_XCVSEQ_RX);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static void
+mcr20a_stop(struct ieee802154_hw *hw)
+{
+ struct mcr20a_local *lp = hw->priv;
+
+ dev_dbg(printdev(lp), "%s\n", __func__);
+
+ /* stop all running sequence */
+ regmap_update_bits(lp->regmap_dar, DAR_PHY_CTRL1,
+ DAR_PHY_CTRL1_XCVSEQ_MASK, MCR20A_XCVSEQ_IDLE);
+
+ /* disable irq */
+ disable_irq(lp->spi->irq);
+}
+
+static int
+mcr20a_set_hw_addr_filt(struct ieee802154_hw *hw,
+ struct ieee802154_hw_addr_filt *filt,
+ unsigned long changed)
+{
+ struct mcr20a_local *lp = hw->priv;
+
+ dev_dbg(printdev(lp), "%s\n", __func__);
+
+ if (changed & IEEE802154_AFILT_SADDR_CHANGED) {
+ u16 addr = le16_to_cpu(filt->short_addr);
+
+ regmap_write(lp->regmap_iar, IAR_MACSHORTADDRS0_LSB, addr);
+ regmap_write(lp->regmap_iar, IAR_MACSHORTADDRS0_MSB, addr >> 8);
+ }
+
+ if (changed & IEEE802154_AFILT_PANID_CHANGED) {
+ u16 pan = le16_to_cpu(filt->pan_id);
+
+ regmap_write(lp->regmap_iar, IAR_MACPANID0_LSB, pan);
+ regmap_write(lp->regmap_iar, IAR_MACPANID0_MSB, pan >> 8);
+ }
+
+ if (changed & IEEE802154_AFILT_IEEEADDR_CHANGED) {
+ u8 addr[8], i;
+
+ memcpy(addr, &filt->ieee_addr, 8);
+ for (i = 0; i < 8; i++)
+ regmap_write(lp->regmap_iar,
+ IAR_MACLONGADDRS0_0 + i, addr[i]);
+ }
+
+ if (changed & IEEE802154_AFILT_PANC_CHANGED) {
+ if (filt->pan_coord) {
+ regmap_update_bits(lp->regmap_dar, DAR_PHY_CTRL4,
+ DAR_PHY_CTRL4_PANCORDNTR0, 0x10);
+ } else {
+ regmap_update_bits(lp->regmap_dar, DAR_PHY_CTRL4,
+ DAR_PHY_CTRL4_PANCORDNTR0, 0x00);
+ }
+ }
+
+ return 0;
+}
+
+/* -30 dBm to 10 dBm */
+#define MCR20A_MAX_TX_POWERS 0x14
+static const s32 mcr20a_powers[MCR20A_MAX_TX_POWERS + 1] = {
+ -3000, -2800, -2600, -2400, -2200, -2000, -1800, -1600, -1400,
+ -1200, -1000, -800, -600, -400, -200, 0, 200, 400, 600, 800, 1000
+};
+
+static int
+mcr20a_set_txpower(struct ieee802154_hw *hw, s32 mbm)
+{
+ struct mcr20a_local *lp = hw->priv;
+ u32 i;
+
+ dev_dbg(printdev(lp), "%s(%d)\n", __func__, mbm);
+
+ for (i = 0; i < lp->hw->phy->supported.tx_powers_size; i++) {
+ if (lp->hw->phy->supported.tx_powers[i] == mbm)
+ return regmap_write(lp->regmap_dar, DAR_PA_PWR,
+ ((i + 8) & 0x1F));
+ }
+
+ return -EINVAL;
+}
+
+#define MCR20A_MAX_ED_LEVELS MCR20A_MIN_CCA_THRESHOLD
+static s32 mcr20a_ed_levels[MCR20A_MAX_ED_LEVELS + 1];
+
+static int
+mcr20a_set_cca_mode(struct ieee802154_hw *hw,
+ const struct wpan_phy_cca *cca)
+{
+ struct mcr20a_local *lp = hw->priv;
+ unsigned int cca_mode = 0xff;
+ bool cca_mode_and = false;
+ int ret;
+
+ dev_dbg(printdev(lp), "%s\n", __func__);
+
+ /* mapping 802.15.4 to driver spec */
+ switch (cca->mode) {
+ case NL802154_CCA_ENERGY:
+ cca_mode = MCR20A_CCA_MODE1;
+ break;
+ case NL802154_CCA_CARRIER:
+ cca_mode = MCR20A_CCA_MODE2;
+ break;
+ case NL802154_CCA_ENERGY_CARRIER:
+ switch (cca->opt) {
+ case NL802154_CCA_OPT_ENERGY_CARRIER_AND:
+ cca_mode = MCR20A_CCA_MODE3;
+ cca_mode_and = true;
+ break;
+ case NL802154_CCA_OPT_ENERGY_CARRIER_OR:
+ cca_mode = MCR20A_CCA_MODE3;
+ cca_mode_and = false;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ ret = regmap_update_bits(lp->regmap_dar, DAR_PHY_CTRL4,
+ DAR_PHY_CTRL4_CCATYPE_MASK,
+ cca_mode << DAR_PHY_CTRL4_CCATYPE_SHIFT);
+ if (ret < 0)
+ return ret;
+
+ if (cca_mode == MCR20A_CCA_MODE3) {
+ if (cca_mode_and) {
+ ret = regmap_update_bits(lp->regmap_iar, IAR_CCA_CTRL,
+ IAR_CCA_CTRL_CCA3_AND_NOT_OR,
+ 0x08);
+ } else {
+ ret = regmap_update_bits(lp->regmap_iar,
+ IAR_CCA_CTRL,
+ IAR_CCA_CTRL_CCA3_AND_NOT_OR,
+ 0x00);
+ }
+ if (ret < 0)
+ return ret;
+ }
+
+ return ret;
+}
+
+static int
+mcr20a_set_cca_ed_level(struct ieee802154_hw *hw, s32 mbm)
+{
+ struct mcr20a_local *lp = hw->priv;
+ u32 i;
+
+ dev_dbg(printdev(lp), "%s\n", __func__);
+
+ for (i = 0; i < hw->phy->supported.cca_ed_levels_size; i++) {
+ if (hw->phy->supported.cca_ed_levels[i] == mbm)
+ return regmap_write(lp->regmap_iar, IAR_CCA1_THRESH, i);
+ }
+
+ return 0;
+}
+
+static int
+mcr20a_set_promiscuous_mode(struct ieee802154_hw *hw, const bool on)
+{
+ struct mcr20a_local *lp = hw->priv;
+ int ret;
+ u8 rx_frame_filter_reg = 0x0;
+
+ dev_dbg(printdev(lp), "%s(%d)\n", __func__, on);
+
+ if (on) {
+ /* All frame types accepted*/
+ rx_frame_filter_reg &= ~(IAR_RX_FRAME_FLT_FRM_VER);
+ rx_frame_filter_reg |= (IAR_RX_FRAME_FLT_ACK_FT |
+ IAR_RX_FRAME_FLT_NS_FT);
+
+ ret = regmap_update_bits(lp->regmap_dar, DAR_PHY_CTRL4,
+ DAR_PHY_CTRL4_PROMISCUOUS,
+ DAR_PHY_CTRL4_PROMISCUOUS);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(lp->regmap_iar, IAR_RX_FRAME_FILTER,
+ rx_frame_filter_reg);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = regmap_update_bits(lp->regmap_dar, DAR_PHY_CTRL4,
+ DAR_PHY_CTRL4_PROMISCUOUS, 0x0);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(lp->regmap_iar, IAR_RX_FRAME_FILTER,
+ IAR_RX_FRAME_FLT_FRM_VER |
+ IAR_RX_FRAME_FLT_BEACON_FT |
+ IAR_RX_FRAME_FLT_DATA_FT |
+ IAR_RX_FRAME_FLT_CMD_FT);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct ieee802154_ops mcr20a_hw_ops = {
+ .owner = THIS_MODULE,
+ .xmit_async = mcr20a_xmit,
+ .ed = mcr20a_ed,
+ .set_channel = mcr20a_set_channel,
+ .start = mcr20a_start,
+ .stop = mcr20a_stop,
+ .set_hw_addr_filt = mcr20a_set_hw_addr_filt,
+ .set_txpower = mcr20a_set_txpower,
+ .set_cca_mode = mcr20a_set_cca_mode,
+ .set_cca_ed_level = mcr20a_set_cca_ed_level,
+ .set_promiscuous_mode = mcr20a_set_promiscuous_mode,
+};
+
+static int
+mcr20a_request_rx(struct mcr20a_local *lp)
+{
+ dev_dbg(printdev(lp), "%s\n", __func__);
+
+ /* Start the RX sequence */
+ regmap_update_bits_async(lp->regmap_dar, DAR_PHY_CTRL1,
+ DAR_PHY_CTRL1_XCVSEQ_MASK, MCR20A_XCVSEQ_RX);
+
+ return 0;
+}
+
+static void
+mcr20a_handle_rx_read_buf_complete(void *context)
+{
+ struct mcr20a_local *lp = context;
+ u8 len = lp->reg_data[0] & DAR_RX_FRAME_LENGTH_MASK;
+ struct sk_buff *skb;
+
+ dev_dbg(printdev(lp), "%s\n", __func__);
+
+ dev_dbg(printdev(lp), "RX is done\n");
+
+ if (!ieee802154_is_valid_psdu_len(len)) {
+ dev_vdbg(&lp->spi->dev, "corrupted frame received\n");
+ len = IEEE802154_MTU;
+ }
+
+ len = len - 2; /* get rid of frame check field */
+
+ skb = dev_alloc_skb(len);
+ if (!skb)
+ return;
+
+ __skb_put_data(skb, lp->rx_buf, len);
+ ieee802154_rx_irqsafe(lp->hw, skb, lp->rx_lqi[0]);
+
+ print_hex_dump_debug("mcr20a rx: ", DUMP_PREFIX_OFFSET, 16, 1,
+ lp->rx_buf, len, 0);
+ pr_debug("mcr20a rx: lqi: %02hhx\n", lp->rx_lqi[0]);
+
+ /* start RX sequence */
+ mcr20a_request_rx(lp);
+}
+
+static void
+mcr20a_handle_rx_read_len_complete(void *context)
+{
+ struct mcr20a_local *lp = context;
+ u8 len;
+ int ret;
+
+ dev_dbg(printdev(lp), "%s\n", __func__);
+
+ /* get the length of received frame */
+ len = lp->reg_data[0] & DAR_RX_FRAME_LENGTH_MASK;
+ dev_dbg(printdev(lp), "frame len : %d\n", len);
+
+ /* prepare to read the rx buf */
+ lp->rx_buf_msg.complete = mcr20a_handle_rx_read_buf_complete;
+ lp->rx_header[0] = MCR20A_BURST_READ_PACKET_BUF;
+ lp->rx_xfer_buf.len = len;
+
+ ret = spi_async(lp->spi, &lp->rx_buf_msg);
+ if (ret)
+ dev_err(printdev(lp), "failed to read rx buffer length\n");
+}
+
+static int
+mcr20a_handle_rx(struct mcr20a_local *lp)
+{
+ dev_dbg(printdev(lp), "%s\n", __func__);
+ lp->reg_msg.complete = mcr20a_handle_rx_read_len_complete;
+ lp->reg_cmd[0] = MCR20A_READ_REG(DAR_RX_FRM_LEN);
+ lp->reg_xfer_data.len = 1;
+
+ return spi_async(lp->spi, &lp->reg_msg);
+}
+
+static int
+mcr20a_handle_tx_complete(struct mcr20a_local *lp)
+{
+ dev_dbg(printdev(lp), "%s\n", __func__);
+
+ ieee802154_xmit_complete(lp->hw, lp->tx_skb, false);
+
+ return mcr20a_request_rx(lp);
+}
+
+static int
+mcr20a_handle_tx(struct mcr20a_local *lp)
+{
+ int ret;
+
+ dev_dbg(printdev(lp), "%s\n", __func__);
+
+ /* write tx buffer */
+ lp->tx_header[0] = MCR20A_BURST_WRITE_PACKET_BUF;
+ /* add 2 bytes of FCS */
+ lp->tx_len[0] = lp->tx_skb->len + 2;
+ lp->tx_xfer_buf.tx_buf = lp->tx_skb->data;
+ /* add 1 byte psduLength */
+ lp->tx_xfer_buf.len = lp->tx_skb->len + 1;
+
+ ret = spi_async(lp->spi, &lp->tx_buf_msg);
+ if (ret) {
+ dev_err(printdev(lp), "SPI write Failed for TX buf\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void
+mcr20a_irq_clean_complete(void *context)
+{
+ struct mcr20a_local *lp = context;
+ u8 seq_state = lp->irq_data[DAR_IRQ_STS1] & DAR_PHY_CTRL1_XCVSEQ_MASK;
+
+ dev_dbg(printdev(lp), "%s\n", __func__);
+
+ enable_irq(lp->spi->irq);
+
+ dev_dbg(printdev(lp), "IRQ STA1 (%02x) STA2 (%02x)\n",
+ lp->irq_data[DAR_IRQ_STS1], lp->irq_data[DAR_IRQ_STS2]);
+
+ switch (seq_state) {
+ /* TX IRQ, RX IRQ and SEQ IRQ */
+ case (DAR_IRQSTS1_TXIRQ | DAR_IRQSTS1_SEQIRQ):
+ if (lp->is_tx) {
+ lp->is_tx = 0;
+ dev_dbg(printdev(lp), "TX is done. No ACK\n");
+ mcr20a_handle_tx_complete(lp);
+ }
+ break;
+ case (DAR_IRQSTS1_RXIRQ | DAR_IRQSTS1_SEQIRQ):
+ /* rx is starting */
+ dev_dbg(printdev(lp), "RX is starting\n");
+ mcr20a_handle_rx(lp);
+ break;
+ case (DAR_IRQSTS1_RXIRQ | DAR_IRQSTS1_TXIRQ | DAR_IRQSTS1_SEQIRQ):
+ if (lp->is_tx) {
+ /* tx is done */
+ lp->is_tx = 0;
+ dev_dbg(printdev(lp), "TX is done. Get ACK\n");
+ mcr20a_handle_tx_complete(lp);
+ } else {
+ /* rx is starting */
+ dev_dbg(printdev(lp), "RX is starting\n");
+ mcr20a_handle_rx(lp);
+ }
+ break;
+ case (DAR_IRQSTS1_SEQIRQ):
+ if (lp->is_tx) {
+ dev_dbg(printdev(lp), "TX is starting\n");
+ mcr20a_handle_tx(lp);
+ } else {
+ dev_dbg(printdev(lp), "MCR20A is stop\n");
+ }
+ break;
+ }
+}
+
+static void mcr20a_irq_status_complete(void *context)
+{
+ int ret;
+ struct mcr20a_local *lp = context;
+
+ dev_dbg(printdev(lp), "%s\n", __func__);
+ regmap_update_bits_async(lp->regmap_dar, DAR_PHY_CTRL1,
+ DAR_PHY_CTRL1_XCVSEQ_MASK, MCR20A_XCVSEQ_IDLE);
+
+ lp->reg_msg.complete = mcr20a_irq_clean_complete;
+ lp->reg_cmd[0] = MCR20A_WRITE_REG(DAR_IRQ_STS1);
+ memcpy(lp->reg_data, lp->irq_data, MCR20A_IRQSTS_NUM);
+ lp->reg_xfer_data.len = MCR20A_IRQSTS_NUM;
+
+ ret = spi_async(lp->spi, &lp->reg_msg);
+
+ if (ret)
+ dev_err(printdev(lp), "failed to clean irq status\n");
+}
+
+static irqreturn_t mcr20a_irq_isr(int irq, void *data)
+{
+ struct mcr20a_local *lp = data;
+ int ret;
+
+ disable_irq_nosync(irq);
+
+ lp->irq_header[0] = MCR20A_READ_REG(DAR_IRQ_STS1);
+ /* read IRQSTSx */
+ ret = spi_async(lp->spi, &lp->irq_msg);
+ if (ret) {
+ enable_irq(irq);
+ return IRQ_NONE;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void mcr20a_hw_setup(struct mcr20a_local *lp)
+{
+ u8 i;
+ struct ieee802154_hw *hw = lp->hw;
+ struct wpan_phy *phy = lp->hw->phy;
+
+ dev_dbg(printdev(lp), "%s\n", __func__);
+
+ hw->flags = IEEE802154_HW_TX_OMIT_CKSUM |
+ IEEE802154_HW_AFILT |
+ IEEE802154_HW_PROMISCUOUS;
+
+ phy->flags = WPAN_PHY_FLAG_TXPOWER | WPAN_PHY_FLAG_CCA_ED_LEVEL |
+ WPAN_PHY_FLAG_CCA_MODE;
+
+ phy->supported.cca_modes = BIT(NL802154_CCA_ENERGY) |
+ BIT(NL802154_CCA_CARRIER) | BIT(NL802154_CCA_ENERGY_CARRIER);
+ phy->supported.cca_opts = BIT(NL802154_CCA_OPT_ENERGY_CARRIER_AND) |
+ BIT(NL802154_CCA_OPT_ENERGY_CARRIER_OR);
+
+ /* initiating cca_ed_levels */
+ for (i = MCR20A_MAX_CCA_THRESHOLD; i < MCR20A_MIN_CCA_THRESHOLD + 1;
+ ++i) {
+ mcr20a_ed_levels[i] = -i * 100;
+ }
+
+ phy->supported.cca_ed_levels = mcr20a_ed_levels;
+ phy->supported.cca_ed_levels_size = ARRAY_SIZE(mcr20a_ed_levels);
+
+ phy->cca.mode = NL802154_CCA_ENERGY;
+
+ phy->supported.channels[0] = MCR20A_VALID_CHANNELS;
+ phy->current_page = 0;
+ /* MCR20A default reset value */
+ phy->current_channel = 20;
+ phy->supported.tx_powers = mcr20a_powers;
+ phy->supported.tx_powers_size = ARRAY_SIZE(mcr20a_powers);
+ phy->cca_ed_level = phy->supported.cca_ed_levels[75];
+ phy->transmit_power = phy->supported.tx_powers[0x0F];
+}
+
+static void
+mcr20a_setup_tx_spi_messages(struct mcr20a_local *lp)
+{
+ spi_message_init(&lp->tx_buf_msg);
+ lp->tx_buf_msg.context = lp;
+ lp->tx_buf_msg.complete = mcr20a_write_tx_buf_complete;
+
+ lp->tx_xfer_header.len = 1;
+ lp->tx_xfer_header.tx_buf = lp->tx_header;
+
+ lp->tx_xfer_len.len = 1;
+ lp->tx_xfer_len.tx_buf = lp->tx_len;
+
+ spi_message_add_tail(&lp->tx_xfer_header, &lp->tx_buf_msg);
+ spi_message_add_tail(&lp->tx_xfer_len, &lp->tx_buf_msg);
+ spi_message_add_tail(&lp->tx_xfer_buf, &lp->tx_buf_msg);
+}
+
+static void
+mcr20a_setup_rx_spi_messages(struct mcr20a_local *lp)
+{
+ spi_message_init(&lp->reg_msg);
+ lp->reg_msg.context = lp;
+
+ lp->reg_xfer_cmd.len = 1;
+ lp->reg_xfer_cmd.tx_buf = lp->reg_cmd;
+ lp->reg_xfer_cmd.rx_buf = lp->reg_cmd;
+
+ lp->reg_xfer_data.rx_buf = lp->reg_data;
+ lp->reg_xfer_data.tx_buf = lp->reg_data;
+
+ spi_message_add_tail(&lp->reg_xfer_cmd, &lp->reg_msg);
+ spi_message_add_tail(&lp->reg_xfer_data, &lp->reg_msg);
+
+ spi_message_init(&lp->rx_buf_msg);
+ lp->rx_buf_msg.context = lp;
+ lp->rx_buf_msg.complete = mcr20a_handle_rx_read_buf_complete;
+ lp->rx_xfer_header.len = 1;
+ lp->rx_xfer_header.tx_buf = lp->rx_header;
+ lp->rx_xfer_header.rx_buf = lp->rx_header;
+
+ lp->rx_xfer_buf.rx_buf = lp->rx_buf;
+
+ lp->rx_xfer_lqi.len = 1;
+ lp->rx_xfer_lqi.rx_buf = lp->rx_lqi;
+
+ spi_message_add_tail(&lp->rx_xfer_header, &lp->rx_buf_msg);
+ spi_message_add_tail(&lp->rx_xfer_buf, &lp->rx_buf_msg);
+ spi_message_add_tail(&lp->rx_xfer_lqi, &lp->rx_buf_msg);
+}
+
+static void
+mcr20a_setup_irq_spi_messages(struct mcr20a_local *lp)
+{
+ spi_message_init(&lp->irq_msg);
+ lp->irq_msg.context = lp;
+ lp->irq_msg.complete = mcr20a_irq_status_complete;
+ lp->irq_xfer_header.len = 1;
+ lp->irq_xfer_header.tx_buf = lp->irq_header;
+ lp->irq_xfer_header.rx_buf = lp->irq_header;
+
+ lp->irq_xfer_data.len = MCR20A_IRQSTS_NUM;
+ lp->irq_xfer_data.rx_buf = lp->irq_data;
+
+ spi_message_add_tail(&lp->irq_xfer_header, &lp->irq_msg);
+ spi_message_add_tail(&lp->irq_xfer_data, &lp->irq_msg);
+}
+
+static int
+mcr20a_phy_init(struct mcr20a_local *lp)
+{
+ u8 index;
+ unsigned int phy_reg = 0;
+ int ret;
+
+ dev_dbg(printdev(lp), "%s\n", __func__);
+
+ /* Disable Tristate on COCO MISO for SPI reads */
+ ret = regmap_write(lp->regmap_iar, IAR_MISC_PAD_CTRL, 0x02);
+ if (ret)
+ goto err_ret;
+
+ /* Clear all PP IRQ bits in IRQSTS1 to avoid unexpected interrupts
+ * immediately after init
+ */
+ ret = regmap_write(lp->regmap_dar, DAR_IRQ_STS1, 0xEF);
+ if (ret)
+ goto err_ret;
+
+ /* Clear all PP IRQ bits in IRQSTS2 */
+ ret = regmap_write(lp->regmap_dar, DAR_IRQ_STS2,
+ DAR_IRQSTS2_ASM_IRQ | DAR_IRQSTS2_PB_ERR_IRQ |
+ DAR_IRQSTS2_WAKE_IRQ);
+ if (ret)
+ goto err_ret;
+
+ /* Disable all timer interrupts */
+ ret = regmap_write(lp->regmap_dar, DAR_IRQ_STS3, 0xFF);
+ if (ret)
+ goto err_ret;
+
+ /* PHY_CTRL1 : default HW settings + AUTOACK enabled */
+ ret = regmap_update_bits(lp->regmap_dar, DAR_PHY_CTRL1,
+ DAR_PHY_CTRL1_AUTOACK, DAR_PHY_CTRL1_AUTOACK);
+
+ /* PHY_CTRL2 : disable all interrupts */
+ ret = regmap_write(lp->regmap_dar, DAR_PHY_CTRL2, 0xFF);
+ if (ret)
+ goto err_ret;
+
+ /* PHY_CTRL3 : disable all timers and remaining interrupts */
+ ret = regmap_write(lp->regmap_dar, DAR_PHY_CTRL3,
+ DAR_PHY_CTRL3_ASM_MSK | DAR_PHY_CTRL3_PB_ERR_MSK |
+ DAR_PHY_CTRL3_WAKE_MSK);
+ if (ret)
+ goto err_ret;
+
+ /* SRC_CTRL : enable Acknowledge Frame Pending and
+ * Source Address Matching Enable
+ */
+ ret = regmap_write(lp->regmap_dar, DAR_SRC_CTRL,
+ DAR_SRC_CTRL_ACK_FRM_PND |
+ (DAR_SRC_CTRL_INDEX << DAR_SRC_CTRL_INDEX_SHIFT));
+ if (ret)
+ goto err_ret;
+
+ /* RX_FRAME_FILTER */
+ /* FRM_VER[1:0] = b11. Accept FrameVersion 0 and 1 packets */
+ ret = regmap_write(lp->regmap_iar, IAR_RX_FRAME_FILTER,
+ IAR_RX_FRAME_FLT_FRM_VER |
+ IAR_RX_FRAME_FLT_BEACON_FT |
+ IAR_RX_FRAME_FLT_DATA_FT |
+ IAR_RX_FRAME_FLT_CMD_FT);
+ if (ret)
+ goto err_ret;
+
+ dev_info(printdev(lp), "MCR20A DAR overwrites version: 0x%02x\n",
+ MCR20A_OVERWRITE_VERSION);
+
+ /* Overwrites direct registers */
+ ret = regmap_write(lp->regmap_dar, DAR_OVERWRITE_VER,
+ MCR20A_OVERWRITE_VERSION);
+ if (ret)
+ goto err_ret;
+
+ /* Overwrites indirect registers */
+ ret = regmap_multi_reg_write(lp->regmap_iar, mar20a_iar_overwrites,
+ ARRAY_SIZE(mar20a_iar_overwrites));
+ if (ret)
+ goto err_ret;
+
+ /* Clear HW indirect queue */
+ dev_dbg(printdev(lp), "clear HW indirect queue\n");
+ for (index = 0; index < MCR20A_PHY_INDIRECT_QUEUE_SIZE; index++) {
+ phy_reg = (u8)(((index & DAR_SRC_CTRL_INDEX) <<
+ DAR_SRC_CTRL_INDEX_SHIFT)
+ | (DAR_SRC_CTRL_SRCADDR_EN)
+ | (DAR_SRC_CTRL_INDEX_DISABLE));
+ ret = regmap_write(lp->regmap_dar, DAR_SRC_CTRL, phy_reg);
+ if (ret)
+ goto err_ret;
+ phy_reg = 0;
+ }
+
+ /* Assign HW Indirect hash table to PAN0 */
+ ret = regmap_read(lp->regmap_iar, IAR_DUAL_PAN_CTRL, &phy_reg);
+ if (ret)
+ goto err_ret;
+
+ /* Clear current lvl */
+ phy_reg &= ~IAR_DUAL_PAN_CTRL_DUAL_PAN_SAM_LVL_MSK;
+
+ /* Set new lvl */
+ phy_reg |= MCR20A_PHY_INDIRECT_QUEUE_SIZE <<
+ IAR_DUAL_PAN_CTRL_DUAL_PAN_SAM_LVL_SHIFT;
+ ret = regmap_write(lp->regmap_iar, IAR_DUAL_PAN_CTRL, phy_reg);
+ if (ret)
+ goto err_ret;
+
+ /* Set CCA threshold to -75 dBm */
+ ret = regmap_write(lp->regmap_iar, IAR_CCA1_THRESH, 0x4B);
+ if (ret)
+ goto err_ret;
+
+ /* Set prescaller to obtain 1 symbol (16us) timebase */
+ ret = regmap_write(lp->regmap_iar, IAR_TMR_PRESCALE, 0x05);
+ if (ret)
+ goto err_ret;
+
+ /* Enable autodoze mode. */
+ ret = regmap_update_bits(lp->regmap_dar, DAR_PWR_MODES,
+ DAR_PWR_MODES_AUTODOZE,
+ DAR_PWR_MODES_AUTODOZE);
+ if (ret)
+ goto err_ret;
+
+ /* Disable clk_out */
+ ret = regmap_update_bits(lp->regmap_dar, DAR_CLK_OUT_CTRL,
+ DAR_CLK_OUT_CTRL_EN, 0x0);
+ if (ret)
+ goto err_ret;
+
+ return 0;
+
+err_ret:
+ return ret;
+}
+
+static int
+mcr20a_probe(struct spi_device *spi)
+{
+ struct ieee802154_hw *hw;
+ struct mcr20a_local *lp;
+ struct gpio_desc *rst_b;
+ int irq_type;
+ int ret = -ENOMEM;
+
+ dev_dbg(&spi->dev, "%s\n", __func__);
+
+ if (!spi->irq) {
+ dev_err(&spi->dev, "no IRQ specified\n");
+ return -EINVAL;
+ }
+
+ rst_b = devm_gpiod_get(&spi->dev, "rst_b", GPIOD_OUT_HIGH);
+ if (IS_ERR(rst_b))
+ return dev_err_probe(&spi->dev, PTR_ERR(rst_b),
+ "Failed to get 'rst_b' gpio");
+
+ /* reset mcr20a */
+ usleep_range(10, 20);
+ gpiod_set_value_cansleep(rst_b, 1);
+ usleep_range(10, 20);
+ gpiod_set_value_cansleep(rst_b, 0);
+ usleep_range(120, 240);
+
+ /* allocate ieee802154_hw and private data */
+ hw = ieee802154_alloc_hw(sizeof(*lp), &mcr20a_hw_ops);
+ if (!hw) {
+ dev_crit(&spi->dev, "ieee802154_alloc_hw failed\n");
+ return ret;
+ }
+
+ /* init mcr20a local data */
+ lp = hw->priv;
+ lp->hw = hw;
+ lp->spi = spi;
+
+ /* init ieee802154_hw */
+ hw->parent = &spi->dev;
+ ieee802154_random_extended_addr(&hw->phy->perm_extended_addr);
+
+ /* init buf */
+ lp->buf = devm_kzalloc(&spi->dev, SPI_COMMAND_BUFFER, GFP_KERNEL);
+
+ if (!lp->buf) {
+ ret = -ENOMEM;
+ goto free_dev;
+ }
+
+ mcr20a_setup_tx_spi_messages(lp);
+ mcr20a_setup_rx_spi_messages(lp);
+ mcr20a_setup_irq_spi_messages(lp);
+
+ /* setup regmap */
+ lp->regmap_dar = devm_regmap_init_spi(spi, &mcr20a_dar_regmap);
+ if (IS_ERR(lp->regmap_dar)) {
+ ret = PTR_ERR(lp->regmap_dar);
+ dev_err(&spi->dev, "Failed to allocate dar map: %d\n",
+ ret);
+ goto free_dev;
+ }
+
+ lp->regmap_iar = devm_regmap_init_spi(spi, &mcr20a_iar_regmap);
+ if (IS_ERR(lp->regmap_iar)) {
+ ret = PTR_ERR(lp->regmap_iar);
+ dev_err(&spi->dev, "Failed to allocate iar map: %d\n", ret);
+ goto free_dev;
+ }
+
+ mcr20a_hw_setup(lp);
+
+ spi_set_drvdata(spi, lp);
+
+ ret = mcr20a_phy_init(lp);
+ if (ret < 0) {
+ dev_crit(&spi->dev, "mcr20a_phy_init failed\n");
+ goto free_dev;
+ }
+
+ irq_type = irq_get_trigger_type(spi->irq);
+ if (!irq_type)
+ irq_type = IRQF_TRIGGER_FALLING;
+
+ ret = devm_request_irq(&spi->dev, spi->irq, mcr20a_irq_isr,
+ irq_type, dev_name(&spi->dev), lp);
+ if (ret) {
+ dev_err(&spi->dev, "could not request_irq for mcr20a\n");
+ ret = -ENODEV;
+ goto free_dev;
+ }
+
+ /* disable_irq by default and wait for starting hardware */
+ disable_irq(spi->irq);
+
+ ret = ieee802154_register_hw(hw);
+ if (ret) {
+ dev_crit(&spi->dev, "ieee802154_register_hw failed\n");
+ goto free_dev;
+ }
+
+ return ret;
+
+free_dev:
+ ieee802154_free_hw(lp->hw);
+
+ return ret;
+}
+
+static void mcr20a_remove(struct spi_device *spi)
+{
+ struct mcr20a_local *lp = spi_get_drvdata(spi);
+
+ dev_dbg(&spi->dev, "%s\n", __func__);
+
+ ieee802154_unregister_hw(lp->hw);
+ ieee802154_free_hw(lp->hw);
+}
+
+static const struct of_device_id mcr20a_of_match[] = {
+ { .compatible = "nxp,mcr20a", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, mcr20a_of_match);
+
+static const struct spi_device_id mcr20a_device_id[] = {
+ { .name = "mcr20a", },
+ { },
+};
+MODULE_DEVICE_TABLE(spi, mcr20a_device_id);
+
+static struct spi_driver mcr20a_driver = {
+ .id_table = mcr20a_device_id,
+ .driver = {
+ .of_match_table = mcr20a_of_match,
+ .name = "mcr20a",
+ },
+ .probe = mcr20a_probe,
+ .remove = mcr20a_remove,
+};
+
+module_spi_driver(mcr20a_driver);
+
+MODULE_DESCRIPTION("MCR20A Transceiver Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Xue Liu <liuxuenetmail@gmail>");
diff --git a/drivers/net/ieee802154/mcr20a.h b/drivers/net/ieee802154/mcr20a.h
new file mode 100644
index 0000000000..b363b0a3b5
--- /dev/null
+++ b/drivers/net/ieee802154/mcr20a.h
@@ -0,0 +1,489 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Driver for NXP MCR20A 802.15.4 Wireless-PAN Networking controller
+ *
+ * Copyright (C) 2018 Xue Liu <liuxuenetmail@gmail.com>
+ */
+#ifndef _MCR20A_H
+#define _MCR20A_H
+
+/* Direct Accress Register */
+#define DAR_IRQ_STS1 0x00
+#define DAR_IRQ_STS2 0x01
+#define DAR_IRQ_STS3 0x02
+#define DAR_PHY_CTRL1 0x03
+#define DAR_PHY_CTRL2 0x04
+#define DAR_PHY_CTRL3 0x05
+#define DAR_RX_FRM_LEN 0x06
+#define DAR_PHY_CTRL4 0x07
+#define DAR_SRC_CTRL 0x08
+#define DAR_SRC_ADDRS_SUM_LSB 0x09
+#define DAR_SRC_ADDRS_SUM_MSB 0x0A
+#define DAR_CCA1_ED_FNL 0x0B
+#define DAR_EVENT_TMR_LSB 0x0C
+#define DAR_EVENT_TMR_MSB 0x0D
+#define DAR_EVENT_TMR_USB 0x0E
+#define DAR_TIMESTAMP_LSB 0x0F
+#define DAR_TIMESTAMP_MSB 0x10
+#define DAR_TIMESTAMP_USB 0x11
+#define DAR_T3CMP_LSB 0x12
+#define DAR_T3CMP_MSB 0x13
+#define DAR_T3CMP_USB 0x14
+#define DAR_T2PRIMECMP_LSB 0x15
+#define DAR_T2PRIMECMP_MSB 0x16
+#define DAR_T1CMP_LSB 0x17
+#define DAR_T1CMP_MSB 0x18
+#define DAR_T1CMP_USB 0x19
+#define DAR_T2CMP_LSB 0x1A
+#define DAR_T2CMP_MSB 0x1B
+#define DAR_T2CMP_USB 0x1C
+#define DAR_T4CMP_LSB 0x1D
+#define DAR_T4CMP_MSB 0x1E
+#define DAR_T4CMP_USB 0x1F
+#define DAR_PLL_INT0 0x20
+#define DAR_PLL_FRAC0_LSB 0x21
+#define DAR_PLL_FRAC0_MSB 0x22
+#define DAR_PA_PWR 0x23
+#define DAR_SEQ_STATE 0x24
+#define DAR_LQI_VALUE 0x25
+#define DAR_RSSI_CCA_CONT 0x26
+/*------------------ 0x27 */
+#define DAR_ASM_CTRL1 0x28
+#define DAR_ASM_CTRL2 0x29
+#define DAR_ASM_DATA_0 0x2A
+#define DAR_ASM_DATA_1 0x2B
+#define DAR_ASM_DATA_2 0x2C
+#define DAR_ASM_DATA_3 0x2D
+#define DAR_ASM_DATA_4 0x2E
+#define DAR_ASM_DATA_5 0x2F
+#define DAR_ASM_DATA_6 0x30
+#define DAR_ASM_DATA_7 0x31
+#define DAR_ASM_DATA_8 0x32
+#define DAR_ASM_DATA_9 0x33
+#define DAR_ASM_DATA_A 0x34
+#define DAR_ASM_DATA_B 0x35
+#define DAR_ASM_DATA_C 0x36
+#define DAR_ASM_DATA_D 0x37
+#define DAR_ASM_DATA_E 0x38
+#define DAR_ASM_DATA_F 0x39
+/*----------------------- 0x3A */
+#define DAR_OVERWRITE_VER 0x3B
+#define DAR_CLK_OUT_CTRL 0x3C
+#define DAR_PWR_MODES 0x3D
+#define IAR_INDEX 0x3E
+#define IAR_DATA 0x3F
+
+/* Indirect Resgister Memory */
+#define IAR_PART_ID 0x00
+#define IAR_XTAL_TRIM 0x01
+#define IAR_PMC_LP_TRIM 0x02
+#define IAR_MACPANID0_LSB 0x03
+#define IAR_MACPANID0_MSB 0x04
+#define IAR_MACSHORTADDRS0_LSB 0x05
+#define IAR_MACSHORTADDRS0_MSB 0x06
+#define IAR_MACLONGADDRS0_0 0x07
+#define IAR_MACLONGADDRS0_8 0x08
+#define IAR_MACLONGADDRS0_16 0x09
+#define IAR_MACLONGADDRS0_24 0x0A
+#define IAR_MACLONGADDRS0_32 0x0B
+#define IAR_MACLONGADDRS0_40 0x0C
+#define IAR_MACLONGADDRS0_48 0x0D
+#define IAR_MACLONGADDRS0_56 0x0E
+#define IAR_RX_FRAME_FILTER 0x0F
+#define IAR_PLL_INT1 0x10
+#define IAR_PLL_FRAC1_LSB 0x11
+#define IAR_PLL_FRAC1_MSB 0x12
+#define IAR_MACPANID1_LSB 0x13
+#define IAR_MACPANID1_MSB 0x14
+#define IAR_MACSHORTADDRS1_LSB 0x15
+#define IAR_MACSHORTADDRS1_MSB 0x16
+#define IAR_MACLONGADDRS1_0 0x17
+#define IAR_MACLONGADDRS1_8 0x18
+#define IAR_MACLONGADDRS1_16 0x19
+#define IAR_MACLONGADDRS1_24 0x1A
+#define IAR_MACLONGADDRS1_32 0x1B
+#define IAR_MACLONGADDRS1_40 0x1C
+#define IAR_MACLONGADDRS1_48 0x1D
+#define IAR_MACLONGADDRS1_56 0x1E
+#define IAR_DUAL_PAN_CTRL 0x1F
+#define IAR_DUAL_PAN_DWELL 0x20
+#define IAR_DUAL_PAN_STS 0x21
+#define IAR_CCA1_THRESH 0x22
+#define IAR_CCA1_ED_OFFSET_COMP 0x23
+#define IAR_LQI_OFFSET_COMP 0x24
+#define IAR_CCA_CTRL 0x25
+#define IAR_CCA2_CORR_PEAKS 0x26
+#define IAR_CCA2_CORR_THRESH 0x27
+#define IAR_TMR_PRESCALE 0x28
+/*-------------------- 0x29 */
+#define IAR_GPIO_DATA 0x2A
+#define IAR_GPIO_DIR 0x2B
+#define IAR_GPIO_PUL_EN 0x2C
+#define IAR_GPIO_PUL_SEL 0x2D
+#define IAR_GPIO_DS 0x2E
+/*------------------ 0x2F */
+#define IAR_ANT_PAD_CTRL 0x30
+#define IAR_MISC_PAD_CTRL 0x31
+#define IAR_BSM_CTRL 0x32
+/*------------------- 0x33 */
+#define IAR_RNG 0x34
+#define IAR_RX_BYTE_COUNT 0x35
+#define IAR_RX_WTR_MARK 0x36
+#define IAR_SOFT_RESET 0x37
+#define IAR_TXDELAY 0x38
+#define IAR_ACKDELAY 0x39
+#define IAR_SEQ_MGR_CTRL 0x3A
+#define IAR_SEQ_MGR_STS 0x3B
+#define IAR_SEQ_T_STS 0x3C
+#define IAR_ABORT_STS 0x3D
+#define IAR_CCCA_BUSY_CNT 0x3E
+#define IAR_SRC_ADDR_CHECKSUM1 0x3F
+#define IAR_SRC_ADDR_CHECKSUM2 0x40
+#define IAR_SRC_TBL_VALID1 0x41
+#define IAR_SRC_TBL_VALID2 0x42
+#define IAR_FILTERFAIL_CODE1 0x43
+#define IAR_FILTERFAIL_CODE2 0x44
+#define IAR_SLOT_PRELOAD 0x45
+/*-------------------- 0x46 */
+#define IAR_CORR_VT 0x47
+#define IAR_SYNC_CTRL 0x48
+#define IAR_PN_LSB_0 0x49
+#define IAR_PN_LSB_1 0x4A
+#define IAR_PN_MSB_0 0x4B
+#define IAR_PN_MSB_1 0x4C
+#define IAR_CORR_NVAL 0x4D
+#define IAR_TX_MODE_CTRL 0x4E
+#define IAR_SNF_THR 0x4F
+#define IAR_FAD_THR 0x50
+#define IAR_ANT_AGC_CTRL 0x51
+#define IAR_AGC_THR1 0x52
+#define IAR_AGC_THR2 0x53
+#define IAR_AGC_HYS 0x54
+#define IAR_AFC 0x55
+/*------------------- 0x56 */
+/*------------------- 0x57 */
+#define IAR_PHY_STS 0x58
+#define IAR_RX_MAX_CORR 0x59
+#define IAR_RX_MAX_PREAMBLE 0x5A
+#define IAR_RSSI 0x5B
+/*------------------- 0x5C */
+/*------------------- 0x5D */
+#define IAR_PLL_DIG_CTRL 0x5E
+#define IAR_VCO_CAL 0x5F
+#define IAR_VCO_BEST_DIFF 0x60
+#define IAR_VCO_BIAS 0x61
+#define IAR_KMOD_CTRL 0x62
+#define IAR_KMOD_CAL 0x63
+#define IAR_PA_CAL 0x64
+#define IAR_PA_PWRCAL 0x65
+#define IAR_ATT_RSSI1 0x66
+#define IAR_ATT_RSSI2 0x67
+#define IAR_RSSI_OFFSET 0x68
+#define IAR_RSSI_SLOPE 0x69
+#define IAR_RSSI_CAL1 0x6A
+#define IAR_RSSI_CAL2 0x6B
+/*------------------- 0x6C */
+/*------------------- 0x6D */
+#define IAR_XTAL_CTRL 0x6E
+#define IAR_XTAL_COMP_MIN 0x6F
+#define IAR_XTAL_COMP_MAX 0x70
+#define IAR_XTAL_GM 0x71
+/*------------------- 0x72 */
+/*------------------- 0x73 */
+#define IAR_LNA_TUNE 0x74
+#define IAR_LNA_AGCGAIN 0x75
+/*------------------- 0x76 */
+/*------------------- 0x77 */
+#define IAR_CHF_PMA_GAIN 0x78
+#define IAR_CHF_IBUF 0x79
+#define IAR_CHF_QBUF 0x7A
+#define IAR_CHF_IRIN 0x7B
+#define IAR_CHF_QRIN 0x7C
+#define IAR_CHF_IL 0x7D
+#define IAR_CHF_QL 0x7E
+#define IAR_CHF_CC1 0x7F
+#define IAR_CHF_CCL 0x80
+#define IAR_CHF_CC2 0x81
+#define IAR_CHF_IROUT 0x82
+#define IAR_CHF_QROUT 0x83
+/*------------------- 0x84 */
+/*------------------- 0x85 */
+#define IAR_RSSI_CTRL 0x86
+/*------------------- 0x87 */
+/*------------------- 0x88 */
+#define IAR_PA_BIAS 0x89
+#define IAR_PA_TUNING 0x8A
+/*------------------- 0x8B */
+/*------------------- 0x8C */
+#define IAR_PMC_HP_TRIM 0x8D
+#define IAR_VREGA_TRIM 0x8E
+/*------------------- 0x8F */
+/*------------------- 0x90 */
+#define IAR_VCO_CTRL1 0x91
+#define IAR_VCO_CTRL2 0x92
+/*------------------- 0x93 */
+/*------------------- 0x94 */
+#define IAR_ANA_SPARE_OUT1 0x95
+#define IAR_ANA_SPARE_OUT2 0x96
+#define IAR_ANA_SPARE_IN 0x97
+#define IAR_MISCELLANEOUS 0x98
+/*------------------- 0x99 */
+#define IAR_SEQ_MGR_OVRD0 0x9A
+#define IAR_SEQ_MGR_OVRD1 0x9B
+#define IAR_SEQ_MGR_OVRD2 0x9C
+#define IAR_SEQ_MGR_OVRD3 0x9D
+#define IAR_SEQ_MGR_OVRD4 0x9E
+#define IAR_SEQ_MGR_OVRD5 0x9F
+#define IAR_SEQ_MGR_OVRD6 0xA0
+#define IAR_SEQ_MGR_OVRD7 0xA1
+/*------------------- 0xA2 */
+#define IAR_TESTMODE_CTRL 0xA3
+#define IAR_DTM_CTRL1 0xA4
+#define IAR_DTM_CTRL2 0xA5
+#define IAR_ATM_CTRL1 0xA6
+#define IAR_ATM_CTRL2 0xA7
+#define IAR_ATM_CTRL3 0xA8
+/*------------------- 0xA9 */
+#define IAR_LIM_FE_TEST_CTRL 0xAA
+#define IAR_CHF_TEST_CTRL 0xAB
+#define IAR_VCO_TEST_CTRL 0xAC
+#define IAR_PLL_TEST_CTRL 0xAD
+#define IAR_PA_TEST_CTRL 0xAE
+#define IAR_PMC_TEST_CTRL 0xAF
+#define IAR_SCAN_DTM_PROTECT_1 0xFE
+#define IAR_SCAN_DTM_PROTECT_0 0xFF
+
+/* IRQSTS1 bits */
+#define DAR_IRQSTS1_RX_FRM_PEND BIT(7)
+#define DAR_IRQSTS1_PLL_UNLOCK_IRQ BIT(6)
+#define DAR_IRQSTS1_FILTERFAIL_IRQ BIT(5)
+#define DAR_IRQSTS1_RXWTRMRKIRQ BIT(4)
+#define DAR_IRQSTS1_CCAIRQ BIT(3)
+#define DAR_IRQSTS1_RXIRQ BIT(2)
+#define DAR_IRQSTS1_TXIRQ BIT(1)
+#define DAR_IRQSTS1_SEQIRQ BIT(0)
+
+/* IRQSTS2 bits */
+#define DAR_IRQSTS2_CRCVALID BIT(7)
+#define DAR_IRQSTS2_CCA BIT(6)
+#define DAR_IRQSTS2_SRCADDR BIT(5)
+#define DAR_IRQSTS2_PI BIT(4)
+#define DAR_IRQSTS2_TMRSTATUS BIT(3)
+#define DAR_IRQSTS2_ASM_IRQ BIT(2)
+#define DAR_IRQSTS2_PB_ERR_IRQ BIT(1)
+#define DAR_IRQSTS2_WAKE_IRQ BIT(0)
+
+/* IRQSTS3 bits */
+#define DAR_IRQSTS3_TMR4MSK BIT(7)
+#define DAR_IRQSTS3_TMR3MSK BIT(6)
+#define DAR_IRQSTS3_TMR2MSK BIT(5)
+#define DAR_IRQSTS3_TMR1MSK BIT(4)
+#define DAR_IRQSTS3_TMR4IRQ BIT(3)
+#define DAR_IRQSTS3_TMR3IRQ BIT(2)
+#define DAR_IRQSTS3_TMR2IRQ BIT(1)
+#define DAR_IRQSTS3_TMR1IRQ BIT(0)
+
+/* PHY_CTRL1 bits */
+#define DAR_PHY_CTRL1_TMRTRIGEN BIT(7)
+#define DAR_PHY_CTRL1_SLOTTED BIT(6)
+#define DAR_PHY_CTRL1_CCABFRTX BIT(5)
+#define DAR_PHY_CTRL1_CCABFRTX_SHIFT 5
+#define DAR_PHY_CTRL1_RXACKRQD BIT(4)
+#define DAR_PHY_CTRL1_AUTOACK BIT(3)
+#define DAR_PHY_CTRL1_XCVSEQ_MASK 0x07
+
+/* PHY_CTRL2 bits */
+#define DAR_PHY_CTRL2_CRC_MSK BIT(7)
+#define DAR_PHY_CTRL2_PLL_UNLOCK_MSK BIT(6)
+#define DAR_PHY_CTRL2_FILTERFAIL_MSK BIT(5)
+#define DAR_PHY_CTRL2_RX_WMRK_MSK BIT(4)
+#define DAR_PHY_CTRL2_CCAMSK BIT(3)
+#define DAR_PHY_CTRL2_RXMSK BIT(2)
+#define DAR_PHY_CTRL2_TXMSK BIT(1)
+#define DAR_PHY_CTRL2_SEQMSK BIT(0)
+
+/* PHY_CTRL3 bits */
+#define DAR_PHY_CTRL3_TMR4CMP_EN BIT(7)
+#define DAR_PHY_CTRL3_TMR3CMP_EN BIT(6)
+#define DAR_PHY_CTRL3_TMR2CMP_EN BIT(5)
+#define DAR_PHY_CTRL3_TMR1CMP_EN BIT(4)
+#define DAR_PHY_CTRL3_ASM_MSK BIT(2)
+#define DAR_PHY_CTRL3_PB_ERR_MSK BIT(1)
+#define DAR_PHY_CTRL3_WAKE_MSK BIT(0)
+
+/* RX_FRM_LEN bits */
+#define DAR_RX_FRAME_LENGTH_MASK (0x7F)
+
+/* PHY_CTRL4 bits */
+#define DAR_PHY_CTRL4_TRCV_MSK BIT(7)
+#define DAR_PHY_CTRL4_TC3TMOUT BIT(6)
+#define DAR_PHY_CTRL4_PANCORDNTR0 BIT(5)
+#define DAR_PHY_CTRL4_CCATYPE (3)
+#define DAR_PHY_CTRL4_CCATYPE_SHIFT (3)
+#define DAR_PHY_CTRL4_CCATYPE_MASK (0x18)
+#define DAR_PHY_CTRL4_TMRLOAD BIT(2)
+#define DAR_PHY_CTRL4_PROMISCUOUS BIT(1)
+#define DAR_PHY_CTRL4_TC2PRIME_EN BIT(0)
+
+/* SRC_CTRL bits */
+#define DAR_SRC_CTRL_INDEX (0x0F)
+#define DAR_SRC_CTRL_INDEX_SHIFT (4)
+#define DAR_SRC_CTRL_ACK_FRM_PND BIT(3)
+#define DAR_SRC_CTRL_SRCADDR_EN BIT(2)
+#define DAR_SRC_CTRL_INDEX_EN BIT(1)
+#define DAR_SRC_CTRL_INDEX_DISABLE BIT(0)
+
+/* DAR_ASM_CTRL1 bits */
+#define DAR_ASM_CTRL1_CLEAR BIT(7)
+#define DAR_ASM_CTRL1_START BIT(6)
+#define DAR_ASM_CTRL1_SELFTST BIT(5)
+#define DAR_ASM_CTRL1_CTR BIT(4)
+#define DAR_ASM_CTRL1_CBC BIT(3)
+#define DAR_ASM_CTRL1_AES BIT(2)
+#define DAR_ASM_CTRL1_LOAD_MAC BIT(1)
+
+/* DAR_ASM_CTRL2 bits */
+#define DAR_ASM_CTRL2_DATA_REG_TYPE_SEL (7)
+#define DAR_ASM_CTRL2_DATA_REG_TYPE_SEL_SHIFT (5)
+#define DAR_ASM_CTRL2_TSTPAS BIT(1)
+
+/* DAR_CLK_OUT_CTRL bits */
+#define DAR_CLK_OUT_CTRL_EXTEND BIT(7)
+#define DAR_CLK_OUT_CTRL_HIZ BIT(6)
+#define DAR_CLK_OUT_CTRL_SR BIT(5)
+#define DAR_CLK_OUT_CTRL_DS BIT(4)
+#define DAR_CLK_OUT_CTRL_EN BIT(3)
+#define DAR_CLK_OUT_CTRL_DIV (7)
+
+/* DAR_PWR_MODES bits */
+#define DAR_PWR_MODES_XTAL_READY BIT(5)
+#define DAR_PWR_MODES_XTALEN BIT(4)
+#define DAR_PWR_MODES_ASM_CLK_EN BIT(3)
+#define DAR_PWR_MODES_AUTODOZE BIT(1)
+#define DAR_PWR_MODES_PMC_MODE BIT(0)
+
+/* RX_FRAME_FILTER bits */
+#define IAR_RX_FRAME_FLT_FRM_VER (0xC0)
+#define IAR_RX_FRAME_FLT_FRM_VER_SHIFT (6)
+#define IAR_RX_FRAME_FLT_ACTIVE_PROMISCUOUS BIT(5)
+#define IAR_RX_FRAME_FLT_NS_FT BIT(4)
+#define IAR_RX_FRAME_FLT_CMD_FT BIT(3)
+#define IAR_RX_FRAME_FLT_ACK_FT BIT(2)
+#define IAR_RX_FRAME_FLT_DATA_FT BIT(1)
+#define IAR_RX_FRAME_FLT_BEACON_FT BIT(0)
+
+/* DUAL_PAN_CTRL bits */
+#define IAR_DUAL_PAN_CTRL_DUAL_PAN_SAM_LVL_MSK (0xF0)
+#define IAR_DUAL_PAN_CTRL_DUAL_PAN_SAM_LVL_SHIFT (4)
+#define IAR_DUAL_PAN_CTRL_CURRENT_NETWORK BIT(3)
+#define IAR_DUAL_PAN_CTRL_PANCORDNTR1 BIT(2)
+#define IAR_DUAL_PAN_CTRL_DUAL_PAN_AUTO BIT(1)
+#define IAR_DUAL_PAN_CTRL_ACTIVE_NETWORK BIT(0)
+
+/* DUAL_PAN_STS bits */
+#define IAR_DUAL_PAN_STS_RECD_ON_PAN1 BIT(7)
+#define IAR_DUAL_PAN_STS_RECD_ON_PAN0 BIT(6)
+#define IAR_DUAL_PAN_STS_DUAL_PAN_REMAIN (0x3F)
+
+/* CCA_CTRL bits */
+#define IAR_CCA_CTRL_AGC_FRZ_EN BIT(6)
+#define IAR_CCA_CTRL_CONT_RSSI_EN BIT(5)
+#define IAR_CCA_CTRL_LQI_RSSI_NOT_CORR BIT(4)
+#define IAR_CCA_CTRL_CCA3_AND_NOT_OR BIT(3)
+#define IAR_CCA_CTRL_POWER_COMP_EN_LQI BIT(2)
+#define IAR_CCA_CTRL_POWER_COMP_EN_ED BIT(1)
+#define IAR_CCA_CTRL_POWER_COMP_EN_CCA1 BIT(0)
+
+/* ANT_PAD_CTRL bits */
+#define IAR_ANT_PAD_CTRL_ANTX_POL (0x0F)
+#define IAR_ANT_PAD_CTRL_ANTX_POL_SHIFT (4)
+#define IAR_ANT_PAD_CTRL_ANTX_CTRLMODE BIT(3)
+#define IAR_ANT_PAD_CTRL_ANTX_HZ BIT(2)
+#define IAR_ANT_PAD_CTRL_ANTX_EN (3)
+
+/* MISC_PAD_CTRL bits */
+#define IAR_MISC_PAD_CTRL_MISO_HIZ_EN BIT(3)
+#define IAR_MISC_PAD_CTRL_IRQ_B_OD BIT(2)
+#define IAR_MISC_PAD_CTRL_NON_GPIO_DS BIT(1)
+#define IAR_MISC_PAD_CTRL_ANTX_CURR (1)
+
+/* ANT_AGC_CTRL bits */
+#define IAR_ANT_AGC_CTRL_FAD_EN_SHIFT (0)
+#define IAR_ANT_AGC_CTRL_FAD_EN_MASK (1)
+#define IAR_ANT_AGC_CTRL_ANTX_SHIFT (1)
+#define IAR_ANT_AGC_CTRL_ANTX_MASK BIT(AR_ANT_AGC_CTRL_ANTX_SHIFT)
+
+/* BSM_CTRL bits */
+#define BSM_CTRL_BSM_EN (1)
+
+/* SOFT_RESET bits */
+#define IAR_SOFT_RESET_SOG_RST BIT(7)
+#define IAR_SOFT_RESET_REGS_RST BIT(4)
+#define IAR_SOFT_RESET_PLL_RST BIT(3)
+#define IAR_SOFT_RESET_TX_RST BIT(2)
+#define IAR_SOFT_RESET_RX_RST BIT(1)
+#define IAR_SOFT_RESET_SEQ_MGR_RST BIT(0)
+
+/* SEQ_MGR_CTRL bits */
+#define IAR_SEQ_MGR_CTRL_SEQ_STATE_CTRL (3)
+#define IAR_SEQ_MGR_CTRL_SEQ_STATE_CTRL_SHIFT (6)
+#define IAR_SEQ_MGR_CTRL_NO_RX_RECYCLE BIT(5)
+#define IAR_SEQ_MGR_CTRL_LATCH_PREAMBLE BIT(4)
+#define IAR_SEQ_MGR_CTRL_EVENT_TMR_DO_NOT_LATCH BIT(3)
+#define IAR_SEQ_MGR_CTRL_CLR_NEW_SEQ_INHIBIT BIT(2)
+#define IAR_SEQ_MGR_CTRL_PSM_LOCK_DIS BIT(1)
+#define IAR_SEQ_MGR_CTRL_PLL_ABORT_OVRD BIT(0)
+
+/* SEQ_MGR_STS bits */
+#define IAR_SEQ_MGR_STS_TMR2_SEQ_TRIG_ARMED BIT(7)
+#define IAR_SEQ_MGR_STS_RX_MODE BIT(6)
+#define IAR_SEQ_MGR_STS_RX_TIMEOUT_PENDING BIT(5)
+#define IAR_SEQ_MGR_STS_NEW_SEQ_INHIBIT BIT(4)
+#define IAR_SEQ_MGR_STS_SEQ_IDLE BIT(3)
+#define IAR_SEQ_MGR_STS_XCVSEQ_ACTUAL (7)
+
+/* ABORT_STS bits */
+#define IAR_ABORT_STS_PLL_ABORTED BIT(2)
+#define IAR_ABORT_STS_TC3_ABORTED BIT(1)
+#define IAR_ABORT_STS_SW_ABORTED BIT(0)
+
+/* IAR_FILTERFAIL_CODE2 bits */
+#define IAR_FILTERFAIL_CODE2_PAN_SEL BIT(7)
+#define IAR_FILTERFAIL_CODE2_9_8 (3)
+
+/* PHY_STS bits */
+#define IAR_PHY_STS_PLL_UNLOCK BIT(7)
+#define IAR_PHY_STS_PLL_LOCK_ERR BIT(6)
+#define IAR_PHY_STS_PLL_LOCK BIT(5)
+#define IAR_PHY_STS_CRCVALID BIT(3)
+#define IAR_PHY_STS_FILTERFAIL_FLAG_SEL BIT(2)
+#define IAR_PHY_STS_SFD_DET BIT(1)
+#define IAR_PHY_STS_PREAMBLE_DET BIT(0)
+
+/* TESTMODE_CTRL bits */
+#define IAR_TEST_MODE_CTRL_HOT_ANT BIT(4)
+#define IAR_TEST_MODE_CTRL_IDEAL_RSSI_EN BIT(3)
+#define IAR_TEST_MODE_CTRL_IDEAL_PFC_EN BIT(2)
+#define IAR_TEST_MODE_CTRL_CONTINUOUS_EN BIT(1)
+#define IAR_TEST_MODE_CTRL_FPGA_EN BIT(0)
+
+/* DTM_CTRL1 bits */
+#define IAR_DTM_CTRL1_ATM_LOCKED BIT(7)
+#define IAR_DTM_CTRL1_DTM_EN BIT(6)
+#define IAR_DTM_CTRL1_PAGE5 BIT(5)
+#define IAR_DTM_CTRL1_PAGE4 BIT(4)
+#define IAR_DTM_CTRL1_PAGE3 BIT(3)
+#define IAR_DTM_CTRL1_PAGE2 BIT(2)
+#define IAR_DTM_CTRL1_PAGE1 BIT(1)
+#define IAR_DTM_CTRL1_PAGE0 BIT(0)
+
+/* TX_MODE_CTRL */
+#define IAR_TX_MODE_CTRL_TX_INV BIT(4)
+#define IAR_TX_MODE_CTRL_BT_EN BIT(3)
+#define IAR_TX_MODE_CTRL_DTS2 BIT(2)
+#define IAR_TX_MODE_CTRL_DTS1 BIT(1)
+#define IAR_TX_MODE_CTRL_DTS0 BIT(0)
+
+#define TX_MODE_CTRL_DTS_MASK (7)
+
+#endif /* _MCR20A_H */
diff --git a/drivers/net/ieee802154/mrf24j40.c b/drivers/net/ieee802154/mrf24j40.c
new file mode 100644
index 0000000000..ee4cfbf2c5
--- /dev/null
+++ b/drivers/net/ieee802154/mrf24j40.c
@@ -0,0 +1,1401 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Microchip MRF24J40 802.15.4 Wireless-PAN Networking controller
+ *
+ * Copyright (C) 2012 Alan Ott <alan@signal11.us>
+ * Signal 11 Software
+ */
+
+#include <linux/spi/spi.h>
+#include <linux/interrupt.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/ieee802154.h>
+#include <linux/irq.h>
+#include <net/cfg802154.h>
+#include <net/mac802154.h>
+
+/* MRF24J40 Short Address Registers */
+#define REG_RXMCR 0x00 /* Receive MAC control */
+#define BIT_PROMI BIT(0)
+#define BIT_ERRPKT BIT(1)
+#define BIT_NOACKRSP BIT(5)
+#define BIT_PANCOORD BIT(3)
+
+#define REG_PANIDL 0x01 /* PAN ID (low) */
+#define REG_PANIDH 0x02 /* PAN ID (high) */
+#define REG_SADRL 0x03 /* Short address (low) */
+#define REG_SADRH 0x04 /* Short address (high) */
+#define REG_EADR0 0x05 /* Long address (low) (high is EADR7) */
+#define REG_EADR1 0x06
+#define REG_EADR2 0x07
+#define REG_EADR3 0x08
+#define REG_EADR4 0x09
+#define REG_EADR5 0x0A
+#define REG_EADR6 0x0B
+#define REG_EADR7 0x0C
+#define REG_RXFLUSH 0x0D
+#define REG_ORDER 0x10
+#define REG_TXMCR 0x11 /* Transmit MAC control */
+#define TXMCR_MIN_BE_SHIFT 3
+#define TXMCR_MIN_BE_MASK 0x18
+#define TXMCR_CSMA_RETRIES_SHIFT 0
+#define TXMCR_CSMA_RETRIES_MASK 0x07
+
+#define REG_ACKTMOUT 0x12
+#define REG_ESLOTG1 0x13
+#define REG_SYMTICKL 0x14
+#define REG_SYMTICKH 0x15
+#define REG_PACON0 0x16 /* Power Amplifier Control */
+#define REG_PACON1 0x17 /* Power Amplifier Control */
+#define REG_PACON2 0x18 /* Power Amplifier Control */
+#define REG_TXBCON0 0x1A
+#define REG_TXNCON 0x1B /* Transmit Normal FIFO Control */
+#define BIT_TXNTRIG BIT(0)
+#define BIT_TXNSECEN BIT(1)
+#define BIT_TXNACKREQ BIT(2)
+
+#define REG_TXG1CON 0x1C
+#define REG_TXG2CON 0x1D
+#define REG_ESLOTG23 0x1E
+#define REG_ESLOTG45 0x1F
+#define REG_ESLOTG67 0x20
+#define REG_TXPEND 0x21
+#define REG_WAKECON 0x22
+#define REG_FROMOFFSET 0x23
+#define REG_TXSTAT 0x24 /* TX MAC Status Register */
+#define REG_TXBCON1 0x25
+#define REG_GATECLK 0x26
+#define REG_TXTIME 0x27
+#define REG_HSYMTMRL 0x28
+#define REG_HSYMTMRH 0x29
+#define REG_SOFTRST 0x2A /* Soft Reset */
+#define REG_SECCON0 0x2C
+#define REG_SECCON1 0x2D
+#define REG_TXSTBL 0x2E /* TX Stabilization */
+#define REG_RXSR 0x30
+#define REG_INTSTAT 0x31 /* Interrupt Status */
+#define BIT_TXNIF BIT(0)
+#define BIT_RXIF BIT(3)
+#define BIT_SECIF BIT(4)
+#define BIT_SECIGNORE BIT(7)
+
+#define REG_INTCON 0x32 /* Interrupt Control */
+#define BIT_TXNIE BIT(0)
+#define BIT_RXIE BIT(3)
+#define BIT_SECIE BIT(4)
+
+#define REG_GPIO 0x33 /* GPIO */
+#define REG_TRISGPIO 0x34 /* GPIO direction */
+#define REG_SLPACK 0x35
+#define REG_RFCTL 0x36 /* RF Control Mode Register */
+#define BIT_RFRST BIT(2)
+
+#define REG_SECCR2 0x37
+#define REG_BBREG0 0x38
+#define REG_BBREG1 0x39 /* Baseband Registers */
+#define BIT_RXDECINV BIT(2)
+
+#define REG_BBREG2 0x3A /* */
+#define BBREG2_CCA_MODE_SHIFT 6
+#define BBREG2_CCA_MODE_MASK 0xc0
+
+#define REG_BBREG3 0x3B
+#define REG_BBREG4 0x3C
+#define REG_BBREG6 0x3E /* */
+#define REG_CCAEDTH 0x3F /* Energy Detection Threshold */
+
+/* MRF24J40 Long Address Registers */
+#define REG_RFCON0 0x200 /* RF Control Registers */
+#define RFCON0_CH_SHIFT 4
+#define RFCON0_CH_MASK 0xf0
+#define RFOPT_RECOMMEND 3
+
+#define REG_RFCON1 0x201
+#define REG_RFCON2 0x202
+#define REG_RFCON3 0x203
+
+#define TXPWRL_MASK 0xc0
+#define TXPWRL_SHIFT 6
+#define TXPWRL_30 0x3
+#define TXPWRL_20 0x2
+#define TXPWRL_10 0x1
+#define TXPWRL_0 0x0
+
+#define TXPWRS_MASK 0x38
+#define TXPWRS_SHIFT 3
+#define TXPWRS_6_3 0x7
+#define TXPWRS_4_9 0x6
+#define TXPWRS_3_7 0x5
+#define TXPWRS_2_8 0x4
+#define TXPWRS_1_9 0x3
+#define TXPWRS_1_2 0x2
+#define TXPWRS_0_5 0x1
+#define TXPWRS_0 0x0
+
+#define REG_RFCON5 0x205
+#define REG_RFCON6 0x206
+#define REG_RFCON7 0x207
+#define REG_RFCON8 0x208
+#define REG_SLPCAL0 0x209
+#define REG_SLPCAL1 0x20A
+#define REG_SLPCAL2 0x20B
+#define REG_RFSTATE 0x20F
+#define REG_RSSI 0x210
+#define REG_SLPCON0 0x211 /* Sleep Clock Control Registers */
+#define BIT_INTEDGE BIT(1)
+
+#define REG_SLPCON1 0x220
+#define REG_WAKETIMEL 0x222 /* Wake-up Time Match Value Low */
+#define REG_WAKETIMEH 0x223 /* Wake-up Time Match Value High */
+#define REG_REMCNTL 0x224
+#define REG_REMCNTH 0x225
+#define REG_MAINCNT0 0x226
+#define REG_MAINCNT1 0x227
+#define REG_MAINCNT2 0x228
+#define REG_MAINCNT3 0x229
+#define REG_TESTMODE 0x22F /* Test mode */
+#define REG_ASSOEAR0 0x230
+#define REG_ASSOEAR1 0x231
+#define REG_ASSOEAR2 0x232
+#define REG_ASSOEAR3 0x233
+#define REG_ASSOEAR4 0x234
+#define REG_ASSOEAR5 0x235
+#define REG_ASSOEAR6 0x236
+#define REG_ASSOEAR7 0x237
+#define REG_ASSOSAR0 0x238
+#define REG_ASSOSAR1 0x239
+#define REG_UNONCE0 0x240
+#define REG_UNONCE1 0x241
+#define REG_UNONCE2 0x242
+#define REG_UNONCE3 0x243
+#define REG_UNONCE4 0x244
+#define REG_UNONCE5 0x245
+#define REG_UNONCE6 0x246
+#define REG_UNONCE7 0x247
+#define REG_UNONCE8 0x248
+#define REG_UNONCE9 0x249
+#define REG_UNONCE10 0x24A
+#define REG_UNONCE11 0x24B
+#define REG_UNONCE12 0x24C
+#define REG_RX_FIFO 0x300 /* Receive FIFO */
+
+/* Device configuration: Only channels 11-26 on page 0 are supported. */
+#define MRF24J40_CHAN_MIN 11
+#define MRF24J40_CHAN_MAX 26
+#define CHANNEL_MASK (((u32)1 << (MRF24J40_CHAN_MAX + 1)) \
+ - ((u32)1 << MRF24J40_CHAN_MIN))
+
+#define TX_FIFO_SIZE 128 /* From datasheet */
+#define RX_FIFO_SIZE 144 /* From datasheet */
+#define SET_CHANNEL_DELAY_US 192 /* From datasheet */
+
+enum mrf24j40_modules { MRF24J40, MRF24J40MA, MRF24J40MC };
+
+/* Device Private Data */
+struct mrf24j40 {
+ struct spi_device *spi;
+ struct ieee802154_hw *hw;
+
+ struct regmap *regmap_short;
+ struct regmap *regmap_long;
+
+ /* for writing txfifo */
+ struct spi_message tx_msg;
+ u8 tx_hdr_buf[2];
+ struct spi_transfer tx_hdr_trx;
+ u8 tx_len_buf[2];
+ struct spi_transfer tx_len_trx;
+ struct spi_transfer tx_buf_trx;
+ struct sk_buff *tx_skb;
+
+ /* post transmit message to send frame out */
+ struct spi_message tx_post_msg;
+ u8 tx_post_buf[2];
+ struct spi_transfer tx_post_trx;
+
+ /* for protect/unprotect/read length rxfifo */
+ struct spi_message rx_msg;
+ u8 rx_buf[3];
+ struct spi_transfer rx_trx;
+
+ /* receive handling */
+ struct spi_message rx_buf_msg;
+ u8 rx_addr_buf[2];
+ struct spi_transfer rx_addr_trx;
+ u8 rx_lqi_buf[2];
+ struct spi_transfer rx_lqi_trx;
+ u8 rx_fifo_buf[RX_FIFO_SIZE];
+ struct spi_transfer rx_fifo_buf_trx;
+
+ /* isr handling for reading intstat */
+ struct spi_message irq_msg;
+ u8 irq_buf[2];
+ struct spi_transfer irq_trx;
+};
+
+/* regmap information for short address register access */
+#define MRF24J40_SHORT_WRITE 0x01
+#define MRF24J40_SHORT_READ 0x00
+#define MRF24J40_SHORT_NUMREGS 0x3F
+
+/* regmap information for long address register access */
+#define MRF24J40_LONG_ACCESS 0x80
+#define MRF24J40_LONG_NUMREGS 0x38F
+
+/* Read/Write SPI Commands for Short and Long Address registers. */
+#define MRF24J40_READSHORT(reg) ((reg) << 1)
+#define MRF24J40_WRITESHORT(reg) ((reg) << 1 | 1)
+#define MRF24J40_READLONG(reg) (1 << 15 | (reg) << 5)
+#define MRF24J40_WRITELONG(reg) (1 << 15 | (reg) << 5 | 1 << 4)
+
+/* The datasheet indicates the theoretical maximum for SCK to be 10MHz */
+#define MAX_SPI_SPEED_HZ 10000000
+
+#define printdev(X) (&X->spi->dev)
+
+static bool
+mrf24j40_short_reg_writeable(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case REG_RXMCR:
+ case REG_PANIDL:
+ case REG_PANIDH:
+ case REG_SADRL:
+ case REG_SADRH:
+ case REG_EADR0:
+ case REG_EADR1:
+ case REG_EADR2:
+ case REG_EADR3:
+ case REG_EADR4:
+ case REG_EADR5:
+ case REG_EADR6:
+ case REG_EADR7:
+ case REG_RXFLUSH:
+ case REG_ORDER:
+ case REG_TXMCR:
+ case REG_ACKTMOUT:
+ case REG_ESLOTG1:
+ case REG_SYMTICKL:
+ case REG_SYMTICKH:
+ case REG_PACON0:
+ case REG_PACON1:
+ case REG_PACON2:
+ case REG_TXBCON0:
+ case REG_TXNCON:
+ case REG_TXG1CON:
+ case REG_TXG2CON:
+ case REG_ESLOTG23:
+ case REG_ESLOTG45:
+ case REG_ESLOTG67:
+ case REG_TXPEND:
+ case REG_WAKECON:
+ case REG_FROMOFFSET:
+ case REG_TXBCON1:
+ case REG_GATECLK:
+ case REG_TXTIME:
+ case REG_HSYMTMRL:
+ case REG_HSYMTMRH:
+ case REG_SOFTRST:
+ case REG_SECCON0:
+ case REG_SECCON1:
+ case REG_TXSTBL:
+ case REG_RXSR:
+ case REG_INTCON:
+ case REG_TRISGPIO:
+ case REG_GPIO:
+ case REG_RFCTL:
+ case REG_SECCR2:
+ case REG_SLPACK:
+ case REG_BBREG0:
+ case REG_BBREG1:
+ case REG_BBREG2:
+ case REG_BBREG3:
+ case REG_BBREG4:
+ case REG_BBREG6:
+ case REG_CCAEDTH:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool
+mrf24j40_short_reg_readable(struct device *dev, unsigned int reg)
+{
+ bool rc;
+
+ /* all writeable are also readable */
+ rc = mrf24j40_short_reg_writeable(dev, reg);
+ if (rc)
+ return rc;
+
+ /* readonly regs */
+ switch (reg) {
+ case REG_TXSTAT:
+ case REG_INTSTAT:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool
+mrf24j40_short_reg_volatile(struct device *dev, unsigned int reg)
+{
+ /* can be changed during runtime */
+ switch (reg) {
+ case REG_TXSTAT:
+ case REG_INTSTAT:
+ case REG_RXFLUSH:
+ case REG_TXNCON:
+ case REG_SOFTRST:
+ case REG_RFCTL:
+ case REG_TXBCON0:
+ case REG_TXG1CON:
+ case REG_TXG2CON:
+ case REG_TXBCON1:
+ case REG_SECCON0:
+ case REG_RXSR:
+ case REG_SLPACK:
+ case REG_SECCR2:
+ case REG_BBREG6:
+ /* use them in spi_async and regmap so it's volatile */
+ case REG_BBREG1:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool
+mrf24j40_short_reg_precious(struct device *dev, unsigned int reg)
+{
+ /* don't clear irq line on read */
+ switch (reg) {
+ case REG_INTSTAT:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config mrf24j40_short_regmap = {
+ .name = "mrf24j40_short",
+ .reg_bits = 7,
+ .val_bits = 8,
+ .pad_bits = 1,
+ .write_flag_mask = MRF24J40_SHORT_WRITE,
+ .read_flag_mask = MRF24J40_SHORT_READ,
+ .cache_type = REGCACHE_RBTREE,
+ .max_register = MRF24J40_SHORT_NUMREGS,
+ .writeable_reg = mrf24j40_short_reg_writeable,
+ .readable_reg = mrf24j40_short_reg_readable,
+ .volatile_reg = mrf24j40_short_reg_volatile,
+ .precious_reg = mrf24j40_short_reg_precious,
+};
+
+static bool
+mrf24j40_long_reg_writeable(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case REG_RFCON0:
+ case REG_RFCON1:
+ case REG_RFCON2:
+ case REG_RFCON3:
+ case REG_RFCON5:
+ case REG_RFCON6:
+ case REG_RFCON7:
+ case REG_RFCON8:
+ case REG_SLPCAL2:
+ case REG_SLPCON0:
+ case REG_SLPCON1:
+ case REG_WAKETIMEL:
+ case REG_WAKETIMEH:
+ case REG_REMCNTL:
+ case REG_REMCNTH:
+ case REG_MAINCNT0:
+ case REG_MAINCNT1:
+ case REG_MAINCNT2:
+ case REG_MAINCNT3:
+ case REG_TESTMODE:
+ case REG_ASSOEAR0:
+ case REG_ASSOEAR1:
+ case REG_ASSOEAR2:
+ case REG_ASSOEAR3:
+ case REG_ASSOEAR4:
+ case REG_ASSOEAR5:
+ case REG_ASSOEAR6:
+ case REG_ASSOEAR7:
+ case REG_ASSOSAR0:
+ case REG_ASSOSAR1:
+ case REG_UNONCE0:
+ case REG_UNONCE1:
+ case REG_UNONCE2:
+ case REG_UNONCE3:
+ case REG_UNONCE4:
+ case REG_UNONCE5:
+ case REG_UNONCE6:
+ case REG_UNONCE7:
+ case REG_UNONCE8:
+ case REG_UNONCE9:
+ case REG_UNONCE10:
+ case REG_UNONCE11:
+ case REG_UNONCE12:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool
+mrf24j40_long_reg_readable(struct device *dev, unsigned int reg)
+{
+ bool rc;
+
+ /* all writeable are also readable */
+ rc = mrf24j40_long_reg_writeable(dev, reg);
+ if (rc)
+ return rc;
+
+ /* readonly regs */
+ switch (reg) {
+ case REG_SLPCAL0:
+ case REG_SLPCAL1:
+ case REG_RFSTATE:
+ case REG_RSSI:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool
+mrf24j40_long_reg_volatile(struct device *dev, unsigned int reg)
+{
+ /* can be changed during runtime */
+ switch (reg) {
+ case REG_SLPCAL0:
+ case REG_SLPCAL1:
+ case REG_SLPCAL2:
+ case REG_RFSTATE:
+ case REG_RSSI:
+ case REG_MAINCNT3:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config mrf24j40_long_regmap = {
+ .name = "mrf24j40_long",
+ .reg_bits = 11,
+ .val_bits = 8,
+ .pad_bits = 5,
+ .write_flag_mask = MRF24J40_LONG_ACCESS,
+ .read_flag_mask = MRF24J40_LONG_ACCESS,
+ .cache_type = REGCACHE_RBTREE,
+ .max_register = MRF24J40_LONG_NUMREGS,
+ .writeable_reg = mrf24j40_long_reg_writeable,
+ .readable_reg = mrf24j40_long_reg_readable,
+ .volatile_reg = mrf24j40_long_reg_volatile,
+};
+
+static int mrf24j40_long_regmap_write(void *context, const void *data,
+ size_t count)
+{
+ struct spi_device *spi = context;
+ u8 buf[3];
+
+ if (count > 3)
+ return -EINVAL;
+
+ /* regmap supports read/write mask only in frist byte
+ * long write access need to set the 12th bit, so we
+ * make special handling for write.
+ */
+ memcpy(buf, data, count);
+ buf[1] |= (1 << 4);
+
+ return spi_write(spi, buf, count);
+}
+
+static int
+mrf24j40_long_regmap_read(void *context, const void *reg, size_t reg_size,
+ void *val, size_t val_size)
+{
+ struct spi_device *spi = context;
+
+ return spi_write_then_read(spi, reg, reg_size, val, val_size);
+}
+
+static const struct regmap_bus mrf24j40_long_regmap_bus = {
+ .write = mrf24j40_long_regmap_write,
+ .read = mrf24j40_long_regmap_read,
+ .reg_format_endian_default = REGMAP_ENDIAN_BIG,
+ .val_format_endian_default = REGMAP_ENDIAN_BIG,
+};
+
+static void write_tx_buf_complete(void *context)
+{
+ struct mrf24j40 *devrec = context;
+ __le16 fc = ieee802154_get_fc_from_skb(devrec->tx_skb);
+ u8 val = BIT_TXNTRIG;
+ int ret;
+
+ if (ieee802154_is_secen(fc))
+ val |= BIT_TXNSECEN;
+
+ if (ieee802154_is_ackreq(fc))
+ val |= BIT_TXNACKREQ;
+
+ devrec->tx_post_msg.complete = NULL;
+ devrec->tx_post_buf[0] = MRF24J40_WRITESHORT(REG_TXNCON);
+ devrec->tx_post_buf[1] = val;
+
+ ret = spi_async(devrec->spi, &devrec->tx_post_msg);
+ if (ret)
+ dev_err(printdev(devrec), "SPI write Failed for transmit buf\n");
+}
+
+/* This function relies on an undocumented write method. Once a write command
+ and address is set, as many bytes of data as desired can be clocked into
+ the device. The datasheet only shows setting one byte at a time. */
+static int write_tx_buf(struct mrf24j40 *devrec, u16 reg,
+ const u8 *data, size_t length)
+{
+ u16 cmd;
+ int ret;
+
+ /* Range check the length. 2 bytes are used for the length fields.*/
+ if (length > TX_FIFO_SIZE-2) {
+ dev_err(printdev(devrec), "write_tx_buf() was passed too large a buffer. Performing short write.\n");
+ length = TX_FIFO_SIZE-2;
+ }
+
+ cmd = MRF24J40_WRITELONG(reg);
+ devrec->tx_hdr_buf[0] = cmd >> 8 & 0xff;
+ devrec->tx_hdr_buf[1] = cmd & 0xff;
+ devrec->tx_len_buf[0] = 0x0; /* Header Length. Set to 0 for now. TODO */
+ devrec->tx_len_buf[1] = length; /* Total length */
+ devrec->tx_buf_trx.tx_buf = data;
+ devrec->tx_buf_trx.len = length;
+
+ ret = spi_async(devrec->spi, &devrec->tx_msg);
+ if (ret)
+ dev_err(printdev(devrec), "SPI write Failed for TX buf\n");
+
+ return ret;
+}
+
+static int mrf24j40_tx(struct ieee802154_hw *hw, struct sk_buff *skb)
+{
+ struct mrf24j40 *devrec = hw->priv;
+
+ dev_dbg(printdev(devrec), "tx packet of %d bytes\n", skb->len);
+ devrec->tx_skb = skb;
+
+ return write_tx_buf(devrec, 0x000, skb->data, skb->len);
+}
+
+static int mrf24j40_ed(struct ieee802154_hw *hw, u8 *level)
+{
+ /* TODO: */
+ pr_warn("mrf24j40: ed not implemented\n");
+ *level = 0;
+ return 0;
+}
+
+static int mrf24j40_start(struct ieee802154_hw *hw)
+{
+ struct mrf24j40 *devrec = hw->priv;
+
+ dev_dbg(printdev(devrec), "start\n");
+
+ /* Clear TXNIE and RXIE. Enable interrupts */
+ return regmap_update_bits(devrec->regmap_short, REG_INTCON,
+ BIT_TXNIE | BIT_RXIE | BIT_SECIE, 0);
+}
+
+static void mrf24j40_stop(struct ieee802154_hw *hw)
+{
+ struct mrf24j40 *devrec = hw->priv;
+
+ dev_dbg(printdev(devrec), "stop\n");
+
+ /* Set TXNIE and RXIE. Disable Interrupts */
+ regmap_update_bits(devrec->regmap_short, REG_INTCON,
+ BIT_TXNIE | BIT_RXIE, BIT_TXNIE | BIT_RXIE);
+}
+
+static int mrf24j40_set_channel(struct ieee802154_hw *hw, u8 page, u8 channel)
+{
+ struct mrf24j40 *devrec = hw->priv;
+ u8 val;
+ int ret;
+
+ dev_dbg(printdev(devrec), "Set Channel %d\n", channel);
+
+ WARN_ON(page != 0);
+ WARN_ON(channel < MRF24J40_CHAN_MIN);
+ WARN_ON(channel > MRF24J40_CHAN_MAX);
+
+ /* Set Channel TODO */
+ val = (channel - 11) << RFCON0_CH_SHIFT | RFOPT_RECOMMEND;
+ ret = regmap_update_bits(devrec->regmap_long, REG_RFCON0,
+ RFCON0_CH_MASK, val);
+ if (ret)
+ return ret;
+
+ /* RF Reset */
+ ret = regmap_update_bits(devrec->regmap_short, REG_RFCTL, BIT_RFRST,
+ BIT_RFRST);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(devrec->regmap_short, REG_RFCTL, BIT_RFRST, 0);
+ if (!ret)
+ udelay(SET_CHANNEL_DELAY_US); /* per datasheet */
+
+ return ret;
+}
+
+static int mrf24j40_filter(struct ieee802154_hw *hw,
+ struct ieee802154_hw_addr_filt *filt,
+ unsigned long changed)
+{
+ struct mrf24j40 *devrec = hw->priv;
+
+ dev_dbg(printdev(devrec), "filter\n");
+
+ if (changed & IEEE802154_AFILT_SADDR_CHANGED) {
+ /* Short Addr */
+ u8 addrh, addrl;
+
+ addrh = le16_to_cpu(filt->short_addr) >> 8 & 0xff;
+ addrl = le16_to_cpu(filt->short_addr) & 0xff;
+
+ regmap_write(devrec->regmap_short, REG_SADRH, addrh);
+ regmap_write(devrec->regmap_short, REG_SADRL, addrl);
+ dev_dbg(printdev(devrec),
+ "Set short addr to %04hx\n", filt->short_addr);
+ }
+
+ if (changed & IEEE802154_AFILT_IEEEADDR_CHANGED) {
+ /* Device Address */
+ u8 i, addr[8];
+
+ memcpy(addr, &filt->ieee_addr, 8);
+ for (i = 0; i < 8; i++)
+ regmap_write(devrec->regmap_short, REG_EADR0 + i,
+ addr[i]);
+
+#ifdef DEBUG
+ pr_debug("Set long addr to: ");
+ for (i = 0; i < 8; i++)
+ pr_debug("%02hhx ", addr[7 - i]);
+ pr_debug("\n");
+#endif
+ }
+
+ if (changed & IEEE802154_AFILT_PANID_CHANGED) {
+ /* PAN ID */
+ u8 panidl, panidh;
+
+ panidh = le16_to_cpu(filt->pan_id) >> 8 & 0xff;
+ panidl = le16_to_cpu(filt->pan_id) & 0xff;
+ regmap_write(devrec->regmap_short, REG_PANIDH, panidh);
+ regmap_write(devrec->regmap_short, REG_PANIDL, panidl);
+
+ dev_dbg(printdev(devrec), "Set PANID to %04hx\n", filt->pan_id);
+ }
+
+ if (changed & IEEE802154_AFILT_PANC_CHANGED) {
+ /* Pan Coordinator */
+ u8 val;
+ int ret;
+
+ if (filt->pan_coord)
+ val = BIT_PANCOORD;
+ else
+ val = 0;
+ ret = regmap_update_bits(devrec->regmap_short, REG_RXMCR,
+ BIT_PANCOORD, val);
+ if (ret)
+ return ret;
+
+ /* REG_SLOTTED is maintained as default (unslotted/CSMA-CA).
+ * REG_ORDER is maintained as default (no beacon/superframe).
+ */
+
+ dev_dbg(printdev(devrec), "Set Pan Coord to %s\n",
+ filt->pan_coord ? "on" : "off");
+ }
+
+ return 0;
+}
+
+static void mrf24j40_handle_rx_read_buf_unlock(struct mrf24j40 *devrec)
+{
+ int ret;
+
+ /* Turn back on reception of packets off the air. */
+ devrec->rx_msg.complete = NULL;
+ devrec->rx_buf[0] = MRF24J40_WRITESHORT(REG_BBREG1);
+ devrec->rx_buf[1] = 0x00; /* CLR RXDECINV */
+ ret = spi_async(devrec->spi, &devrec->rx_msg);
+ if (ret)
+ dev_err(printdev(devrec), "failed to unlock rx buffer\n");
+}
+
+static void mrf24j40_handle_rx_read_buf_complete(void *context)
+{
+ struct mrf24j40 *devrec = context;
+ u8 len = devrec->rx_buf[2];
+ u8 rx_local_buf[RX_FIFO_SIZE];
+ struct sk_buff *skb;
+
+ memcpy(rx_local_buf, devrec->rx_fifo_buf, len);
+ mrf24j40_handle_rx_read_buf_unlock(devrec);
+
+ skb = dev_alloc_skb(IEEE802154_MTU);
+ if (!skb) {
+ dev_err(printdev(devrec), "failed to allocate skb\n");
+ return;
+ }
+
+ skb_put_data(skb, rx_local_buf, len);
+ ieee802154_rx_irqsafe(devrec->hw, skb, 0);
+
+#ifdef DEBUG
+ print_hex_dump(KERN_DEBUG, "mrf24j40 rx: ", DUMP_PREFIX_OFFSET, 16, 1,
+ rx_local_buf, len, 0);
+ pr_debug("mrf24j40 rx: lqi: %02hhx rssi: %02hhx\n",
+ devrec->rx_lqi_buf[0], devrec->rx_lqi_buf[1]);
+#endif
+}
+
+static void mrf24j40_handle_rx_read_buf(void *context)
+{
+ struct mrf24j40 *devrec = context;
+ u16 cmd;
+ int ret;
+
+ /* if length is invalid read the full MTU */
+ if (!ieee802154_is_valid_psdu_len(devrec->rx_buf[2]))
+ devrec->rx_buf[2] = IEEE802154_MTU;
+
+ cmd = MRF24J40_READLONG(REG_RX_FIFO + 1);
+ devrec->rx_addr_buf[0] = cmd >> 8 & 0xff;
+ devrec->rx_addr_buf[1] = cmd & 0xff;
+ devrec->rx_fifo_buf_trx.len = devrec->rx_buf[2];
+ ret = spi_async(devrec->spi, &devrec->rx_buf_msg);
+ if (ret) {
+ dev_err(printdev(devrec), "failed to read rx buffer\n");
+ mrf24j40_handle_rx_read_buf_unlock(devrec);
+ }
+}
+
+static void mrf24j40_handle_rx_read_len(void *context)
+{
+ struct mrf24j40 *devrec = context;
+ u16 cmd;
+ int ret;
+
+ /* read the length of received frame */
+ devrec->rx_msg.complete = mrf24j40_handle_rx_read_buf;
+ devrec->rx_trx.len = 3;
+ cmd = MRF24J40_READLONG(REG_RX_FIFO);
+ devrec->rx_buf[0] = cmd >> 8 & 0xff;
+ devrec->rx_buf[1] = cmd & 0xff;
+
+ ret = spi_async(devrec->spi, &devrec->rx_msg);
+ if (ret) {
+ dev_err(printdev(devrec), "failed to read rx buffer length\n");
+ mrf24j40_handle_rx_read_buf_unlock(devrec);
+ }
+}
+
+static int mrf24j40_handle_rx(struct mrf24j40 *devrec)
+{
+ /* Turn off reception of packets off the air. This prevents the
+ * device from overwriting the buffer while we're reading it.
+ */
+ devrec->rx_msg.complete = mrf24j40_handle_rx_read_len;
+ devrec->rx_trx.len = 2;
+ devrec->rx_buf[0] = MRF24J40_WRITESHORT(REG_BBREG1);
+ devrec->rx_buf[1] = BIT_RXDECINV; /* SET RXDECINV */
+
+ return spi_async(devrec->spi, &devrec->rx_msg);
+}
+
+static int
+mrf24j40_csma_params(struct ieee802154_hw *hw, u8 min_be, u8 max_be,
+ u8 retries)
+{
+ struct mrf24j40 *devrec = hw->priv;
+ u8 val;
+
+ /* min_be */
+ val = min_be << TXMCR_MIN_BE_SHIFT;
+ /* csma backoffs */
+ val |= retries << TXMCR_CSMA_RETRIES_SHIFT;
+
+ return regmap_update_bits(devrec->regmap_short, REG_TXMCR,
+ TXMCR_MIN_BE_MASK | TXMCR_CSMA_RETRIES_MASK,
+ val);
+}
+
+static int mrf24j40_set_cca_mode(struct ieee802154_hw *hw,
+ const struct wpan_phy_cca *cca)
+{
+ struct mrf24j40 *devrec = hw->priv;
+ u8 val;
+
+ /* mapping 802.15.4 to driver spec */
+ switch (cca->mode) {
+ case NL802154_CCA_ENERGY:
+ val = 2;
+ break;
+ case NL802154_CCA_CARRIER:
+ val = 1;
+ break;
+ case NL802154_CCA_ENERGY_CARRIER:
+ switch (cca->opt) {
+ case NL802154_CCA_OPT_ENERGY_CARRIER_AND:
+ val = 3;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return regmap_update_bits(devrec->regmap_short, REG_BBREG2,
+ BBREG2_CCA_MODE_MASK,
+ val << BBREG2_CCA_MODE_SHIFT);
+}
+
+/* array for representing ed levels */
+static const s32 mrf24j40_ed_levels[] = {
+ -9000, -8900, -8800, -8700, -8600, -8500, -8400, -8300, -8200, -8100,
+ -8000, -7900, -7800, -7700, -7600, -7500, -7400, -7300, -7200, -7100,
+ -7000, -6900, -6800, -6700, -6600, -6500, -6400, -6300, -6200, -6100,
+ -6000, -5900, -5800, -5700, -5600, -5500, -5400, -5300, -5200, -5100,
+ -5000, -4900, -4800, -4700, -4600, -4500, -4400, -4300, -4200, -4100,
+ -4000, -3900, -3800, -3700, -3600, -3500
+};
+
+/* map ed levels to register value */
+static const s32 mrf24j40_ed_levels_map[][2] = {
+ { -9000, 0 }, { -8900, 1 }, { -8800, 2 }, { -8700, 5 }, { -8600, 9 },
+ { -8500, 13 }, { -8400, 18 }, { -8300, 23 }, { -8200, 27 },
+ { -8100, 32 }, { -8000, 37 }, { -7900, 43 }, { -7800, 48 },
+ { -7700, 53 }, { -7600, 58 }, { -7500, 63 }, { -7400, 68 },
+ { -7300, 73 }, { -7200, 78 }, { -7100, 83 }, { -7000, 89 },
+ { -6900, 95 }, { -6800, 100 }, { -6700, 107 }, { -6600, 111 },
+ { -6500, 117 }, { -6400, 121 }, { -6300, 125 }, { -6200, 129 },
+ { -6100, 133 }, { -6000, 138 }, { -5900, 143 }, { -5800, 148 },
+ { -5700, 153 }, { -5600, 159 }, { -5500, 165 }, { -5400, 170 },
+ { -5300, 176 }, { -5200, 183 }, { -5100, 188 }, { -5000, 193 },
+ { -4900, 198 }, { -4800, 203 }, { -4700, 207 }, { -4600, 212 },
+ { -4500, 216 }, { -4400, 221 }, { -4300, 225 }, { -4200, 228 },
+ { -4100, 233 }, { -4000, 239 }, { -3900, 245 }, { -3800, 250 },
+ { -3700, 253 }, { -3600, 254 }, { -3500, 255 },
+};
+
+static int mrf24j40_set_cca_ed_level(struct ieee802154_hw *hw, s32 mbm)
+{
+ struct mrf24j40 *devrec = hw->priv;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mrf24j40_ed_levels_map); i++) {
+ if (mrf24j40_ed_levels_map[i][0] == mbm)
+ return regmap_write(devrec->regmap_short, REG_CCAEDTH,
+ mrf24j40_ed_levels_map[i][1]);
+ }
+
+ return -EINVAL;
+}
+
+static const s32 mrf24j40ma_powers[] = {
+ 0, -50, -120, -190, -280, -370, -490, -630, -1000, -1050, -1120, -1190,
+ -1280, -1370, -1490, -1630, -2000, -2050, -2120, -2190, -2280, -2370,
+ -2490, -2630, -3000, -3050, -3120, -3190, -3280, -3370, -3490, -3630,
+};
+
+static int mrf24j40_set_txpower(struct ieee802154_hw *hw, s32 mbm)
+{
+ struct mrf24j40 *devrec = hw->priv;
+ s32 small_scale;
+ u8 val;
+
+ if (0 >= mbm && mbm > -1000) {
+ val = TXPWRL_0 << TXPWRL_SHIFT;
+ small_scale = mbm;
+ } else if (-1000 >= mbm && mbm > -2000) {
+ val = TXPWRL_10 << TXPWRL_SHIFT;
+ small_scale = mbm + 1000;
+ } else if (-2000 >= mbm && mbm > -3000) {
+ val = TXPWRL_20 << TXPWRL_SHIFT;
+ small_scale = mbm + 2000;
+ } else if (-3000 >= mbm && mbm > -4000) {
+ val = TXPWRL_30 << TXPWRL_SHIFT;
+ small_scale = mbm + 3000;
+ } else {
+ return -EINVAL;
+ }
+
+ switch (small_scale) {
+ case 0:
+ val |= (TXPWRS_0 << TXPWRS_SHIFT);
+ break;
+ case -50:
+ val |= (TXPWRS_0_5 << TXPWRS_SHIFT);
+ break;
+ case -120:
+ val |= (TXPWRS_1_2 << TXPWRS_SHIFT);
+ break;
+ case -190:
+ val |= (TXPWRS_1_9 << TXPWRS_SHIFT);
+ break;
+ case -280:
+ val |= (TXPWRS_2_8 << TXPWRS_SHIFT);
+ break;
+ case -370:
+ val |= (TXPWRS_3_7 << TXPWRS_SHIFT);
+ break;
+ case -490:
+ val |= (TXPWRS_4_9 << TXPWRS_SHIFT);
+ break;
+ case -630:
+ val |= (TXPWRS_6_3 << TXPWRS_SHIFT);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return regmap_update_bits(devrec->regmap_long, REG_RFCON3,
+ TXPWRL_MASK | TXPWRS_MASK, val);
+}
+
+static int mrf24j40_set_promiscuous_mode(struct ieee802154_hw *hw, bool on)
+{
+ struct mrf24j40 *devrec = hw->priv;
+ int ret;
+
+ if (on) {
+ /* set PROMI, ERRPKT and NOACKRSP */
+ ret = regmap_update_bits(devrec->regmap_short, REG_RXMCR,
+ BIT_PROMI | BIT_ERRPKT | BIT_NOACKRSP,
+ BIT_PROMI | BIT_ERRPKT | BIT_NOACKRSP);
+ } else {
+ /* clear PROMI, ERRPKT and NOACKRSP */
+ ret = regmap_update_bits(devrec->regmap_short, REG_RXMCR,
+ BIT_PROMI | BIT_ERRPKT | BIT_NOACKRSP,
+ 0);
+ }
+
+ return ret;
+}
+
+static const struct ieee802154_ops mrf24j40_ops = {
+ .owner = THIS_MODULE,
+ .xmit_async = mrf24j40_tx,
+ .ed = mrf24j40_ed,
+ .start = mrf24j40_start,
+ .stop = mrf24j40_stop,
+ .set_channel = mrf24j40_set_channel,
+ .set_hw_addr_filt = mrf24j40_filter,
+ .set_csma_params = mrf24j40_csma_params,
+ .set_cca_mode = mrf24j40_set_cca_mode,
+ .set_cca_ed_level = mrf24j40_set_cca_ed_level,
+ .set_txpower = mrf24j40_set_txpower,
+ .set_promiscuous_mode = mrf24j40_set_promiscuous_mode,
+};
+
+static void mrf24j40_intstat_complete(void *context)
+{
+ struct mrf24j40 *devrec = context;
+ u8 intstat = devrec->irq_buf[1];
+
+ enable_irq(devrec->spi->irq);
+
+ /* Ignore Rx security decryption */
+ if (intstat & BIT_SECIF)
+ regmap_write_async(devrec->regmap_short, REG_SECCON0,
+ BIT_SECIGNORE);
+
+ /* Check for TX complete */
+ if (intstat & BIT_TXNIF)
+ ieee802154_xmit_complete(devrec->hw, devrec->tx_skb, false);
+
+ /* Check for Rx */
+ if (intstat & BIT_RXIF)
+ mrf24j40_handle_rx(devrec);
+}
+
+static irqreturn_t mrf24j40_isr(int irq, void *data)
+{
+ struct mrf24j40 *devrec = data;
+ int ret;
+
+ disable_irq_nosync(irq);
+
+ devrec->irq_buf[0] = MRF24J40_READSHORT(REG_INTSTAT);
+ devrec->irq_buf[1] = 0;
+
+ /* Read the interrupt status */
+ ret = spi_async(devrec->spi, &devrec->irq_msg);
+ if (ret) {
+ enable_irq(irq);
+ return IRQ_NONE;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int mrf24j40_hw_init(struct mrf24j40 *devrec)
+{
+ u32 irq_type;
+ int ret;
+
+ /* Initialize the device.
+ From datasheet section 3.2: Initialization. */
+ ret = regmap_write(devrec->regmap_short, REG_SOFTRST, 0x07);
+ if (ret)
+ goto err_ret;
+
+ ret = regmap_write(devrec->regmap_short, REG_PACON2, 0x98);
+ if (ret)
+ goto err_ret;
+
+ ret = regmap_write(devrec->regmap_short, REG_TXSTBL, 0x95);
+ if (ret)
+ goto err_ret;
+
+ ret = regmap_write(devrec->regmap_long, REG_RFCON0, 0x03);
+ if (ret)
+ goto err_ret;
+
+ ret = regmap_write(devrec->regmap_long, REG_RFCON1, 0x01);
+ if (ret)
+ goto err_ret;
+
+ ret = regmap_write(devrec->regmap_long, REG_RFCON2, 0x80);
+ if (ret)
+ goto err_ret;
+
+ ret = regmap_write(devrec->regmap_long, REG_RFCON6, 0x90);
+ if (ret)
+ goto err_ret;
+
+ ret = regmap_write(devrec->regmap_long, REG_RFCON7, 0x80);
+ if (ret)
+ goto err_ret;
+
+ ret = regmap_write(devrec->regmap_long, REG_RFCON8, 0x10);
+ if (ret)
+ goto err_ret;
+
+ ret = regmap_write(devrec->regmap_long, REG_SLPCON1, 0x21);
+ if (ret)
+ goto err_ret;
+
+ ret = regmap_write(devrec->regmap_short, REG_BBREG2, 0x80);
+ if (ret)
+ goto err_ret;
+
+ ret = regmap_write(devrec->regmap_short, REG_CCAEDTH, 0x60);
+ if (ret)
+ goto err_ret;
+
+ ret = regmap_write(devrec->regmap_short, REG_BBREG6, 0x40);
+ if (ret)
+ goto err_ret;
+
+ ret = regmap_write(devrec->regmap_short, REG_RFCTL, 0x04);
+ if (ret)
+ goto err_ret;
+
+ ret = regmap_write(devrec->regmap_short, REG_RFCTL, 0x0);
+ if (ret)
+ goto err_ret;
+
+ udelay(192);
+
+ /* Set RX Mode. RXMCR<1:0>: 0x0 normal, 0x1 promisc, 0x2 error */
+ ret = regmap_update_bits(devrec->regmap_short, REG_RXMCR, 0x03, 0x00);
+ if (ret)
+ goto err_ret;
+
+ if (spi_get_device_id(devrec->spi)->driver_data == MRF24J40MC) {
+ /* Enable external amplifier.
+ * From MRF24J40MC datasheet section 1.3: Operation.
+ */
+ regmap_update_bits(devrec->regmap_long, REG_TESTMODE, 0x07,
+ 0x07);
+
+ /* Set GPIO3 as output. */
+ regmap_update_bits(devrec->regmap_short, REG_TRISGPIO, 0x08,
+ 0x08);
+
+ /* Set GPIO3 HIGH to enable U5 voltage regulator */
+ regmap_update_bits(devrec->regmap_short, REG_GPIO, 0x08, 0x08);
+
+ /* Reduce TX pwr to meet FCC requirements.
+ * From MRF24J40MC datasheet section 3.1.1
+ */
+ regmap_write(devrec->regmap_long, REG_RFCON3, 0x28);
+ }
+
+ irq_type = irq_get_trigger_type(devrec->spi->irq);
+ if (irq_type == IRQ_TYPE_EDGE_RISING ||
+ irq_type == IRQ_TYPE_EDGE_FALLING)
+ dev_warn(&devrec->spi->dev,
+ "Using edge triggered irq's are not recommended, because it can cause races and result in a non-functional driver!\n");
+ switch (irq_type) {
+ case IRQ_TYPE_EDGE_RISING:
+ case IRQ_TYPE_LEVEL_HIGH:
+ /* set interrupt polarity to rising */
+ ret = regmap_update_bits(devrec->regmap_long, REG_SLPCON0,
+ BIT_INTEDGE, BIT_INTEDGE);
+ if (ret)
+ goto err_ret;
+ break;
+ default:
+ /* default is falling edge */
+ break;
+ }
+
+ return 0;
+
+err_ret:
+ return ret;
+}
+
+static void
+mrf24j40_setup_tx_spi_messages(struct mrf24j40 *devrec)
+{
+ spi_message_init(&devrec->tx_msg);
+ devrec->tx_msg.context = devrec;
+ devrec->tx_msg.complete = write_tx_buf_complete;
+ devrec->tx_hdr_trx.len = 2;
+ devrec->tx_hdr_trx.tx_buf = devrec->tx_hdr_buf;
+ spi_message_add_tail(&devrec->tx_hdr_trx, &devrec->tx_msg);
+ devrec->tx_len_trx.len = 2;
+ devrec->tx_len_trx.tx_buf = devrec->tx_len_buf;
+ spi_message_add_tail(&devrec->tx_len_trx, &devrec->tx_msg);
+ spi_message_add_tail(&devrec->tx_buf_trx, &devrec->tx_msg);
+
+ spi_message_init(&devrec->tx_post_msg);
+ devrec->tx_post_msg.context = devrec;
+ devrec->tx_post_trx.len = 2;
+ devrec->tx_post_trx.tx_buf = devrec->tx_post_buf;
+ spi_message_add_tail(&devrec->tx_post_trx, &devrec->tx_post_msg);
+}
+
+static void
+mrf24j40_setup_rx_spi_messages(struct mrf24j40 *devrec)
+{
+ spi_message_init(&devrec->rx_msg);
+ devrec->rx_msg.context = devrec;
+ devrec->rx_trx.len = 2;
+ devrec->rx_trx.tx_buf = devrec->rx_buf;
+ devrec->rx_trx.rx_buf = devrec->rx_buf;
+ spi_message_add_tail(&devrec->rx_trx, &devrec->rx_msg);
+
+ spi_message_init(&devrec->rx_buf_msg);
+ devrec->rx_buf_msg.context = devrec;
+ devrec->rx_buf_msg.complete = mrf24j40_handle_rx_read_buf_complete;
+ devrec->rx_addr_trx.len = 2;
+ devrec->rx_addr_trx.tx_buf = devrec->rx_addr_buf;
+ spi_message_add_tail(&devrec->rx_addr_trx, &devrec->rx_buf_msg);
+ devrec->rx_fifo_buf_trx.rx_buf = devrec->rx_fifo_buf;
+ spi_message_add_tail(&devrec->rx_fifo_buf_trx, &devrec->rx_buf_msg);
+ devrec->rx_lqi_trx.len = 2;
+ devrec->rx_lqi_trx.rx_buf = devrec->rx_lqi_buf;
+ spi_message_add_tail(&devrec->rx_lqi_trx, &devrec->rx_buf_msg);
+}
+
+static void
+mrf24j40_setup_irq_spi_messages(struct mrf24j40 *devrec)
+{
+ spi_message_init(&devrec->irq_msg);
+ devrec->irq_msg.context = devrec;
+ devrec->irq_msg.complete = mrf24j40_intstat_complete;
+ devrec->irq_trx.len = 2;
+ devrec->irq_trx.tx_buf = devrec->irq_buf;
+ devrec->irq_trx.rx_buf = devrec->irq_buf;
+ spi_message_add_tail(&devrec->irq_trx, &devrec->irq_msg);
+}
+
+static void mrf24j40_phy_setup(struct mrf24j40 *devrec)
+{
+ ieee802154_random_extended_addr(&devrec->hw->phy->perm_extended_addr);
+ devrec->hw->phy->current_channel = 11;
+
+ /* mrf24j40 supports max_minbe 0 - 3 */
+ devrec->hw->phy->supported.max_minbe = 3;
+ /* datasheet doesn't say anything about max_be, but we have min_be
+ * So we assume the max_be default.
+ */
+ devrec->hw->phy->supported.min_maxbe = 5;
+ devrec->hw->phy->supported.max_maxbe = 5;
+
+ devrec->hw->phy->cca.mode = NL802154_CCA_CARRIER;
+ devrec->hw->phy->supported.cca_modes = BIT(NL802154_CCA_ENERGY) |
+ BIT(NL802154_CCA_CARRIER) |
+ BIT(NL802154_CCA_ENERGY_CARRIER);
+ devrec->hw->phy->supported.cca_opts = BIT(NL802154_CCA_OPT_ENERGY_CARRIER_AND);
+
+ devrec->hw->phy->cca_ed_level = -6900;
+ devrec->hw->phy->supported.cca_ed_levels = mrf24j40_ed_levels;
+ devrec->hw->phy->supported.cca_ed_levels_size = ARRAY_SIZE(mrf24j40_ed_levels);
+
+ switch (spi_get_device_id(devrec->spi)->driver_data) {
+ case MRF24J40:
+ case MRF24J40MA:
+ devrec->hw->phy->supported.tx_powers = mrf24j40ma_powers;
+ devrec->hw->phy->supported.tx_powers_size = ARRAY_SIZE(mrf24j40ma_powers);
+ devrec->hw->phy->flags |= WPAN_PHY_FLAG_TXPOWER;
+ break;
+ default:
+ break;
+ }
+}
+
+static int mrf24j40_probe(struct spi_device *spi)
+{
+ int ret = -ENOMEM, irq_type;
+ struct ieee802154_hw *hw;
+ struct mrf24j40 *devrec;
+
+ dev_info(&spi->dev, "probe(). IRQ: %d\n", spi->irq);
+
+ /* Register with the 802154 subsystem */
+
+ hw = ieee802154_alloc_hw(sizeof(*devrec), &mrf24j40_ops);
+ if (!hw)
+ goto err_ret;
+
+ devrec = hw->priv;
+ devrec->spi = spi;
+ spi_set_drvdata(spi, devrec);
+ devrec->hw = hw;
+ devrec->hw->parent = &spi->dev;
+ devrec->hw->phy->supported.channels[0] = CHANNEL_MASK;
+ devrec->hw->flags = IEEE802154_HW_TX_OMIT_CKSUM | IEEE802154_HW_AFILT |
+ IEEE802154_HW_CSMA_PARAMS |
+ IEEE802154_HW_PROMISCUOUS;
+
+ devrec->hw->phy->flags = WPAN_PHY_FLAG_CCA_MODE |
+ WPAN_PHY_FLAG_CCA_ED_LEVEL;
+
+ mrf24j40_setup_tx_spi_messages(devrec);
+ mrf24j40_setup_rx_spi_messages(devrec);
+ mrf24j40_setup_irq_spi_messages(devrec);
+
+ devrec->regmap_short = devm_regmap_init_spi(spi,
+ &mrf24j40_short_regmap);
+ if (IS_ERR(devrec->regmap_short)) {
+ ret = PTR_ERR(devrec->regmap_short);
+ dev_err(&spi->dev, "Failed to allocate short register map: %d\n",
+ ret);
+ goto err_register_device;
+ }
+
+ devrec->regmap_long = devm_regmap_init(&spi->dev,
+ &mrf24j40_long_regmap_bus,
+ spi, &mrf24j40_long_regmap);
+ if (IS_ERR(devrec->regmap_long)) {
+ ret = PTR_ERR(devrec->regmap_long);
+ dev_err(&spi->dev, "Failed to allocate long register map: %d\n",
+ ret);
+ goto err_register_device;
+ }
+
+ if (spi->max_speed_hz > MAX_SPI_SPEED_HZ) {
+ dev_warn(&spi->dev, "spi clock above possible maximum: %d",
+ MAX_SPI_SPEED_HZ);
+ ret = -EINVAL;
+ goto err_register_device;
+ }
+
+ ret = mrf24j40_hw_init(devrec);
+ if (ret)
+ goto err_register_device;
+
+ mrf24j40_phy_setup(devrec);
+
+ /* request IRQF_TRIGGER_LOW as fallback default */
+ irq_type = irq_get_trigger_type(spi->irq);
+ if (!irq_type)
+ irq_type = IRQF_TRIGGER_LOW;
+
+ ret = devm_request_irq(&spi->dev, spi->irq, mrf24j40_isr,
+ irq_type, dev_name(&spi->dev), devrec);
+ if (ret) {
+ dev_err(printdev(devrec), "Unable to get IRQ");
+ goto err_register_device;
+ }
+
+ dev_dbg(printdev(devrec), "registered mrf24j40\n");
+ ret = ieee802154_register_hw(devrec->hw);
+ if (ret)
+ goto err_register_device;
+
+ return 0;
+
+err_register_device:
+ ieee802154_free_hw(devrec->hw);
+err_ret:
+ return ret;
+}
+
+static void mrf24j40_remove(struct spi_device *spi)
+{
+ struct mrf24j40 *devrec = spi_get_drvdata(spi);
+
+ dev_dbg(printdev(devrec), "remove\n");
+
+ ieee802154_unregister_hw(devrec->hw);
+ ieee802154_free_hw(devrec->hw);
+ /* TODO: Will ieee802154_free_device() wait until ->xmit() is
+ * complete? */
+}
+
+static const struct of_device_id mrf24j40_of_match[] = {
+ { .compatible = "microchip,mrf24j40", .data = (void *)MRF24J40 },
+ { .compatible = "microchip,mrf24j40ma", .data = (void *)MRF24J40MA },
+ { .compatible = "microchip,mrf24j40mc", .data = (void *)MRF24J40MC },
+ { },
+};
+MODULE_DEVICE_TABLE(of, mrf24j40_of_match);
+
+static const struct spi_device_id mrf24j40_ids[] = {
+ { "mrf24j40", MRF24J40 },
+ { "mrf24j40ma", MRF24J40MA },
+ { "mrf24j40mc", MRF24J40MC },
+ { },
+};
+MODULE_DEVICE_TABLE(spi, mrf24j40_ids);
+
+static struct spi_driver mrf24j40_driver = {
+ .driver = {
+ .of_match_table = mrf24j40_of_match,
+ .name = "mrf24j40",
+ },
+ .id_table = mrf24j40_ids,
+ .probe = mrf24j40_probe,
+ .remove = mrf24j40_remove,
+};
+
+module_spi_driver(mrf24j40_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Alan Ott");
+MODULE_DESCRIPTION("MRF24J40 SPI 802.15.4 Controller Driver");