This patch is a complete reimplementation of transmit and receive handling for the at86rf2xx driver.
It solves also two bugs: First: The RX_SAFE_MODE is enabled and the transceiver doesn't leave the receive state while the framebuffer isn't read by a CMD_FB command. This is useful to read out the frame and don't get into another receive or transmit state, otherwise the frame would be overwritten. The current driver do twice CMD_FB calls, the first one leaves the this protection. Second: Sometimes the CRC calculation is correct and the length field is above 127. The current mac802154 layer and filter of at86rf2xx doesn't check on this and the kernel crashes. In this case the frame is corrupted, we send the whole receive buffer to the next layer which can be useful for sniffing. In addition the irqworker is removed. Instead we do async spi calls and no scheduling is involved anymore. The transmit function is also asynchron but with a wait_for_completion handling. The mac802154 layer doesn't support asynchron transmit handling right now. The state change behaviour is now changes, before it was: 1. assert while(!STATE_TRANSITION_IN_PROGRESS) 2. state change 3. assert while(!STATE_TRANSITION_IN_PROGRESS) 4. assert once(wanted state != current state) Sometimes a unexcepted state change occurs when 4. assert was violated. The new state change behaviour is: 1. state change 2. wait state change timing according datasheet 3. assert while(wanted state != current state) This behaviour is described in the at86rf231 software programming model [0]. [0] http://www.atmel.com/Images/AVR2022_swpm231-2.0.zip Signed-off-by: Alexander Aring <alex.ar...@gmail.com> --- drivers/net/ieee802154/at86rf2xx.c | 633 ++++++++++++++++++++++--------------- 1 file changed, 370 insertions(+), 263 deletions(-) diff --git a/drivers/net/ieee802154/at86rf2xx.c b/drivers/net/ieee802154/at86rf2xx.c index ae01007..3525931 100644 --- a/drivers/net/ieee802154/at86rf2xx.c +++ b/drivers/net/ieee802154/at86rf2xx.c @@ -35,6 +35,7 @@ #include <linux/skbuff.h> #include <linux/of_gpio.h> +#include <net/ieee802154.h> #include <net/mac802154.h> #include <net/wpan-phy.h> @@ -43,29 +44,41 @@ struct at86rf2xx_local; * All timings are in us. */ struct at86rf2xx_chip_data { + u16 t_tx_on; + u16 t_tx_aret_on; + u16 t_rx_aack_on; + /* short interframe spacing time */ + u16 t_sifs; + /* long interframe spacing time */ + u16 t_lifs; + /* completion timeout for tx in msecs */ + u16 t_tx_timeout; int rssi_base_val; int (*set_channel)(struct at86rf2xx_local *, int, int); int (*get_desense_steps)(struct at86rf2xx_local *, s32); }; +#define AT86RF2XX_MAX_BUF (127 + 3) + struct at86rf2xx_local { struct spi_device *spi; - u8 buf[2]; - struct mutex bmux; - - struct work_struct irqwork; - struct completion tx_complete; - struct ieee802154_dev *dev; + struct at86rf2xx_chip_data *data; struct regmap *regmap; - spinlock_t lock; - bool irq_busy; - bool is_tx; + struct spi_message irq_msg; + struct spi_transfer irq_trx; + u8 irq_buf[AT86RF2XX_MAX_BUF]; - struct at86rf2xx_chip_data *data; bool tx_aret; + bool is_tx; + spinlock_t lock; + struct completion tx_complete; + struct sk_buff *tx_skb; + struct spi_message tx_msg; + struct spi_transfer tx_trx; + u8 tx_buf[AT86RF2XX_MAX_BUF]; }; #define RG_TRX_STATUS (0x01) @@ -412,103 +425,329 @@ static struct regmap_config at86rf2xx_regmap_spi_config = { }; static int -at86rf2xx_write_fbuf(struct at86rf2xx_local *lp, u8 *data, u8 len) +at86rf2xx_async_state_change(struct at86rf2xx_local *lp, + struct spi_message *msg, + struct spi_transfer *trx, const u8 state, + void (*complete)(void *context)) { - u8 *buf = lp->buf; - int status; - struct spi_message msg; - struct spi_transfer xfer_head = { - .len = 2, - .tx_buf = buf, - - }; - struct spi_transfer xfer_buf = { - .len = len, - .tx_buf = data, - }; - - mutex_lock(&lp->bmux); - buf[0] = CMD_WRITE | CMD_FB; - buf[1] = len + 2; /* 2 bytes for CRC that isn't written */ - - dev_vdbg(&lp->spi->dev, "buf[0] = %02x\n", buf[0]); - dev_vdbg(&lp->spi->dev, "buf[1] = %02x\n", buf[1]); - - spi_message_init(&msg); - spi_message_add_tail(&xfer_head, &msg); - spi_message_add_tail(&xfer_buf, &msg); - - status = spi_sync(lp->spi, &msg); - dev_vdbg(&lp->spi->dev, "status = %d\n", status); - if (msg.status) - status = msg.status; - - dev_vdbg(&lp->spi->dev, "status = %d\n", status); - dev_vdbg(&lp->spi->dev, "buf[0] = %02x\n", buf[0]); - dev_vdbg(&lp->spi->dev, "buf[1] = %02x\n", buf[1]); - - mutex_unlock(&lp->bmux); - return status; + u8 *tx_buf = (u8 *)trx->tx_buf; + + tx_buf[0] = (RG_TRX_STATE & CMD_REG_MASK) | CMD_REG | CMD_WRITE; + tx_buf[1] = state; + trx->len = 2; + msg->complete = complete; + return spi_async(lp->spi, msg); } static int -at86rf2xx_read_fbuf(struct at86rf2xx_local *lp, u8 *data, u8 *len, u8 *lqi) +at86rf2xx_async_state_status(struct at86rf2xx_local *lp, + struct spi_message *msg, + struct spi_transfer *trx, + void (*complete)(void *context)) { - u8 *buf = lp->buf; - int status; - struct spi_message msg; - struct spi_transfer xfer_head = { - .len = 2, - .tx_buf = buf, - .rx_buf = buf, - }; - struct spi_transfer xfer_head1 = { - .len = 2, - .tx_buf = buf, - .rx_buf = buf, - }; - struct spi_transfer xfer_buf = { - .len = 0, - .rx_buf = data, - }; - - mutex_lock(&lp->bmux); + u8 *tx_buf = (u8 *)trx->tx_buf; - buf[0] = CMD_FB; - buf[1] = 0x00; + tx_buf[0] = (RG_TRX_STATUS & CMD_REG_MASK) | CMD_REG; + trx->len = 2; + msg->complete = complete; + return spi_async(lp->spi, msg); +} + +static inline void +at86rf2xx_async_error(struct at86rf2xx_local *lp, int rc) +{ + dev_err(&lp->spi->dev, "spi_async error %d\n", rc); +} + +static void at86rf2xx_tx_complete(void *context); +static void +at86rf2xx_tx_complete_assert(void *context) +{ + struct at86rf2xx_local *lp = context; + int rc; + + udelay(lp->data->t_rx_aack_on); + rc = at86rf2xx_async_state_status(lp, &lp->irq_msg, &lp->irq_trx, + at86rf2xx_tx_complete); + if (rc) + at86rf2xx_async_error(lp, rc); +} + +static void +at86rf2xx_tx_complete(void *context) +{ + struct at86rf2xx_local *lp = context; + const u8 *buf = lp->irq_buf; + const u8 state = buf[1] & 0x1f; + int rc = 0; + + switch (state) { + case STATE_RX_AACK_ON: + complete(&lp->tx_complete); + break; + default: + rc = at86rf2xx_async_state_status(lp, &lp->irq_msg, + &lp->irq_trx, + at86rf2xx_tx_complete_assert); + } + + if (rc) + at86rf2xx_async_error(lp, rc); +} + +static void +at86rf2xx_rx(struct at86rf2xx_local *lp, + const u8 *data, u8 len) +{ + u8 lqi; + struct sk_buff *skb; + + /* read full frame buffer and invalid lqi value to lowest + * indicator if frame was is in a corrupted state. + */ + if (len < 2 || len > IEEE802154_MTU) { + lqi = 0; + len = IEEE802154_MTU; + dev_vdbg(&lp->spi->dev, "corrupted frame received\n"); + } else { + lqi = data[len]; + } + + skb = alloc_skb(IEEE802154_MTU, GFP_ATOMIC); + if (!skb) { + dev_vdbg(&lp->spi->dev, "failed to allocate sk_buff\n"); + enable_irq(lp->spi->irq); + return; + } - spi_message_init(&msg); - spi_message_add_tail(&xfer_head, &msg); + memcpy(skb_put(skb, len), data, len); + enable_irq(lp->spi->irq); + + skb_trim(skb, len - 2); /* We do not put CRC into the frame */ - status = spi_sync(lp->spi, &msg); - dev_vdbg(&lp->spi->dev, "status = %d\n", status); + ieee802154_rx_irqsafe(lp->dev, skb, lqi); +} - xfer_buf.len = *(buf + 1) + 1; - *len = buf[1]; +static void +at86rf2xx_read_frame_complete(void *context) +{ + struct at86rf2xx_local *lp = context; + const u8 *buf = lp->irq_buf; + const u8 len = buf[1]; + + at86rf2xx_rx(lp, buf + 2, len); +} + +static int +at86rf2xx_read_frame(struct at86rf2xx_local *lp) +{ + u8 *buf = lp->irq_buf; buf[0] = CMD_FB; - buf[1] = 0x00; + lp->irq_trx.len = AT86RF2XX_MAX_BUF; + lp->irq_msg.complete = at86rf2xx_read_frame_complete; + return spi_async(lp->spi, &lp->irq_msg); +} + +static int +at86rf2xx_irq_trx_end(struct at86rf2xx_local *lp) +{ + int rc; + + spin_lock(&lp->lock); + if (lp->is_tx) { + lp->is_tx = 0; + spin_unlock(&lp->lock); + enable_irq(lp->spi->irq); + return at86rf2xx_async_state_change(lp, &lp->irq_msg, + &lp->irq_trx, + STATE_RX_AACK_ON, + at86rf2xx_tx_complete_assert); + } else { + spin_unlock(&lp->lock); + rc = at86rf2xx_read_frame(lp); + if (rc) + enable_irq(lp->spi->irq); + + return rc; + } +} + +static void +at86rf2xx_irq_status(void *context) +{ + struct at86rf2xx_local *lp = context; + const u8 *buf = lp->irq_buf; + const u8 irq = buf[1]; + int rc; + + if (irq & IRQ_TRX_END) { + rc = at86rf2xx_irq_trx_end(lp); + if (rc) + at86rf2xx_async_error(lp, rc); + } else { + enable_irq(lp->spi->irq); + dev_err(&lp->spi->dev, + "not supported irq %02x received\n", irq); + } +} + +static irqreturn_t at86rf2xx_isr(int irq, void *data) +{ + struct at86rf2xx_local *lp = data; + u8 *buf = lp->irq_buf; + int rc; + + disable_irq_nosync(lp->spi->irq); + + buf[0] = (RG_IRQ_STATUS & CMD_REG_MASK) | CMD_REG; + lp->irq_trx.len = 2; + lp->irq_msg.complete = at86rf2xx_irq_status; + rc = spi_async(lp->spi, &lp->irq_msg); + if (rc) { + at86rf2xx_async_error(lp, rc); + return IRQ_NONE; + } + + return IRQ_HANDLED; +} + +static void +at86rf2xx_write_frame_complete(void *context) +{ + struct at86rf2xx_local *lp = context; + int rc; + + rc = at86rf2xx_async_state_change(lp, &lp->tx_msg, &lp->tx_trx, + STATE_BUSY_TX, NULL); + if (rc) + at86rf2xx_async_error(lp, rc); +} + +static int +at86rf2xx_write_frame(struct at86rf2xx_local *lp) +{ + u8 *buf = lp->tx_buf; + struct sk_buff *skb = lp->tx_skb; + + buf[0] = CMD_FB | CMD_WRITE; + buf[1] = skb->len + 2; + memcpy(buf + 2, skb->data, skb->len); + lp->tx_trx.len = skb->len + 2; + lp->tx_msg.complete = at86rf2xx_write_frame_complete; + return spi_async(lp->spi, &lp->tx_msg); +} + +static void at86rf2xx_xmit_tx_aret_on(void *context); +static void +at86rf2xx_xmit_tx_aret_on_assert(void *context) +{ + struct at86rf2xx_local *lp = context; + int rc; + + udelay(lp->data->t_tx_aret_on); + rc = at86rf2xx_async_state_status(lp, &lp->tx_msg, &lp->tx_trx, + at86rf2xx_xmit_tx_aret_on); + if (rc) + at86rf2xx_async_error(lp, rc); +} + +static void +at86rf2xx_xmit_tx_aret_on(void *context) +{ + struct at86rf2xx_local *lp = context; + const u8 *buf = lp->tx_buf; + const u8 state = buf[1] & 0x1f; + int rc; + + switch (state) { + case STATE_TX_ARET_ON: + rc = at86rf2xx_write_frame(lp); + break; + default: + rc = at86rf2xx_async_state_status(lp, &lp->tx_msg, &lp->tx_trx, + at86rf2xx_xmit_tx_aret_on_assert); + } + + if (rc) + at86rf2xx_async_error(lp, rc); +} + +static void at86rf2xx_xmit_tx_on(void *context); +static void +at86rf2xx_xmit_tx_on_assert(void *context) +{ + struct at86rf2xx_local *lp = context; + int rc; - spi_message_init(&msg); - spi_message_add_tail(&xfer_head1, &msg); - spi_message_add_tail(&xfer_buf, &msg); + udelay(lp->data->t_tx_on); + rc = at86rf2xx_async_state_status(lp, &lp->tx_msg, &lp->tx_trx, + at86rf2xx_xmit_tx_on); + if (rc) + at86rf2xx_async_error(lp, rc); +} - status = spi_sync(lp->spi, &msg); +static void +at86rf2xx_xmit_tx_on(void *context) +{ + struct at86rf2xx_local *lp = context; + const u8 *buf = lp->tx_buf; + const u8 state = buf[1] & 0x1f; + int rc; - if (msg.status) - status = msg.status; + switch (state) { + case STATE_TX_ON: + spin_lock(&lp->lock); + lp->is_tx = 1; + spin_unlock(&lp->lock); + if (lp->tx_aret) { + rc = at86rf2xx_async_state_change(lp, &lp->tx_msg, + &lp->tx_trx, + STATE_TX_ARET_ON, + at86rf2xx_xmit_tx_aret_on_assert); + break; + } + rc = at86rf2xx_write_frame(lp); + break; + default: + rc = at86rf2xx_async_state_status(lp, &lp->tx_msg, &lp->tx_trx, + at86rf2xx_xmit_tx_on_assert); + } - dev_vdbg(&lp->spi->dev, "status = %d\n", status); - dev_vdbg(&lp->spi->dev, "buf[0] = %02x\n", buf[0]); - dev_vdbg(&lp->spi->dev, "buf[1] = %02x\n", buf[1]); + if (rc) + at86rf2xx_async_error(lp, rc); +} + +static int +at86rf2xx_xmit(struct ieee802154_dev *dev, struct sk_buff *skb) +{ + struct at86rf2xx_local *lp = dev->priv; + int rc; - if (status) { - if (lqi && (*len > lp->buf[1])) - *lqi = data[lp->buf[1]]; + lp->tx_skb = skb; + rc = at86rf2xx_async_state_change(lp, &lp->tx_msg, &lp->tx_trx, + STATE_TX_ON, + at86rf2xx_xmit_tx_on_assert); + if (rc) { + at86rf2xx_async_error(lp, rc); + return rc; + } + rc = wait_for_completion_timeout(&lp->tx_complete, + msecs_to_jiffies(lp->data->t_tx_timeout)); + if (!rc) { + spin_lock(&lp->lock); + lp->is_tx = 0; + spin_unlock(&lp->lock); + dev_err(&lp->spi->dev, "transmit timed out\n"); } - mutex_unlock(&lp->bmux); - return status; + reinit_completion(&lp->tx_complete); + if (skb->len > 18) + udelay(lp->data->t_lifs); + else + udelay(lp->data->t_sifs); + + return 0; } static int @@ -651,92 +890,6 @@ at86rf2xx_channel(struct ieee802154_dev *dev, int page, int channel) } static int -at86rf2xx_xmit(struct ieee802154_dev *dev, struct sk_buff *skb) -{ - struct at86rf2xx_local *lp = dev->priv; - int rc; - unsigned long flags; - - spin_lock_irqsave(&lp->lock, flags); - if (lp->irq_busy) { - spin_unlock_irqrestore(&lp->lock, flags); - return -EBUSY; - } - spin_unlock_irqrestore(&lp->lock, flags); - - might_sleep(); - - rc = at86rf2xx_state(dev, STATE_FORCE_TX_ON); - if (rc) - goto err; - - spin_lock_irqsave(&lp->lock, flags); - lp->is_tx = 1; - reinit_completion(&lp->tx_complete); - spin_unlock_irqrestore(&lp->lock, flags); - - rc = at86rf2xx_write_fbuf(lp, skb->data, skb->len); - if (rc) - goto err_rx; - - if (lp->tx_aret) { - rc = at86rf2xx_write_subreg(lp, SR_TRX_CMD, STATE_TX_ARET_ON); - if (rc) - goto err_rx; - } - - rc = at86rf2xx_write_subreg(lp, SR_TRX_CMD, STATE_BUSY_TX); - if (rc) - goto err_rx; - - rc = wait_for_completion_interruptible(&lp->tx_complete); - if (rc < 0) - goto err_rx; - - return at86rf2xx_start(dev); -err_rx: - at86rf2xx_start(dev); -err: - pr_err("error: %d\n", rc); - - spin_lock_irqsave(&lp->lock, flags); - lp->is_tx = 0; - spin_unlock_irqrestore(&lp->lock, flags); - - return rc; -} - -static int at86rf2xx_rx(struct at86rf2xx_local *lp) -{ - u8 len = 128, lqi = 0; - struct sk_buff *skb; - - skb = alloc_skb(len, GFP_KERNEL); - - if (!skb) - return -ENOMEM; - - if (at86rf2xx_read_fbuf(lp, skb_put(skb, len), &len, &lqi)) - goto err; - - if (len < 2) - goto err; - - skb_trim(skb, len - 2); /* We do not put CRC into the frame */ - - ieee802154_rx_irqsafe(lp->dev, skb, lqi); - - dev_dbg(&lp->spi->dev, "READ_FBUF: %d %x\n", len, lqi); - - return 0; -err: - pr_debug("received frame is too small\n"); - - kfree_skb(skb); - return -EINVAL; -} - -static int at86rf2xx_set_hw_addr_filt(struct ieee802154_dev *dev, struct ieee802154_hw_addr_filt *filt, unsigned long changed) @@ -897,87 +1050,41 @@ static struct ieee802154_ops at86rf2xx_ops = { }; static struct at86rf2xx_chip_data at86rf233_data = { + .t_tx_on = 1, + .t_tx_aret_on = 1, + .t_rx_aack_on = 1, + .t_sifs = 192, + .t_lifs = 480, + .t_tx_timeout = 2000, .rssi_base_val = -91, .set_channel = at86rf23x_set_channel, .get_desense_steps = at86rf23x_get_desens_steps }; static struct at86rf2xx_chip_data at86rf231_data = { + .t_tx_on = 1, + .t_tx_aret_on = 1, + .t_rx_aack_on = 1, + .t_sifs = 192, + .t_lifs = 480, + .t_tx_timeout = 2000, .set_channel = at86rf23x_set_channel, .rssi_base_val = -91, .get_desense_steps = at86rf23x_get_desens_steps }; static struct at86rf2xx_chip_data at86rf212_data = { + .t_tx_on = 1, + .t_tx_aret_on = 1, + .t_rx_aack_on = 1, + .t_sifs = 192, + .t_lifs = 480, + .t_tx_timeout = 2000, .rssi_base_val = -100, .set_channel = at86rf212_set_channel, .get_desense_steps = at86rf212_get_desens_steps }; -static void at86rf2xx_irqwork(struct work_struct *work) -{ - struct at86rf2xx_local *lp = - container_of(work, struct at86rf2xx_local, irqwork); - unsigned int status; - int rc; - unsigned long flags; - - rc = at86rf2xx_read_subreg(lp, RG_IRQ_STATUS, 0xff, 0, &status); - - status &= ~IRQ_PLL_LOCK; /* ignore */ - status &= ~IRQ_RX_START; /* ignore */ - status &= ~IRQ_AMI; /* ignore */ - status &= ~IRQ_TRX_UR; /* FIXME: possibly handle ???*/ - - if (status & IRQ_TRX_END) { - status &= ~IRQ_TRX_END; - spin_lock_irqsave(&lp->lock, flags); - if (lp->is_tx) { - lp->is_tx = 0; - spin_unlock_irqrestore(&lp->lock, flags); - complete(&lp->tx_complete); - } else { - spin_unlock_irqrestore(&lp->lock, flags); - at86rf2xx_rx(lp); - } - } - - spin_lock_irqsave(&lp->lock, flags); - lp->irq_busy = 0; - spin_unlock_irqrestore(&lp->lock, flags); -} - -static void at86rf2xx_irqwork_level(struct work_struct *work) -{ - struct at86rf2xx_local *lp = - container_of(work, struct at86rf2xx_local, irqwork); - - at86rf2xx_irqwork(work); - - enable_irq(lp->spi->irq); -} - -static irqreturn_t at86rf2xx_isr(int irq, void *data) -{ - struct at86rf2xx_local *lp = data; - unsigned long flags; - - spin_lock_irqsave(&lp->lock, flags); - lp->irq_busy = 1; - spin_unlock_irqrestore(&lp->lock, flags); - - schedule_work(&lp->irqwork); - - return IRQ_HANDLED; -} - -static irqreturn_t at86rf2xx_isr_level(int irq, void *data) -{ - disable_irq_nosync(irq); - - return at86rf2xx_isr(irq, data); -} - static int at86rf2xx_hw_init(struct at86rf2xx_local *lp) { int rc, irq_pol, irq_type; @@ -1127,15 +1234,29 @@ at86rf2xx_detect_device(struct at86rf2xx_local *lp) return rc; } +static void +at86rf2xx_setup_spi_messages(struct at86rf2xx_local *lp) +{ + spi_message_init(&lp->irq_msg); + lp->irq_msg.context = lp; + 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); + + spi_message_init(&lp->tx_msg); + lp->tx_msg.context = lp; + 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); +} + static int at86rf2xx_probe(struct spi_device *spi) { struct at86rf2xx_platform_data *pdata; struct ieee802154_dev *dev; struct at86rf2xx_local *lp; unsigned int status; - irq_handler_t irq_handler; - work_func_t irq_worker; - int rc, irq_type; + int rc; if (!spi->irq) { dev_err(&spi->dev, "no IRQ specified\n"); @@ -1188,21 +1309,12 @@ static int at86rf2xx_probe(struct spi_device *spi) goto free_dev; } + at86rf2xx_setup_spi_messages(lp); + rc = at86rf2xx_detect_device(lp); if (rc < 0) goto free_dev; - irq_type = irq_get_trigger_type(spi->irq); - if (irq_type & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) { - irq_worker = at86rf2xx_irqwork; - irq_handler = at86rf2xx_isr; - } else { - irq_worker = at86rf2xx_irqwork_level; - irq_handler = at86rf2xx_isr_level; - } - - mutex_init(&lp->bmux); - INIT_WORK(&lp->irqwork, irq_worker); spin_lock_init(&lp->lock); init_completion(&lp->tx_complete); @@ -1210,27 +1322,24 @@ static int at86rf2xx_probe(struct spi_device *spi) rc = at86rf2xx_hw_init(lp); if (rc) - goto err_hw_init; + goto free_dev; /* Read irq status register to reset irq line */ rc = at86rf2xx_read_subreg(lp, RG_IRQ_STATUS, 0xff, 0, &status); if (rc) - goto err_hw_init; + goto free_dev; - rc = devm_request_irq(&spi->dev, spi->irq, irq_handler, IRQF_SHARED, + rc = devm_request_irq(&spi->dev, spi->irq, at86rf2xx_isr, IRQF_SHARED, dev_name(&spi->dev), lp); if (rc) - goto err_hw_init; + goto free_dev; rc = ieee802154_register_device(lp->dev); if (rc) - goto err_hw_init; + goto free_dev; return rc; -err_hw_init: - flush_work(&lp->irqwork); - mutex_destroy(&lp->bmux); free_dev: ieee802154_free_device(lp->dev); @@ -1244,8 +1353,6 @@ static int at86rf2xx_remove(struct spi_device *spi) /* mask all at86rf2xx irq's */ at86rf2xx_write_subreg(lp, SR_IRQ_MASK, 0); ieee802154_unregister_device(lp->dev); - flush_work(&lp->irqwork); - mutex_destroy(&lp->bmux); ieee802154_free_device(lp->dev); dev_dbg(&spi->dev, "unregistered at86rf2xx\n"); -- 2.0.0 ------------------------------------------------------------------------------ HPCC Systems Open Source Big Data Platform from LexisNexis Risk Solutions Find What Matters Most in Your Big Data with HPCC Systems Open Source. Fast. Scalable. Simple. Ideal for Dirty Data. Leverages Graph Analysis for Fast Processing & Easy Data Exploration http://p.sf.net/sfu/hpccsystems _______________________________________________ Linux-zigbee-devel mailing list Linux-zigbee-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/linux-zigbee-devel