/****************************************************************************
 * drivers/contactless/pn532.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * 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/signal.h>

#include "pn532.h"

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

/* Configuration ************************************************************/

/* Bit order H/W feature must be enabled in order to support LSB first
 * operation.
 */

#if !defined(CONFIG_SPI_HWFEATURES) || !defined(CONFIG_SPI_BITORDER)
#  error CONFIG_SPI_HWFEATURES=y and CONFIG_SPI_BITORDER=y required by this driver
#endif

#ifndef CONFIG_ARCH_HAVE_SPI_BITORDER
#  warning This platform does not support SPI LSB-bit order
#endif

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

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

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

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

static inline void pn532_configspi(FAR struct spi_dev_s *spi);
static void pn532_lock(FAR struct spi_dev_s *spi);
static void pn532_unlock(FAR struct spi_dev_s *spi);

/* Character driver methods */

static int     _open(FAR struct file *filep);
static int     _close(FAR struct file *filep);
static ssize_t _read(FAR struct file *filep,
                     FAR char *buffer, size_t buflen);
static ssize_t _write(FAR struct file *filep, FAR const char *buffer,
                      size_t buflen);
static int     _ioctl(FAR struct file *filep, int cmd, unsigned long arg);

static uint8_t pn532_checksum(uint8_t value);
static uint8_t pn532_data_checksum(FAR uint8_t *data, int datalen);

int pn532_read(FAR struct pn532_dev_s *dev, FAR uint8_t *buff, uint8_t n);

#if 0 /* TODO */
/* IRQ Handling */

static int pn532_irqhandler(FAR int irq, FAR void *context, FAR void *dev);
static inline int pn532_attachirq(FAR struct pn532_dev_s *dev, xcpt_t isr);
#endif

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

static const struct file_operations g_pn532fops =
{
  _open,
  _close,
  _read,
  _write,
  NULL,
  _ioctl,
  NULL
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
  , NULL
#endif
};

static const uint8_t pn532ack[] =
{
  0x00, 0x00, 0xff, 0x00, 0xff, 0x00
};

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

static void pn532_lock(FAR struct spi_dev_s *spi)
{
  int ret;

  SPI_LOCK(spi, true);

  SPI_SETMODE(spi, SPIDEV_MODE0);
  SPI_SETBITS(spi, 8);

  ret = SPI_HWFEATURES(spi, HWFEAT_LSBFIRST);
  if (ret < 0)
    {
      ctlserr("ERROR: SPI_HWFEATURES failed to set bit order: %d\n", ret);
    }

  SPI_SETFREQUENCY(spi, CONFIG_PN532_SPI_FREQ);
}

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

static inline void pn532_configspi(FAR struct spi_dev_s *spi)
{
  int ret;

  /* Configure SPI for the PN532 module. */

  SPI_SETMODE(spi, SPIDEV_MODE0);
  SPI_SETBITS(spi, 8);

  ret = SPI_HWFEATURES(spi, HWFEAT_LSBFIRST);
  if (ret < 0)
    {
      ctlserr("ERROR: SPI_HWFEATURES failed to set bit order: %d\n", ret);
    }

  SPI_SETFREQUENCY(spi, CONFIG_PN532_SPI_FREQ);
}

static inline void pn532_select(FAR struct pn532_dev_s *dev)
{
  if (dev->config->select)
    {
      dev->config->select(dev, true);
    }
  else
    {
      SPI_SELECT(dev->spi, SPIDEV_CONTACTLESS(0), true);
    }
}

static inline void pn532_deselect(FAR struct pn532_dev_s *dev)
{
  if (dev->config->select)
    {
      dev->config->select(dev, false);
    }
  else
    {
      SPI_SELECT(dev->spi, SPIDEV_CONTACTLESS(0), false);
    }
}

static void pn532_frame_init(FAR struct pn532_frame *frame, uint8_t cmd)
{
  frame->preamble   = PN532_PREAMBLE;
  frame->start_code = PN532_SOF;
  frame->tfi        = PN532_HOSTTOPN532;
  frame->data[0]    = cmd;
  frame->len        = 2;
}

