/****************************************************************************
 * drivers/wireless/mfrc522.c
 *
 *   Copyright(C) 2016 Uniquix Ltda. All rights reserved.
 *   Author: Alan Carvalho de Assis <acassis@gmail.com>
 *
 * This driver is based on Arduino library for MFRC522 from Miguel
 * Balboa released into the public domain:
 * https://github.com/miguelbalboa/rfid/
 *
 * 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.
 *
 ****************************************************************************/

/****************************************************************************
 * 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 <nuttx/kmalloc.h>
#include <nuttx/wireless/wireless.h>

#include "mfrc522.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#ifdef CONFIG_MFRC522_DEBUG
#  define mfrc522err    _err
#  define mfrc522info   _info
#else
#  ifdef CONFIG_CPP_HAVE_VARARGS
#    define mfrc522err(x...)
#    define mfrc522info(x...)
#  else
#    define mfrc522err  (void)
#    define mfrc522info (void)
#  endif
#endif

#ifdef CONFIG_MFRC522_DEBUG_TX
#  define tracetx errdumpbuffer
#else
#  define tracetx(x...)
#endif

#ifdef CONFIG_MFRC522_DEBUG_RX
#  define tracerx errdumpbuffer
#else
#  define tracerx(x...)
#endif

#define FRAME_SIZE(f) (sizeof(struct mfrc522_frame) + f->len + 2)
#define FRAME_POSTAMBLE(f) (f->data[f->len + 1])

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

static inline void mfrc522_configspi(FAR struct spi_dev_s *spi);
static void mfrc522_lock(FAR struct spi_dev_s *spi);
static void mfrc522_unlock(FAR struct spi_dev_s *spi);

/* Character driver methods */

static int mfrc522_open(FAR struct file *filep);
static int mfrc522_close(FAR struct file *filep);
static ssize_t mfrc522_read(FAR struct file *, FAR char *, size_t);
static ssize_t mfrc522_write(FAR struct file *filep, FAR const char *buffer,
                             size_t buflen);
static int mfrc522_ioctl(FAR struct file *filep, int cmd,
                         unsigned long arg);

uint8_t mfrc522_readu8(FAR struct mfrc522_dev_s *dev, uint8_t regaddr);
void mfrc522_writeu8(FAR struct mfrc522_dev_s *dev, uint8_t regaddr,
                     FAR uint8_t regval);
void mfrc522_writeblk(FAR struct mfrc522_dev_s *dev, uint8_t regaddr,
                      uint8_t *regval, int length);
void mfrc522_readblk(FAR struct mfrc522_dev_s *dev, uint8_t regaddr,
                     FAR uint8_t *regval, int length, uint8_t rxalign);

void mfrc522_softreset(FAR struct mfrc522_dev_s *dev);

int mfrc522_picc_select(FAR struct mfrc522_dev_s *dev,
                        FAR struct picc_uid_s *uid, uint8_t validbits);

/* IRQ Handling TODO:
static int mfrc522_irqhandler(FAR int irq, FAR void *context, FAR void* dev);
static inline int mfrc522_attachirq(FAR struct mfrc522_dev_s *dev, xcpt_t isr);
*/

/****************************************************************************
 * Private Data
 ****************************************************************************/

