diff options
Diffstat (limited to 'kernel/drivers/net/ieee802154')
-rw-r--r-- | kernel/drivers/net/ieee802154/Kconfig | 55 | ||||
-rw-r--r-- | kernel/drivers/net/ieee802154/Makefile | 4 | ||||
-rw-r--r-- | kernel/drivers/net/ieee802154/at86rf230.c | 1795 | ||||
-rw-r--r-- | kernel/drivers/net/ieee802154/cc2520.c | 1061 | ||||
-rw-r--r-- | kernel/drivers/net/ieee802154/fakelb.c | 282 | ||||
-rw-r--r-- | kernel/drivers/net/ieee802154/mrf24j40.c | 827 |
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"); |