static void pn532_frame_finish(FAR struct pn532_frame *frame)
{
  frame->lcs = pn532_checksum(frame->len);
  frame->data[frame->len - 1] = pn532_data_checksum(&frame->tfi, frame->len);
  frame->data[frame->len]   = PN532_POSTAMBLE;
}

static inline uint8_t pn532_checksum(uint8_t value)
{
  return ~value + 1;
}

static uint8_t pn532_data_checksum(FAR uint8_t *data, int datalen)
{
  uint8_t sum = 0x00;
  int i;

  for (i = 0; i < datalen; i++)
    {
      sum += data[i];
    }

  return pn532_checksum(sum);
}

bool pn532_rx_frame_is_valid(FAR struct pn532_frame *f, bool check_data)
{
  uint8_t chk;

  if (f->start_code != PN532_SOF)
    {
      ctlserr("ERROR: Frame startcode 0x%X != 0x%X\n",
               PN532_SOF, f->start_code);
      return false;
    }

  if (f->tfi != PN532_PN532TOHOST)
    {
      return false;
    }

  chk = pn532_checksum(f->len);
  if (chk != f->lcs)
    {
      ctlserr("ERROR: Frame data len checksum failed");
      return false;
    }

  if (check_data)
    {
      chk = pn532_data_checksum(&f->tfi, f->len);
      if (chk != f->data[f->len - 1])
        {
          ctlserr("ERROR: Frame data checksum failed: calc=0x%X != 0x%X",
                   chk, f->data[f->len - 1]);
          return false;
        }
    }

  return true;
}

static uint8_t pn532_status(FAR struct pn532_dev_s *dev)
{
  int rs;

  pn532_lock(dev->spi);
  pn532_select(dev);

  rs = SPI_SEND(dev->spi, PN532_SPI_STATREAD);
  rs = SPI_SEND(dev->spi, PN532_SPI_STATREAD);

  pn532_deselect(dev);
  pn532_unlock(dev->spi);

  return rs;
}

/****************************************************************************
 * Name: pn532_wait_rx_ready
 *
 * Description:
 *   Blocks until Data frame available from chip.
 *
 * Input Parameters:
 *   dev
 *   timeout
 *
 * Returned Value:
 *   0 for OK. -ETIMEDOUT if no data available
 *
 ****************************************************************************/

static int pn532_wait_rx_ready(FAR struct pn532_dev_s *dev, int timeout)
{
  int ret = OK;

#ifdef CONFIG_PN532_USE_IRQ_FLOW_CONTROL
  struct timespec ts;
  clock_gettime(CLOCK_REALTIME, &ts);
  ts.tv_sec += 1;
  nxsem_timedwait(dev->sem_rx, &ts);
#endif

  /* TODO: Handle Exception bits 2, 3 */

  while (pn532_status(dev) != PN532_SPI_READY)
    {
      if (--timeout == 0x00)
        {
          ctlserr("ERROR: wait RX timeout!\n");
          return -ETIMEDOUT;
        }

      nxsig_usleep(1000);
    }

  dev->state = PN532_STATE_DATA_READY;
  return ret;
}

/****************************************************************************
 * Name: pn532_writecommand
 *
 * Description:
 *   Helper for debug/testing
 *
 * Input Parameters:
 *
 * Returned Value:
 *
 ****************************************************************************/

#if 0
static void pn532_writecommand(FAR struct pn532_dev_s *dev, uint8_t cmd)
{
  char cmd_buffer[16];
  FAR struct pn532_frame *f = (FAR struct pn532_frame *)cmd_buffer;

  pn532_frame_init(f, cmd);
  pn532_frame_finish(f);

  pn532_lock(dev->spi);
  pn532_select(dev);
  nxsig_usleep(10000);

  SPI_SEND(dev->spi, PN532_SPI_DATAWRITE);
  SPI_SNDBLOCK(dev->spi, f, FRAME_SIZE(f));

  pn532_deselect(dev);
  pn532_unlock(dev->spi);

  tracetx("command sent", (uint8_t *) f, FRAME_SIZE(f));
}
#endif