static const struct file_operations g_mfrc522fops =
{
  mfrc522_open,
  mfrc522_close,
  mfrc522_read,
  mfrc522_write,
  0,
  mfrc522_ioctl
#ifndef CONFIG_DISABLE_POLL
    , 0
#endif
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
    , 0
#endif
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

static void mfrc522_lock(FAR struct spi_dev_s *spi)
{
  (void)SPI_LOCK(spi, true);

  SPI_SETMODE(spi, SPIDEV_MODE0);
  SPI_SETBITS(spi, 8);
  (void)SPI_HWFEATURES(spi, 0);
  (void)SPI_SETFREQUENCY(spi, CONFIG_MFRC522_SPI_FREQ);
}

static void mfrc522_unlock(FAR struct spi_dev_s *spi)
{
  (void)SPI_LOCK(spi, false);
}

static inline void mfrc522_configspi(FAR struct spi_dev_s *spi)
{
  /* Configure SPI for the MFRC522 module. */

  SPI_SETMODE(spi, SPIDEV_MODE0);
  SPI_SETBITS(spi, 8);
  (void)SPI_HWFEATURES(spi, 0);
  (void)SPI_SETFREQUENCY(spi, CONFIG_MFRC522_SPI_FREQ);
}

static inline void mfrc522_select(struct mfrc522_dev_s *dev)
{
  SPI_SELECT(dev->spi, SPIDEV_WIRELESS, true);
}

static inline void mfrc522_deselect(struct mfrc522_dev_s *dev)
{
  SPI_SELECT(dev->spi, SPIDEV_WIRELESS, false);
}

/****************************************************************************
 * Name: mfrc522_readu8
 *
 * Description:
 *   Read a byte from a register address.
 *
 * Input Parameters:
 *
 * Returned Value: the read byte from the register
 *
 ****************************************************************************/

uint8_t mfrc522_readu8(FAR struct mfrc522_dev_s *dev, uint8_t regaddr)
{
  uint8_t regval;
  uint8_t address = (0x80 | (regaddr & 0x7E));

  mfrc522_lock(dev->spi);
  mfrc522_select(dev);
  SPI_SEND(dev->spi, address);
  regval = SPI_SEND(dev->spi, 0);
  mfrc522_deselect(dev);
  mfrc522_unlock(dev->spi);

  tracerx("read", regval, 1);
  return regval;
}

/****************************************************************************
 * Name: mfrc522_write8
 *
 * Description:
 *   Write a byte to a register address.
 *
 * Input Parameters:
 *
 * Returned Value:
 *
 ****************************************************************************/

void mfrc522_writeu8(FAR struct mfrc522_dev_s *dev, uint8_t regaddr,
                     FAR uint8_t regval)
{
  mfrc522_lock(dev->spi);
  mfrc522_select(dev);
  SPI_SEND(dev->spi, regaddr & 0x7E);
  SPI_SEND(dev->spi, regval);
  mfrc522_deselect(dev);
  mfrc522_unlock(dev->spi);

  tracerx("write", &regval, 1);
}

/****************************************************************************
 * Name: mfrc522_readblk
 *
 * Description:
 *   Read a block of bytes from a register address. Align the bit positions of
 * regval[0] from rxalign..7.
 *
 * Input Parameters:
 *
 * Returned Value: none
 *
 ****************************************************************************/

void mfrc522_readblk(FAR struct mfrc522_dev_s *dev, uint8_t regaddr,
                     FAR uint8_t *regval, int length, uint8_t rxalign)
{
  uint8_t i = 0;
  uint8_t address = (0x80 | (regaddr & 0x7E));

  mfrc522_lock(dev->spi);
  mfrc522_select(dev);

  /* Inform the MFRC522 the address we want to read */

  SPI_SEND(dev->spi, address);

  while (i < length)
    {
      if (i == 0 && rxalign)
        {
          uint8_t mask = 0;
          uint8_t value;
          uint8_t j;

          for (j = rxalign; j <= 7; j++)
            {
              mask |= (1 << j);
            }

          /* Read the first byte */

          value = SPI_SEND(dev->spi, address);

          /* Apply mask to current regval[0] with the read value */

          regval[0] = (regval[0] & ~mask) | (value & mask);
        }
      else
        {
          /* Read the remaining bytes */

          regval[i] = SPI_SEND(dev->spi, address);
        }
      i++;
    }

  /* Read the last byte. Send 0 to stop reading (it maybe wrong, 1 byte out) */

  regval[i] = SPI_SEND(dev->spi, 0);

  mfrc522_deselect(dev);
  mfrc522_unlock(dev->spi);

  tracerx("readblk", regval, size);
}

/****************************************************************************
 * Name: mfrc522_writeblk
 *
 * Description:
 *   Write a block of bytes to a register address.
 *
 * Input Parameters:
 *
 * Returned Value: none
 *
 ****************************************************************************/

void mfrc522_writeblk(FAR struct mfrc522_dev_s *dev, uint8_t regaddr,
                      uint8_t *regval, int length)
{
  uint8_t address = (regaddr & 0x7E);

  mfrc522_lock(dev->spi);
  mfrc522_select(dev);

  /* Inform the MFRC522 the address we want write to */

  SPI_SEND(dev->spi, address);

  /* Send the block of bytes */

  SPI_SNDBLOCK(dev->spi, regval, length);

  mfrc522_deselect(dev);
  mfrc522_unlock(dev->spi);

  tracerx("writeblk", regval, size);
}

/****************************************************************************
 * Name: mfrc522_calc_crc
 *
 * Description:
 *   Use the CRC coprocessor in the MFRC522 to calculate a CRC_A.
 *
 * Input Parameters:
 *
 * Returned Value: OK or -ETIMEDOUT
 *
 ****************************************************************************/

int mfrc522_calc_crc(FAR struct mfrc522_dev_s *dev, uint8_t *buffer,
                     int length, uint8_t *result)
{
  struct timespec tstart;
  struct timespec tend;

  /* Stop any command in execution */

  mfrc522_writeu8(dev, MFRC522_COMMAND_REG, MFRC522_IDLE_CMD);

  /* Clear the CRCIRq interrupt request bit */

  mfrc522_writeu8(dev, MFRC522_DIV_IRQ_REG, MFRC522_CRC_IRQ);

  /* Flush all bytes in the FIFO */

  mfrc522_writeu8(dev, MFRC522_FIFO_LEVEL_REG, MFRC522_FLUSH_BUFFER);

  /* Write data to the FIFO */

  mfrc522_writeblk(dev, MFRC522_FIFO_DATA_REG, buffer, length);

  /* Start the calculation */

  mfrc522_writeu8(dev, MFRC522_COMMAND_REG, MFRC522_CALC_CRC_CMD);

  /* Wait for CRC completion or 200ms time-out */

  clock_gettime(CLOCK_REALTIME, &tstart);
  tstart.tv_nsec += 200000;
  if (tstart.tv_nsec >= 1000 * 1000 * 1000)
    {
      tstart.tv_sec++;
      tstart.tv_nsec -= 1000 * 1000 * 1000;
    }

  while(1)
    {
      uint8_t irqreg;

      irqreg = mfrc522_readu8(dev, MFRC522_DIV_IRQ_REG);
      if ( irqreg & MFRC522_CRC_IRQ)
        {
          break;
        }

      /* Get time now */

      clock_gettime(CLOCK_REALTIME, &tend);

      if ((tend.tv_sec > tstart.tv_sec) && (tend.tv_nsec > tstart.tv_nsec))
        {
          return -ETIMEDOUT;
        }
    }

  /* Stop calculating CRC for new content of FIFO */

  mfrc522_writeu8(dev, MFRC522_COMMAND_REG, MFRC522_IDLE_CMD);

  result[0] = mfrc522_readu8(dev, MFRC522_CRC_RESULT_REGL);
  result[1] = mfrc522_readu8(dev, MFRC522_CRC_RESULT_REGH);

  return OK;
}

/****************************************************************************
 * Name: mfrc522_comm_picc
 *
 * Description:
 *   Transfers data to the MFRC522 FIFO, executes a command, waits for
 * completion and transfers data back from the FIFO.
 * CRC validation can only be done if back_data and back_len are specified.
 *
 * Input Parameters:
 *
 * Returned Value: OK or -ETIMEDOUT
 *
 ****************************************************************************/

int mfrc522_comm_picc(FAR struct mfrc522_dev_s *dev, uint8_t command,
                      uint8_t waitirq, uint8_t *send_data, uint8_t send_len,
                      uint8_t *back_data, uint8_t *back_len,
                      uint8_t *validbits, uint8_t rxalign, bool checkcrc)
{
  int ret;
  uint8_t errors;
  uint8_t vbits;
  uint8_t value;
  struct timespec tstart;
  struct timespec tend;

  /* Prepare values for BitFramingReg */

  uint8_t txlastbits = validbits ? *validbits : 0;
  uint8_t bitframing = (rxalign << 4) + txlastbits;

  /* Stop any active command */

  mfrc522_writeu8(dev, MFRC522_COMMAND_REG, MFRC522_IDLE_CMD);

  /* Clear all seven interrupt request bits */

  value = mfrc522_readu8(dev, MFRC522_COM_IRQ_REG);
  mfrc522_writeu8(dev, MFRC522_COM_IRQ_REG, value | MFRC522_COM_IRQ_MASK);

  /* Flush all bytes in the FIFO */

  mfrc522_writeu8(dev, MFRC522_FIFO_LEVEL_REG, MFRC522_FLUSH_BUFFER);

  /* Write data to FIFO */

  mfrc522_writeblk(dev, MFRC522_FIFO_DATA_REG, send_data, send_len);

  /* Bit adjustments */

  mfrc522_writeu8(dev, MFRC522_BIT_FRAMING_REG, bitframing);

  /* Execute command */

  mfrc522_writeu8(dev, MFRC522_COMMAND_REG, command);

  /* We setup the TAuto flag in the mfrc522_init() then we could use the
   * internal MFC522 Timer to detect timeout, but because there could be some
   * hardware fault, let us to use a NuttX timeout as well.
   */

  clock_gettime(CLOCK_REALTIME, &tstart);
  tstart.tv_nsec += 200000;
  if (tstart.tv_nsec >= 1000 * 1000 * 1000)
    {
      tstart.tv_sec++;
      tstart.tv_nsec -= 1000 * 1000 * 1000;
    }

  /* If it is a Transceive command, then start transmittion */

  if (command == MFRC522_TRANSCV_CMD)
    {
      value = mfrc522_readu8(dev, MFRC522_BIT_FRAMING_REG);
      mfrc522_writeu8(dev, MFRC522_BIT_FRAMING_REG, value | MFRC522_START_SEND);
    }

  /* Wait for the command to complete */

  while (1)
    {
      uint8_t irqsreg;

      irqsreg = mfrc522_readu8(dev, MFRC522_COM_IRQ_REG);

      /* If at least an of selected IRQ happened */

      if (irqsreg & waitirq)
        {
          break;
        }

      /* Timer expired */

      if (irqsreg & MFRC522_TIMER_IRQ)
        {
          return -ETIMEDOUT;
        }

      /* Get time now */

      clock_gettime(CLOCK_REALTIME, &tend);

      if ((tend.tv_sec > tstart.tv_sec) && (tend.tv_nsec > tstart.tv_nsec))
        {
          return -ETIMEDOUT;
        }
    }

  /* Read error register to verify if there are any issue */

  errors = mfrc522_readu8(dev, MFRC522_ERROR_REG);

  /* Check for Protocol error */

  if (errors & (MFRC522_PROTO_ERR))
    {
      return -EPROTO;
    }

  /* Check for Parity and Buffer Overflow errors */

  if (errors & (MFRC522_PARITY_ERR | MFRC522_BUF_OVFL_ERR))
    {
      return -EIO;
    }

  /* Check collision error */

  if (errors & MFRC522_COLL_ERR)
    {
      return -EBUSY;            /* should it be EAGAIN ? */
    }

  /* If the caller wants data back, get it from the MFRC522 */

  if (back_data && back_len)
    {
      uint8_t nbytes;

      /* Number of bytes in the FIFO */

      nbytes = mfrc522_readu8(dev, MFRC522_FIFO_LEVEL_REG);

      /* Returned more bytes than the expected */

      if (nbytes > *back_len)
        {
          return -ENOMEM;
        }

      *back_len = nbytes;

      /* Read the data from FIFO */

      mfrc522_readblk(dev, MFRC522_FIFO_DATA_REG, back_data, nbytes, rxalign);

      /* RxLastBits[2:0] indicates the number of valid bits received */

      vbits = mfrc522_readu8(dev, MFRC522_CONTROL_REG)
              & MFRC522_RX_LAST_BITS_MASK;

      if (validbits)
        {
          *validbits = vbits;
        }
    }

  /* Perform CRC_A validation if requested */

  if (back_data && back_len && checkcrc)
    {
      uint8_t ctrlbuf[2];

      /* In this case a MIFARE Classic NAK is not OK */

      if (*back_len == 1 && vbits == 4)
        {
          return -EACCES;
        }

      /* We need the CRC_A value or all 8 bits of the last byte */

      if (*back_len < 2 || vbits != 0)
        {
          return -EPERM;
        }

      /* Verify CRC_A */

      ret = mfrc522_calc_crc(dev, &back_data[0], *back_len - 2, &ctrlbuf[0]);
      if (ret != OK)
        {
          return ret;
        }

      if ((back_data[*back_len - 2] != ctrlbuf[0]) ||
          (back_data[*back_len - 1] != ctrlbuf[1]))
        {
          return -EFAULT;
        }
    }

  return OK;
}

/****************************************************************************
 * Name: mfrc522_transcv_data
 *
 * Description:
 *   Executes the Transceive command
 *
 * Input Parameters:
 *
 * Returned Value: OK or -ETIMEDOUT
 *
 ****************************************************************************/

int mfrc522_transcv_data(FAR struct mfrc522_dev_s *dev, uint8_t *senddata,
                         uint8_t sendlen, uint8_t *backdata, uint8_t *backlen,
                         uint8_t *validbits, uint8_t rxalign, bool check_crc)
{
  uint8_t waitirq = MFRC522_RX_IRQ | MFRC522_IDLE_IRQ;

  return mfrc522_comm_picc(dev, MFRC522_TRANSCV_CMD, waitirq, senddata,
                           sendlen, backdata, backlen, validbits, rxalign,
                           check_crc);
}

/****************************************************************************
 * Name: mfrc522_picc_reqa_wupa
 *
 * Description:
 *   Transmits REQA or WUPA commands
 *
 * Input Parameters:
 *
 * Returned Value: OK or -ETIMEDOUT
 *
 ****************************************************************************/

int mfrc522_picc_reqa_wupa(FAR struct mfrc522_dev_s *dev, uint8_t command,
                           uint8_t *buffer, uint8_t length)
{
  uint8_t validbits;
  uint8_t value;
  int status;

  if (!buffer || length < 2)
    {
      return -EINVAL;
    }

  /* Force clear of received bits if a collision is detected */

  value = mfrc522_readu8(dev, MFRC522_COLL_REG);
  mfrc522_writeu8(dev, MFRC522_COLL_REG, value & MFRC522_VALUES_AFTER_COLL);

  validbits = 7;
  status = mfrc522_transcv_data(dev, &command, 1, buffer, &length, &validbits,
                                0, false);

  /* For REQA and WUPA we need to transmit only 7 bits */

  if (status != OK)
    {
      return status;
    }

  /* ATQA must be exactly 16 bits */

  if (length != 2 || validbits != 0)
    {
      return -EAGAIN;
    }

  mfrc522info("buffer[0]=0x%02X | buffer[1]=0x%02X\n", buffer[0], buffer[1]);
  return OK;
}

/****************************************************************************
 * Name: mfrc522_picc_request_a
 *
 * Description:
 *   Transmits a REQuest command, Type A. Invites PICCs in state IDLE to go to
 * READY and prepare for anticollision or selection.
 *
 * Input Parameters:
 *
 * Returned Value: OK or -ETIMEDOUT
 *
 ****************************************************************************/

int mfrc522_picc_request_a(FAR struct mfrc522_dev_s *dev, uint8_t *buffer,
                           uint8_t length)
{
  return mfrc522_picc_reqa_wupa(dev, PICC_CMD_REQA, buffer, length);
}

/****************************************************************************
 * Name: mfrc522_picc_detect
 *
 * Description:
 *   Detects if a Contactless Card is near
 *
 * Input Parameters:
 *
 * Returned Value: OK or -ETIMEDOUT
 *
 ****************************************************************************/

int mfrc522_picc_detect(FAR struct mfrc522_dev_s *dev)
{
  int ret;
  uint8_t buffer_atqa[2];
  uint8_t length = sizeof(buffer_atqa);

  /* Send a REQA command */

  ret = mfrc522_picc_request_a(dev, buffer_atqa, length);
  return (ret == OK || ret == -EBUSY);
}

/****************************************************************************
 * Name: mfrc522_picc_select
 *
 * Description:
 *   Selects a near Card and read its UID.
 *
 * Input Parameters:
 *
 * Returned Value: OK or -ETIMEDOUT
 *
 ****************************************************************************/

int mfrc522_picc_select(FAR struct mfrc522_dev_s *dev,
                        FAR struct picc_uid_s *uid, uint8_t validbits)
{
  bool uid_complete;
  bool select_done;
  bool use_cascade_tag;
  uint8_t cascade_level = 1;
  int result;
  uint8_t i;
  uint8_t value;
  uint8_t count;

  /* The first index in uid->data[] that is used in the current Cascade Level */

  uint8_t uid_index;

  /* The number of known UID bits in the current Cascade Level. */

  uint8_t curr_level_known_bits;

  /* The SELECT/ANTICOLLISION uses a 7 byte standard frame + 2 bytes CRC_A */

  uint8_t buffer[9];

  /* The number of bytes used in the buffer, number bytes on FIFO */

  uint8_t buffer_used;

  /* Used to defines the bit position for the first bit received */

  uint8_t rxalign;

  /* The number of valid bits in the last transmitted byte. */

  uint8_t txlastbits;

  uint8_t *resp_buf;
  uint8_t resp_len;

  /* Sanity check */

  if (validbits > 80)
    {
      return -EINVAL;
    }

  /* Force clear of received bits if a collision is detected */

  value = mfrc522_readu8(dev, MFRC522_COLL_REG);
  mfrc522_writeu8(dev, MFRC522_COLL_REG, value & MFRC522_VALUES_AFTER_COLL);

  /* Repeat cascade level loop until we have a complete UID */

  uid_complete = false;
  while (!uid_complete)
    {
      uint8_t bytes_to_copy;

      /* Set the Cascade Level in the SEL byte, find out if we need to use the
       * Cascade Tag in byte 2.
       */

      switch (cascade_level)
        {
        case 1:
          buffer[0] = PICC_CMD_SEL_CL1;
          uid_index = 0;

          /* When we know that the UID has more than 4 bytes */

          use_cascade_tag = validbits && (uid->size > 4);
          break;

        case 2:
          buffer[0] = PICC_CMD_SEL_CL2;
          uid_index = 3;

          /* When we know that the UID has more than 7 bytes */

          use_cascade_tag = validbits && (uid->size > 7);
          break;

        case 3:
          buffer[0] = PICC_CMD_SEL_CL3;
          uid_index = 6;
          use_cascade_tag = false;
          break;

        default:
          return -EIO;          /* Internal error */
        }

      /* How many UID bits are known in this Cascade Level? */

      curr_level_known_bits = validbits - (8 * uid_index);
      if (curr_level_known_bits < 0)
        {
          curr_level_known_bits = 0;
        }

      /* Copy the known bits from uid->uid_data[] to buffer[] */

      i = 2;                    /* destination index in buffer[] */
      if (use_cascade_tag)
        {
          buffer[i++] = PICC_CMD_CT;
        }

      /* Number of bytes needed to represent the known bits for this level */

      bytes_to_copy = curr_level_known_bits / 8 +
                      (curr_level_known_bits % 8 ? 1 : 0);

      if (bytes_to_copy)
        {
          /* Max 4 bytes in each Cascade Level. Only 3 left if we use the
           * Cascade Tag.
           */

          uint8_t max_bytes = use_cascade_tag ? 3 : 4;

          if (bytes_to_copy > max_bytes)
            {
              bytes_to_copy = max_bytes;
            }

          for (count = 0; count < bytes_to_copy; count++)
            {
              buffer[i++] = uid->uid_data[uid_index + count];
            }
        }

      /* Now that the data has been copied we need to include the 8 bits in CT
       * in curr_level_known_bits.
       */

      if (use_cascade_tag)
        {
          curr_level_known_bits += 8;
        }

      /* Repeat anti collision loop until we can transmit all UID bits + BCC
       * and receive a SAK - max 32 iterations.
       */

      select_done = false;
      while (!select_done)
        {
          /* Find out how many bits and bytes to send and receive. */

          if (curr_level_known_bits >= 32)
            {
              /* All UID bits in this Cascade Level are known. This is a
               * SELECT.
               */

              /* NVB - Number of Valid Bits: Seven whole bytes */

              buffer[1] = 0x70;

              /* Calculate BCC - Block Check Character */

              buffer[6] = buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5];

              /* Calculate CRC_A */

              result = mfrc522_calc_crc(dev, buffer, 7, &buffer[7]);
              if (result != OK)
                {
                  return result;
                }

              txlastbits = 0;   /* 0 => All 8 bits are valid. */
              buffer_used = 9;

              /* Store response in the last 3 bytes of buffer (BCC and CRC_A -
               * not needed after tx).
               */

              resp_buf = &buffer[6];
              resp_len = 3;
            }
          else
            {
              /* This is an ANTICOLLISION */

              txlastbits = curr_level_known_bits % 8;

              /* Number of whole bytes in the UID part. */

              count = curr_level_known_bits / 8;
              i = 2 + count;

              /* NVB - Number of Valid Bits */

              buffer[1] = (i << 4) + txlastbits;
              buffer_used = i + (txlastbits ? 1 : 0);

              /* Store response in the unused part of buffer */

              resp_buf = &buffer[i];
              resp_len = sizeof(buffer) - i;
            }

          /* Set bit adjustments */

          rxalign = txlastbits;
          mfrc522_writeu8(dev, MFRC522_BIT_FRAMING_REG,
                          (rxalign << 4) + txlastbits);

          /* Transmit the buffer and receive the response */

          result = mfrc522_transcv_data(dev, buffer, buffer_used, resp_buf,
                                        &resp_len, &txlastbits, rxalign, false);

          /* More than one PICC in the field => collision */

          if (result == -EBUSY)
            {
              uint8_t coll_pos;
              uint8_t coll_reg = mfrc522_readu8(dev, MFRC522_COLL_REG);

              /* CollPosNotValid */

              if (coll_reg & 0x20)
                {
                  /* Without a valid collision position we cannot continue */

                  return -EBUSY;
                }

              coll_pos = coll_reg & 0x1F; /* Values 0-31, 0 means bit 32. */
              if (coll_pos == 0)
                {
                  coll_pos = 32;
                }

              if (coll_pos <= curr_level_known_bits)
                {
                  /* No progress - should not happen */

                  return -EIO;
                }

              /* Choose the PICC with the bit set. */

              curr_level_known_bits = coll_pos;

              /* The bit to modify */

              count = (curr_level_known_bits - 1) % 8;

              /* First byte is index 0. */

              i = 1 + (curr_level_known_bits / 8) + (count ? 1 : 0);
              buffer[i] |= (1 << count);
            }
          else if (result != OK)
            {
              return result;
            }
          else                  /* OK */
            {
              /* This was a SELECT. */

              if (curr_level_known_bits >= 32)
                {
                  /* No more collision */

                  select_done = true;
                }
              else
                {
                  /* This was an ANTICOLLISION. */
                  /* We have all 32 bits of the UID in this Cascade Level */

                  curr_level_known_bits = 32;

                  /* Run loop again to do the SELECT */
                }
            }
        }

      /* We do not check the CBB - it was constructed by us above. */
      /* Copy the found UID bytes from buffer[] to uid->uid_data[] */

      i = (buffer[2] == PICC_CMD_CT) ? 3 : 2;   /* source index in buffer[] */
      bytes_to_copy = (buffer[2] == PICC_CMD_CT) ? 3 : 4;

      for (count = 0; count < bytes_to_copy; count++)
        {
          uid->uid_data[uid_index + count] = buffer[i++];
        }

      /* Check response SAK (Select Acknowledge) */

      if (resp_len != 3 || txlastbits != 0)
        {
          /* SAK must be exactly 24 bits (1 byte + CRC_A). */

          return -EIO;
        }

      /* Verify CRC_A - do our own calculation and store the control in
       * buffer[2..3] - those bytes are not needed anymore.
       */

      result = mfrc522_calc_crc(dev, resp_buf, 1, &buffer[2]);
      if (result != OK)
        {
          return result;
        }

      /* Is it correct */

      if ((buffer[2] != resp_buf[1]) || (buffer[3] != resp_buf[2]))
        {
          return -EINVAL;
        }

      /* Cascade bit set - UID not complete yes */

      if (resp_buf[0] & 0x04)
        {
          cascade_level++;
        }
      else
        {
          uid_complete = true;
          uid->sak = resp_buf[0];
        }
    }

  /* Set correct uid->size */

  uid->size = 3 * cascade_level + 1;

  return OK;
}

