text: /****************************************************************************
   * drivers/wireless/nrf24l01/nrf24l01.c
   *
   *   Copyright (C) 2013 Laurent Latil. All rights reserved.
   *   Authors: Laurent Latil <laurent@latil.nom.fr>
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   * 3. Neither the name NuttX nor the names of its contributors may be
   *    used to endorse or promote products derived from this software
   *    without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
   * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
   * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   * POSSIBILITY OF SUCH DAMAGE.
   *
   ****************************************************************************/
  
  /* Features:
   *   - Fixed length and dynamically sized payloads  (1 - 32 bytes)
   *   - Management of the 6 receiver pipes
   *   - Configuration of each pipe: address, packet length, auto-acknowledge, etc.
   *   - Use a FIFO buffer to store the received packets
   *
   * Todo:
   *   - Add support for payloads in ACK packets  (?)
   *   - Add compatibility with nRF24L01 (not +)  hardware  (?)
   *
   */
  
  /****************************************************************************
   * Included Files
   ****************************************************************************/
  
  #include <nuttx/config.h>
  #include <assert.h>
  #include <stdlib.h>
  #include <string.h>
  #include <errno.h>
  #include <stdio.h>
  #include <unistd.h>
  #include <semaphore.h>
  #include <poll.h>
  #include <debug.h>
  
  #include <nuttx/kmalloc.h>
  
  #ifdef CONFIG_WL_NRF24L01_RXSUPPORT
  #  include <nuttx/wqueue.h>
  #endif
  
  #include <nuttx/wireless/nrf24l01.h>
  #include "nrf24l01.h"
  
  /****************************************************************************
   * Pre-processor Definitions
   ****************************************************************************/
  
  #ifndef CONFIG_WL_NRF24L01_DFLT_ADDR_WIDTH
  #  define CONFIG_WL_NRF24L01_DFLT_ADDR_WIDTH 5
  #endif
  
  #ifndef CONFIG_WL_NRF24L01_RXFIFO_LEN
  #  define CONFIG_WL_NRF24L01_RXFIFO_LEN 128
  #endif
  
  #ifdef CONFIG_WL_NRF24L01_CHECK_PARAMS
  #  define CHECK_ARGS(cond) do { if (!(cond)) return -EINVAL; } while (0)
  #else
  #  define CHECK_ARGS(cond)
  #endif
  
  /* Default SPI bus frequency (in Hz) */
  #define NRF24L01_SPIFREQ   9000000 /* Can go up to 10 Mbs according to datasheet */
  
  /* power-down -> standby transition timing (in us).  Note: this value is probably larger than required. */
  #define NRF24L01_TPD2STBY_DELAY  4500
  
  #define FIFO_PKTLEN_MASK  0x1F   /* 5 ls bits used to store packet length */
  #define FIFO_PKTLEN_SHIFT 0
  #define FIFO_PIPENO_MASK  0xE0   /* 3 ms bits used to store pipe # */
  #define FIFO_PIPENO_SHIFT 4
  
  #define FIFO_PKTLEN(dev) (((dev->rx_fifo[dev->nxt_read] & FIFO_PKTLEN_MASK) >> FIFO_PKTLEN_SHIFT) + 1)
  #define FIFO_PIPENO(dev) (((dev->rx_fifo[dev->nxt_read] & FIFO_PIPENO_MASK) >> FIFO_PIPENO_SHIFT))
  #define FIFO_HEADER(pktlen,pipeno) ((pktlen - 1) | (pipeno << FIFO_PIPENO_SHIFT))
  
  #define DEV_NAME   "/dev/nrf24l01"
  
  /****************************************************************************
   * Private Data Types
   ****************************************************************************/
  
  typedef enum
  {
    MODE_READ,
    MODE_WRITE
  } nrf24l01_access_mode_t;
  
  #define FL_AA_ENABLED  (1 << 0)
  
  struct nrf24l01_dev_s
  {
    FAR struct spi_dev_s *spi;            /* Reference to SPI bus device */
    FAR struct nrf24l01_config_s *config; /* Board specific GPIO functions */
  
    nrf24l01_state_t state;       /* Current state of the nRF24L01 */
  
    uint8_t en_aa;            /* Cache EN_AA register value */
    uint8_t en_pipes;         /* Cache EN_RXADDR register value */
    bool ce_enabled;          /* Cache the value of CE pin */
    uint8_t lastxmitcount;    /* Retransmit count of the last succeeded AA transmission */
    uint8_t addrlen;          /* Address width (3-5) */
    uint8_t pipedatalen[NRF24L01_PIPE_COUNT];
  
    uint8_t pipe0addr[NRF24L01_MAX_ADDR_LEN];  /* Configured address on pipe 0 */
  
    uint8_t last_recvpipeno;
    sem_t sem_tx;
  
  #ifdef CONFIG_WL_NRF24L01_RXSUPPORT
    uint8_t *rx_fifo;         /* Circular RX buffer.  [pipe# / pkt_len] [packet data...] */
    uint16_t fifo_len;        /* Number of bytes stored in fifo */
    uint16_t nxt_read;        /* Next read index */
    uint16_t nxt_write;       /* Next write index */
    sem_t sem_fifo;           /* Protect access to rx fifo */
    sem_t sem_rx;             /* Wait for availability of received data */
  
    struct work_s irq_work;   /* Interrupt handling "bottom half" */
  #endif
  
    uint8_t nopens;           /* Number of times the device has been opened */
    sem_t devsem;             /* Ensures exclusive access to this structure */
  #ifndef CONFIG_DISABLE_POLL
    FAR struct pollfd *pfd;   /* Polled file descr  (or NULL if any) */
  #endif
  };
  
  /****************************************************************************
   * Private Function Prototypes
   ****************************************************************************/
  /* Low-level SPI helpers */
  
  static inline void nrf24l01_configspi(FAR struct spi_dev_s *spi);
  static void nrf24l01_lock(FAR struct spi_dev_s *spi);
  static void nrf24l01_unlock(FAR struct spi_dev_s *spi);
  
  static uint8_t nrf24l01_access(FAR struct nrf24l01_dev_s *dev,
      nrf24l01_access_mode_t mode, uint8_t cmd, uint8_t *buf, int length);
  static uint8_t nrf24l01_flush_rx(FAR struct nrf24l01_dev_s *dev);
  static uint8_t nrf24l01_flush_tx(FAR struct nrf24l01_dev_s *dev);
  
  /* Read register from nrf24 */
  
  static uint8_t nrf24l01_readreg(FAR struct nrf24l01_dev_s *dev, uint8_t reg,
      uint8_t *value, int len);
  
  /* Read single byte value from a register of nrf24 */
  
  static uint8_t nrf24l01_readregbyte(FAR struct nrf24l01_dev_s *dev,
      uint8_t reg);
  
  static void nrf24l01_writeregbyte(FAR struct nrf24l01_dev_s *dev, uint8_t reg,
      uint8_t value);
  
  static uint8_t nrf24l01_setregbit(FAR struct nrf24l01_dev_s *dev, uint8_t reg,
      uint8_t value, bool set);
  
  static void nrf24l01_tostate(FAR struct nrf24l01_dev_s *dev, nrf24l01_state_t state);
  
  static int nrf24l01_irqhandler(FAR int irq, FAR void *context);
  
  static inline int nrf24l01_attachirq(FAR struct nrf24l01_dev_s *dev, xcpt_t isr);
  
  static int dosend(FAR struct nrf24l01_dev_s *dev, FAR const uint8_t *data, size_t datalen);
  
  static int nrf24l01_unregister(FAR struct nrf24l01_dev_s *dev);
  
  #ifdef CONFIG_WL_NRF24L01_RXSUPPORT
  
  void fifoput(struct nrf24l01_dev_s *dev, uint8_t pipeno, uint8_t *buffer, uint8_t buflen);
  
  uint8_t fifoget(struct nrf24l01_dev_s *dev, uint8_t *buffer, uint8_t buflen, uint8_t *pipeno);
  
  static void nrf24l01_worker(FAR void *arg);
  
  #endif
  
  /* POSIX API */
  
  static int nrf24l01_open(FAR struct file *filep);
  
  static int nrf24l01_close(FAR struct file *filep);
  
  static ssize_t nrf24l01_read(FAR struct file *filep, FAR char *buffer, size_t buflen);
  
  static ssize_t nrf24l01_write(FAR struct file *filep, FAR const char *buffer, size_t buflen);
  
  static int nrf24l01_ioctl(FAR struct file *filep, int cmd, unsigned long arg);
  
  static int nrf24l01_poll(FAR struct file *filep, FAR struct pollfd *fds,
                          bool setup);
  
  /****************************************************************************
   * Private Data
   ****************************************************************************/
  
  static FAR struct nrf24l01_dev_s *g_nrf24l01dev;
  
  static const struct file_operations nrf24l01_fops =
  {
    nrf24l01_open,    /* open */
    nrf24l01_close,   /* close */
    nrf24l01_read,    /* read */
    nrf24l01_write,   /* write */
    NULL,             /* seek */
    nrf24l01_ioctl,   /* ioctl */
  #ifndef CONFIG_DISABLE_POLL
    nrf24l01_poll,    /* poll */
  #endif
    NULL              /* unlink */
  };
  
  /****************************************************************************
   * Private Functions
   ****************************************************************************/
  
  static void nrf24l01_lock(FAR struct spi_dev_s *spi)
  {
    /* Lock the SPI bus because there are multiple devices competing for the
     * SPI bus
     */
  
    (void)SPI_LOCK(spi, true);
  
    /* We have the lock.  Now make sure that the SPI bus is configured for the
     * NRF24L01 (it might have gotten configured for a different device while
     * unlocked)
     */
  
    SPI_SELECT(spi, SPIDEV_WIRELESS, true);
    SPI_SETMODE(spi, SPIDEV_MODE0);
    SPI_SETBITS(spi, 8);
    (void)SPI_HWFEATURES(spi, 0);
    (void)SPI_SETFREQUENCY(spi, NRF24L01_SPIFREQ);
    SPI_SELECT(spi, SPIDEV_WIRELESS, false);
  }
  
  /****************************************************************************
   * Function: nrf24l01_unlock
   *
   * Description:
   *   Un-lock the SPI bus after each transfer, possibly losing the current
   *   configuration if we are sharing the SPI bus with other devices.
   *
   * Parameters:
   *   spi  - Reference to the SPI driver structure
   *
   * Returned Value:
   *   None
   *
   * Assumptions:
   *
   ****************************************************************************/
  
  static void nrf24l01_unlock(FAR struct spi_dev_s *spi)
  {
    /* Relinquish the SPI bus. */
  
    (void)SPI_LOCK(spi, false);
  }
  
  /****************************************************************************
   * Function: nrf24l01_configspi
   *
   * Description:
   *   Configure the SPI for use with the NRF24L01.
   *
   * Parameters:
   *   spi  - Reference to the SPI driver structure
   *
   * Returned Value:
   *   None
   *
   * Assumptions:
   *
   ****************************************************************************/
  
  static inline void nrf24l01_configspi(FAR struct spi_dev_s *spi)
  {
    /* Configure SPI for the NRF24L01 module. */
  
    SPI_SELECT(spi, SPIDEV_WIRELESS, true);  /* Useful ? */
    SPI_SETMODE(spi, SPIDEV_MODE0);
    SPI_SETBITS(spi, 8);
    (void)SPI_HWFEATURES(spi, 0);
    (void)SPI_SETFREQUENCY(spi, NRF24L01_SPIFREQ);
    SPI_SELECT(spi, SPIDEV_WIRELESS, false);
  }
  
  static inline void nrf24l01_select(struct nrf24l01_dev_s * dev)
  {
    SPI_SELECT(dev->spi, SPIDEV_WIRELESS, true);
  }
  
  static inline void nrf24l01_deselect(struct nrf24l01_dev_s * dev)
  {
    SPI_SELECT(dev->spi, SPIDEV_WIRELESS, false);
  }
  
  static uint8_t nrf24l01_access(FAR struct nrf24l01_dev_s *dev,
      nrf24l01_access_mode_t mode, uint8_t cmd, FAR uint8_t *buf, int length)
  {
    uint8_t status;
  
    /* Prepare SPI */
  
    nrf24l01_select(dev);
  
    /* Transfer */
  
    status = SPI_SEND(dev->spi, cmd);
  
    switch (mode)
      {
      case MODE_WRITE:
        if (length > 0)
          {
            SPI_SNDBLOCK(dev->spi, buf, length);
          }
        break;
  
      case MODE_READ:
        SPI_RECVBLOCK(dev->spi, buf, length);
        break;
      }
  
    nrf24l01_deselect(dev);
    return status;
  }
  
  static inline uint8_t nrf24l01_flush_rx(struct nrf24l01_dev_s *dev)
  {
    return nrf24l01_access(dev, MODE_WRITE, NRF24L01_FLUSH_RX, NULL, 0);
  }
  
  static inline uint8_t nrf24l01_flush_tx(struct nrf24l01_dev_s *dev)
  {
    return nrf24l01_access(dev, MODE_WRITE, NRF24L01_FLUSH_TX, NULL, 0);
  }
  
  /* Read register from nrf24l01 */
  
  static inline uint8_t nrf24l01_readreg(struct nrf24l01_dev_s *dev, uint8_t reg,
      uint8_t *value, int len)
  {
    return nrf24l01_access(dev, MODE_READ, reg | NRF24L01_R_REGISTER, value, len);
  }
  
  /* Read single byte value from a register of nrf24l01 */
  
  static inline uint8_t nrf24l01_readregbyte(struct nrf24l01_dev_s *dev,
      uint8_t reg)
  {
    uint8_t val;
    nrf24l01_readreg(dev, reg, &val, 1);
    return val;
  }
  
  /* Write value to a register of nrf24l01 */
  
  static inline int nrf24l01_writereg(FAR struct nrf24l01_dev_s *dev, uint8_t reg,
      FAR const uint8_t *value, int len)
  {
    return nrf24l01_access(dev, MODE_WRITE, reg | NRF24L01_W_REGISTER, (FAR uint8_t *)value, len);
  }
  
  /* Write single byte value to a register of nrf24l01 */
  
  static inline void nrf24l01_writeregbyte(struct nrf24l01_dev_s *dev, uint8_t reg,
      uint8_t value)
  {
    nrf24l01_writereg(dev, reg, &value, 1);
  }
  
  static uint8_t nrf24l01_setregbit(struct nrf24l01_dev_s *dev, uint8_t reg,
      uint8_t value, bool set)
  {
    uint8_t val;
  
    nrf24l01_readreg(dev, reg, &val, 1);
    if (set)
      {
        val |= value;
      }
    else
      {
        val &= ~value;
      }
  
    nrf24l01_writereg(dev, reg, &val, 1);
    return val;
  }
  
  #ifdef CONFIG_WL_NRF24L01_RXSUPPORT
  
  /* RX fifo mgt */
  
  void fifoput(struct nrf24l01_dev_s *dev, uint8_t pipeno, uint8_t *buffer, uint8_t buflen)
  {
    sem_wait(&dev->sem_fifo);
    while (dev->fifo_len + buflen + 1 > CONFIG_WL_NRF24L01_RXFIFO_LEN)
      {
        /* TODO: Set fifo overrun flag ! */
  
        int skiplen = FIFO_PKTLEN(dev) + 1;
  
        dev->nxt_read = (dev->nxt_read + skiplen) % CONFIG_WL_NRF24L01_RXFIFO_LEN;
        dev->fifo_len -= skiplen;
      }
  
    dev->rx_fifo[dev->nxt_write] = FIFO_HEADER(buflen, pipeno);
    dev->nxt_write = (dev->nxt_write + 1) % CONFIG_WL_NRF24L01_RXFIFO_LEN;
  
    /* Adjust fifo bytes count */
  
    dev->fifo_len += (buflen + 1);
    while (buflen--)
      {
        dev->rx_fifo[dev->nxt_write] = *(buffer++);
        dev->nxt_write = (dev->nxt_write + 1) % CONFIG_WL_NRF24L01_RXFIFO_LEN;
      }
  
    sem_post(&dev->sem_fifo);
  }
  
  uint8_t fifoget(struct nrf24l01_dev_s *dev, uint8_t *buffer, uint8_t buflen, uint8_t *pipeno)
  {
    uint8_t pktlen;
    uint8_t i;
  
    sem_wait(&dev->sem_fifo);
  
    ASSERT(dev->fifo_len > 0);
  
    pktlen = FIFO_PKTLEN(dev);
    if (NULL != pipeno)
      {
        *pipeno = FIFO_PIPENO(dev);
      }
  
    dev->nxt_read = (dev->nxt_read + 1) % CONFIG_WL_NRF24L01_RXFIFO_LEN;
  
    for (i = 0; i < pktlen && i < buflen; i++)
      {
        *(buffer++) = dev->rx_fifo[dev->nxt_read];
        dev->nxt_read = (dev->nxt_read + 1) % CONFIG_WL_NRF24L01_RXFIFO_LEN;
      }
  
    if (i < pktlen)
      {
        dev->nxt_read = (dev->nxt_read + pktlen - i) % CONFIG_WL_NRF24L01_RXFIFO_LEN;
      }
  
    /* Adjust fifo bytes count */
  
    dev->fifo_len -= (pktlen + 1);
  
    sem_post(&dev->sem_fifo);
    return pktlen;
  }
  
  #endif
  
  static int nrf24l01_irqhandler(int irq, FAR void *context)
  {
    FAR struct nrf24l01_dev_s *dev = g_nrf24l01dev;
  
    wllvdbg("*IRQ*");
  
  #ifdef CONFIG_WL_NRF24L01_RXSUPPORT
  
    /* If RX is enabled we delegate the actual work to bottom-half handler */
  
    work_queue(HPWORK, &g_nrf24l01dev->irq_work, nrf24l01_worker, dev, 0);
  #else
  
    /* Otherwise we simply wake up the send function */
  
    sem_post(&dev->sem_tx);  /* Wake up the send function */
  #endif
  
    return OK;
  }
  
  /* Configure IRQ pin  (falling edge) */
  
  static inline int nrf24l01_attachirq(FAR struct nrf24l01_dev_s *dev, xcpt_t isr)
  {
    return dev->config->irqattach(isr);
  }
  
  static inline bool nrf24l01_chipenable(FAR struct nrf24l01_dev_s *dev, bool enable)
  {
    if (dev->ce_enabled != enable)
      {
        dev->config->chipenable(enable);
        dev->ce_enabled = enable;
        return !enable;
      }
    else
      {
        return enable;
      }
  }
  
  #ifdef CONFIG_WL_NRF24L01_RXSUPPORT
  
  static void nrf24l01_worker(FAR void *arg)
  {
    FAR struct nrf24l01_dev_s *dev = (FAR struct nrf24l01_dev_s *) arg;
    uint8_t status;
    uint8_t fifo_status;
  
    nrf24l01_lock(dev->spi);
  
    status = nrf24l01_readregbyte(dev, NRF24L01_STATUS);
  
    if (status & NRF24L01_RX_DR)
      {
        /* put CE low */
  
        bool ce = nrf24l01_chipenable(dev, false);
  
        wdbg("RX_DR is set!\n");
  
        /* Read and store all received payloads */
  
        do
          {
            uint8_t pipeno;
            uint8_t pktlen;
            uint8_t buf[NRF24L01_MAX_PAYLOAD_LEN];
  
            /* For each packet:
             *   - Get pipe #
             *   - Get payload length  (either static or dynamic)
             *   - Read payload content
             */
  
            pipeno = (status & NRF24L01_RX_P_NO_MASK) >> NRF24L01_RX_P_NO_SHIFT;
  
            pktlen = dev->pipedatalen[pipeno];
            if (NRF24L01_DYN_LENGTH == pktlen)
              {
                /* If dynamic length payload need to use R_RX_PL_WID command to get actual length */
  
                nrf24l01_access(dev, MODE_READ, NRF24L01_R_RX_PL_WID, &pktlen, 1);
              }
  
            /* Get payload content */
  
            nrf24l01_access(dev, MODE_READ, NRF24L01_R_RX_PAYLOAD, buf, pktlen);
  
            fifoput(dev, pipeno, buf, pktlen);
            sem_post(&dev->sem_rx);  /* Wake-up any thread waiting in recv */
  
            status = nrf24l01_readreg(dev, NRF24L01_FIFO_STATUS, &fifo_status, 1);
  
            wdbg("FIFO_STATUS=%02x\n", fifo_status);
            wdbg("STATUS=%02x\n", status);
          }
        while (!(fifo_status | NRF24L01_RX_EMPTY));
  
        /* Clear interrupt sources */
  
        nrf24l01_writeregbyte(dev, NRF24L01_STATUS, NRF24L01_RX_DR);
  
        /* Restore CE */
  
        nrf24l01_chipenable(dev, ce);
  
  #ifndef CONFIG_DISABLE_POLL
        if (dev->pfd)
          {
            dev->pfd->revents |= POLLIN;  /* Data available for input */
  
            wvdbg("Wake up polled fd");
            sem_post(dev->pfd->sem);
          }
  #endif
      }
  
    if (status & (NRF24L01_TX_DS | NRF24L01_MAX_RT))
      {
        /* The actual work is done in the send function */
  
        sem_post(&dev->sem_tx);
      }
  
    if (dev->state == ST_RX)
      {
        /* re-enable CE   (to go back to RX mode state) */
  
        nrf24l01_chipenable(dev, true);
      }
    nrf24l01_unlock(dev->spi);
  }
  
  #endif
  
  static void nrf24l01_tostate(struct nrf24l01_dev_s *dev, nrf24l01_state_t state)
  {
    nrf24l01_state_t oldstate = dev->state;
  
    if (oldstate == state)
      {
        return;
      }
  
    if (oldstate == ST_POWER_DOWN)
      {
        /* Leaving power down   (note: new state cannot be power down here) */
  
        nrf24l01_setregbit(dev, NRF24L01_CONFIG, NRF24L01_PWR_UP, true);
        usleep(NRF24L01_TPD2STBY_DELAY);
      }
  
    /* Entering new state */
  
    switch (state)
      {
      case ST_UNKNOWN:
        /* Power down the module here... */
      case ST_POWER_DOWN:
        nrf24l01_chipenable(dev, false);
        nrf24l01_setregbit(dev, NRF24L01_CONFIG, NRF24L01_PWR_UP, false);
        break;
  
      case ST_STANDBY:
        nrf24l01_chipenable(dev, false);
        nrf24l01_setregbit(dev, NRF24L01_CONFIG, NRF24L01_PRIM_RX, false);
        break;
  
  #ifdef CONFIG_WL_NRF24L01_RXSUPPORT
      case ST_RX:
        nrf24l01_setregbit(dev, NRF24L01_CONFIG, NRF24L01_PRIM_RX, true);
        nrf24l01_chipenable(dev, true);
        break;
  #endif
      }
  
    dev->state = state;
  }
  
  static int dosend(FAR struct nrf24l01_dev_s *dev, FAR const uint8_t *data, size_t datalen)
  {
    uint8_t status;
    uint8_t obsvalue;
    int result;
  
    /* Store the current lifecycle state in order to restore it after transmit done */
  
    nrf24l01_state_t prevstate = dev->state;
  
    nrf24l01_tostate(dev, ST_STANDBY);
  
    /* Write payload */
  
    nrf24l01_access(dev, MODE_WRITE, NRF24L01_W_TX_PAYLOAD, (FAR uint8_t *)data, datalen);
  
    /* Enable CE to start transmission */
  
    nrf24l01_chipenable(dev, true);
  
    /* Free the SPI bus during the IRQ wait */
  
    nrf24l01_unlock(dev->spi);
  
    /* Wait for IRQ  (TX_DS or MAX_RT) */
  
    while (sem_wait(&dev->sem_tx) != 0)
      {
        /* Note that we really need to wait here, as the interrupt source
         * (either TX_DS in case of success, or MAX_RT for failure) needs to be cleared.
         */
  
        DEBUGASSERT(errno == EINTR);
      }
  
    /* Re-acquire the SPI bus */
  
    nrf24l01_lock(dev->spi);
  
    status = nrf24l01_readreg(dev, NRF24L01_OBSERVE_TX, &obsvalue, 1);
    if (status & NRF24L01_TX_DS)
      {
        /* transmit OK */
  
        result = OK;
        dev->lastxmitcount = (obsvalue & NRF24L01_ARC_CNT_MASK)
            >> NRF24L01_ARC_CNT_SHIFT;
  
        wvdbg("Transmission OK (lastxmitcount=%d)\n", dev->lastxmitcount);
      }
    else if (status & NRF24L01_MAX_RT)
      {
        wvdbg("MAX_RT!\n", dev->lastxmitcount);
        result = -ECOMM;
        dev->lastxmitcount = NRF24L01_XMIT_MAXRT;
  
        /* If no ACK packet is received the payload remains in TX fifo.  We need to flush it. */
  
        nrf24l01_flush_tx(dev);
      }
    else
      {
        /* Unexpected... */
  
        wdbg("No TX_DS nor MAX_RT bit set in STATUS reg!\n");
        result = -EIO;
      }
  
    /* Clear interrupt sources */
  
    nrf24l01_writeregbyte(dev, NRF24L01_STATUS, NRF24L01_TX_DS | NRF24L01_MAX_RT);
  
    /* Restore state */
  
    nrf24l01_tostate(dev, prevstate);
    return result;
  }
  
  /* POSIX API */
  
  static int nrf24l01_open(FAR struct file *filep)
  {
    FAR struct inode *inode;
    FAR struct nrf24l01_dev_s *dev;
    int result;
  
    wvdbg("Opening nRF24L01 dev\n");
  
    DEBUGASSERT(filep);
    inode = filep->f_inode;
  
    DEBUGASSERT(inode && inode->i_private);
    dev = (FAR struct nrf24l01_dev_s *)inode->i_private;
  
    /* Get exclusive access to the driver data structure */
  
    if (sem_wait(&dev->devsem) < 0)
      {
        /* This should only happen if the wait was canceled by an signal */
  
        DEBUGASSERT(errno == EINTR);
        return -EINTR;
      }
  
    /* Check if device is not already used */
  
    if (dev->nopens > 0)
      {
        result = -EBUSY;
        goto errout;
      }
  
    result = nrf24l01_init(dev);
    if (!result)
      {
        dev->nopens++;
      }
  
  errout:
    sem_post(&dev->devsem);
    return result;
  }
  
  static int nrf24l01_close(FAR struct file *filep)
  {
    FAR struct inode *inode;
    FAR struct nrf24l01_dev_s *dev;
  
    wvdbg("Closing nRF24L01 dev\n");
    DEBUGASSERT(filep);
    inode = filep->f_inode;
  
    DEBUGASSERT(inode && inode->i_private);
    dev  = (FAR struct nrf24l01_dev_s *)inode->i_private;
  
    /* Get exclusive access to the driver data structure */
  
    if (sem_wait(&dev->devsem) < 0)
      {
        /* This should only happen if the wait was canceled by an signal */
  
        DEBUGASSERT(errno == EINTR);
        return -EINTR;
      }
  
    nrf24l01_changestate(dev, ST_POWER_DOWN);
    dev->nopens--;
  
    sem_post(&dev->devsem);
    return OK;
  }
  
  static ssize_t nrf24l01_read(FAR struct file *filep, FAR char *buffer, size_t buflen)
  {
  #ifndef CONFIG_WL_NRF24L01_RXSUPPORT
    return -ENOSYS;
  #else
    FAR struct nrf24l01_dev_s *dev;
    FAR struct inode *inode;
    int result;
  
    DEBUGASSERT(filep);
    inode = filep->f_inode;
  
    DEBUGASSERT(inode && inode->i_private);
    dev = (FAR struct nrf24l01_dev_s *)inode->i_private;
  
    if (sem_wait(&dev->devsem) < 0)
      {
        /* This should only happen if the wait was canceled by an signal */
  
        DEBUGASSERT(errno == EINTR);
        return -EINTR;
      }
    result = nrf24l01_recv(dev, (uint8_t *)buffer, buflen, &dev->last_recvpipeno);
  
    sem_post(&dev->devsem);
    return result;
  #endif
  }
  
  static ssize_t nrf24l01_write(FAR struct file *filep, FAR const char *buffer, size_t buflen)
  {
    FAR struct nrf24l01_dev_s *dev;
    FAR struct inode *inode;
    int result;
  
    DEBUGASSERT(filep);
    inode = filep->f_inode;
  
    DEBUGASSERT(inode && inode->i_private);
    dev = (FAR struct nrf24l01_dev_s *)inode->i_private;
  
    if (sem_wait(&dev->devsem) < 0)
      {
        /* This should only happen if the wait was canceled by an signal */
  
        DEBUGASSERT(errno == EINTR);
        return -EINTR;
      }
  
    result = nrf24l01_send(dev, (const uint8_t *)buffer, buflen);
  
    sem_post(&dev->devsem);
    return result;
  }
  
  static int nrf24l01_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
  {
    FAR struct inode *inode;
    FAR struct nrf24l01_dev_s *dev;
    int result = OK;
  
    wvdbg("cmd: %d arg: %ld\n", cmd, arg);
    DEBUGASSERT(filep);
    inode = filep->f_inode;
  
    DEBUGASSERT(inode && inode->i_private);
    dev  = (FAR struct nrf24l01_dev_s *)inode->i_private;
  
    /* Get exclusive access to the driver data structure */
  
    if (sem_wait(&dev->devsem) < 0)
      {
        /* This should only happen if the wait was canceled by an signal */
  
        DEBUGASSERT(errno == EINTR);
        return -EINTR;
      }
  
    /* Process the IOCTL by command */
  
    switch (cmd)
      {
        case WLIOC_SETRADIOFREQ:  /* Set radio frequency. Arg: Pointer to uint32_t frequency value */
          {
            FAR uint32_t *ptr = (FAR uint32_t *)((uintptr_t)arg);
            DEBUGASSERT(ptr != NULL);
  
            nrf24l01_setradiofreq(dev, *ptr);
          }
          break;
  
        case WLIOC_GETRADIOFREQ:  /* Get current radio frequency. arg: Pointer to uint32_t frequency value */
          {
            FAR uint32_t *ptr = (FAR uint32_t *)((uintptr_t)arg);
            DEBUGASSERT(ptr != NULL);
            *ptr = nrf24l01_getradiofreq(dev);
          }
          break;
  
        case NRF24L01IOC_SETTXADDR:  /* Set current TX addr. arg: Pointer to uint8_t array defining the address */
          {
            FAR const uint8_t *addr = (FAR const uint8_t *)(arg);
            DEBUGASSERT(addr != NULL);
            nrf24l01_settxaddr(dev, addr);
          }
          break;
  
        case NRF24L01IOC_GETTXADDR:  /* Get current TX addr. arg: Pointer to uint8_t array defining the address */
          {
            FAR uint8_t *addr = (FAR uint8_t *)(arg);
            DEBUGASSERT(addr != NULL);
            nrf24l01_gettxaddr(dev, addr);
          }
          break;
  
        case WLIOC_SETTXPOWER:  /* Set current radio frequency. arg: Pointer to int32_t, output power */
          {
            FAR int32_t *ptr = (FAR int32_t *)(arg);
            DEBUGASSERT(ptr != NULL);
            nrf24l01_settxpower(dev, *ptr);
          }
          break;
  
        case WLIOC_GETTXPOWER:  /* Get current radio frequency. arg: Pointer to int32_t, output power */
          {
            FAR int32_t *ptr = (FAR int32_t *)(arg);
            DEBUGASSERT(ptr != NULL);
            *ptr = nrf24l01_gettxpower(dev);
          }
          break;
  
        case NRF24L01IOC_SETRETRCFG:  /* Set retransmit params. arg: Pointer to nrf24l01_retrcfg_t */
          {
            FAR nrf24l01_retrcfg_t *ptr = (FAR nrf24l01_retrcfg_t *)(arg);
            DEBUGASSERT(ptr != NULL);
            nrf24l01_setretransmit(dev, ptr->delay, ptr->count);
          }
          break;
  
        case NRF24L01IOC_GETRETRCFG:  /* Get retransmit params. arg: Pointer to nrf24l01_retrcfg_t */
          result = -ENOSYS;  /* TODO */
          break;
  
        case NRF24L01IOC_SETPIPESCFG:
          {
            int i;
            FAR nrf24l01_pipecfg_t **cfg_array = (FAR nrf24l01_pipecfg_t **)(arg);
  
            DEBUGASSERT(cfg_array != NULL);
            for (i = 0; i < NRF24L01_PIPE_COUNT; i++)
              {
                if (cfg_array[i])
                  {
                    nrf24l01_setpipeconfig(dev, i, cfg_array[i]);
                  }
              }
          }
          break;
  
        case NRF24L01IOC_GETPIPESCFG:
          {
            int i;
            FAR nrf24l01_pipecfg_t **cfg_array = (FAR nrf24l01_pipecfg_t **)(arg);
  
            DEBUGASSERT(cfg_array != NULL);
            for (i = 0; i < NRF24L01_PIPE_COUNT; i++)
              {
                if (cfg_array[i])
                  {
                    nrf24l01_getpipeconfig(dev, i, cfg_array[i]);
                  }
              }
          }
          break;
  
        case NRF24L01IOC_SETPIPESENABLED:
          {
            int i;
            uint8_t en_pipes;
  
            FAR uint8_t *en_pipesp = (FAR uint8_t *)(arg);
  
            DEBUGASSERT(en_pipesp != NULL);
            en_pipes = *en_pipesp;
            for (i = 0; i < NRF24L01_PIPE_COUNT; i++)
              {
                if ((dev->en_pipes & (1 << i)) != (en_pipes & (1 << i)))
                  {
                    nrf24l01_enablepipe(dev, i, en_pipes & (1 << i));
                  }
              }
          }
          break;
  
        case NRF24L01IOC_GETPIPESENABLED:
          {
             FAR uint8_t *en_pipesp = (FAR uint8_t *)(arg);
  
             DEBUGASSERT(en_pipesp != NULL);
             *en_pipesp = dev->en_pipes;
             break;
          }
  
        case NRF24L01IOC_SETDATARATE:
          {
             FAR nrf24l01_datarate_t *drp = (FAR nrf24l01_datarate_t *)(arg);
             DEBUGASSERT(drp != NULL);
  
             nrf24l01_setdatarate(dev, *drp);
             break;
          }
  
        case NRF24L01IOC_GETDATARATE:
          result = -ENOSYS;  /* TODO */
          break;
  
        case NRF24L01IOC_SETADDRWIDTH:
          {
             FAR uint32_t *widthp = (FAR uint32_t *)(arg);
             DEBUGASSERT(widthp != NULL);
  
             nrf24l01_setaddrwidth(dev, *widthp);
             break;
          }
  
        case NRF24L01IOC_GETADDRWIDTH:
          {
             FAR int *widthp = (FAR int *)(arg);
             DEBUGASSERT(widthp != NULL);
  
             *widthp = (int)dev->addrlen;
             break;
          }
  
        case NRF24L01IOC_SETSTATE:
          {
             FAR nrf24l01_state_t *statep = (FAR nrf24l01_state_t *)(arg);
             DEBUGASSERT(statep != NULL);
  
             nrf24l01_changestate(dev, *statep);
             break;
          }
  
        case NRF24L01IOC_GETSTATE:
          {
             FAR nrf24l01_state_t *statep = (FAR nrf24l01_state_t *)(arg);
             DEBUGASSERT(statep != NULL);
  
             *statep = dev->state;
             break;
          }
  
        case NRF24L01IOC_GETLASTXMITCOUNT:
          {
             FAR uint32_t *xmitcntp = (FAR uint32_t *)(arg);
             DEBUGASSERT(xmitcntp != NULL);
  
             *xmitcntp = dev->lastxmitcount;
             break;
          }
  
        case NRF24L01IOC_GETLASTPIPENO:
          {
            FAR uint32_t *lastpipep = (FAR uint32_t *)(arg);
            DEBUGASSERT(lastpipep != NULL);
  
            *lastpipep = dev->last_recvpipeno;
            break;
          }
  
        default:
          result = -ENOTTY;
          break;
      }
  
    sem_post(&dev->devsem);
    return result;
  }
  
  #ifndef CONFIG_DISABLE_POLL
  
  static int nrf24l01_poll(FAR struct file *filep, FAR struct pollfd *fds,
                          bool setup)
  {
  #ifndef CONFIG_WL_NRF24L01_RXSUPPORT
    /* Polling is currently implemented for data input only */
    return -ENOSYS;
  #else
  
    FAR struct inode *inode;
    FAR struct nrf24l01_dev_s *dev;
    int result = OK;
  
    wvdbg("setup: %d\n", (int)setup);
    DEBUGASSERT(filep && fds);
    inode = filep->f_inode;
  
    DEBUGASSERT(inode && inode->i_private);
    dev  = (FAR struct nrf24l01_dev_s *)inode->i_private;
  
    /* Exclusive access */
  
    if (sem_wait(&dev->devsem) < 0)
      {
        /* This should only happen if the wait was canceled by an signal */
  
        DEBUGASSERT(errno == EINTR);
        return -EINTR;
      }
  
    /* Are we setting up the poll?  Or tearing it down? */
  
    if (setup)
      {
        /* Ignore waits that do not include POLLIN */
  
        if ((fds->events & POLLIN) == 0)
          {
            result = -EDEADLK;
            goto errout;
          }
  
        /* Check if we can accept this poll.
         * For now, only one thread can poll the device at any time (shorter / simpler code)
         */
  
        if (dev->pfd)
          {
            result = -EBUSY;
            goto errout;
          }
  
        dev->pfd = fds;
      }
    else  /* Tear it down */
      {
        dev->pfd = NULL;
      }
  
  errout:
    sem_post(&dev->devsem);
    return result;
  #endif
  }
  
  #endif
  
  static int nrf24l01_unregister(FAR struct nrf24l01_dev_s *dev)
  {
    CHECK_ARGS(dev);
  
    /* Release IRQ */
  
    nrf24l01_attachirq(dev, NULL);
  
    g_nrf24l01dev = NULL;
  
    /* Free memory */
  #ifdef CONFIG_WL_NRF24L01_RXSUPPORT
    kmm_free(dev->rx_fifo);
  #endif
    kmm_free(dev);
  
    return OK;
  }
  
  /****************************************************************************
   * Public Functions
   ****************************************************************************/
  
  int nrf24l01_register(FAR struct spi_dev_s *spi, FAR struct nrf24l01_config_s *cfg)
  {
    FAR struct nrf24l01_dev_s *dev;
    int result = OK;
  
  #ifdef CONFIG_WL_NRF24L01_RXSUPPORT
    uint8_t *rx_fifo;
  #endif
  
    ASSERT((spi != NULL) & (cfg != NULL));
  
    if ((dev = kmm_malloc(sizeof(struct nrf24l01_dev_s))) == NULL)
      {
        return -ENOMEM;
      }
  
    dev->spi = spi;
    dev->config = cfg;
  
    dev->state = ST_UNKNOWN;
    dev->en_aa = 0;
    dev->ce_enabled = false;
  
    sem_init(&(dev->devsem), 0, 1);
    dev->nopens = 0;
  
  #ifndef CONFIG_DISABLE_POLL
    dev->pfd = NULL;
  #endif
  
    sem_init(&(dev->sem_tx), 0, 0);
  
  #ifdef CONFIG_WL_NRF24L01_RXSUPPORT
    if ((rx_fifo = kmm_malloc(CONFIG_WL_NRF24L01_RXFIFO_LEN)) == NULL)
      {
        kmm_free(dev);
        return -ENOMEM;
      }
  
    dev->rx_fifo = rx_fifo;
    dev->nxt_read = 0;
    dev->nxt_write = 0;
    dev->fifo_len = 0;
  
    sem_init(&(dev->sem_fifo), 0, 1);
    sem_init(&(dev->sem_rx), 0, 0);
  #endif
  
    /* Set the global reference */
  
    g_nrf24l01dev = dev;
  
    /* Configure IRQ pin  (falling edge) */
  
    nrf24l01_attachirq(dev, nrf24l01_irqhandler);
  
    /* Register the device as an input device */
  
    ivdbg("Registering " DEV_NAME "\n");
  
    result = register_driver(DEV_NAME, &nrf24l01_fops, 0666, dev);
    if (result < 0)
      {
        wdbg("register_driver() failed: %d\n", result);
        nrf24l01_unregister(dev);
      }
  
    return result;
  }
  
  FAR struct nrf24l01_dev_s * nrf24l01_getinstance(void)
  {
    return g_nrf24l01dev;
  }
  
  /* (re)set the device in a default initial state */
  
  int nrf24l01_init(FAR struct nrf24l01_dev_s *dev)
  {
    int result = OK;
    uint8_t features;
  
    CHECK_ARGS(dev);
    nrf24l01_lock(dev->spi);
  
    /* Configure the SPI parameters before communicating */
  
    nrf24l01_configspi(dev->spi);
  
    /* Enable features. */
  
    nrf24l01_writeregbyte(dev, NRF24L01_FEATURE, NRF24L01_EN_DPL);
    features = nrf24l01_readregbyte(dev, NRF24L01_FEATURE);
    if (0 == features)
      {
        /* The ACTIVATE instruction is not documented in the nRF24L01+ docs.
         * However it is referenced / described by many sources on Internet,
         *
         * Is it for nRF24L01  (not +) hardware ?
         */
  
        uint8_t v = 0x73;
        nrf24l01_access(dev, MODE_WRITE, NRF24L01_ACTIVATE, &v, 1);
  
        features = nrf24l01_readregbyte(dev, NRF24L01_FEATURE);
        if (0 == features)
          {
            /* If FEATURES reg is still unset here, consider there is no actual hardware */
  
            result = -ENODEV;
            goto out;
          }
      }
  
    /* Set initial state */
  
    nrf24l01_tostate(dev, ST_POWER_DOWN);
  
    /* Disable all pipes */
  
    dev->en_pipes = 0;
    nrf24l01_writeregbyte(dev, NRF24L01_EN_RXADDR, 0);
  
    /* Set addr width to default   */
  
    dev->addrlen = CONFIG_WL_NRF24L01_DFLT_ADDR_WIDTH;
    nrf24l01_writeregbyte(dev, NRF24L01_SETUP_AW, CONFIG_WL_NRF24L01_DFLT_ADDR_WIDTH - 2);
  
    /* Get pipe #0 addr */
  
    nrf24l01_readreg(dev, NRF24L01_RX_ADDR_P0, dev->pipe0addr, dev->addrlen);
  
    dev->en_aa = nrf24l01_readregbyte(dev, NRF24L01_EN_AA);
  
    /* Flush HW fifo */
  
    nrf24l01_flush_rx(dev);
    nrf24l01_flush_tx(dev);
  
    /* Clear interrupt sources (useful ?) */
  
    nrf24l01_writeregbyte(dev, NRF24L01_STATUS,
                          NRF24L01_RX_DR | NRF24L01_TX_DS | NRF24L01_MAX_RT);
  
  out:
    nrf24l01_unlock(dev->spi);
    return result;
  }
  
  int nrf24l01_setpipeconfig(FAR struct nrf24l01_dev_s *dev, unsigned int pipeno,
      FAR const nrf24l01_pipecfg_t *pipecfg)
  {
    bool dynlength;
    bool en_aa;
  
    CHECK_ARGS(dev && pipecfg && pipeno < NRF24L01_PIPE_COUNT);
  
    dynlength = (pipecfg->payload_length == NRF24L01_DYN_LENGTH);
  
    /* Need to enable AA to enable dynamic length payload */
  
    en_aa = dynlength || pipecfg->en_aa;
  
    nrf24l01_lock(dev->spi);
  
    /* Set addr  */
  
    int addrlen = (pipeno <= 1) ? dev->addrlen : 1;   /* Pipe 0 & 1 are the only ones to have a full length address */
    nrf24l01_writereg(dev, NRF24L01_RX_ADDR_P0 + pipeno, pipecfg->rx_addr, addrlen);
  
    /* Auto ack */
  
    if (en_aa)
      {
        dev->en_aa |= 1 << pipeno;
      }
    else
      {
        dev->en_aa &= ~(1 << pipeno);
      }
  
    nrf24l01_setregbit(dev, NRF24L01_EN_AA, 1 << pipeno, en_aa);
  
    /* Payload config */
  
    nrf24l01_setregbit(dev, NRF24L01_DYNPD, 1 << pipeno, dynlength);
    if (!dynlength)
      {
        nrf24l01_writeregbyte(dev, NRF24L01_RX_PW_P0 + pipeno, pipecfg->payload_length);
      }
    nrf24l01_unlock(dev->spi);
  
    dev->pipedatalen[pipeno] = pipecfg->payload_length;
    return OK;
  }
  
  int nrf24l01_getpipeconfig(FAR struct nrf24l01_dev_s *dev, unsigned int pipeno,
      FAR nrf24l01_pipecfg_t *pipecfg)
  {
    bool dynlength;
  
    CHECK_ARGS(dev && pipecfg && pipeno < NRF24L01_PIPE_COUNT);
  
    nrf24l01_lock(dev->spi);
  
    /* Get pipe address  */
  
    int addrlen = (pipeno <= 1) ? dev->addrlen : 1;   /* Pipe 0 & 1 are the only ones to have a full length address */
    nrf24l01_readreg(dev, NRF24L01_RX_ADDR_P0 + pipeno, pipecfg->rx_addr, addrlen);
  
    /* Auto ack */
  
    pipecfg->en_aa = ((nrf24l01_readregbyte(dev, NRF24L01_EN_AA) & (1 << pipeno)) != 0);
  
    /* Payload config */
  
    dynlength = ((nrf24l01_readregbyte(dev, NRF24L01_DYNPD) & (1 << pipeno)) != 0);
  
    if (dynlength)
      {
        pipecfg->payload_length = NRF24L01_DYN_LENGTH;
      }
    else
      {
        pipecfg->payload_length = nrf24l01_readregbyte(dev, NRF24L01_RX_PW_P0 + pipeno);
      }
  
    nrf24l01_unlock(dev->spi);
  
    return OK;
  }
  
  int nrf24l01_enablepipe(FAR struct nrf24l01_dev_s *dev, unsigned int pipeno, bool enable)
  {
    CHECK_ARGS(dev && pipeno < NRF24L01_PIPE_COUNT);
  
    uint8_t rxaddrval;
    uint8_t pipemask = 1 << pipeno;
  
    nrf24l01_lock(dev->spi);
  
    /* Enable pipe on nRF24L01 */
  
    rxaddrval = nrf24l01_readregbyte(dev, NRF24L01_EN_RXADDR);
  
    if (enable)
      {
        rxaddrval |= pipemask;
      }
    else
      {
        rxaddrval &= ~pipemask;
      }
  
    nrf24l01_writeregbyte(dev, NRF24L01_EN_RXADDR, rxaddrval);
    nrf24l01_unlock(dev->spi);
  
    /* Update cached value */
  
    dev->en_pipes = rxaddrval;
  
    return OK;
  }
  
  int nrf24l01_settxaddr(FAR struct nrf24l01_dev_s *dev, FAR const uint8_t *txaddr)
  {
    CHECK_ARGS(dev && txaddr);
  
    nrf24l01_lock(dev->spi);
  
    nrf24l01_writereg(dev, NRF24L01_TX_ADDR, txaddr, dev->addrlen);
    nrf24l01_unlock(dev->spi);
    return OK;
  }
  
  int nrf24l01_gettxaddr(FAR struct nrf24l01_dev_s *dev, FAR uint8_t *txaddr)
  {
    CHECK_ARGS(dev && txaddr);
  
    nrf24l01_lock(dev->spi);
  
    nrf24l01_readreg(dev, NRF24L01_TX_ADDR, txaddr, dev->addrlen);
    nrf24l01_unlock(dev->spi);
    return OK;
  }
  
  int nrf24l01_setretransmit(FAR struct nrf24l01_dev_s *dev, nrf24l01_retransmit_delay_t retrdelay, uint8_t retrcount)
  {
    uint8_t val;
  
    CHECK_ARGS(dev && retrcount <= NRF24L01_MAX_XMIT_RETR);
  
    val = (retrdelay << NRF24L01_ARD_SHIFT) | (retrcount << NRF24L01_ARC_SHIFT);
  
    nrf24l01_lock(dev->spi);
  
    nrf24l01_writeregbyte(dev, NRF24L01_SETUP_RETR, val);
    nrf24l01_unlock(dev->spi);
    return OK;
  }
  
  int nrf24l01_settxpower(FAR struct nrf24l01_dev_s *dev, int outpower)
  {
    uint8_t value;
    uint8_t hwpow;
  
    /**  RF_PWR value  <->  Output power in dBm
     *
     * '00' – -18dBm
     * '01' – -12dBm
     * '10' – -6dBm
     * '11' – 0dBm
     */
  
    switch (outpower)
      {
      case 0:
        hwpow = 3 << NRF24L01_RF_PWR_SHIFT;
        break;
  
      case -6:
        hwpow = 2 << NRF24L01_RF_PWR_SHIFT;
        break;
  
      case -12:
        hwpow = 1 << NRF24L01_RF_PWR_SHIFT;
        break;
  
      case -18:
        hwpow = 0;
        break;
  
      default:
        return -EINVAL;
    }
  
    nrf24l01_lock(dev->spi);
  
    value = nrf24l01_readregbyte(dev, NRF24L01_RF_SETUP);
  
    value &= ~(NRF24L01_RF_PWR_MASK);
    value |= hwpow;
  
    nrf24l01_writeregbyte(dev, NRF24L01_RF_SETUP, value);
    nrf24l01_unlock(dev->spi);
    return OK;
  }
  
  int nrf24l01_gettxpower(FAR struct nrf24l01_dev_s *dev)
  {
    uint8_t value;
    int powers[] = { -18, -12, -6, 0};
  
    nrf24l01_lock(dev->spi);
  
    value = nrf24l01_readregbyte(dev, NRF24L01_RF_SETUP);
    nrf24l01_unlock(dev->spi);
  
    value = (value & NRF24L01_RF_PWR_MASK) >> NRF24L01_RF_PWR_SHIFT;
    return powers[value];
  }
  
  int nrf24l01_setdatarate(FAR struct nrf24l01_dev_s *dev, nrf24l01_datarate_t datarate)
  {
    uint8_t value;
  
    nrf24l01_lock(dev->spi);
  
    value = nrf24l01_readregbyte(dev, NRF24L01_RF_SETUP);
    value &= ~(NRF24L01_RF_DR_HIGH | NRF24L01_RF_DR_LOW);
  
    switch (datarate)
      {
        case RATE_1Mbps:
          break;
  
        case RATE_2Mbps:
          value |= NRF24L01_RF_DR_HIGH;
          break;
  
        case RATE_250kbps:
          value |= NRF24L01_RF_DR_LOW;
          break;
      }
  
    nrf24l01_writeregbyte(dev, NRF24L01_RF_SETUP, value);
    nrf24l01_unlock(dev->spi);
    return OK;
  }
  
  int nrf24l01_setradiofreq(FAR struct nrf24l01_dev_s *dev, uint32_t freq)
  {
    uint8_t value;
  
    CHECK_ARGS(dev && freq >= NRF24L01_MIN_FREQ && freq <= NRF24L01_MAX_FREQ);
  
    value = NRF24L01_MIN_FREQ - freq;
    nrf24l01_lock(dev->spi);
    nrf24l01_writeregbyte(dev, NRF24L01_RF_CH, value);
    nrf24l01_unlock(dev->spi);
    return OK;
  }
  
  uint32_t nrf24l01_getradiofreq(FAR struct nrf24l01_dev_s *dev)
  {
    int rffreq;
  
    CHECK_ARGS(dev);
  
    nrf24l01_lock(dev->spi);
    rffreq = (int)nrf24l01_readregbyte(dev, NRF24L01_RF_CH);
    nrf24l01_unlock(dev->spi);
  
    return rffreq + NRF24L01_MIN_FREQ;
  }
  
  int nrf24l01_setaddrwidth(FAR struct nrf24l01_dev_s *dev, uint32_t width)
  {
    CHECK_ARGS(dev && width <= NRF24L01_MAX_ADDR_LEN && width >= NRF24L01_MIN_ADDR_LEN);
  
    nrf24l01_lock(dev->spi);
    nrf24l01_writeregbyte(dev, NRF24L01_SETUP_AW, width-2);
    nrf24l01_unlock(dev->spi);
    dev->addrlen = width;
    return OK;
  }
  
  int nrf24l01_changestate(FAR struct nrf24l01_dev_s *dev, nrf24l01_state_t state)
  {
    nrf24l01_lock(dev->spi);
    nrf24l01_tostate(dev, state);
    nrf24l01_unlock(dev->spi);
    return OK;
  }
  
  int nrf24l01_send(FAR struct nrf24l01_dev_s *dev, FAR const uint8_t *data, size_t datalen)
  {
    int result;
  
    CHECK_ARGS(dev && data && datalen <= NRF24L01_MAX_PAYLOAD_LEN);
  
    nrf24l01_lock(dev->spi);
  
    result = dosend(dev, data, datalen);
  
    nrf24l01_unlock(dev->spi);
    return result;
  }
  
  int nrf24l01_sendto(FAR struct nrf24l01_dev_s *dev, FAR const uint8_t *data,
      size_t datalen, FAR const uint8_t *destaddr)
  {
    bool pipeaddrchg = false;
    int result;
  
    nrf24l01_lock(dev->spi);
  
    /* If AA is enabled (pipe 0 is active and its AA flag is set) and the dest
     * addr is not the current pipe 0 addr we need to change pipe 0 addr in
     * order to receive the ACK packet.
     */
  
    if ((dev->en_aa & 1) && (memcmp(destaddr, dev->pipe0addr, dev->addrlen)))
      {
        wdbg("Change pipe #0 addr to dest addr\n");
        nrf24l01_writereg(dev, NRF24L01_RX_ADDR_P0, destaddr, NRF24L01_MAX_ADDR_LEN);
        pipeaddrchg = true;
      }
  
    result = dosend(dev, data, datalen);
  
    if (pipeaddrchg)
      {
        /* Restore pipe #0 addr */
  
        nrf24l01_writereg(dev, NRF24L01_RX_ADDR_P0, dev->pipe0addr, NRF24L01_MAX_ADDR_LEN);
        wdbg("Pipe #0 default addr restored\n");
      }
  
    nrf24l01_unlock(dev->spi);
    return result;
  }
  
  int nrf24l01_lastxmitcount(FAR struct nrf24l01_dev_s *dev)
  {
    return dev->lastxmitcount;
  }
  
  #ifdef CONFIG_WL_NRF24L01_RXSUPPORT
  
  ssize_t nrf24l01_recv(struct nrf24l01_dev_s *dev, uint8_t *buffer,
      size_t buflen, uint8_t *recvpipe)
  {
    if (sem_wait(&dev->sem_rx) != 0)
      {
         /* This should only happen if the wait was canceled by an signal */
  
         DEBUGASSERT(errno == EINTR);
         return -EINTR;
      }
  
    return fifoget(dev, buffer, buflen, recvpipe);
  }
  
  #endif
  
  #ifdef NRF24L01_DEBUG
  
  static void binarycvt(char *deststr, const uint8_t *srcbin, size_t srclen)
  {
    int i = 0;
    while (i < srclen)
      {
        sprintf(deststr + i*2, "%02x", srcbin[i]);
        ++i;
      }
  
    *(deststr + i*2) = '\0';
  }
  
  void nrf24l01_dumpregs(struct nrf24l01_dev_s *dev)
  {
    uint8_t addr[NRF24L01_MAX_ADDR_LEN];
    char addrstr[NRF24L01_MAX_ADDR_LEN * 2 +1];
  
    syslog(LOG_INFO, "CONFIG:    %02x\n",
           nrf24l01_readregbyte(dev, NRF24L01_CONFIG));
    syslog(LOG_INFO, "EN_AA:     %02x\n",
           nrf24l01_readregbyte(dev, NRF24L01_EN_AA));
    syslog(LOG_INFO, "EN_RXADDR: %02x\n",
           nrf24l01_readregbyte(dev, NRF24L01_EN_RXADDR));
    syslog(LOG_INFO, "SETUP_AW:  %02x\n",
           nrf24l01_readregbyte(dev, NRF24L01_SETUP_AW));
  
    syslog(LOG_INFO, "SETUP_RETR:%02x\n",
           nrf24l01_readregbyte(dev, NRF24L01_SETUP_RETR));
    syslog(LOG_INFO, "RF_CH:     %02x\n",
           nrf24l01_readregbyte(dev, NRF24L01_RF_CH));
    syslog(LOG_INFO, "RF_SETUP:  %02x\n",
           nrf24l01_readregbyte(dev, NRF24L01_RF_SETUP));
    syslog(LOG_INFO, "STATUS:    %02x\n",
           nrf24l01_readregbyte(dev, NRF24L01_STATUS));
    syslog(LOG_INFO, "OBS_TX:    %02x\n",
           nrf24l01_readregbyte(dev, NRF24L01_OBSERVE_TX));
  
    nrf24l01_readreg(dev, NRF24L01_TX_ADDR, addr, dev->addrlen);
    binarycvt(addrstr, addr, dev->addrlen);
    syslog(LOG_INFO, "TX_ADDR:   %s\n", addrstr);
  
    syslog(LOG_INFO, "CD:        %02x\n",
           nrf24l01_readregbyte(dev, NRF24L01_CD));
    syslog(LOG_INFO, "RX_PW_P0:  %02x\n",
           nrf24l01_readregbyte(dev, NRF24L01_RX_PW_P0));
    syslog(LOG_INFO, "RX_PW_P1:  %02x\n",
           nrf24l01_readregbyte(dev, NRF24L01_RX_PW_P1));
    syslog(LOG_INFO, "RX_PW_P2:  %02x\n",
           nrf24l01_readregbyte(dev, NRF24L01_RX_PW_P2));
    syslog(LOG_INFO, "RX_PW_P3:  %02x\n",
           nrf24l01_readregbyte(dev, NRF24L01_RX_PW_P3));
    syslog(LOG_INFO, "RX_PW_P4:  %02x\n",
           nrf24l01_readregbyte(dev, NRF24L01_RX_PW_P4));
    syslog(LOG_INFO, "RX_PW_P5:  %02x\n",
           nrf24l01_readregbyte(dev, NRF24L01_RX_PW_P5));
  
    syslog(LOG_INFO, "FIFO_STAT: %02x\n",
           nrf24l01_readregbyte(dev, NRF24L01_FIFO_STATUS));
    syslog(LOG_INFO, "DYNPD:     %02x\n",
           nrf24l01_readregbyte(dev, NRF24L01_DYNPD));
    syslog(LOG_INFO, "FEATURE:   %02x\n",
           nrf24l01_readregbyte(dev, NRF24L01_FEATURE));
  }
  
  #ifdef CONFIG_WL_NRF24L01_RXSUPPORT
  void nrf24l01_dumprxfifo(struct nrf24l01_dev_s *dev)
  {
    syslog(LOG_INFO, "bytes count: %d\n", dev->fifo_len);
    syslog(LOG_INFO, "next read:   %d,  next write: %d\n",
           dev->nxt_read, dev-> nxt_write);
  }
  #endif
  
  #endif
  