/****************************************************************************
 * Name: pn532_read
 *
 * Description:
 *   RAW Read data from chip.
 *   NOTE: This WON'T wait if data is available!
 *
 * Input Parameters:
 *
 * Returned Value:
 *
 ****************************************************************************/

int pn532_read(FAR struct pn532_dev_s *dev, FAR uint8_t *buff, uint8_t n)
{
  pn532_lock(dev->spi);
  pn532_select(dev);
  SPI_SEND(dev->spi, PN532_SPI_DATAREAD);
  SPI_RECVBLOCK(dev->spi, buff, n);
  pn532_deselect(dev);
  pn532_unlock(dev->spi);

  tracerx("read", buff, n);
  return n;
}

int pn532_read_more(FAR struct pn532_dev_s *dev,
                    FAR uint8_t *buff, uint8_t n)
{
  pn532_lock(dev->spi);
  pn532_select(dev);
  SPI_RECVBLOCK(dev->spi, buff, n);
  pn532_deselect(dev);
  pn532_unlock(dev->spi);

  tracerx("read_more", buff, n);
  return n;
}

/****************************************************************************
 * Name: pn532_read_ack
 *
 * Description:
 *   Read Ack response from device
 *
 * Input Parameters:
 *   dev
 *
 * Returned Value:
 *   0 = NOK, 1 = OK
 *
 ****************************************************************************/

int pn532_read_ack(FAR struct pn532_dev_s *dev)
{
  int res = 0;
  uint8_t ack[6];

  pn532_read(dev, (uint8_t *) &ack, 6);

  if (memcmp(&ack, &pn532ack, 6) == 0x00)
    {
      res = 1;
    }
  else
    {
      ctlsinfo("ACK NOK");
      res = 0;
    }

  return res;
}

/****************************************************************************
 * Name: pn532_write_frame
 *
 * Description:
 *   Write frame to chip.  Also waits and reads ACK frame from chip.
 *
 *   Construct frame with
 *      pn532_frame_init(), pn532_frame_finish()
 *
 * Input Parameters:
 *   dev - Device instance
 *   f   - Pointer to start frame
 *
 * Returned Value:
 *   0 for OK, negative for error
 *
 ****************************************************************************/

int pn532_write_frame(FAR struct pn532_dev_s *dev, FAR struct pn532_frame *f)
{
  int res = OK;

  pn532_lock(dev->spi);
  pn532_select(dev);
  nxsig_usleep(2000);

  SPI_SEND(dev->spi, PN532_SPI_DATAWRITE);
  SPI_SNDBLOCK(dev->spi, f, FRAME_SIZE(f));
  pn532_deselect(dev);
  pn532_unlock(dev->spi);
  tracetx("WriteFrame", (uint8_t *) f, FRAME_SIZE(f));

  /* Wait ACK frame */

  res = pn532_wait_rx_ready(dev, 30);
  if (res == OK)
    {
      if (!pn532_read_ack(dev))
        {
          ctlserr("ERROR: command FAILED\n");
          res = -EIO;
        }
    }

  return res;
}

int pn532_read_frame(FAR struct pn532_dev_s *dev, FAR struct pn532_frame *f,
                     int max_size)
{
  int res = -EIO;

  /* Wait for frame available */

  if ((res = pn532_wait_rx_ready(dev, 100)) == OK)
    {
      /* Read header */

      pn532_read(dev, (uint8_t *) f, sizeof(struct pn532_frame));
      if (pn532_rx_frame_is_valid(f, false))
        {
          if (max_size < f->len)
            {
              return -EINVAL;
            }

          pn532_read_more(dev, &f->data[0], f->len);

          /* TODO: optimize frame integrity check...
           * pn532_data_checksum(&f.tfi, f->len);
           * errdumpbuffer("RX Frame:", f, f->len+6);
           */

          if (pn532_rx_frame_is_valid(f, true))
            {
              res = OK;
            }
        }
    }

  return res;
}