/****************************************************************************
 * Name: mfrc522_softreset
 *
 * Description:
 *   Send a software reset command
 *
 * Input Parameters: a pointer to mfrc522_dev_s structure
 *
 * Returned Value: none
 *
 ****************************************************************************/

void mfrc522_softreset(FAR struct mfrc522_dev_s *dev)
{
  /* Send a software reset command */

  mfrc522_writeu8(dev, MFRC522_COMMAND_REG, MFRC522_SOFTRST_CMD);

  /* Wait the internal state machine to initialize */

  usleep(50000);

  /* Wait for the PowerDown bit in COMMAND_REG to be cleared */

  while (mfrc522_readu8(dev, MFRC522_COMMAND_REG) & MFRC522_POWER_DOWN);
}

/****************************************************************************
 * Name: mfrc522_enableantenna
 *
 * Description:
 *   Turns the antenna on by enabling the pins TX1 and TX2
 *
 * Input Parameters: a pointer to mfrc522_dev_s structure
 *
 * Returned Value: none
 *
 ****************************************************************************/

void mfrc522_enableantenna(FAR struct mfrc522_dev_s *dev)
{
  uint8_t value = mfrc522_readu8(dev, MFRC522_TX_CTRL_REG);

  if ((value & (MFRC522_TX1_RF_EN | MFRC522_TX2_RF_EN)) != 0x03)
    {
      mfrc522_writeu8(dev, MFRC522_TX_CTRL_REG, value | 0x03);
    }
}

