summaryrefslogtreecommitdiffstats
path: root/kernel/drivers/net/ieee802154
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/drivers/net/ieee802154')
-rw-r--r--kernel/drivers/net/ieee802154/Kconfig55
-rw-r--r--kernel/drivers/net/ieee802154/Makefile4
-rw-r--r--kernel/drivers/net/ieee802154/at86rf230.c1795
-rw-r--r--kernel/drivers/net/ieee802154/cc2520.c1061
-rw-r--r--kernel/drivers/net/ieee802154/fakelb.c282
-rw-r--r--kernel/drivers/net/ieee802154/mrf24j40.c827
6 files changed, 4024 insertions, 0 deletions
diff --git a/kernel/drivers/net/ieee802154/Kconfig b/kernel/drivers/net/ieee802154/Kconfig
new file mode 100644
index 000000000..1a3c3e57a
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/Kconfig
@@ -0,0 +1,55 @@
+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
+ ---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'.
diff --git a/kernel/drivers/net/ieee802154/Makefile b/kernel/drivers/net/ieee802154/Makefile
new file mode 100644
index 000000000..d77fa4d77
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/Makefile
@@ -0,0 +1,4 @@
+obj-$(CONFIG_IEEE802154_FAKELB) += fakelb.o
+obj-$(CONFIG_IEEE802154_AT86RF230) += at86rf230.o
+obj-$(CONFIG_IEEE802154_MRF24J40) += mrf24j40.o
+obj-$(CONFIG_IEEE802154_CC2520) += cc2520.o
diff --git a/kernel/drivers/net/ieee802154/at86rf230.c b/kernel/drivers/net/ieee802154/at86rf230.c
new file mode 100644
index 000000000..67d00fbc2
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/at86rf230.c
@@ -0,0 +1,1795 @@
+/*
+ * AT86RF230/RF231 driver
+ *
+ * Copyright (C) 2009-2012 Siemens AG
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ * 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/spi/spi.h>
+#include <linux/spi/at86rf230.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>
+
+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_frame;
+ u16 t_p_ack;
+ int rssi_base_val;
+
+ int (*set_channel)(struct at86rf230_local *, u8, u8);
+ int (*get_desense_steps)(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;
+
+ bool irq_enable;
+};
+
+struct at86rf230_local {
+ struct spi_device *spi;
+
+ struct ieee802154_hw *hw;
+ struct at86rf2xx_chip_data *data;
+ struct regmap *regmap;
+ int slp_tr;
+
+ struct completion state_complete;
+ struct at86rf230_state_change state;
+
+ struct at86rf230_state_change irq;
+
+ bool tx_aret;
+ unsigned long cal_timeout;
+ s8 max_frame_retries;
+ bool is_tx;
+ bool is_tx_from_off;
+ u8 tx_retry;
+ struct sk_buff *tx_skb;
+ struct at86rf230_state_change tx;
+};
+
+#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 0x05, 0x0f, 0
+#define SR_PA_LT 0x05, 0x30, 4
+#define SR_PA_BUF_LT 0x05, 0xc0, 6
+#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 (1 << 7)
+#define IRQ_TRX_UR (1 << 6)
+#define IRQ_AMI (1 << 5)
+#define IRQ_CCA_ED (1 << 4)
+#define IRQ_TRX_END (1 << 3)
+#define IRQ_RX_START (1 << 2)
+#define IRQ_PLL_UNL (1 << 1)
+#define IRQ_PLL_LOCK (1 << 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 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),
+ const bool irq_enable);
+
+static inline int
+__at86rf230_write(struct at86rf230_local *lp,
+ unsigned int addr, unsigned int data)
+{
+ return regmap_write(lp->regmap, addr, data);
+}
+
+static inline int
+__at86rf230_read(struct at86rf230_local *lp,
+ unsigned int addr, unsigned int *data)
+{
+ return regmap_read(lp->regmap, addr, data);
+}
+
+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)
+{
+ return regmap_update_bits(lp->regmap, addr, mask, data << shift);
+}
+
+static inline void
+at86rf230_slp_tr_rising_edge(struct at86rf230_local *lp)
+{
+ gpio_set_value(lp->slp_tr, 1);
+ udelay(1);
+ gpio_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(void *context)
+{
+ struct at86rf230_state_change *ctx = context;
+ struct at86rf230_local *lp = ctx->lp;
+
+ lp->is_tx = 0;
+ at86rf230_async_state_change(lp, ctx, STATE_RX_AACK_ON, NULL, false);
+ ieee802154_wake_queue(lp->hw);
+}
+
+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, false);
+}
+
+/* Generic function to get some register value in async mode */
+static void
+at86rf230_async_read_reg(struct at86rf230_local *lp, const u8 reg,
+ struct at86rf230_state_change *ctx,
+ void (*complete)(void *context),
+ const bool irq_enable)
+{
+ int rc;
+
+ u8 *tx_buf = ctx->buf;
+
+ tx_buf[0] = (reg & CMD_REG_MASK) | CMD_REG;
+ ctx->msg.complete = complete;
+ ctx->irq_enable = irq_enable;
+ rc = spi_async(lp->spi, &ctx->msg);
+ if (rc) {
+ if (irq_enable)
+ enable_irq(ctx->irq);
+
+ at86rf230_async_error(lp, ctx, rc);
+ }
+}
+
+static inline u8 at86rf230_state_to_force(u8 state)
+{
+ if (state == STATE_TX_ON)
+ return STATE_FORCE_TX_ON;
+ else
+ return STATE_FORCE_TRX_OFF;
+}
+
+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 = at86rf230_state_to_force(state);
+ lp->tx_retry++;
+
+ at86rf230_async_state_change(lp, ctx, state,
+ ctx->complete,
+ ctx->irq_enable);
+ 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,
+ ctx->irq_enable);
+
+ 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 = ktime_set(0, 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 = ktime_set(0, 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 = ktime_set(0, (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 = ktime_set(0, c->t_reset_to_off * NSEC_PER_USEC);
+ goto change;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ /* Default delay is 1us in the most cases */
+ tim = ktime_set(0, NSEC_PER_USEC);
+
+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;
+ int rc;
+
+ /* 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,
+ ctx->irq_enable);
+ 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.
+ */
+ buf[0] = (RG_TRX_STATE & CMD_REG_MASK) | CMD_REG | CMD_WRITE;
+ buf[1] = ctx->to_state;
+ ctx->msg.complete = at86rf230_async_state_delay;
+ rc = spi_async(lp->spi, &ctx->msg);
+ if (rc) {
+ if (ctx->irq_enable)
+ enable_irq(ctx->irq);
+
+ at86rf230_async_error(lp, ctx, rc);
+ }
+}
+
+static void
+at86rf230_async_state_change(struct at86rf230_local *lp,
+ struct at86rf230_state_change *ctx,
+ const u8 state, void (*complete)(void *context),
+ const bool irq_enable)
+{
+ /* Initialization for the state change context */
+ ctx->to_state = state;
+ ctx->complete = complete;
+ ctx->irq_enable = irq_enable;
+ at86rf230_async_read_reg(lp, RG_TRX_STATUS, ctx,
+ at86rf230_async_state_change_start,
+ irq_enable);
+}
+
+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,
+ false);
+
+ 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;
+
+ enable_irq(ctx->irq);
+
+ ieee802154_xmit_complete(lp->hw, lp->tx_skb, !lp->tx_aret);
+}
+
+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, true);
+}
+
+static void
+at86rf230_tx_trac_check(void *context)
+{
+ struct at86rf230_state_change *ctx = context;
+ struct at86rf230_local *lp = ctx->lp;
+ const u8 *buf = ctx->buf;
+ const u8 trac = (buf[1] & 0xe0) >> 5;
+
+ /* If trac status is different than zero we need to do a state change
+ * to STATE_FORCE_TRX_OFF then STATE_RX_AACK_ON to recover the
+ * transceiver.
+ */
+ if (trac)
+ at86rf230_async_state_change(lp, ctx, STATE_FORCE_TRX_OFF,
+ at86rf230_tx_on, true);
+ else
+ at86rf230_tx_on(context);
+}
+
+static void
+at86rf230_tx_trac_status(void *context)
+{
+ struct at86rf230_state_change *ctx = context;
+ struct at86rf230_local *lp = ctx->lp;
+
+ at86rf230_async_read_reg(lp, RG_TRX_STATE, ctx,
+ at86rf230_tx_trac_check, true);
+}
+
+static void
+at86rf230_rx_read_frame_complete(void *context)
+{
+ struct at86rf230_state_change *ctx = context;
+ struct at86rf230_local *lp = ctx->lp;
+ u8 rx_local_buf[AT86RF2XX_MAX_BUF];
+ 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];
+
+ memcpy(rx_local_buf, buf + 2, len);
+ ctx->trx.len = 2;
+ enable_irq(ctx->irq);
+
+ skb = dev_alloc_skb(IEEE802154_MTU);
+ if (!skb) {
+ dev_vdbg(&lp->spi->dev, "failed to allocate sk_buff\n");
+ return;
+ }
+
+ memcpy(skb_put(skb, len), rx_local_buf, len);
+ ieee802154_rx_irqsafe(lp->hw, skb, lqi);
+}
+
+static void
+at86rf230_rx_read_frame(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;
+ enable_irq(ctx->irq);
+ at86rf230_async_error(lp, ctx, rc);
+ }
+}
+
+static void
+at86rf230_rx_trac_check(void *context)
+{
+ /* Possible check on trac status here. This could be useful to make
+ * some stats why receive is failed. Not used at the moment, but it's
+ * maybe timing relevant. Datasheet doesn't say anything about this.
+ * The programming guide say do it so.
+ */
+
+ at86rf230_rx_read_frame(context);
+}
+
+static void
+at86rf230_irq_trx_end(struct at86rf230_local *lp)
+{
+ if (lp->is_tx) {
+ lp->is_tx = 0;
+
+ if (lp->tx_aret)
+ at86rf230_async_state_change(lp, &lp->irq,
+ STATE_FORCE_TX_ON,
+ at86rf230_tx_trac_status,
+ true);
+ else
+ at86rf230_async_state_change(lp, &lp->irq,
+ STATE_RX_AACK_ON,
+ at86rf230_tx_complete,
+ true);
+ } else {
+ at86rf230_async_read_reg(lp, RG_TRX_STATE, &lp->irq,
+ at86rf230_rx_trac_check, true);
+ }
+}
+
+static void
+at86rf230_irq_status(void *context)
+{
+ struct at86rf230_state_change *ctx = context;
+ struct at86rf230_local *lp = ctx->lp;
+ const u8 *buf = ctx->buf;
+ const u8 irq = buf[1];
+
+ if (irq & IRQ_TRX_END) {
+ at86rf230_irq_trx_end(lp);
+ } else {
+ enable_irq(ctx->irq);
+ dev_err(&lp->spi->dev, "not supported irq %02x received\n",
+ irq);
+ }
+}
+
+static irqreturn_t at86rf230_isr(int irq, void *data)
+{
+ struct at86rf230_local *lp = data;
+ struct at86rf230_state_change *ctx = &lp->irq;
+ u8 *buf = ctx->buf;
+ int rc;
+
+ disable_irq_nosync(irq);
+
+ 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) {
+ enable_irq(irq);
+ at86rf230_async_error(lp, ctx, rc);
+ 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;
+ u8 *buf = ctx->buf;
+ int rc;
+
+ ctx->trx.len = 2;
+
+ if (gpio_is_valid(lp->slp_tr)) {
+ at86rf230_slp_tr_rising_edge(lp);
+ } else {
+ buf[0] = (RG_TRX_STATE & CMD_REG_MASK) | CMD_REG | CMD_WRITE;
+ buf[1] = STATE_BUSY_TX;
+ ctx->msg.complete = NULL;
+ rc = spi_async(lp->spi, &ctx->msg);
+ if (rc)
+ at86rf230_async_error(lp, ctx, rc);
+ }
+}
+
+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, false);
+}
+
+static void
+at86rf230_xmit_start(void *context)
+{
+ struct at86rf230_state_change *ctx = context;
+ struct at86rf230_local *lp = ctx->lp;
+
+ /* In ARET mode we need to go into STATE_TX_ARET_ON after we
+ * are in STATE_TX_ON. The pfad differs here, so we change
+ * the complete handler.
+ */
+ if (lp->tx_aret) {
+ if (lp->is_tx_from_off) {
+ lp->is_tx_from_off = false;
+ at86rf230_async_state_change(lp, ctx, STATE_TX_ARET_ON,
+ at86rf230_xmit_tx_on,
+ false);
+ } else {
+ at86rf230_async_state_change(lp, ctx, STATE_TX_ON,
+ at86rf230_xmit_tx_on,
+ false);
+ }
+ } else {
+ at86rf230_async_state_change(lp, ctx, STATE_TX_ON,
+ at86rf230_write_frame, false);
+ }
+}
+
+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, false);
+ } else {
+ at86rf230_xmit_start(ctx);
+ }
+
+ return 0;
+}
+
+static int
+at86rf230_ed(struct ieee802154_hw *hw, u8 *level)
+{
+ BUG_ON(!level);
+ *level = 0xbe;
+ return 0;
+}
+
+static int
+at86rf230_start(struct ieee802154_hw *hw)
+{
+ return at86rf230_sync_state_change(hw->priv, STATE_RX_AACK_ON);
+}
+
+static void
+at86rf230_stop(struct ieee802154_hw *hw)
+{
+ at86rf230_sync_state_change(hw->priv, STATE_FORCE_TRX_OFF);
+}
+
+static int
+at86rf23x_set_channel(struct at86rf230_local *lp, u8 page, u8 channel)
+{
+ return at86rf230_write_subreg(lp, SR_CHANNEL, channel);
+}
+
+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;
+
+ /* This sets the symbol_duration according frequency on the 212.
+ * TODO move this handling while set channel and page in cfg802154.
+ * We can do that, this timings are according 802.15.4 standard.
+ * If we do that in cfg802154, this is a more generic calculation.
+ *
+ * This should also protected from ifs_timer. Means cancel timer and
+ * init with a new value. For now, this is okay.
+ */
+ if (channel == 0) {
+ if (page == 0) {
+ /* SUB:0 and BPSK:0 -> BPSK-20 */
+ lp->hw->phy->symbol_duration = 50;
+ } else {
+ /* SUB:1 and BPSK:0 -> BPSK-40 */
+ lp->hw->phy->symbol_duration = 25;
+ }
+ } else {
+ if (page == 0)
+ /* SUB:0 and BPSK:1 -> OQPSK-100/200/400 */
+ lp->hw->phy->symbol_duration = 40;
+ else
+ /* SUB:1 and BPSK:1 -> OQPSK-250/500/1000 */
+ lp->hw->phy->symbol_duration = 16;
+ }
+
+ lp->hw->phy->lifs_period = IEEE802154_LIFS_PERIOD *
+ lp->hw->phy->symbol_duration;
+ lp->hw->phy->sifs_period = IEEE802154_SIFS_PERIOD *
+ lp->hw->phy->symbol_duration;
+
+ 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,
+ "at86rf230_set_hw_addr_filt called for saddr\n");
+ __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,
+ "at86rf230_set_hw_addr_filt called for pan id\n");
+ __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,
+ "at86rf230_set_hw_addr_filt called for IEEE addr\n");
+ 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,
+ "at86rf230_set_hw_addr_filt called for panc change\n");
+ 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;
+}
+
+static int
+at86rf230_set_txpower(struct ieee802154_hw *hw, s8 db)
+{
+ struct at86rf230_local *lp = hw->priv;
+
+ /* typical maximum output is 5dBm with RG_PHY_TX_PWR 0x60, lower five
+ * bits decrease power in 1dB steps. 0x60 represents extra PA gain of
+ * 0dB.
+ * thus, supported values for db range from -26 to 5, for 31dB of
+ * reduction to 0dB of reduction.
+ */
+ if (db > 5 || db < -26)
+ return -EINVAL;
+
+ db = -(db - 5);
+
+ return __at86rf230_write(lp, RG_PHY_TX_PWR, 0x60 | db);
+}
+
+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
+at86rf212_get_desens_steps(struct at86rf230_local *lp, s32 level)
+{
+ return (level - lp->data->rssi_base_val) * 100 / 207;
+}
+
+static int
+at86rf23x_get_desens_steps(struct at86rf230_local *lp, s32 level)
+{
+ return (level - lp->data->rssi_base_val) / 2;
+}
+
+static int
+at86rf230_set_cca_ed_level(struct ieee802154_hw *hw, s32 level)
+{
+ struct at86rf230_local *lp = hw->priv;
+
+ if (level < lp->data->rssi_base_val || level > 30)
+ return -EINVAL;
+
+ return at86rf230_write_subreg(lp, SR_CCA_ED_THRES,
+ lp->data->get_desense_steps(lp, level));
+}
+
+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;
+ int rc = 0;
+
+ lp->tx_aret = retries >= 0;
+ lp->max_frame_retries = retries;
+
+ if (retries >= 0)
+ rc = at86rf230_write_subreg(lp, SR_MAX_FRAME_RETRIES, retries);
+
+ return rc;
+}
+
+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_frame = 4096,
+ .t_p_ack = 545,
+ .rssi_base_val = -91,
+ .set_channel = at86rf23x_set_channel,
+ .get_desense_steps = at86rf23x_get_desens_steps
+};
+
+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_frame = 4096,
+ .t_p_ack = 545,
+ .rssi_base_val = -91,
+ .set_channel = at86rf23x_set_channel,
+ .get_desense_steps = at86rf23x_get_desens_steps
+};
+
+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_frame = 4096,
+ .t_p_ack = 545,
+ .rssi_base_val = -100,
+ .set_channel = at86rf212_set_channel,
+ .get_desense_steps = at86rf212_get_desens_steps
+};
+
+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_RISING ||
+ irq_type == IRQ_TYPE_EDGE_FALLING)
+ dev_warn(&lp->spi->dev,
+ "Using edge triggered irq's are not recommended!\n");
+ 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_get_pdata(struct spi_device *spi, int *rstn, int *slp_tr,
+ u8 *xtal_trim)
+{
+ struct at86rf230_platform_data *pdata = spi->dev.platform_data;
+ int ret;
+
+ if (!IS_ENABLED(CONFIG_OF) || !spi->dev.of_node) {
+ if (!pdata)
+ return -ENOENT;
+
+ *rstn = pdata->rstn;
+ *slp_tr = pdata->slp_tr;
+ *xtal_trim = pdata->xtal_trim;
+ return 0;
+ }
+
+ *rstn = of_get_named_gpio(spi->dev.of_node, "reset-gpio", 0);
+ *slp_tr = of_get_named_gpio(spi->dev.of_node, "sleep-gpio", 0);
+ ret = of_property_read_u8(spi->dev.of_node, "xtal-trim", xtal_trim);
+ if (ret < 0 && ret != -EINVAL)
+ return ret;
+
+ return 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_AACK |
+ IEEE802154_HW_TXPOWER | IEEE802154_HW_ARET |
+ IEEE802154_HW_AFILT | IEEE802154_HW_PROMISCUOUS;
+
+ lp->hw->phy->cca.mode = NL802154_CCA_ENERGY;
+
+ switch (part) {
+ case 2:
+ chip = "at86rf230";
+ rc = -ENOTSUPP;
+ break;
+ case 3:
+ chip = "at86rf231";
+ lp->data = &at86rf231_data;
+ lp->hw->phy->channels_supported[0] = 0x7FFF800;
+ lp->hw->phy->current_channel = 11;
+ lp->hw->phy->symbol_duration = 16;
+ break;
+ case 7:
+ chip = "at86rf212";
+ lp->data = &at86rf212_data;
+ lp->hw->flags |= IEEE802154_HW_LBT;
+ lp->hw->phy->channels_supported[0] = 0x00007FF;
+ lp->hw->phy->channels_supported[2] = 0x00007FF;
+ lp->hw->phy->current_channel = 5;
+ lp->hw->phy->symbol_duration = 25;
+ break;
+ case 11:
+ chip = "at86rf233";
+ lp->data = &at86rf233_data;
+ lp->hw->phy->channels_supported[0] = 0x7FFF800;
+ lp->hw->phy->current_channel = 13;
+ lp->hw->phy->symbol_duration = 16;
+ break;
+ default:
+ chip = "unknown";
+ rc = -ENOTSUPP;
+ break;
+ }
+
+ dev_info(&lp->spi->dev, "Detected %s chip version %d\n", chip, version);
+
+ return rc;
+}
+
+static void
+at86rf230_setup_spi_messages(struct at86rf230_local *lp)
+{
+ lp->state.lp = lp;
+ lp->state.irq = lp->spi->irq;
+ spi_message_init(&lp->state.msg);
+ lp->state.msg.context = &lp->state;
+ lp->state.trx.len = 2;
+ lp->state.trx.tx_buf = lp->state.buf;
+ lp->state.trx.rx_buf = lp->state.buf;
+ spi_message_add_tail(&lp->state.trx, &lp->state.msg);
+ hrtimer_init(&lp->state.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ lp->state.timer.function = at86rf230_async_state_timer;
+
+ lp->irq.lp = lp;
+ lp->irq.irq = lp->spi->irq;
+ spi_message_init(&lp->irq.msg);
+ lp->irq.msg.context = &lp->irq;
+ lp->irq.trx.len = 2;
+ lp->irq.trx.tx_buf = lp->irq.buf;
+ lp->irq.trx.rx_buf = lp->irq.buf;
+ spi_message_add_tail(&lp->irq.trx, &lp->irq.msg);
+ hrtimer_init(&lp->irq.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ lp->irq.timer.function = at86rf230_async_state_timer;
+
+ lp->tx.lp = lp;
+ lp->tx.irq = lp->spi->irq;
+ spi_message_init(&lp->tx.msg);
+ lp->tx.msg.context = &lp->tx;
+ lp->tx.trx.len = 2;
+ lp->tx.trx.tx_buf = lp->tx.buf;
+ lp->tx.trx.rx_buf = lp->tx.buf;
+ spi_message_add_tail(&lp->tx.trx, &lp->tx.msg);
+ hrtimer_init(&lp->tx.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ lp->tx.timer.function = at86rf230_async_state_timer;
+}
+
+static int at86rf230_probe(struct spi_device *spi)
+{
+ struct ieee802154_hw *hw;
+ struct at86rf230_local *lp;
+ unsigned int status;
+ int rc, irq_type, rstn, slp_tr;
+ u8 xtal_trim = 0;
+
+ if (!spi->irq) {
+ dev_err(&spi->dev, "no IRQ specified\n");
+ return -EINVAL;
+ }
+
+ rc = at86rf230_get_pdata(spi, &rstn, &slp_tr, &xtal_trim);
+ if (rc < 0) {
+ dev_err(&spi->dev, "failed to parse platform_data: %d\n", rc);
+ return rc;
+ }
+
+ if (gpio_is_valid(rstn)) {
+ rc = devm_gpio_request_one(&spi->dev, rstn,
+ GPIOF_OUT_INIT_HIGH, "rstn");
+ if (rc)
+ return rc;
+ }
+
+ if (gpio_is_valid(slp_tr)) {
+ rc = devm_gpio_request_one(&spi->dev, slp_tr,
+ GPIOF_OUT_INIT_LOW, "slp_tr");
+ if (rc)
+ return rc;
+ }
+
+ /* Reset */
+ if (gpio_is_valid(rstn)) {
+ udelay(1);
+ gpio_set_value(rstn, 0);
+ udelay(1);
+ gpio_set_value(rstn, 1);
+ 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;
+ hw->vif_data_size = sizeof(*lp);
+ 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);
+
+ 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_RISING;
+
+ rc = devm_request_irq(&spi->dev, spi->irq, at86rf230_isr,
+ IRQF_SHARED | irq_type, dev_name(&spi->dev), lp);
+ if (rc)
+ goto free_dev;
+
+ rc = ieee802154_register_hw(lp->hw);
+ if (rc)
+ goto free_dev;
+
+ return rc;
+
+free_dev:
+ ieee802154_free_hw(lp->hw);
+
+ return rc;
+}
+
+static int 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");
+
+ return 0;
+}
+
+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 = of_match_ptr(at86rf230_of_match),
+ .name = "at86rf230",
+ .owner = THIS_MODULE,
+ },
+ .probe = at86rf230_probe,
+ .remove = at86rf230_remove,
+};
+
+module_spi_driver(at86rf230_driver);
+
+MODULE_DESCRIPTION("AT86RF230 Transceiver Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/kernel/drivers/net/ieee802154/cc2520.c b/kernel/drivers/net/ieee802154/cc2520.c
new file mode 100644
index 000000000..f833b8bb6
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/cc2520.c
@@ -0,0 +1,1061 @@
+/* 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>
+ *
+ * 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.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/cc2520.h>
+#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+#include <linux/skbuff.h>
+#include <linux/of_gpio.h>
+#include <linux/ieee802154.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
+
+/* 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 */
+ int 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 */
+};
+
+/* Generic Functions */
+static int
+cc2520_cmd_strobe(struct cc2520_private *priv, u8 cmd)
+{
+ int ret;
+ u8 status = 0xff;
+ 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);
+ 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_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 *data, u8 len)
+{
+ int status;
+
+ /* length byte must include FCS even
+ * if it is calculated in the hardware
+ */
+ int len_byte = len + 2;
+
+ 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, u8 *lqi)
+{
+ 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;
+
+ rc = cc2520_cmd_strobe(priv, CC2520_CMD_SFLUSHTX);
+ if (rc)
+ goto err_tx;
+
+ rc = cc2520_write_txfifo(priv, 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) {
+ dev_err(&priv->spi->dev, "cc2520 tx underflow exception\n");
+ goto err_tx;
+ }
+
+ spin_lock_irqsave(&priv->lock, flags);
+ BUG_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;
+
+ cc2520_read_rxfifo(priv, &len, bytes, &lqi);
+
+ if (len < 2 || len > IEEE802154_MTU)
+ return -EINVAL;
+
+ skb = dev_alloc_skb(len);
+ if (!skb)
+ return -ENOMEM;
+
+ if (cc2520_read_rxfifo(priv, skb_put(skb, len), len, &lqi)) {
+ dev_dbg(&priv->spi->dev, "frame reception failed\n");
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ skb_trim(skb, skb->len - 2);
+
+ 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");
+
+ BUG_ON(page != 0);
+ BUG_ON(channel < CC2520_MINCHANNEL);
+ BUG_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;
+
+ if (changed & IEEE802154_AFILT_PANID_CHANGED) {
+ u16 panid = le16_to_cpu(filt->pan_id);
+
+ dev_vdbg(&priv->spi->dev,
+ "cc2520_filter called for pan id\n");
+ cc2520_write_ram(priv, CC2520RAM_PANID,
+ sizeof(panid), (u8 *)&panid);
+ }
+
+ if (changed & IEEE802154_AFILT_IEEEADDR_CHANGED) {
+ dev_vdbg(&priv->spi->dev,
+ "cc2520_filter called for IEEE addr\n");
+ 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,
+ "cc2520_filter called for saddr\n");
+ cc2520_write_ram(priv, CC2520RAM_SHORTADDR,
+ sizeof(addr), (u8 *)&addr);
+ }
+
+ if (changed & IEEE802154_AFILT_PANC_CHANGED) {
+ dev_vdbg(&priv->spi->dev,
+ "cc2520_filter called for panc change\n");
+ if (filt->pan_coord)
+ cc2520_write_register(priv, CC2520_FRMFILT0, 0x02);
+ else
+ cc2520_write_register(priv, CC2520_FRMFILT0, 0x00);
+ }
+
+ return 0;
+}
+
+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,
+};
+
+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;
+ priv->hw->vif_data_size = sizeof(*priv);
+ ieee802154_random_extended_addr(&priv->hw->phy->perm_extended_addr);
+
+ /* We do support only 2.4 Ghz */
+ priv->hw->phy->channels_supported[0] = 0x7FFF800;
+ priv->hw->flags = IEEE802154_HW_OMIT_CKSUM | IEEE802154_HW_AACK |
+ IEEE802154_HW_AFILT;
+
+ 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 (gpio_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_get_platform_data(struct spi_device *spi,
+ struct cc2520_platform_data *pdata)
+{
+ struct device_node *np = spi->dev.of_node;
+ struct cc2520_private *priv = spi_get_drvdata(spi);
+
+ if (!np) {
+ struct cc2520_platform_data *spi_pdata = spi->dev.platform_data;
+ if (!spi_pdata)
+ return -ENOENT;
+ *pdata = *spi_pdata;
+ return 0;
+ }
+
+ pdata->fifo = of_get_named_gpio(np, "fifo-gpio", 0);
+ priv->fifo_pin = pdata->fifo;
+
+ pdata->fifop = of_get_named_gpio(np, "fifop-gpio", 0);
+
+ pdata->sfd = of_get_named_gpio(np, "sfd-gpio", 0);
+ pdata->cca = of_get_named_gpio(np, "cca-gpio", 0);
+ pdata->vreg = of_get_named_gpio(np, "vreg-gpio", 0);
+ pdata->reset = of_get_named_gpio(np, "reset-gpio", 0);
+
+ pdata->amplified = of_property_read_bool(np, "amplified");
+
+ return 0;
+}
+
+static int cc2520_hw_init(struct cc2520_private *priv)
+{
+ u8 status = 0, state = 0xff;
+ int ret;
+ int timeout = 100;
+ struct cc2520_platform_data pdata;
+
+ ret = cc2520_get_platform_data(priv->spi, &pdata);
+ if (ret)
+ goto err_ret;
+
+ 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 ret;
+ }
+ 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 (pdata.amplified) {
+ ret = cc2520_write_register(priv, CC2520_TXPOWER, 0xF9);
+ if (ret)
+ goto err_ret;
+
+ 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_TXPOWER, 0xF7);
+ if (ret)
+ goto err_ret;
+
+ ret = cc2520_write_register(priv, CC2520_AGCCTRL1, 0x11);
+ if (ret)
+ goto err_ret;
+ }
+
+ /* Registers default value: section 28.1 in 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;
+
+ ret = cc2520_write_register(priv, CC2520_FRMCTRL0, 0x60);
+ if (ret)
+ goto err_ret;
+
+ ret = cc2520_write_register(priv, CC2520_FRMCTRL1, 0x03);
+ if (ret)
+ goto err_ret;
+
+ ret = cc2520_write_register(priv, CC2520_FRMFILT0, 0x00);
+ 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 cc2520_platform_data pdata;
+ int ret;
+
+ priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ spi_set_drvdata(spi, priv);
+
+ ret = cc2520_get_platform_data(spi, &pdata);
+ if (ret < 0) {
+ dev_err(&spi->dev, "no platform data\n");
+ return -EINVAL;
+ }
+
+ 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 */
+ if (!gpio_is_valid(pdata.fifo)) {
+ dev_err(&spi->dev, "fifo gpio is not valid\n");
+ ret = -EINVAL;
+ goto err_hw_init;
+ }
+
+ ret = devm_gpio_request_one(&spi->dev, pdata.fifo,
+ GPIOF_IN, "fifo");
+ if (ret)
+ goto err_hw_init;
+
+ if (!gpio_is_valid(pdata.cca)) {
+ dev_err(&spi->dev, "cca gpio is not valid\n");
+ ret = -EINVAL;
+ goto err_hw_init;
+ }
+
+ ret = devm_gpio_request_one(&spi->dev, pdata.cca,
+ GPIOF_IN, "cca");
+ if (ret)
+ goto err_hw_init;
+
+ if (!gpio_is_valid(pdata.fifop)) {
+ dev_err(&spi->dev, "fifop gpio is not valid\n");
+ ret = -EINVAL;
+ goto err_hw_init;
+ }
+
+ ret = devm_gpio_request_one(&spi->dev, pdata.fifop,
+ GPIOF_IN, "fifop");
+ if (ret)
+ goto err_hw_init;
+
+ if (!gpio_is_valid(pdata.sfd)) {
+ dev_err(&spi->dev, "sfd gpio is not valid\n");
+ ret = -EINVAL;
+ goto err_hw_init;
+ }
+
+ ret = devm_gpio_request_one(&spi->dev, pdata.sfd,
+ GPIOF_IN, "sfd");
+ if (ret)
+ goto err_hw_init;
+
+ if (!gpio_is_valid(pdata.reset)) {
+ dev_err(&spi->dev, "reset gpio is not valid\n");
+ ret = -EINVAL;
+ goto err_hw_init;
+ }
+
+ ret = devm_gpio_request_one(&spi->dev, pdata.reset,
+ GPIOF_OUT_INIT_LOW, "reset");
+ if (ret)
+ goto err_hw_init;
+
+ if (!gpio_is_valid(pdata.vreg)) {
+ dev_err(&spi->dev, "vreg gpio is not valid\n");
+ ret = -EINVAL;
+ goto err_hw_init;
+ }
+
+ ret = devm_gpio_request_one(&spi->dev, pdata.vreg,
+ GPIOF_OUT_INIT_LOW, "vreg");
+ if (ret)
+ goto err_hw_init;
+
+ gpio_set_value(pdata.vreg, HIGH);
+ usleep_range(100, 150);
+
+ gpio_set_value(pdata.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,
+ gpio_to_irq(pdata.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,
+ gpio_to_irq(pdata.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 int 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);
+
+ return 0;
+}
+
+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",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(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/kernel/drivers/net/ieee802154/fakelb.c b/kernel/drivers/net/ieee802154/fakelb.c
new file mode 100644
index 000000000..dc2bfb600
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/fakelb.c
@@ -0,0 +1,282 @@
+/*
+ * Loopback IEEE 802.15.4 interface
+ *
+ * Copyright 2007-2012 Siemens AG
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ * 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 = 1;
+
+struct fakelb_dev_priv {
+ struct ieee802154_hw *hw;
+
+ struct list_head list;
+ struct fakelb_priv *fake;
+
+ spinlock_t lock;
+ bool working;
+};
+
+struct fakelb_priv {
+ struct list_head list;
+ rwlock_t lock;
+};
+
+static int
+fakelb_hw_ed(struct ieee802154_hw *hw, u8 *level)
+{
+ BUG_ON(!level);
+ *level = 0xbe;
+
+ return 0;
+}
+
+static int
+fakelb_hw_channel(struct ieee802154_hw *hw, u8 page, u8 channel)
+{
+ pr_debug("set channel to %d\n", channel);
+
+ return 0;
+}
+
+static void
+fakelb_hw_deliver(struct fakelb_dev_priv *priv, struct sk_buff *skb)
+{
+ struct sk_buff *newskb;
+
+ spin_lock(&priv->lock);
+ if (priv->working) {
+ newskb = pskb_copy(skb, GFP_ATOMIC);
+ ieee802154_rx_irqsafe(priv->hw, newskb, 0xcc);
+ }
+ spin_unlock(&priv->lock);
+}
+
+static int
+fakelb_hw_xmit(struct ieee802154_hw *hw, struct sk_buff *skb)
+{
+ struct fakelb_dev_priv *priv = hw->priv;
+ struct fakelb_priv *fake = priv->fake;
+
+ read_lock_bh(&fake->lock);
+ if (priv->list.next == priv->list.prev) {
+ /* we are the only one device */
+ fakelb_hw_deliver(priv, skb);
+ } else {
+ struct fakelb_dev_priv *dp;
+ list_for_each_entry(dp, &priv->fake->list, list) {
+ if (dp != priv &&
+ (dp->hw->phy->current_channel ==
+ priv->hw->phy->current_channel))
+ fakelb_hw_deliver(dp, skb);
+ }
+ }
+ read_unlock_bh(&fake->lock);
+
+ return 0;
+}
+
+static int
+fakelb_hw_start(struct ieee802154_hw *hw) {
+ struct fakelb_dev_priv *priv = hw->priv;
+ int ret = 0;
+
+ spin_lock(&priv->lock);
+ if (priv->working)
+ ret = -EBUSY;
+ else
+ priv->working = 1;
+ spin_unlock(&priv->lock);
+
+ return ret;
+}
+
+static void
+fakelb_hw_stop(struct ieee802154_hw *hw) {
+ struct fakelb_dev_priv *priv = hw->priv;
+
+ spin_lock(&priv->lock);
+ priv->working = 0;
+ spin_unlock(&priv->lock);
+}
+
+static const struct ieee802154_ops fakelb_ops = {
+ .owner = THIS_MODULE,
+ .xmit_sync = fakelb_hw_xmit,
+ .ed = fakelb_hw_ed,
+ .set_channel = fakelb_hw_channel,
+ .start = fakelb_hw_start,
+ .stop = fakelb_hw_stop,
+};
+
+/* 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 fakelb_priv *fake)
+{
+ struct fakelb_dev_priv *priv;
+ int err;
+ struct ieee802154_hw *hw;
+
+ hw = ieee802154_alloc_hw(sizeof(*priv), &fakelb_ops);
+ if (!hw)
+ return -ENOMEM;
+
+ priv = hw->priv;
+ priv->hw = hw;
+
+ /* 868 MHz BPSK 802.15.4-2003 */
+ hw->phy->channels_supported[0] |= 1;
+ /* 915 MHz BPSK 802.15.4-2003 */
+ hw->phy->channels_supported[0] |= 0x7fe;
+ /* 2.4 GHz O-QPSK 802.15.4-2003 */
+ hw->phy->channels_supported[0] |= 0x7FFF800;
+ /* 868 MHz ASK 802.15.4-2006 */
+ hw->phy->channels_supported[1] |= 1;
+ /* 915 MHz ASK 802.15.4-2006 */
+ hw->phy->channels_supported[1] |= 0x7fe;
+ /* 868 MHz O-QPSK 802.15.4-2006 */
+ hw->phy->channels_supported[2] |= 1;
+ /* 915 MHz O-QPSK 802.15.4-2006 */
+ hw->phy->channels_supported[2] |= 0x7fe;
+ /* 2.4 GHz CSS 802.15.4a-2007 */
+ hw->phy->channels_supported[3] |= 0x3fff;
+ /* UWB Sub-gigahertz 802.15.4a-2007 */
+ hw->phy->channels_supported[4] |= 1;
+ /* UWB Low band 802.15.4a-2007 */
+ hw->phy->channels_supported[4] |= 0x1e;
+ /* UWB High band 802.15.4a-2007 */
+ hw->phy->channels_supported[4] |= 0xffe0;
+ /* 750 MHz O-QPSK 802.15.4c-2009 */
+ hw->phy->channels_supported[5] |= 0xf;
+ /* 750 MHz MPSK 802.15.4c-2009 */
+ hw->phy->channels_supported[5] |= 0xf0;
+ /* 950 MHz BPSK 802.15.4d-2009 */
+ hw->phy->channels_supported[6] |= 0x3ff;
+ /* 950 MHz GFSK 802.15.4d-2009 */
+ hw->phy->channels_supported[6] |= 0x3ffc00;
+
+ INIT_LIST_HEAD(&priv->list);
+ priv->fake = fake;
+
+ spin_lock_init(&priv->lock);
+
+ hw->parent = dev;
+
+ err = ieee802154_register_hw(hw);
+ if (err)
+ goto err_reg;
+
+ write_lock_bh(&fake->lock);
+ list_add_tail(&priv->list, &fake->list);
+ write_unlock_bh(&fake->lock);
+
+ return 0;
+
+err_reg:
+ ieee802154_free_hw(priv->hw);
+ return err;
+}
+
+static void fakelb_del(struct fakelb_dev_priv *priv)
+{
+ write_lock_bh(&priv->fake->lock);
+ list_del(&priv->list);
+ write_unlock_bh(&priv->fake->lock);
+
+ ieee802154_unregister_hw(priv->hw);
+ ieee802154_free_hw(priv->hw);
+}
+
+static int fakelb_probe(struct platform_device *pdev)
+{
+ struct fakelb_priv *priv;
+ struct fakelb_dev_priv *dp;
+ int err = -ENOMEM;
+ int i;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(struct fakelb_priv),
+ GFP_KERNEL);
+ if (!priv)
+ goto err_alloc;
+
+ INIT_LIST_HEAD(&priv->list);
+ rwlock_init(&priv->lock);
+
+ for (i = 0; i < numlbs; i++) {
+ err = fakelb_add_one(&pdev->dev, priv);
+ if (err < 0)
+ goto err_slave;
+ }
+
+ platform_set_drvdata(pdev, priv);
+ dev_info(&pdev->dev, "added ieee802154 hardware\n");
+ return 0;
+
+err_slave:
+ list_for_each_entry(dp, &priv->list, list)
+ fakelb_del(dp);
+err_alloc:
+ return err;
+}
+
+static int fakelb_remove(struct platform_device *pdev)
+{
+ struct fakelb_priv *priv = platform_get_drvdata(pdev);
+ struct fakelb_dev_priv *dp, *temp;
+
+ list_for_each_entry_safe(dp, temp, &priv->list, list)
+ fakelb_del(dp);
+
+ 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);
+ 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/kernel/drivers/net/ieee802154/mrf24j40.c b/kernel/drivers/net/ieee802154/mrf24j40.c
new file mode 100644
index 000000000..fba2dfd91
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/mrf24j40.c
@@ -0,0 +1,827 @@
+/*
+ * Driver for Microchip MRF24J40 802.15.4 Wireless-PAN Networking controller
+ *
+ * Copyright (C) 2012 Alan Ott <alan@signal11.us>
+ * Signal 11 Software
+ *
+ * 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.
+ */
+
+#include <linux/spi/spi.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/ieee802154.h>
+#include <net/cfg802154.h>
+#include <net/mac802154.h>
+
+/* MRF24J40 Short Address Registers */
+#define REG_RXMCR 0x00 /* Receive MAC control */
+#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_TXMCR 0x11 /* Transmit MAC control */
+#define REG_PACON0 0x16 /* Power Amplifier Control */
+#define REG_PACON1 0x17 /* Power Amplifier Control */
+#define REG_PACON2 0x18 /* Power Amplifier Control */
+#define REG_TXNCON 0x1B /* Transmit Normal FIFO Control */
+#define REG_TXSTAT 0x24 /* TX MAC Status Register */
+#define REG_SOFTRST 0x2A /* Soft Reset */
+#define REG_TXSTBL 0x2E /* TX Stabilization */
+#define REG_INTSTAT 0x31 /* Interrupt Status */
+#define REG_INTCON 0x32 /* Interrupt Control */
+#define REG_GPIO 0x33 /* GPIO */
+#define REG_TRISGPIO 0x34 /* GPIO direction */
+#define REG_RFCTL 0x36 /* RF Control Mode Register */
+#define REG_BBREG1 0x39 /* Baseband Registers */
+#define REG_BBREG2 0x3A /* */
+#define REG_BBREG6 0x3E /* */
+#define REG_CCAEDTH 0x3F /* Energy Detection Threshold */
+
+/* MRF24J40 Long Address Registers */
+#define REG_RFCON0 0x200 /* RF Control Registers */
+#define REG_RFCON1 0x201
+#define REG_RFCON2 0x202
+#define REG_RFCON3 0x203
+#define REG_RFCON5 0x205
+#define REG_RFCON6 0x206
+#define REG_RFCON7 0x207
+#define REG_RFCON8 0x208
+#define REG_RSSI 0x210
+#define REG_SLPCON0 0x211 /* Sleep Clock Control Registers */
+#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_TESTMODE 0x22F /* Test mode */
+#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 mutex buffer_mutex; /* only used to protect buf */
+ struct completion tx_complete;
+ u8 *buf; /* 3 bytes. Used for SPI single-register transfers. */
+};
+
+/* 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 int write_short_reg(struct mrf24j40 *devrec, u8 reg, u8 value)
+{
+ int ret;
+ struct spi_message msg;
+ struct spi_transfer xfer = {
+ .len = 2,
+ .tx_buf = devrec->buf,
+ .rx_buf = devrec->buf,
+ };
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&xfer, &msg);
+
+ mutex_lock(&devrec->buffer_mutex);
+ devrec->buf[0] = MRF24J40_WRITESHORT(reg);
+ devrec->buf[1] = value;
+
+ ret = spi_sync(devrec->spi, &msg);
+ if (ret)
+ dev_err(printdev(devrec),
+ "SPI write Failed for short register 0x%hhx\n", reg);
+
+ mutex_unlock(&devrec->buffer_mutex);
+ return ret;
+}
+
+static int read_short_reg(struct mrf24j40 *devrec, u8 reg, u8 *val)
+{
+ int ret = -1;
+ struct spi_message msg;
+ struct spi_transfer xfer = {
+ .len = 2,
+ .tx_buf = devrec->buf,
+ .rx_buf = devrec->buf,
+ };
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&xfer, &msg);
+
+ mutex_lock(&devrec->buffer_mutex);
+ devrec->buf[0] = MRF24J40_READSHORT(reg);
+ devrec->buf[1] = 0;
+
+ ret = spi_sync(devrec->spi, &msg);
+ if (ret)
+ dev_err(printdev(devrec),
+ "SPI read Failed for short register 0x%hhx\n", reg);
+ else
+ *val = devrec->buf[1];
+
+ mutex_unlock(&devrec->buffer_mutex);
+ return ret;
+}
+
+static int read_long_reg(struct mrf24j40 *devrec, u16 reg, u8 *value)
+{
+ int ret;
+ u16 cmd;
+ struct spi_message msg;
+ struct spi_transfer xfer = {
+ .len = 3,
+ .tx_buf = devrec->buf,
+ .rx_buf = devrec->buf,
+ };
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&xfer, &msg);
+
+ cmd = MRF24J40_READLONG(reg);
+ mutex_lock(&devrec->buffer_mutex);
+ devrec->buf[0] = cmd >> 8 & 0xff;
+ devrec->buf[1] = cmd & 0xff;
+ devrec->buf[2] = 0;
+
+ ret = spi_sync(devrec->spi, &msg);
+ if (ret)
+ dev_err(printdev(devrec),
+ "SPI read Failed for long register 0x%hx\n", reg);
+ else
+ *value = devrec->buf[2];
+
+ mutex_unlock(&devrec->buffer_mutex);
+ return ret;
+}
+
+static int write_long_reg(struct mrf24j40 *devrec, u16 reg, u8 val)
+{
+ int ret;
+ u16 cmd;
+ struct spi_message msg;
+ struct spi_transfer xfer = {
+ .len = 3,
+ .tx_buf = devrec->buf,
+ .rx_buf = devrec->buf,
+ };
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&xfer, &msg);
+
+ cmd = MRF24J40_WRITELONG(reg);
+ mutex_lock(&devrec->buffer_mutex);
+ devrec->buf[0] = cmd >> 8 & 0xff;
+ devrec->buf[1] = cmd & 0xff;
+ devrec->buf[2] = val;
+
+ ret = spi_sync(devrec->spi, &msg);
+ if (ret)
+ dev_err(printdev(devrec),
+ "SPI write Failed for long register 0x%hx\n", reg);
+
+ mutex_unlock(&devrec->buffer_mutex);
+ return ret;
+}
+
+/* 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)
+{
+ int ret;
+ u16 cmd;
+ u8 lengths[2];
+ struct spi_message msg;
+ struct spi_transfer addr_xfer = {
+ .len = 2,
+ .tx_buf = devrec->buf,
+ };
+ struct spi_transfer lengths_xfer = {
+ .len = 2,
+ .tx_buf = &lengths, /* TODO: Is DMA really required for SPI? */
+ };
+ struct spi_transfer data_xfer = {
+ .len = length,
+ .tx_buf = data,
+ };
+
+ /* 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;
+ }
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&addr_xfer, &msg);
+ spi_message_add_tail(&lengths_xfer, &msg);
+ spi_message_add_tail(&data_xfer, &msg);
+
+ cmd = MRF24J40_WRITELONG(reg);
+ mutex_lock(&devrec->buffer_mutex);
+ devrec->buf[0] = cmd >> 8 & 0xff;
+ devrec->buf[1] = cmd & 0xff;
+ lengths[0] = 0x0; /* Header Length. Set to 0 for now. TODO */
+ lengths[1] = length; /* Total length */
+
+ ret = spi_sync(devrec->spi, &msg);
+ if (ret)
+ dev_err(printdev(devrec), "SPI write Failed for TX buf\n");
+
+ mutex_unlock(&devrec->buffer_mutex);
+ return ret;
+}
+
+static int mrf24j40_read_rx_buf(struct mrf24j40 *devrec,
+ u8 *data, u8 *len, u8 *lqi)
+{
+ u8 rx_len;
+ u8 addr[2];
+ u8 lqi_rssi[2];
+ u16 cmd;
+ int ret;
+ struct spi_message msg;
+ struct spi_transfer addr_xfer = {
+ .len = 2,
+ .tx_buf = &addr,
+ };
+ struct spi_transfer data_xfer = {
+ .len = 0x0, /* set below */
+ .rx_buf = data,
+ };
+ struct spi_transfer status_xfer = {
+ .len = 2,
+ .rx_buf = &lqi_rssi,
+ };
+
+ /* Get the length of the data in the RX FIFO. The length in this
+ * register exclues the 1-byte length field at the beginning. */
+ ret = read_long_reg(devrec, REG_RX_FIFO, &rx_len);
+ if (ret)
+ goto out;
+
+ /* Range check the RX FIFO length, accounting for the one-byte
+ * length field at the beginning. */
+ if (rx_len > RX_FIFO_SIZE-1) {
+ dev_err(printdev(devrec), "Invalid length read from device. Performing short read.\n");
+ rx_len = RX_FIFO_SIZE-1;
+ }
+
+ if (rx_len > *len) {
+ /* Passed in buffer wasn't big enough. Should never happen. */
+ dev_err(printdev(devrec), "Buffer not big enough. Performing short read\n");
+ rx_len = *len;
+ }
+
+ /* Set up the commands to read the data. */
+ cmd = MRF24J40_READLONG(REG_RX_FIFO+1);
+ addr[0] = cmd >> 8 & 0xff;
+ addr[1] = cmd & 0xff;
+ data_xfer.len = rx_len;
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&addr_xfer, &msg);
+ spi_message_add_tail(&data_xfer, &msg);
+ spi_message_add_tail(&status_xfer, &msg);
+
+ ret = spi_sync(devrec->spi, &msg);
+ if (ret) {
+ dev_err(printdev(devrec), "SPI RX Buffer Read Failed.\n");
+ goto out;
+ }
+
+ *lqi = lqi_rssi[0];
+ *len = rx_len;
+
+#ifdef DEBUG
+ print_hex_dump(KERN_DEBUG, "mrf24j40 rx: ",
+ DUMP_PREFIX_OFFSET, 16, 1, data, *len, 0);
+ pr_debug("mrf24j40 rx: lqi: %02hhx rssi: %02hhx\n",
+ lqi_rssi[0], lqi_rssi[1]);
+#endif
+
+out:
+ return ret;
+}
+
+static int mrf24j40_tx(struct ieee802154_hw *hw, struct sk_buff *skb)
+{
+ struct mrf24j40 *devrec = hw->priv;
+ u8 val;
+ int ret = 0;
+
+ dev_dbg(printdev(devrec), "tx packet of %d bytes\n", skb->len);
+
+ ret = write_tx_buf(devrec, 0x000, skb->data, skb->len);
+ if (ret)
+ goto err;
+
+ reinit_completion(&devrec->tx_complete);
+
+ /* Set TXNTRIG bit of TXNCON to send packet */
+ ret = read_short_reg(devrec, REG_TXNCON, &val);
+ if (ret)
+ goto err;
+ val |= 0x1;
+ /* Set TXNACKREQ if the ACK bit is set in the packet. */
+ if (skb->data[0] & IEEE802154_FC_ACK_REQ)
+ val |= 0x4;
+ write_short_reg(devrec, REG_TXNCON, val);
+
+ /* Wait for the device to send the TX complete interrupt. */
+ ret = wait_for_completion_interruptible_timeout(
+ &devrec->tx_complete,
+ 5 * HZ);
+ if (ret == -ERESTARTSYS)
+ goto err;
+ if (ret == 0) {
+ dev_warn(printdev(devrec), "Timeout waiting for TX interrupt\n");
+ ret = -ETIMEDOUT;
+ goto err;
+ }
+
+ /* Check for send error from the device. */
+ ret = read_short_reg(devrec, REG_TXSTAT, &val);
+ if (ret)
+ goto err;
+ if (val & 0x1) {
+ dev_dbg(printdev(devrec), "Error Sending. Retry count exceeded\n");
+ ret = -ECOMM; /* TODO: Better error code ? */
+ } else
+ dev_dbg(printdev(devrec), "Packet Sent\n");
+
+err:
+
+ return ret;
+}
+
+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;
+ u8 val;
+ int ret;
+
+ dev_dbg(printdev(devrec), "start\n");
+
+ ret = read_short_reg(devrec, REG_INTCON, &val);
+ if (ret)
+ return ret;
+ val &= ~(0x1|0x8); /* Clear TXNIE and RXIE. Enable interrupts */
+ write_short_reg(devrec, REG_INTCON, val);
+
+ return 0;
+}
+
+static void mrf24j40_stop(struct ieee802154_hw *hw)
+{
+ struct mrf24j40 *devrec = hw->priv;
+ u8 val;
+ int ret;
+
+ dev_dbg(printdev(devrec), "stop\n");
+
+ ret = read_short_reg(devrec, REG_INTCON, &val);
+ if (ret)
+ return;
+ val |= 0x1|0x8; /* Set TXNIE and RXIE. Disable Interrupts */
+ write_short_reg(devrec, REG_INTCON, val);
+}
+
+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) << 4 | 0x03;
+ write_long_reg(devrec, REG_RFCON0, val);
+
+ /* RF Reset */
+ ret = read_short_reg(devrec, REG_RFCTL, &val);
+ if (ret)
+ return ret;
+ val |= 0x04;
+ write_short_reg(devrec, REG_RFCTL, val);
+ val &= ~0x04;
+ write_short_reg(devrec, REG_RFCTL, val);
+
+ udelay(SET_CHANNEL_DELAY_US); /* per datasheet */
+
+ return 0;
+}
+
+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;
+
+ write_short_reg(devrec, REG_SADRH, addrh);
+ write_short_reg(devrec, 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++)
+ write_short_reg(devrec, 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;
+ write_short_reg(devrec, REG_PANIDH, panidh);
+ write_short_reg(devrec, 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;
+
+ ret = read_short_reg(devrec, REG_RXMCR, &val);
+ if (ret)
+ return ret;
+ if (filt->pan_coord)
+ val |= 0x8;
+ else
+ val &= ~0x8;
+ write_short_reg(devrec, REG_RXMCR, val);
+
+ /* 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 int mrf24j40_handle_rx(struct mrf24j40 *devrec)
+{
+ u8 len = RX_FIFO_SIZE;
+ u8 lqi = 0;
+ u8 val;
+ int ret = 0;
+ struct sk_buff *skb;
+
+ /* Turn off reception of packets off the air. This prevents the
+ * device from overwriting the buffer while we're reading it. */
+ ret = read_short_reg(devrec, REG_BBREG1, &val);
+ if (ret)
+ goto out;
+ val |= 4; /* SET RXDECINV */
+ write_short_reg(devrec, REG_BBREG1, val);
+
+ skb = dev_alloc_skb(len);
+ if (!skb) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ ret = mrf24j40_read_rx_buf(devrec, skb_put(skb, len), &len, &lqi);
+ if (ret < 0) {
+ dev_err(printdev(devrec), "Failure reading RX FIFO\n");
+ kfree_skb(skb);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* Cut off the checksum */
+ skb_trim(skb, len-2);
+
+ /* TODO: Other drivers call ieee20154_rx_irqsafe() here (eg: cc2040,
+ * also from a workqueue). I think irqsafe is not necessary here.
+ * Can someone confirm? */
+ ieee802154_rx_irqsafe(devrec->hw, skb, lqi);
+
+ dev_dbg(printdev(devrec), "RX Handled\n");
+
+out:
+ /* Turn back on reception of packets off the air. */
+ ret = read_short_reg(devrec, REG_BBREG1, &val);
+ if (ret)
+ return ret;
+ val &= ~0x4; /* Clear RXDECINV */
+ write_short_reg(devrec, REG_BBREG1, val);
+
+ return ret;
+}
+
+static const struct ieee802154_ops mrf24j40_ops = {
+ .owner = THIS_MODULE,
+ .xmit_sync = mrf24j40_tx,
+ .ed = mrf24j40_ed,
+ .start = mrf24j40_start,
+ .stop = mrf24j40_stop,
+ .set_channel = mrf24j40_set_channel,
+ .set_hw_addr_filt = mrf24j40_filter,
+};
+
+static irqreturn_t mrf24j40_isr(int irq, void *data)
+{
+ struct mrf24j40 *devrec = data;
+ u8 intstat;
+ int ret;
+
+ /* Read the interrupt status */
+ ret = read_short_reg(devrec, REG_INTSTAT, &intstat);
+ if (ret)
+ goto out;
+
+ /* Check for TX complete */
+ if (intstat & 0x1)
+ complete(&devrec->tx_complete);
+
+ /* Check for Rx */
+ if (intstat & 0x8)
+ mrf24j40_handle_rx(devrec);
+
+out:
+ return IRQ_HANDLED;
+}
+
+static int mrf24j40_hw_init(struct mrf24j40 *devrec)
+{
+ int ret;
+ u8 val;
+
+ /* Initialize the device.
+ From datasheet section 3.2: Initialization. */
+ ret = write_short_reg(devrec, REG_SOFTRST, 0x07);
+ if (ret)
+ goto err_ret;
+
+ ret = write_short_reg(devrec, REG_PACON2, 0x98);
+ if (ret)
+ goto err_ret;
+
+ ret = write_short_reg(devrec, REG_TXSTBL, 0x95);
+ if (ret)
+ goto err_ret;
+
+ ret = write_long_reg(devrec, REG_RFCON0, 0x03);
+ if (ret)
+ goto err_ret;
+
+ ret = write_long_reg(devrec, REG_RFCON1, 0x01);
+ if (ret)
+ goto err_ret;
+
+ ret = write_long_reg(devrec, REG_RFCON2, 0x80);
+ if (ret)
+ goto err_ret;
+
+ ret = write_long_reg(devrec, REG_RFCON6, 0x90);
+ if (ret)
+ goto err_ret;
+
+ ret = write_long_reg(devrec, REG_RFCON7, 0x80);
+ if (ret)
+ goto err_ret;
+
+ ret = write_long_reg(devrec, REG_RFCON8, 0x10);
+ if (ret)
+ goto err_ret;
+
+ ret = write_long_reg(devrec, REG_SLPCON1, 0x21);
+ if (ret)
+ goto err_ret;
+
+ ret = write_short_reg(devrec, REG_BBREG2, 0x80);
+ if (ret)
+ goto err_ret;
+
+ ret = write_short_reg(devrec, REG_CCAEDTH, 0x60);
+ if (ret)
+ goto err_ret;
+
+ ret = write_short_reg(devrec, REG_BBREG6, 0x40);
+ if (ret)
+ goto err_ret;
+
+ ret = write_short_reg(devrec, REG_RFCTL, 0x04);
+ if (ret)
+ goto err_ret;
+
+ ret = write_short_reg(devrec, REG_RFCTL, 0x0);
+ if (ret)
+ goto err_ret;
+
+ udelay(192);
+
+ /* Set RX Mode. RXMCR<1:0>: 0x0 normal, 0x1 promisc, 0x2 error */
+ ret = read_short_reg(devrec, REG_RXMCR, &val);
+ if (ret)
+ goto err_ret;
+
+ val &= ~0x3; /* Clear RX mode (normal) */
+
+ ret = write_short_reg(devrec, REG_RXMCR, val);
+ 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.
+ */
+ read_long_reg(devrec, REG_TESTMODE, &val);
+ val |= 0x7; /* Configure GPIO 0-2 to control amplifier */
+ write_long_reg(devrec, REG_TESTMODE, val);
+
+ read_short_reg(devrec, REG_TRISGPIO, &val);
+ val |= 0x8; /* Set GPIO3 as output. */
+ write_short_reg(devrec, REG_TRISGPIO, val);
+
+ read_short_reg(devrec, REG_GPIO, &val);
+ val |= 0x8; /* Set GPIO3 HIGH to enable U5 voltage regulator */
+ write_short_reg(devrec, REG_GPIO, val);
+
+ /* Reduce TX pwr to meet FCC requirements.
+ * From MRF24J40MC datasheet section 3.1.1
+ */
+ write_long_reg(devrec, REG_RFCON3, 0x28);
+ }
+
+ return 0;
+
+err_ret:
+ return ret;
+}
+
+static int mrf24j40_probe(struct spi_device *spi)
+{
+ int ret = -ENOMEM;
+ struct mrf24j40 *devrec;
+
+ dev_info(&spi->dev, "probe(). IRQ: %d\n", spi->irq);
+
+ devrec = devm_kzalloc(&spi->dev, sizeof(struct mrf24j40), GFP_KERNEL);
+ if (!devrec)
+ goto err_ret;
+ devrec->buf = devm_kzalloc(&spi->dev, 3, GFP_KERNEL);
+ if (!devrec->buf)
+ goto err_ret;
+
+ spi->mode = SPI_MODE_0; /* TODO: Is this appropriate for right here? */
+ if (spi->max_speed_hz > MAX_SPI_SPEED_HZ)
+ spi->max_speed_hz = MAX_SPI_SPEED_HZ;
+
+ mutex_init(&devrec->buffer_mutex);
+ init_completion(&devrec->tx_complete);
+ devrec->spi = spi;
+ spi_set_drvdata(spi, devrec);
+
+ /* Register with the 802154 subsystem */
+
+ devrec->hw = ieee802154_alloc_hw(0, &mrf24j40_ops);
+ if (!devrec->hw)
+ goto err_ret;
+
+ devrec->hw->priv = devrec;
+ devrec->hw->parent = &devrec->spi->dev;
+ devrec->hw->phy->channels_supported[0] = CHANNEL_MASK;
+ devrec->hw->flags = IEEE802154_HW_OMIT_CKSUM | IEEE802154_HW_AACK |
+ IEEE802154_HW_AFILT;
+
+ dev_dbg(printdev(devrec), "registered mrf24j40\n");
+ ret = ieee802154_register_hw(devrec->hw);
+ if (ret)
+ goto err_register_device;
+
+ ret = mrf24j40_hw_init(devrec);
+ if (ret)
+ goto err_hw_init;
+
+ ret = devm_request_threaded_irq(&spi->dev,
+ spi->irq,
+ NULL,
+ mrf24j40_isr,
+ IRQF_TRIGGER_LOW|IRQF_ONESHOT,
+ dev_name(&spi->dev),
+ devrec);
+
+ if (ret) {
+ dev_err(printdev(devrec), "Unable to get IRQ");
+ goto err_irq;
+ }
+
+ return 0;
+
+err_irq:
+err_hw_init:
+ ieee802154_unregister_hw(devrec->hw);
+err_register_device:
+ ieee802154_free_hw(devrec->hw);
+err_ret:
+ return ret;
+}
+
+static int 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? */
+
+ return 0;
+}
+
+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 = {
+ .name = "mrf24j40",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ },
+ .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");