bool pn532_set_config(FAR struct pn532_dev_s *dev, uint8_t flags)
{
  char cmd_buffer[2 + 7];
  FAR struct pn532_frame *f = (FAR struct pn532_frame *)cmd_buffer;

  pn532_frame_init(f, PN532_COMMAND_SETPARAMETERS);
  f->data[1] = flags;
  f->len += 1;
  pn532_frame_finish(f);

  uint8_t resp[9];
  bool res = false;

  if (pn532_write_frame(dev, f) == OK)
    {
      pn532_read(dev, (uint8_t *) &resp, 9);
      tracerx("set config response", resp, 9);
      res = true;
    }

  return res;
}

int pn532_sam_config(FAR struct pn532_dev_s *dev,
                     FAR struct pn_sam_settings_s *settings)
{
  char cmd_buffer[4 + 7];
  FAR struct pn532_frame *f = (FAR struct pn532_frame *) cmd_buffer;
  int res;

  pn532_frame_init(f, PN532_COMMAND_SAMCONFIGURATION);
  f->data[1] = PN532_SAM_NORMAL_MODE;
  f->data[2] = 0x14;    /* Timeout LSB=50ms 0x14*50ms = 1sec */
  f->data[3] = 0x01;    /* P-70, IRQ enabled */

  if (settings)
    {
      /*  TODO: !!! */
    }

  f->len += 3;
  pn532_frame_finish(f);

  res = -EIO;

  if (pn532_write_frame(dev, f) == OK)
    {
      if (pn532_read_frame(dev, f, 4) == OK)
        {
          tracerx("sam config response", (uint8_t *) f->data, 3);
          if (f->data[0] == PN532_COMMAND_SAMCONFIGURATION + 1)
            {
              res = OK;
            }
        }
    }

  return res;
}

int pn532_get_fw_version(FAR struct pn532_dev_s *dev,
                         FAR struct pn_firmware_version *fv)
{
  uint8_t cmd_buffer[4 + 8 + 1];
  FAR struct pn532_frame *f = (FAR struct pn532_frame *) cmd_buffer;
  struct pn_firmware_version *fw;
  int res = -EIO;

  pn532_frame_init(f, PN532_COMMAND_GETFIRMWAREVERSION);
  pn532_frame_finish(f);

  if (pn532_write_frame(dev, f) == OK)
    {
      if (pn532_read_frame(dev, f, 6) == OK)
        {
          if (f->data[0] == PN532_COMMAND_GETFIRMWAREVERSION + 1)
            {
              fw = (FAR struct pn_firmware_version *) &f->data[1];
              ctlsinfo("FW: %d.%d on IC:0x%X (Features: 0x%X)\n",
                        fw->ver, fw->rev, fw->ic, fw->support);
              if (fv)
                {
                  memcpy(fv, fw, sizeof(struct pn_firmware_version));
                }

              res = OK;
            }
        }
    }

  return res;
}

int pn532_write_gpio(FAR struct pn532_dev_s *dev, uint8_t p3, uint8_t p7)
{
  uint8_t cmd_buffer[3 + 7];
  FAR struct pn532_frame *f = (FAR struct pn532_frame *) cmd_buffer;
  int res = -EIO;

  pn532_frame_init(f, PN532_COMMAND_WRITEGPIO);
  f->data[1] = p3;
  f->data[2] = p7;
  f->len += 2;
  pn532_frame_finish(f);

  if (pn532_write_frame(dev, f))
    {
      pn532_read(dev, cmd_buffer, 10);
      tracetx("Resp:", cmd_buffer, 10);
      ctlsinfo("TFI=%x, data0=%X", f->tfi, f->data[0]);
      if ((f->tfi == PN532_PN532TOHOST) &&
          (f->data[0] == PN532_COMMAND_WRITEGPIO + 1))
        {
          res = OK;
        }
    }

  return res;
}