/****************************************************************************
 * Name: mfrc522_disableantenna
 *
 * Description:
 *   Turns the antenna off cutting the signals on TX1 and TX2
 *
 * Input Parameters: a pointer to mfrc522_dev_s structure
 *
 * Returned Value: none
 *
 ****************************************************************************/

void mfrc522_disableantenna(FAR struct mfrc522_dev_s *dev)
{
  uint8_t value = mfrc522_readu8(dev, MFRC522_TX_CTRL_REG);

  value &= ~(MFRC522_TX1_RF_EN | MFRC522_TX2_RF_EN);
  mfrc522_writeu8(dev, MFRC522_TX_CTRL_REG, value);
}

/****************************************************************************
 * Name: mfrc522_getfwversion
 *
 * Description:
 *   Read the MFRC522 firmware version.
 *
 * Input Parameters: a pointer to mfrc522_dev_s structure
 *
 * Returned Value: the firmware version byte
 *
 ****************************************************************************/

uint8_t mfrc522_getfwversion(FAR struct mfrc522_dev_s *dev)
{
  return mfrc522_readu8(dev, MFRC522_VERSION_REG);
}

/****************************************************************************
 * Name: mfrc522_getantennagain
 *
 * Description:
 *   Read the MFRC522 receiver gain (RxGain).
 * See 9.3.3.6 / table 98 in MFRC522 datasheet.
 *
 * Input Parameters: a pointer to mfrc522_dev_s structure
 *
 * Returned Value: none
 *
 ****************************************************************************/