uint32_t pn532_write_passive_data(FAR struct pn532_dev_s *dev,
                                  uint8_t address, FAR uint8_t *data,
                                  uint8_t len)
{
  uint8_t cmd_buffer[8 + 7];
  FAR struct pn532_frame *f = (FAR struct pn532_frame *) cmd_buffer;
  uint8_t resp[20];
  uint32_t res = -EIO;

  pn532_frame_init(f, PN532_COMMAND_INDATAEXCHANGE);
  f->data[1] = 1;       /* max n cards at once */
  f->data[2] = 0xa2;    /* command WRITE */
  f->data[3] = address; /* ADDRESS, 0 = serial */
  memcpy(&f->data[4], data, len);
  f->len += 7;
  pn532_frame_finish(f);

  if (pn532_write_frame(dev, f) == OK)
    {
      if (dev->state == PN532_STATE_DATA_READY)
        {
          if (pn532_read_frame(dev, (FAR struct pn532_frame *) resp, 15)
              == OK)
            {
              dev->state = PN532_STATE_IDLE;
              f = (FAR struct pn532_frame *) resp;
              tracerx("passive target id resp:", (FAR uint8_t *)f,
                      f->len + 6);

              if (f->data[0] == PN532_COMMAND_INDATAEXCHANGE + 1)
                {
                  res = f->data[1];
                }
            }
        }
    }

  return res;
}

uint32_t pn532_read_passive_data(FAR struct pn532_dev_s *dev,
                                 uint8_t address, FAR uint8_t *data,
                                 uint8_t len)
{
  uint8_t cmd_buffer[4 + 7];
  FAR struct pn532_frame *f = (FAR struct pn532_frame *) cmd_buffer;
  uint8_t resp[30];
  uint32_t res = -1;

  pn532_frame_init(f, PN532_COMMAND_INDATAEXCHANGE);
  f->data[1] = 1;       /* max n cards at once */
  f->data[2] = 0x30;    /* command READ */
  f->data[3] = address; /* ADDRESS, 0 = serial */
  f->len += 3;
  pn532_frame_finish(f);

  if (pn532_write_frame(dev, f) == OK)
    {
      if (dev->state == PN532_STATE_DATA_READY)
        {
          if (pn532_read_frame(dev,
                               (FAR struct pn532_frame *)resp, 25) == OK)
            {
              dev->state = PN532_STATE_IDLE;
              f = (FAR struct pn532_frame *) resp;
              tracerx("passive target id resp:", (FAR uint8_t *)f,
                      f->len + 6);

              if (f->data[0] == PN532_COMMAND_INDATAEXCHANGE + 1)
                {
                  if (f->data[1] == 0 && data && len)
                    {
                      memcpy(data, &f->data[2], len);
                    }

                  res = f->data[1];
                }
            }
        }
    }

  return res;
}

uint32_t pn532_read_passive_target_id(FAR struct pn532_dev_s *dev,
                                      uint8_t baudrate)
{
  uint8_t cmd_buffer[4 + 7];
  FAR struct pn532_frame *f = (FAR struct pn532_frame *) cmd_buffer;
  uint8_t resp[20];
  uint32_t res = -EAGAIN;
  int i;

  if (dev->state == PN532_STATE_DATA_READY)
    {
      res = -EIO;
      if (pn532_read_frame(dev, (FAR struct pn532_frame *) resp, 15) == OK)
        {
          FAR struct pn_poll_response *r;

          dev->state = PN532_STATE_IDLE;
          f = (FAR struct pn532_frame *) resp;
          r = (FAR struct pn_poll_response *) &f->data[1];

          tracerx("passive target id resp:", (FAR uint8_t *)f, f->len + 6);

          if (f->data[0] == PN532_COMMAND_INLISTPASSIVETARGET + 1)
            {
              uint32_t cid = 0;

              if (r->nbtg == 1)
                {
                  FAR struct pn_target_type_a *t =
                    (FAR struct pn_target_type_a *)&r->target_data;

                  ctlsinfo("Found %d card(s)\n", r->nbtg);

                  /* now supports only type_a cards
                   * if (poll_mode == PN532_POLL_MOD_106KBPS_A)
                   */

                  ctlsinfo("sens:0x%x  sel:0x%x", t->sens_res, t->sel_res);
                  ctlsinfo("idlen:0x%x ", t->nfcid_len);

                  /* generate 32bit cid from id (could be longer)
                   * HACK: Using only top 4 bytes.
                   */

                  for (i = 0; i < 4 /* t->nfcid_len */; i++)
                    {
                      cid <<= 8;
                      cid |= t->nfcid_data[i];
                    }
                }

              res = cid;
            }
        }
    }

  return res;
}

static int pn532_read_passive_target(FAR struct pn532_dev_s *dev,
                                     uint8_t baudrate)
{
  uint8_t cmd_buffer[4 + 7];
  FAR struct pn532_frame *f = (FAR struct pn532_frame *) cmd_buffer;

  pn532_frame_init(f, PN532_COMMAND_INLISTPASSIVETARGET);
  f->data[1] = 1;
  f->data[2] = baudrate;
  f->len += 2;
  pn532_frame_finish(f);
  return pn532_write_frame(dev, f);
}

bool pn532_set_rf_config(struct pn532_dev_s * dev,
                         struct pn_rf_config_s * conf)
{
  bool res = false;
  uint8_t cmd_buffer[15 + 7];
  FAR struct pn532_frame *f = (FAR struct pn532_frame *) cmd_buffer;

  pn532_frame_init(f, PN532_COMMAND_RFCONFIGURATION);
  f->data[1] = conf->cfg_item;
  memcpy(&f->data[2], conf->config, conf->data_size);
  f->len += conf->data_size + 1;
  pn532_frame_finish(f);

  if (pn532_write_frame(dev, f) == OK)
    {
      pn532_read(dev, (uint8_t *) f, 10);
      tracerx("rf config response", (uint8_t *) f, 10);
      if (pn532_rx_frame_is_valid(f, true))
        {
          if (f->data[0] == PN532_COMMAND_RFCONFIGURATION + 1)
            {
              res = true;
            }
        }
    }

  return res;
}

/****************************************************************************
 * Name: pn532_attachirq
 *
 * Description:
 *   IRQ handling TODO:
 *
 * Input Parameters:
 *
 * Returned Value:
 *
 ****************************************************************************/

#if 0
static inline int (FAR struct pn532_dev_s *dev, xcpt_t isr)
{
  return dev->config->irqattach(dev, isr);
}

static int irq_handler(int irq, FAR void *context)
{
  ctlsinfo("*IRQ*\n");
  work_queue(HPWORK, &g_dev->irq_work, pn532_worker, dev, 0);

  return OK;
}
#endif

/****************************************************************************
 * Name: pn532_open
 *
 * Description:
 *   This function is called whenever the PN532 device is opened.
 *
 ****************************************************************************/

static int _open(FAR struct file *filep)
{
  FAR struct inode *inode;
  FAR struct pn532_dev_s *dev;

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

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

  pn532_configspi(dev->spi);

  dev->config->reset(1);
  nxsig_usleep(10000);

  pn532_sam_config(dev, NULL);
  pn532_get_fw_version(dev, NULL);

  dev->state = PN532_STATE_IDLE;
  return OK;
}

/****************************************************************************
 * Name: _close
 *
 * Description:
 *   This routine is called when the PN532 device is closed.
 *
 ****************************************************************************/

static int _close(FAR struct file *filep)
{
  FAR struct inode *inode;
  FAR struct pn532_dev_s *dev;

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

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

  dev->config->reset(0);
  dev->state = PN532_STATE_NOT_INIT;

#ifdef CONFIG_PM
  if (dev->pm_level >= PM_SLEEP)
    {
#if 0
      priv->config->reset(0);
#endif
    }
#endif

  return OK;
}

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