uint8_t mfrc522_getantennagain(FAR struct mfrc522_dev_s *dev)
{
  return mfrc522_readu8(dev, MFRC522_RF_CFG_REG) & MFRC522_RX_GAIN_MASK;
}

/****************************************************************************
 * Name: mfrc522_setantennagain
 *
 * Description:
 *   Set the MFRC522 receiver gain (RxGain) to value value specified in mask.
 * See 9.3.3.6 / table 98 in MFRC522 datasheet.
 *
 * Input Parameters: a pointer to mfrc522_dev_s structure
 *
 * Returned Value: none
 *
 ****************************************************************************/

void mfrc522_setantennagain(FAR struct mfrc522_dev_s *dev, uint8_t mask)
{
  uint8_t value;

  if ((value = mfrc522_getantennagain(dev)) != mask)
    {
      mfrc522_writeu8(dev, MFRC522_RF_CFG_REG, value & ~MFRC522_RX_GAIN_MASK);
      mfrc522_writeu8(dev, MFRC522_RF_CFG_REG, mask & MFRC522_RX_GAIN_MASK);
    }
}

/****************************************************************************
 * Name: mfrc522_init
 *
 * Description:
 *   Initializes the MFRC522 chip
 *
 * Input Parameters: a pointer to mfrc522_dev_s structure
 *
 * Returned Value: none
 *
 ****************************************************************************/

void mfrc522_init(FAR struct mfrc522_dev_s *dev)
{
  /* Force a reset */

  mfrc522_softreset(dev);

  /* We need a timeout if something when communicating with a TAG case
   * something goes wrong. f_timer = 13.56 MHz / (2*TPreScaler+1) where:
   * TPreScaler = [TPrescaler_Hi:Tprescaler_Lo]. Tprescaler_Hi are the four
   * low bits in TmodeReg. Tprescaler_Lo is on TPrescalerReg.
   *
   * TAuto=1; timer starts automatically at the end of the transmission in
   * all communication modes at all speeds.
   */

  mfrc522_writeu8(dev, MFRC522_TMODE_REG, MFRC522_TAUTO);

  /* TPreScaler = TModeReg[3..0]:TPrescalerReg, ie: 0x0A9 = 169 =>
   * f_timer=40kHz, then the timer period will be 25us.
   */

  mfrc522_writeu8(dev, MFRC522_TPRESCALER_REG, 0xA9);

  /* Reload timer with 0x3E8 = 1000, ie 25ms before timeout. */

  mfrc522_writeu8(dev, MFRC522_TRELOAD_REGH, 0x06);
  mfrc522_writeu8(dev, MFRC522_TRELOAD_REGL, 0xE8);

  /* Force 100% ASK modulation independent of the ModGsPReg setting */

  mfrc522_writeu8(dev, MFRC522_TX_ASK_REG, MFRC522_FORCE_100ASK);

  /* Set the preset value for the CRC to 0x6363 (ISO 14443-3 part 6.2.4) */

  mfrc522_writeu8(dev, MFRC522_MODE_REG, 0x3D);

  /* Enable the Antenna pins */

  mfrc522_enableantenna(dev);
}