static ssize_t _read(FAR struct file *filep, FAR char *buffer, size_t buflen)
{
  FAR struct inode *inode;
  FAR struct pn532_dev_s *dev;

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

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

  uint32_t id = pn532_read_passive_target_id(dev, PN532_MIFARE_ISO14443A);
  if (id != 0xffffffff)
    {
      if (buffer)
        {
          return snprintf(buffer, buflen, "0X%X", id);
        }
    }

  return -EIO;
}

/****************************************************************************
 * Name: pn532_write
 ****************************************************************************/

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

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

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

  UNUSED(dev);

  return -ENOSYS;
}

/****************************************************************************
 * Name: pn532_ioctl
 ****************************************************************************/

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

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

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

  switch (cmd)
    {
    case PN532IOC_READ_TAG_DATA:
      {
        FAR struct pn_mifare_tag_data_s *tag_data =
          (FAR struct pn_mifare_tag_data_s *) arg;

        if (tag_data != 0)
          {
            /* HACK: get rid of previous command */

            if (dev->state == PN532_STATE_CMD_SENT)
              {
                if (pn532_wait_rx_ready(dev, 1))
                  {
                    pn532_read_passive_target_id(dev, 0);
                  }
              }

            ret = pn532_read_passive_data(dev, tag_data->address,
                                          (uint8_t *) &tag_data->data,
                                          sizeof(tag_data->data));

            dev->state = PN532_STATE_IDLE;
          }
      }
      break;

    case PN532IOC_WRITE_TAG_DATA:
      {
        FAR struct pn_mifare_tag_data_s *tag_data =
          (FAR struct pn_mifare_tag_data_s *) arg;

        if (tag_data != 0)
          {
            /* HACK: get rid of previous command */

            if (dev->state == PN532_STATE_CMD_SENT)
              {
                if (pn532_wait_rx_ready(dev, 1))
                  {
                    pn532_read_passive_target_id(dev, 0);
                  }
              }

            ret = pn532_write_passive_data(dev, tag_data->address,
                                           (FAR uint8_t *) &tag_data->data,
                                           sizeof(tag_data->data));

            dev->state = PN532_STATE_IDLE;
          }
      }
      break;

    case PN532IOC_SET_SAM_CONF:
      pn532_sam_config(dev, (FAR struct pn_sam_settings_s *) arg);
      break;

    case PN532IOC_READ_PASSIVE:
      if (dev->state == PN532_STATE_CMD_SENT)
        {
          FAR uint32_t *ptr = (FAR uint32_t *)((uintptr_t)arg);
          *ptr = pn532_read_passive_target_id(dev, 0);
        }
      else
        {
          FAR uint32_t *ptr = (FAR uint32_t *)((uintptr_t)arg);
          *ptr = -1;
        }
      break;

    case PN532IOC_SET_RF_CONF:
      pn532_set_rf_config(dev, (FAR struct pn_rf_config_s *) arg);
      break;

    case PN532IOC_SEND_CMD_READ_PASSIVE:
      ret = pn532_read_passive_target(dev, 0);
      if (ret == 0)
        {
          dev->state = PN532_STATE_CMD_SENT;
        }
      else
        {
          dev->state = PN532_STATE_IDLE;
        }
      break;

    case PN532IOC_GET_DATA_READY:
      if (pn532_wait_rx_ready(dev, 1))
        {
          ret = 0;
        }
      else
        {
          ret = 1;
        }
      break;

    case PN532IOC_GET_TAG_ID:
      {
        FAR uint32_t *ptr = (FAR uint32_t *)((uintptr_t)arg);
        *ptr = pn532_read_passive_target_id(dev, 0);
      }
      break;

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

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

  return ret;
}

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

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

int pn532_register(FAR const char *devpath, FAR struct spi_dev_s *spi,
                   FAR struct pn532_config_s *config)
{
  FAR struct pn532_dev_s *dev;
  int ret;

  /* Initialize the PN532 device structure */

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

  dev->spi = spi;
  dev->config = config;

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

  /* pn532_attachirq(dev, pn532_irqhandler); */

  /* Register the character driver */

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

  return ret;
}