/****************************************************************************
 * Name: mfrc522_selftest
 *
 * Description:
 *   Executes a self-test of the MFRC522 chip
 *
 * See 16.1.1 in the MFRC522 datasheet
 *
 * Input Parameters: a pointer to mfrc522_dev_s structure
 *
 * Returned Value: none
 *
 ****************************************************************************/

int mfrc522_selftest(FAR struct mfrc522_dev_s *dev)
{
  uint8_t i;
  uint8_t result[64];
  uint8_t zeros[25] = {0, 0, 0, 0, 0,
                       0, 0, 0, 0, 0,
                       0, 0, 0, 0, 0,
                       0, 0, 0, 0, 0,
                       0, 0, 0, 0, 0};

  /* Execute a software reset */

  mfrc522_softreset(dev);

  /* Flush the FIFO buffer */

  mfrc522_writeu8(dev, MFRC522_FIFO_LEVEL_REG, MFRC522_FLUSH_BUFFER);

  /* Clear the internal buffer by writing 25 bytes 0x00 */

  mfrc522_writeblk(dev, MFRC522_FIFO_DATA_REG, zeros, 25);

  /* Transfer to internal buffer */

  mfrc522_writeu8(dev, MFRC522_COMMAND_REG, MFRC522_MEM_CMD);

  /* Enable self-test */

  mfrc522_writeu8(dev, MFRC522_AUTOTEST_REG, MFRC522_SELFTEST_EN);

  /* Write 0x00 to FIFO buffer */

  mfrc522_writeu8(dev, MFRC522_FIFO_DATA_REG, 0x00);

  /* Start self-test by issuing the CalcCRC command */

  mfrc522_writeu8(dev, MFRC522_COMMAND_REG, MFRC522_CALC_CRC_CMD);

  /* Wait for self-test to complete */

  for (i = 0; i < 255; i++)
    {
      uint8_t n;

      n = mfrc522_readu8(dev, MFRC522_DIV_IRQ_REG);
      if (n & MFRC522_CRC_IRQ)
        {
          break;
        }
    }

  /* Stop calculating CRC for new content in the FIFO */

  mfrc522_writeu8(dev, MFRC522_COMMAND_REG, MFRC522_IDLE_CMD);

  /* Read out the 64 bytes result from the FIFO buffer */

  mfrc522_readblk(dev, MFRC522_FIFO_DATA_REG, result, 64, 0);

  /* Self-test done. Reset AutoTestReg register to normal operation */

  mfrc522_writeu8(dev, MFRC522_AUTOTEST_REG, 0x00);

  mfrc522info("Self Test Result:\n");
  for (i = 1; i <= 64; i++)
    {
      printf("0x%02X ", result[i - 1]);

      if ((i % 8) == 0)
        {
          printf("\n");
        }
    }

  mfrc522info("Done!\n");
  return OK;
}

/****************************************************************************
 * Name: mfrc522_open
 *
 * Description:
 *   This function is called whenever the MFRC522 device is opened.
 *
 ****************************************************************************/

static int mfrc522_open(FAR struct file *filep)
{
  FAR struct inode *inode;
  FAR struct mfrc522_dev_s *dev;

  DEBUGASSERT(filep);
  inode = filep->f_inode;

  DEBUGASSERT(inode && inode->i_private);
  dev = inode->i_private;

  mfrc522_configspi(dev->spi);

  usleep(10000);

  mfrc522_getfwversion(dev);

  dev->state = MFRC522_STATE_IDLE;
  return OK;
}

/****************************************************************************
 * Name: mfrc522_close
 *
 * Description:
 *   This routine is called when the MFRC522 device is closed.
 *
 ****************************************************************************/

static int mfrc522_close(FAR struct file *filep)
{
  FAR struct inode *inode;
  FAR struct mfrc522_dev_s *dev;

  DEBUGASSERT(filep);
  inode = filep->f_inode;

  DEBUGASSERT(inode && inode->i_private);
  dev = inode->i_private;

  dev->state = MFRC522_STATE_NOT_INIT;

  return OK;
}

/****************************************************************************
 * Name: mfrc522_read
 *
 * Description:
 *   This routine is called when the device is read.
 *
 * Returns TAG id as string to buffer.
 * or -EIO if no TAG found
 *
 ****************************************************************************/

static ssize_t mfrc522_read(FAR struct file *filep, FAR char *buffer,
                            size_t buflen)
{
  FAR struct inode *inode;
  FAR struct mfrc522_dev_s *dev;
  FAR struct picc_uid_s uid;

  DEBUGASSERT(filep);
  inode = filep->f_inode;

  DEBUGASSERT(inode && inode->i_private);
  dev = inode->i_private;

  /* Is a card near? */

  if (!mfrc522_picc_detect(dev))
    {
      mfrc522err("Card is not present!\n");
      return -EAGAIN;
    }

  /* Now read the UID */

  mfrc522_picc_select(dev, &uid, 0);

  if (uid.sak != 0)
    {
      if (buffer)
        {
          snprintf(buffer, buflen, "0x%02X%02X%02X%02X",
                   uid.uid_data[0], uid.uid_data[1],
                   uid.uid_data[2], uid.uid_data[3]);
          return buflen;
        }
    }

  return OK;
}

/****************************************************************************
 * Name: mfrc522_write
 ****************************************************************************/

static ssize_t mfrc522_write(FAR struct file *filep, FAR const char *buffer,
                             size_t buflen)
{
  FAR struct inode *inode;
  FAR struct mfrc522_dev_s *dev;

  DEBUGASSERT(filep);
  inode = filep->f_inode;

  DEBUGASSERT(inode && inode->i_private);
  dev = inode->i_private;

  (void)dev;

  return -ENOSYS;
}

/****************************************************************************
 * Name: mfrc522_ioctl
 ****************************************************************************/

static int mfrc522_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
  FAR struct inode *inode;
  FAR struct mfrc522_dev_s *dev;
  int ret = OK;

  DEBUGASSERT(filep);
  inode = filep->f_inode;

  DEBUGASSERT(inode && inode->i_private);
  dev = inode->i_private;

  switch (cmd)
    {
    case MFRC522IOC_GET_PICC_UID:
      {
        struct picc_uid_s *uid = (struct picc_uid_s *)arg;

        /* Is a card near? */

        if (mfrc522_picc_detect(dev))
          {
            ret = mfrc522_picc_select(dev, uid, 0);
          }
      }
      break;

    case MFRC522IOC_GET_STATE:
      ret = dev->state;
      break;

    default:
      mfrc522err("ERROR: Unrecognized cmd: %d\n", cmd);
      ret = -ENOTTY;
      break;
    }

  return ret;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: mfrc522_register
 *
 * Description:
 *   Register the MFRC522 character device as 'devpath'
 *
 * Input Parameters:
 *   devpath - The full path to the driver to register.
 *             E.g., "/dev/rfid0"
 *   spi     - An instance of the SPI interface to use to communicate with
 *             MFRC522.
 *   config  - chip config
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno value on failure.
 *
 ****************************************************************************/

int mfrc522_register(FAR const char *devpath, FAR struct spi_dev_s *spi)
{
  FAR struct mfrc522_dev_s *dev;
  uint8_t fwver;
  int ret = 0;

  /* Initialize the MFRC522 device structure */

  dev = (FAR struct mfrc522_dev_s *)kmm_malloc(sizeof(struct mfrc522_dev_s));
  if (!dev)
    {
      mfrc522err("ERROR: Failed to allocate instance\n");
      return -ENOMEM;
    }

  dev->spi = spi;

  /* Device is not initialized yet */

  dev->state = MFRC522_STATE_NOT_INIT;

#if defined CONFIG_PM
  dev->pm_level = PM_IDLE;
#endif

  /* mfrc522_attachirq(dev, mfrc522_irqhandler); */

  /* Initialize the MFRC522 */

  mfrc522_init(dev);

  /* Device initialized and idle */

  dev->state = MFRC522_STATE_IDLE;

  /* Read the Firmware Version */

  fwver = mfrc522_getfwversion(dev);

  mfrc522info("MFRC522 Firmware Version: 0x%02X!\n", fwver);

  /* If returned firmware version is unknown don't register the device */

  if (fwver != 0x90 && fwver != 0x91 && fwver != 0x92 && fwver != 0x88 )
    {
      mfrc522err("None supported device detected!\n");
      goto firmware_error;
    }

  /* Register the character driver */

  ret = register_driver(devpath, &g_mfrc522fops, 0666, dev);
  if (ret < 0)
    {
      mfrc522err("ERROR: Failed to register driver: %d\n", ret);
      kmm_free(dev);
    }

  return ret;

firmware_error:
  kmm_free(dev);
  return -ENODEV;
}