/****************************************************************************
 * drivers/modem/altair/altmdm_spi.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 <string.h>

#include <nuttx/kmalloc.h>
#include <nuttx/signal.h>
#include <nuttx/spi/spi.h>
#include <nuttx/modem/altmdm.h>

#include "altmdm_sys.h"
#include "altmdm_pm.h"
#include "altmdm_pm_state.h"

#if defined(CONFIG_MODEM_ALTMDM)

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

#define EVENT_BIT(b)       (1<<(b))
#define EVENT_NONE         (0)
#define EVENT_TXREQ        EVENT_BIT(0)
#define EVENT_RXREQ        EVENT_BIT(1)
#define EVENT_CANCEL       EVENT_BIT(2)
#define EVENT_EXIT         EVENT_BIT(3)
#define EVENT_RXRDY        EVENT_BIT(4)
#define EVENT_RXBUFRDY     EVENT_BIT(5)
#define EVENT_TX_DONE      EVENT_BIT(6)
#define EVENT_RX_DONE      EVENT_BIT(7)
#define EVENT_SLEEPREQ     EVENT_BIT(8)
#define EVENT_SLEEP_DONE   EVENT_BIT(9)
#define EVENT_SV_TIMER_EXP EVENT_BIT(10)
#define EVENT_XFERRDY      EVENT_BIT(11)
#define EVENT_REQMASK      (EVENT_TXREQ | EVENT_RXREQ | EVENT_SLEEPREQ | \
                            EVENT_SV_TIMER_EXP)
#define EVENT_TRANS_WAIT   (EVENT_TXREQ | EVENT_RXREQ | EVENT_RXBUFRDY | \
                            EVENT_SLEEPREQ | EVENT_SV_TIMER_EXP | EVENT_EXIT)
#if defined(CONFIG_MODEM_ALTMDM_XFER_TASK_PRIORITY)
#  define XFER_TASK_PRI    (CONFIG_MODEM_ALTMDM_XFER_TASK_PRIORITY)
#else
#  define XFER_TASK_PRI    (170)
#endif

#define XFER_TASK_STKSIZE  (1536)
#define XFER_TASK_NAME     "altmdm_xfer_task"
#if defined(CONFIG_MODEM_ALTMDM_MAX_PACKET_SIZE)
#  if ((CONFIG_MODEM_ALTMDM_MAX_PACKET_SIZE & 0x3) != 0)
#    error MODEM_ALTMDM_MAX_PACKET_SIZE must be aligned 0x4 (4B)
#  endif
#  define MAX_PKT_SIZE     (CONFIG_MODEM_ALTMDM_MAX_PACKET_SIZE)
#else
#  define MAX_PKT_SIZE     (2064)
#endif
#define UNIT_SIZE          (4)
#define RX_ENABLE          (0)
#define RX_DISABLE         (1)
#define SLEEP_OK           (0)
#define SLEEP_NG           (-EBUSY)

/* Timeout is counted in units of millisecond. */

#define WAIT_RXREQ_TIMEOUT    (5)
#define WAIT_RXREQ_HEADER_TIMEOUT (5 * 1000)
#define WAIT_XFERRDY_TIMEOUT  (25 * 1000)
#if defined(CONFIG_MODEM_ALTMDM_SLEEP_TIMER_VAL)
#  if (CONFIG_MODEM_ALTMDM_SLEEP_TIMER_VAL < 20)
#    error MODEM_ALTMDM_SLEEP_TIMER_VAL too small
#  endif
#  define SV_TIMER_TIMOUT_VAL (CONFIG_MODEM_ALTMDM_SLEEP_TIMER_VAL)
#else
#  define SV_TIMER_TIMOUT_VAL (20)
#endif
#define WRITE_WAIT_TIMEOUT    (ALTMDM_SYS_FLAG_TMOFEVR)
#define SREQ_WAIT_TIMEOUT     (ALTMDM_SYS_FLAG_TMOFEVR)
#define YIELD_TASK_NOBUFF     (100 * 1000) /* microsecond */

/* Defines for transfer mode */

#define MODE_RXDATA             (0)   /* Data receive mode. */
#define MODE_TXDATA             (1)   /* Data send mode. */
#define MODE_TRXDATA            (2)   /* Data send and receive mode. */
#define MODE_RXDATANOBUFF       (3)   /* Data receive mode when there is no
                                       * receiving buffer.
                                       */
#define MODE_TRXDATANOBUFF      (4)   /* Data send and receive mode when
                                       * there is no receiving buffer.
                                       */
#define MODE_RXINVALID          (5)   /* Data receive mode when receiving
                                       * header is wrong.
                                       */
#define MODE_TRXHEADERFAILTXREQ (6)   /* Data send and receive mode when
                                       * header transfer fails. Send request
                                       * triggered.
                                       */
#define MODE_TRXHEADERFAILRXREQ (7)   /* Data receive mode when header
                                       * transfer fails. Receive request
                                       * triggered.
                                       */
#define MODE_RXRESET            (8)   /* Reset packet receive mode. */
#define MODE_TRXRESET           (9)   /* Data send and reset packet receive
                                       * mode.
                                       */

/* Defines for transfer result code */

#define TRANS_OK                 (1)  /* OK. */
#define TRANS_OK_RXDATANOBUFF    (-1) /* OK when MODE_RXDATANOBUFF mode. */
#define TRANS_OK_TRXDATANORXBUFF (-2) /* OK when MODE_TRXDATANOBUFF mode. */
#define TRANS_RXINVALID          (-3) /* NG when MODE_RXINVALID mode. */
#define TRANS_WAITRCVRTMO        (-4) /* NG when receiver ready timeout. */
#define TRANS_OK_RCVBUFFUL       (-5) /* OK when receiver is buffer full. */

#define RESET_BOOTSTAT_BOOTING   (0)  /* Boot stat: booting */
#define RESET_BOOTSTAT_UPDATING  (1)  /* Boot stat: updating */

#define STAT_INF_GET_BOOTSTAT    (1 << 1) /* Status info: Get bootstatus bit */
#define STAT_INF_RESET           (1 << 2) /* Status info: Reset bit */
#define STAT_INF_BUFFFULL        (1 << 3) /* Status info: Buffer full bit */

#define STAT_INF_IS_RESET(info)     ((info & STAT_INF_RESET)    >> 2)
#define STAT_INF_IS_BUFF_FULL(info) ((info & STAT_INF_BUFFFULL) >> 3)

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

static struct altmdm_dev_s *g_privdata = NULL;

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

/****************************************************************************
 * Name: svtimer_handler
 *
 * Description:
 *   Timeout handler for supervisory timer.
 *
 ****************************************************************************/

static void svtimer_handler(int signo, FAR siginfo_t * info,
                            FAR void *ucontext)
{
  FAR struct altmdm_dev_s *priv =
    (FAR struct altmdm_dev_s *)(info->si_value.sival_ptr);
  struct altmdm_sys_flagstate_s flag_status;

  altmdm_sys_referflag(&priv->spidev.xfer_flag, &flag_status);
  if (!(flag_status.flag_pattern & EVENT_SV_TIMER_EXP))
    {
      altmdm_sys_setflag(&priv->spidev.xfer_flag, EVENT_SV_TIMER_EXP);
    }
}

/****************************************************************************
 * Name: init_svtimer
 *
 * Description:
 *   Initialize supervisory timer.
 *
 ****************************************************************************/

static int init_svtimer(FAR struct altmdm_dev_s *priv)
{
  priv->spidev.sleep_param.sv_timerid = NULL;

  return 0;
}

/****************************************************************************
 * Name: delete_svtimer
 *
 * Description:
 *   Delete supervisory timer.
 *
 ****************************************************************************/

static int delete_svtimer(FAR struct altmdm_dev_s *priv)
{
  altmdm_sys_stoptimer(priv->spidev.sleep_param.sv_timerid);
  priv->spidev.sleep_param.sv_timerid = NULL;

  return 0;
}

/****************************************************************************
 * Name: start_svtimer
 *
 * Description:
 *   Start supervisory timer.
 *
 ****************************************************************************/

static int start_svtimer(FAR struct altmdm_dev_s *priv)
{
  timer_t timerid;

  timerid = altmdm_sys_starttimer(SV_TIMER_TIMOUT_VAL, SV_TIMER_TIMOUT_VAL,
                                  svtimer_handler, 0, priv);
  if (timerid == NULL)
    {
      return -1;
    }

  priv->spidev.sleep_param.sv_timerid = timerid;

  return 0;
}

/****************************************************************************
 * Name: stop_svtimer
 *
 * Description:
 *   Stop supervisory timer.
 *
 ****************************************************************************/

static int stop_svtimer(FAR struct altmdm_dev_s *priv)
{
  altmdm_sys_stoptimer(priv->spidev.sleep_param.sv_timerid);
  priv->spidev.sleep_param.sv_timerid = NULL;

  return 0;
}

/****************************************************************************
 * Name: do_dmaxfer
 *
 * Description:
 *   Execute DMA transfer.
 *
 ****************************************************************************/

static int do_dmaxfer(FAR struct altmdm_dev_s *priv, FAR void *tx_buffer,
                      FAR void *rx_buffer, size_t dma_size)
{
  SPI_EXCHANGE(priv->spi, tx_buffer, rx_buffer, dma_size);

  return 0;
}

/****************************************************************************
 * Name: get_dmasize
 *
 * Description:
 *   Get the data size used for DMA transfer.
 *
 ****************************************************************************/

static int get_dmasize(FAR struct altmdm_dev_s *priv, int data_size)
{
  return data_size;
}

/****************************************************************************
 * Name: wait_receiverready
 *
 * Description:
 *   Wait until receiver is ready.
 *
 ****************************************************************************/

static int wait_receiverready(FAR struct altmdm_dev_s *priv,
                              uint32_t timeout_ms)
{
  int ret;
  uint32_t ptn;
  FAR struct altmdm_spi_dev_s *spidev = &priv->spidev;

  ret = altmdm_sys_waitflag(&spidev->xfer_flag, EVENT_RXREQ,
                            ALTMDM_SYS_FLAG_WMODEOR, &ptn,
                            timeout_ms);
  if (ret != 0)
    {
      m_err("receiver ready timeout.\n");
      return TRANS_WAITRCVRTMO;
    }

  return 0;
}

/****************************************************************************
 * Name: wait_xferready
 *
 * Description:
 *   Wait until xfer is ready.
 *
 ****************************************************************************/

static int wait_xferready(FAR struct altmdm_dev_s *priv)
{
  int ret;
  uint32_t ptn;
  FAR struct altmdm_spi_dev_s *spidev = &priv->spidev;

  ret = altmdm_sys_waitflag(&spidev->xferready_flag, EVENT_XFERRDY,
                            ALTMDM_SYS_FLAG_WMODEOR, &ptn,
                            WAIT_XFERRDY_TIMEOUT);
  if (ret != 0)
    {
      m_err("xfer ready timeout.\n");

      /* Make it ready to transfer.
       * It is assumed that modem does not implement reset packet.
       */

      if (!spidev->is_xferready)
        {
          spidev->is_xferready = true;
          m_info("ready to xfer\n");
        }

      return -1;
    }

  return 0;
}

/****************************************************************************
 * Name: notify_xferready
 *
 * Description:
 *   Notify xfer is ready.
 *
 ****************************************************************************/

static int notify_xferready(FAR struct altmdm_dev_s *priv)
{
  int ret;
  FAR struct altmdm_spi_dev_s *spidev = &priv->spidev;

  ret = altmdm_sys_setflag(&spidev->xferready_flag, EVENT_XFERRDY);

  return ret;
}

/****************************************************************************
 * Name: init_rxbuffer
 *
 * Description:
 *   Initialize the receive buffer used for data transfer.
 *
 ****************************************************************************/

static void init_rxbuffer(FAR struct altmdm_dev_s *priv,
                          FAR struct altmdm_spi_rxbuff_s **rxbuff,
                          uint32_t unit_size)
{
  FAR struct altmdm_spi_rxbuff_s *l_buff = NULL;

  l_buff = (FAR struct altmdm_spi_rxbuff_s *)kmm_malloc
    (sizeof(struct altmdm_spi_rxbuff_s));
  if (l_buff == NULL)
    {
      m_err("cannot allocate memory for received buffer\n");
    }
  else
    {
      memset(l_buff, 0x00, sizeof(struct altmdm_spi_rxbuff_s));

      l_buff->buff_addr = (char *)kmm_malloc(unit_size);
      if (l_buff->buff_addr == NULL)
        {
          m_err("cannot allocate memory for received buffer\n");
          kmm_free(l_buff);
          l_buff = NULL;
        }
      else
        {
          l_buff->buff_size = unit_size;
        }
    }

  *rxbuff = l_buff;
}

/****************************************************************************
 * Name: uninit_rxbuffer
 *
 * Description:
 *   Uninitialize the receive buffer used for data transfer.
 *
 ****************************************************************************/

static void uninit_rxbuffer(FAR struct altmdm_dev_s *priv,
                            FAR struct altmdm_spi_rxbuff_s *rxbuff)
{
  if (rxbuff != NULL)
    {
      if (rxbuff->buff_addr != NULL)
        {
          kmm_free(rxbuff->buff_addr);
        }

      kmm_free(rxbuff);
    }
}

/****************************************************************************
 * Name: alloc_rxbuffer
 *
 * Description:
 *   Get the allocated receive buffer.
 *
 ****************************************************************************/

static void alloc_rxbuffer(FAR struct altmdm_dev_s *priv,
                           FAR struct altmdm_spi_rxbuff_s **rxbuff,
                           uint32_t size)
{
  if (priv->spidev.rxbuffinfo.free_buff == NULL)
    {
      m_err("cannot allocate free rx buffer\n");
    }
  else
    {
      *rxbuff = priv->spidev.rxbuffinfo.free_buff;
      priv->spidev.rxbuffinfo.free_buff = NULL;
    }
}

/****************************************************************************
 * Name: free_rxbuffer
 *
 * Description:
 *   Release allocated receive buffer.
 *
 ****************************************************************************/

static void free_rxbuffer(FAR struct altmdm_dev_s *priv,
                          FAR struct altmdm_spi_rxbuff_s *rxbuff)
{
  if (priv->spidev.rxbuffinfo.free_buff != NULL)
    {
      m_err("cannot free rx buffer\n");
    }
  else
    {
      priv->spidev.rxbuffinfo.free_buff = rxbuff;
    }
}

/****************************************************************************
 * Name: create_rxbufffifo
 *
 * Description:
 *   Create receiving FIFO. This fifo is used when copying to the buffer
 *   specified by the user.
 *
 ****************************************************************************/

static void create_rxbufffifo(FAR struct altmdm_dev_s *priv)
{
  FAR struct altmdm_spi_rxbufffifo_s *fifo = &priv->spidev.rxbuffinfo.fifo;

  init_rxbuffer(priv, &priv->spidev.rxbuffinfo.free_buff, MAX_PKT_SIZE);

  fifo->head = NULL;
  fifo->tail = NULL;

  altmdm_sys_initcsem(&fifo->csem);
}

/****************************************************************************
 * Name: destroy_rxbufffifo
 *
 * Description:
 *   Destroy receiving FIFO.
 *
 ****************************************************************************/

static void destroy_rxbufffifo(FAR struct altmdm_dev_s *priv)
{
  FAR struct altmdm_spi_rxbufffifo_s *fifo = &priv->spidev.rxbuffinfo.fifo;

  uninit_rxbuffer(priv, priv->spidev.rxbuffinfo.free_buff);

  altmdm_sys_deletecsem(&fifo->csem);
}

/****************************************************************************
 * Name: put_rxbufffifo
 *
 * Description:
 *   Put date into the receiving FIFO.
 *
 ****************************************************************************/

static void put_rxbufffifo(FAR struct altmdm_dev_s *priv,
                           FAR struct altmdm_spi_rxbuff_s *rxbuff)
{
  FAR struct altmdm_spi_rxbufffifo_s *fifo = &priv->spidev.rxbuffinfo.fifo;
  irqstate_t flags;

  flags = enter_critical_section();

  if (fifo->head == NULL)
    {
      fifo->head = rxbuff;
      fifo->tail = rxbuff;
      rxbuff->next = NULL;
    }
  else
    {
      fifo->tail->next = rxbuff;
      fifo->tail = rxbuff;
      rxbuff->next = NULL;
    }

  leave_critical_section(flags);

  altmdm_sys_postcsem(&fifo->csem);
}

/****************************************************************************
 * Name: get_rxbufffifo
 *
 * Description:
 *   Get date from the receiving FIFO. If the FIFO is empty,
 *   it is kept waiting until data is put in the FIFO.
 *
 ****************************************************************************/

static void get_rxbufffifo(FAR struct altmdm_dev_s *priv,
                           FAR struct altmdm_spi_rxbuff_s **rxbuff)
{
  FAR struct altmdm_spi_rxbufffifo_s *fifo = &priv->spidev.rxbuffinfo.fifo;
  irqstate_t flags;

  *rxbuff = NULL;

  altmdm_sys_waitcsem(&fifo->csem);

  flags = enter_critical_section();

  if (fifo->head != NULL)
    {
      *rxbuff = fifo->head;
      fifo->head = fifo->head->next;
    }

  leave_critical_section(flags);
}

/****************************************************************************
 * Name: abort_get_rxbufffifo
 *
 * Description:
 *   If waiting for data to be put in the FIFO, then abort it.
 *
 ****************************************************************************/

static void abort_get_rxbufffifo(FAR struct altmdm_dev_s *priv)
{
  int val;
  FAR struct altmdm_spi_dev_s *spidev = &priv->spidev;
  FAR struct altmdm_spi_rxbufffifo_s *fifo = &priv->spidev.rxbuffinfo.fifo;

  if (!altmdm_sys_getcsemvalue(&fifo->csem, &val))
    {
      if (val < 0)
        {
          spidev->rx_param.rxabort = true;
          altmdm_sys_postcsem(&fifo->csem);
        }
    }
}

/****************************************************************************
 * Name: clear_txheader
 *
 * Description:
 *   Clear the header for transmission.
 *
 ****************************************************************************/

static void clear_txheader(FAR struct altmdm_dev_s *priv)
{
  memset(&priv->spidev.tx_param.header, 0x00,
         sizeof(struct altmdm_spi_xferhdr_s));
}

/****************************************************************************
 * Name: clear_rxheader
 *
 * Description:
 *   Clear the header for reception.
 *
 ****************************************************************************/

static void clear_rxheader(FAR struct altmdm_dev_s *priv)
{
  memset(&priv->spidev.rx_param.header, 0x00,
         sizeof(struct altmdm_spi_xferhdr_s));
}

/****************************************************************************
 * Name: set_txheader_datasize
 *
 * Description:
 *   Set transmission data size in the header.
 *
 ****************************************************************************/

static void set_txheader_datasize(FAR struct altmdm_dev_s *priv,
                                  int total_size, int actual_size)
{
  FAR struct altmdm_spi_xferhdr_s *tx_header = &priv->spidev.tx_param.header;

  tx_header->header[0] =
    (tx_header->header[0] & 0xf0) + ((total_size >> 10) & 0x0000000f);
  tx_header->header[1] = (total_size >> 2) & 0x000000ff;
  tx_header->header[2] =
    ((total_size & 0x00000003) << 6) + ((actual_size >> 8) & 0x0000003f);
  tx_header->header[3] = (actual_size & 0x000000ff);
}

/****************************************************************************
 * Name: set_txheader_possibleofrx
 *
 * Description:
 *   Set flag in the header. The flag indicates that master can receive data.
 *
 ****************************************************************************/

static void set_txheader_possibleofrx(FAR struct altmdm_dev_s *priv,
                                      bool possible)
{
  FAR struct altmdm_spi_xferhdr_s *tx_header = &priv->spidev.tx_param.header;

  if (!possible)
    {
      tx_header->header[0] = 0x80 + (tx_header->header[0] & 0x7f);
    }
  else
    {
      tx_header->header[0] = 0x00 + (tx_header->header[0] & 0x7f);
    }
}

/****************************************************************************
 * Name: set_txheader_sleepreq
 *
 * Description:
 *   Set flag in the header. The flag indicates that sleep request.
 *
 ****************************************************************************/

static void set_txheader_sleepreq(FAR struct altmdm_dev_s *priv)
{
  FAR struct altmdm_spi_xferhdr_s *tx_header = &priv->spidev.tx_param.header;

  tx_header->header[0] = 0x10 + (tx_header->header[0] & 0xef);
}

/****************************************************************************
 * Name: show_txheader
 *
 * Description:
 *   Show header for transmission.
 *
 ****************************************************************************/

static void show_txheader(FAR struct altmdm_dev_s *priv)
{
#ifdef MODEM_ALTMDM_DEBUG
  FAR struct altmdm_spi_xferhdr_s *tx_header = &priv->spidev.tx_param.header;

  m_info("[TXHDR]=%02x,%02x,%02x,%02x\n",
         tx_header->header[0], tx_header->header[1],
         tx_header->header[2], tx_header->header[3]);
#endif
}

/****************************************************************************
 * Name: parse_rxheader
 *
 * Description:
 *   Parse header for receiving.
 *
 ****************************************************************************/

static void parse_rxheader(FAR struct altmdm_dev_s *priv,
                           FAR int32_t *total_size, FAR int32_t *actual_size,
                           FAR int *is_reset, FAR int32_t *is_bufful)
{
  FAR struct altmdm_spi_xferhdr_s *rx_header = &priv->spidev.rx_param.header;

  m_info("[RXHDR]%02x,%02x,%02x,%02x\n",
         rx_header->header[0], rx_header->header[1],
         rx_header->header[2], rx_header->header[3]);

  *total_size = ((rx_header->header[0] & 0x0f) << 10) +
    ((rx_header->header[1] & 0xff) << 2) + (rx_header->header[2] >> 6);
  *actual_size = ((rx_header->header[2] & 0x3f) << 8) +
    (rx_header->header[3] & 0xff);

  priv->spidev.rx_param.status_info = (rx_header->header[0] & 0xf0) >> 4;

  *is_reset = STAT_INF_IS_RESET(priv->spidev.rx_param.status_info);
  *is_bufful = STAT_INF_IS_BUFF_FULL(priv->spidev.rx_param.status_info);

  m_info("t=%d a=%d r=%d b=%d\n",
         *total_size, *actual_size, *is_reset, *is_bufful);

  return;
}

/****************************************************************************
 * Name: verify_rxheader
 *
 * Description:
 *   Verify header for receiving.
 *
 ****************************************************************************/

static int verify_rxheader(FAR struct altmdm_dev_s *priv,
                           int total_size, int actual_size)
{
  int calc_total_size;

  if ((total_size == 0) || (actual_size == 0))
    {
      return -1;
    }

  if ((total_size > MAX_PKT_SIZE) || (actual_size > MAX_PKT_SIZE))
    {
      return -1;
    }

  if (total_size % UNIT_SIZE)
    {
      return -1;
    }

  if (total_size != actual_size)
    {
      calc_total_size = ((actual_size / UNIT_SIZE) + 1) * UNIT_SIZE;

      if (total_size != calc_total_size)
        {
          return -1;
        }
    }

  return 0;
}

/****************************************************************************
 * Name: do_xferheader
 *
 * Description:
 *   Execute header transfer.
 *
 ****************************************************************************/

static int do_xferheader(FAR struct altmdm_dev_s *priv,
                         uint32_t is_rxreq, uint32_t is_txreq,
                         uint32_t is_sleepreq, uint32_t is_rcvrready)
{
  int ret;
  int dma_xfer_size;
  struct altmdm_spi_dev_s *spidev = &priv->spidev;
  bool possibleofrx = true;

  /* Make transfer header */

  clear_txheader(priv);

  if ((is_sleepreq) || (is_txreq))
    {
      DEBUGASSERT(priv->lower);
      priv->lower->master_request(true);

      if (is_sleepreq)
        {
          set_txheader_sleepreq(priv);
        }
      else
        {
          set_txheader_datasize(priv, spidev->tx_param.total_size,
                                spidev->tx_param.actual_size);
        }
    }

  if (!is_sleepreq)
    {
      alloc_rxbuffer(priv, &spidev->rx_param.rxbuff, MAX_PKT_SIZE);
      if (spidev->rx_param.rxbuff == NULL)
        {
          possibleofrx = false;
        }

      set_txheader_possibleofrx(priv, possibleofrx);
    }

  show_txheader(priv);

  /* Wait for Receiver Ready to receive. */

  if ((!is_rxreq) && (!is_rcvrready))
    {
      ret = wait_receiverready(priv, WAIT_RXREQ_HEADER_TIMEOUT);
      if (ret < 0)
        {
          goto trans_header_error;
        }
    }

  /* Get DMA transfer size */

  dma_xfer_size = get_dmasize(priv, sizeof(struct altmdm_spi_xferhdr_s));

  ret = do_dmaxfer(priv, (FAR void *)&spidev->tx_param.header,
                   (FAR void *)&spidev->rx_param.header, dma_xfer_size);
  if (ret < 0)
    {
      goto trans_header_error;
    }

  return ret;

trans_header_error:
  m_err("ERR:%04d Transfer Header Failed. ret = %d.\n", __LINE__, ret);

  /* Clear Header */

  clear_rxheader(priv);
  set_txheader_datasize(priv, 0, 0);

  return ret;
}

/****************************************************************************
 * Name: do_receivedata
 *
 * Description:
 *   Executes the receive only transfer mode.
 *
 ****************************************************************************/

static int do_receivedata(FAR struct altmdm_dev_s *priv)
{
  int ret;
  int dma_xfer_size;
  FAR void *rxbuff;
  FAR struct altmdm_spi_dev_s *spidev = &priv->spidev;

  /* Wait for Receiver Ready to receive. */

  ret = wait_receiverready(priv, WAIT_RXREQ_TIMEOUT);
  if (ret >= 0)
    {
      /* Get DMA transfer size */

      dma_xfer_size = get_dmasize(priv, spidev->rx_param.total_size);

      rxbuff = spidev->rx_param.rxbuff->buff_addr;

      /* Do DMA transfer */

      ret = do_dmaxfer(priv, NULL, rxbuff, dma_xfer_size);
    }

  if (ret < 0)
    {
      m_err("Rcv Data Failed. ret = %d.\n", ret);
      clear_rxheader(priv);
    }

  return ret;
}

/****************************************************************************
 * Name: do_senddata
 *
 * Description:
 *   Executes the transmission only transfer mode.
 *
 ****************************************************************************/

static int do_senddata(FAR struct altmdm_dev_s *priv)
{
  int ret;
  int dma_xfer_size;
  FAR struct altmdm_spi_dev_s *spidev = &priv->spidev;

  /* Wait for Receiver Ready to receive. */

  ret = wait_receiverready(priv, WAIT_RXREQ_TIMEOUT);
  if (ret >= 0)
    {
      /* Get DMA transfer size */

      dma_xfer_size = get_dmasize(priv, spidev->tx_param.total_size);

      /* Do DMA transfer */

      ret = do_dmaxfer(priv, (FAR void *)spidev->tx_param.buff_addr,
                       NULL, dma_xfer_size);
    }

  if (ret < 0)
    {
      m_err("ERR:%04d Snd Data Failed. ret = %d.\n", __LINE__, ret);
      clear_txheader(priv);
    }

  return ret;
}

/****************************************************************************
 * Name: do_trxdata
 *
 * Description:
 *   Executes the transmission and receive transfer mode.
 *
 ****************************************************************************/

static int do_trxdata(FAR struct altmdm_dev_s *priv)
{
  int ret;
  int xfer_size;
  int dma_xfer_size;
  FAR void *rxbuff;
  FAR struct altmdm_spi_dev_s *spidev = &priv->spidev;

  /* Wait for Receiver Ready to receive. */

  ret = wait_receiverready(priv, WAIT_RXREQ_TIMEOUT);
  if (ret >= 0)
    {
      /* Choose the larger one. */

      if (spidev->tx_param.total_size < spidev->rx_param.total_size)
        {
          xfer_size = spidev->rx_param.total_size;
        }
      else
        {
          xfer_size = spidev->tx_param.total_size;
        }

      rxbuff = spidev->rx_param.rxbuff->buff_addr;

      /* Get DMA transfer size */

      dma_xfer_size = get_dmasize(priv, xfer_size);

      /* Do DMA transfer */

      ret = do_dmaxfer(priv, (FAR void *)spidev->tx_param.buff_addr, rxbuff,
                       dma_xfer_size);
    }

  if (ret < 0)
    {
      m_err("ERR:%04d Trx Data Failed. ret = %d.\n", __LINE__, ret);
      clear_txheader(priv);
      clear_rxheader(priv);
    }

  return ret;
}

/****************************************************************************
 * Name: do_receivedata_nobuff
 *
 * Description:
 *   Executes the receive only transfer mode. When receiving buffer cannot
 *   be prepared.
 *
 ****************************************************************************/

static int do_receivedata_nobuff(FAR struct altmdm_dev_s *priv)
{
  int ret;
  int dma_xfer_size;
  FAR struct altmdm_spi_dev_s *spidev = &priv->spidev;

  /* Wait for Receiver Ready to receive. */

  ret = wait_receiverready(priv, WAIT_RXREQ_TIMEOUT);
  if (ret >= 0)
    {
      /* Get DMA transfer size */

      dma_xfer_size = get_dmasize(priv, spidev->rx_param.total_size);

      /* Do DMA transfer */

      ret = do_dmaxfer(priv, NULL, NULL, dma_xfer_size);
    }

  if (ret < 0)
    {
      m_err("ERR:%04d Rcv Data Nobuff Failed. ret = %d.\n", __LINE__, ret);
      clear_rxheader(priv);
    }
  else
    {
      ret = TRANS_OK_RXDATANOBUFF;
    }

  return ret;
}

/****************************************************************************
 * Name: do_trxdata_norxbuff
 *
 * Description:
 *   Executes the transmission and receive transfer mode. When receiving
 *   buffer cannot be prepared.
 *
 ****************************************************************************/

static int do_trxdata_norxbuff(FAR struct altmdm_dev_s *priv)
{
  int ret;
  int xfer_size;
  int dma_xfer_size;
  FAR struct altmdm_spi_dev_s *spidev = &priv->spidev;

  /* Wait for Receiver Ready to receive. */

  ret = wait_receiverready(priv, WAIT_RXREQ_TIMEOUT);
  if (ret >= 0)
    {
      /* Choose the larger one. */

      if (spidev->tx_param.total_size < spidev->rx_param.total_size)
        {
          xfer_size = spidev->rx_param.total_size;
        }
      else
        {
          xfer_size = spidev->tx_param.total_size;
        }

      /* Get DMA transfer size */

      dma_xfer_size = get_dmasize(priv, xfer_size);

      /* Do DMA transfer */

      ret = do_dmaxfer(priv, (FAR void *)spidev->tx_param.buff_addr,
                       NULL, dma_xfer_size);
    }

  if (ret < 0)
    {
      m_err("ERR:%04d Trx Data Norxbuff Failed. ret = %d.\n", __LINE__, ret);
      clear_txheader(priv);
      clear_rxheader(priv);
    }
  else
    {
      ret = TRANS_OK_TRXDATANORXBUFF;
    }

  return ret;
}

/****************************************************************************
 * Name: do_receivesleepdata
 *
 * Description:
 *   Executes the sleep data transfer mode.
 *
 ****************************************************************************/

static int do_receivesleepdata(FAR struct altmdm_dev_s *priv, FAR int *resp)
{
  int ret;
  int dma_xfer_size;
  char rxbuff[UNIT_SIZE];

  /* Wait for Receiver Ready to receive. */

  ret = wait_receiverready(priv, WAIT_RXREQ_TIMEOUT);
  if (ret >= 0)
    {
      /* Get DMA transfer size */

      dma_xfer_size = get_dmasize(priv, UNIT_SIZE);

      /* Do DMA transfer */

      ret = do_dmaxfer(priv, NULL, (FAR void *)rxbuff, dma_xfer_size);
    }

  if (ret < 0)
    {
      m_err("ERR:%04d Rcv Sleep Resp Data Failed. ret = %d.\n",
             __LINE__, ret);
    }
  else
    {
      m_info("[SRESP] 0x%02x,0x%02x,0x%02x,0x%02x\n",
             rxbuff[0], rxbuff[1],
             rxbuff[2], rxbuff[3]);

      if (!memcmp(rxbuff, "OK", 2))
        {
          *resp = SLEEP_OK;
        }
      else
        {
          *resp = SLEEP_NG;
        }
    }

  return ret;
}

/****************************************************************************
 * Name: do_receivereset
 *
 * Description:
 *   Executes the reset packet receive only transfer mode.
 *
 ****************************************************************************/

static int do_receivereset(FAR struct altmdm_dev_s *priv)
{
  int ret;
  int dma_xfer_size;
  FAR struct altmdm_spi_dev_s *spidev = &priv->spidev;
  char rxbuff[UNIT_SIZE];

  /* Wait for Receiver Ready to receive. */

  ret = wait_receiverready(priv, WAIT_RXREQ_TIMEOUT);
  if (ret >= 0)
    {
      /* Get DMA transfer size */

      dma_xfer_size = get_dmasize(priv, spidev->rx_param.total_size);

      /* Do DMA transfer */

      ret = do_dmaxfer(priv, NULL, (FAR void *)rxbuff, dma_xfer_size);
    }

  if (ret < 0)
    {
      m_err("ERR:%04d Rcv Reset Failed. ret = %d.\n", __LINE__, ret);
      clear_rxheader(priv);
    }
  else
    {
      if ((STAT_INF_GET_BOOTSTAT | STAT_INF_RESET) ==
          (spidev->rx_param.
           status_info & (STAT_INF_GET_BOOTSTAT | STAT_INF_RESET)))
        {
          switch (rxbuff[0])
            {
            case RESET_BOOTSTAT_BOOTING:
              altmdm_pm_set_bootstatus(priv,
                                       MODEM_PM_ERR_RESET_BOOTSTAT_BOOTING);
              break;

            case RESET_BOOTSTAT_UPDATING:
              altmdm_pm_set_bootstatus(priv,
                                       MODEM_PM_ERR_RESET_BOOTSTAT_UPDATING);
              break;

            default:
              m_err
                ("ERR:%04d Invalid payload of reset packet. " \
                 "%02x,%02x,%02x,%02x\n",
                 __LINE__, rxbuff[0], rxbuff[1], rxbuff[2], rxbuff[3]);
              break;
            }
        }
      else if (STAT_INF_RESET ==
               (spidev->rx_param.status_info & STAT_INF_RESET))
        {
          altmdm_pm_set_bootstatus(priv, MODEM_PM_ERR_RESET_BOOTSTAT_DONE);
        }
      else
        {
          m_err("ERR:%04d Unexpected status info. %04x\n", __LINE__,
                spidev->rx_param.status_info);
        }
    }

  return ret;
}

/****************************************************************************
 * Name: do_trxreset
 *
 * Description:
 *   Executes the transmission and receive reset packet transfer mode.
 *
 ****************************************************************************/

static int do_trxreset(FAR struct altmdm_dev_s *priv)
{
  int ret;
  int xfer_size;
  int dma_xfer_size;
  FAR struct altmdm_spi_dev_s *spidev = &priv->spidev;

  /* Wait for Receiver Ready to receive. */

  ret = wait_receiverready(priv, WAIT_RXREQ_TIMEOUT);
  if (ret >= 0)
    {
      /* Even in the case of a conflict between a reset packet
       * and a send packet, the send size is compared and the
       * larger size is sent as the payload.
       * If the send packet that conflicts with the reset packet
       * is in ALTCOM command format, the modem may assert.
       * If it is not in the ALTCOM command format, it will be
       * discarded by the modem, so dummy data will be sent to
       * avoid assertion.
       */

      if (spidev->tx_param.total_size < spidev->rx_param.total_size)
        {
          xfer_size = spidev->rx_param.total_size;
        }
      else
        {
          xfer_size = spidev->tx_param.total_size;
        }

      /* Get DMA transfer size */

      dma_xfer_size = get_dmasize(priv, xfer_size);

      /* Do DMA transfer */

      ret = do_dmaxfer(priv, NULL, NULL, dma_xfer_size);
    }

  if (ret < 0)
    {
      m_err("ERR:%04d Trx Reset Failed. ret = %d.\n", __LINE__, ret);
      clear_txheader(priv);
      clear_rxheader(priv);
    }

  return ret;
}

/****************************************************************************
 * Name: do_xfersleep
 *
 * Description:
 *   Executes the sleep request.
 *
 ****************************************************************************/

static int do_xfersleep(FAR struct altmdm_dev_s *priv, uint32_t is_rcvrready)
{
  int ret;
  int resp = 0;
  int is_reset = 0;
  int32_t total_size;
  int32_t actual_size;
  int32_t is_bufful;

  /* Transfer header for sleep request */

  ret = do_xferheader(priv, 0, 0, 1, is_rcvrready);
  if (ret >= 0)
    {
      parse_rxheader(priv, &total_size, &actual_size, &is_reset, &is_bufful);

      /* Transfer data for sleep request */

      ret = do_receivesleepdata(priv, &resp);
      if (ret >= 0)
        {
          ret = resp;
        }
    }

  if (ret < 0)
    {
      ret = SLEEP_NG;
    }

  DEBUGASSERT(priv && priv->lower);
  priv->lower->master_request(false);

  if (is_reset)
    {
      if (!priv->spidev.is_xferready)
        {
          priv->spidev.is_xferready = true;
          m_info("ready to xfer\n");
          notify_xferready(priv);
        }

      altmdm_pm_notify_reset(priv);
    }

  return ret;
}

/****************************************************************************
 * Name: decide_xfermode
 *
 * Description:
 *   Decide transfer mode.
 *
 ****************************************************************************/

static uint32_t decide_xfermode(FAR struct altmdm_dev_s *priv,
                                uint32_t is_rxreq, uint32_t is_txreq,
                                int ret)
{
  int retval;
  int is_reset;
  FAR struct altmdm_spi_dev_s *spidev = &priv->spidev;
  uint32_t mode = MODE_RXINVALID;

  if (ret < 0)
    {
      if (is_txreq)
        {
          mode = MODE_TRXHEADERFAILTXREQ;
        }
      else
        {
          mode = MODE_TRXHEADERFAILRXREQ;
        }

      if (spidev->rx_param.rxbuff != NULL)
        {
          free_rxbuffer(priv, spidev->rx_param.rxbuff);
          spidev->rx_param.rxbuff = NULL;
        }
    }
  else
    {
      parse_rxheader(priv, &spidev->rx_param.total_size,
                     &spidev->rx_param.actual_size, &is_reset,
                     &spidev->tx_param.is_bufful);

      if (is_rxreq)
        {
          retval = verify_rxheader(priv, spidev->rx_param.total_size,
                                   spidev->rx_param.actual_size);
          if (retval != 0)
            {
              m_info("RX header:total=0x%02x, actual=0x%02x.\n",
                     spidev->rx_param.total_size,
                     spidev->rx_param.actual_size);

              if (spidev->rx_param.rxbuff != NULL)
                {
                  free_rxbuffer(priv, spidev->rx_param.rxbuff);
                  spidev->rx_param.rxbuff = NULL;
                }

              if (!is_txreq)
                {
                  return mode;
                }

              is_rxreq = 0;

              m_info("RX Header invalid. But Send will be done.\n");
            }
        }
      else if ((spidev->rx_param.actual_size != 0) ||
               (spidev->rx_param.total_size != 0))
        {
          retval = verify_rxheader(priv, spidev->rx_param.total_size,
                                   spidev->rx_param.actual_size);
          if (retval == 0)
            {
              is_rxreq = 1;
            }
        }

      /* Diceide transfer mode here. */

      if (is_txreq)
        {
          if (is_rxreq)
            {
              if (is_reset)
                {
                  if (spidev->rx_param.rxbuff != NULL)
                    {
                      free_rxbuffer(priv, spidev->rx_param.rxbuff);
                      spidev->rx_param.rxbuff = NULL;
                    }

                  mode = MODE_TRXRESET;
                }
              else
                {
                  if (spidev->rx_param.rxbuff == NULL)
                    {
                      mode = MODE_TRXDATANOBUFF;
                    }
                  else
                    {
                      mode = MODE_TRXDATA;
                      spidev->rx_param.rxbuff->rx_size =
                        spidev->rx_param.actual_size;
                      m_info("received size = %d.\n",
                             spidev->rx_param.rxbuff->rx_size);
                    }
                }
            }
          else
            {
              if (spidev->rx_param.rxbuff != NULL)
                {
                  free_rxbuffer(priv, spidev->rx_param.rxbuff);
                  spidev->rx_param.rxbuff = NULL;
                }

              mode = MODE_TXDATA;
            }
        }
      else
        {
          if (is_reset)
            {
              if (spidev->rx_param.rxbuff != NULL)
                {
                  free_rxbuffer(priv, spidev->rx_param.rxbuff);
                  spidev->rx_param.rxbuff = NULL;
                }

              mode = MODE_RXRESET;
            }
          else
            {
              if (spidev->rx_param.rxbuff == NULL)
                {
                  mode = MODE_RXDATANOBUFF;
                }
              else
                {
                  mode = MODE_RXDATA;
                  spidev->rx_param.rxbuff->rx_size =
                    spidev->rx_param.actual_size;
                  m_info("received size = %d.\n",
                         spidev->rx_param.rxbuff->rx_size);
                }
            }
        }
    }

  clear_rxheader(priv);
  clear_txheader(priv);

  return mode;
}

/****************************************************************************
 * Name: done_xfer
 *
 * Description:
 *   Notify that transfer has completed.
 *
 ****************************************************************************/

static void done_xfer(FAR struct altmdm_dev_s *priv, uint32_t xfer_mode,
                      int ret)
{
  FAR struct altmdm_spi_dev_s *spidev = &priv->spidev;

  switch (xfer_mode)
    {
    case MODE_RXDATA:
      if (ret < 0)
        {
          free_rxbuffer(priv, spidev->rx_param.rxbuff);
          spidev->rx_param.rxbuff = NULL;
        }
      else
        {
          put_rxbufffifo(priv, spidev->rx_param.rxbuff);
          spidev->rx_param.rxbuff = NULL;
        }
      break;

    case MODE_TXDATA:
      if (spidev->tx_param.is_bufful)
        {
          spidev->tx_param.is_bufful = 0;
          spidev->tx_param.result = (ret < 0) ? ret : TRANS_OK_RCVBUFFUL;
        }
      else
        {
          spidev->tx_param.result = (ret < 0) ? ret : TRANS_OK;
        }

      altmdm_sys_setflag(&spidev->tx_param.done_flag, EVENT_TX_DONE);
      break;

    case MODE_TRXDATA:
      if (spidev->tx_param.is_bufful)
        {
          spidev->tx_param.is_bufful = 0;
          spidev->tx_param.result = (ret < 0) ? ret : TRANS_OK_RCVBUFFUL;
        }
      else
        {
          spidev->tx_param.result = (ret < 0) ? ret : TRANS_OK;
        }

      altmdm_sys_setflag(&spidev->tx_param.done_flag, EVENT_TX_DONE);

      if (ret < 0)
        {
          free_rxbuffer(priv, spidev->rx_param.rxbuff);
          spidev->rx_param.rxbuff = NULL;
        }
      else
        {
          put_rxbufffifo(priv, spidev->rx_param.rxbuff);
          spidev->rx_param.rxbuff = NULL;
        }
      break;

    case MODE_TRXDATANOBUFF:
      if (spidev->tx_param.is_bufful)
        {
          spidev->tx_param.is_bufful = 0;
          spidev->tx_param.result = (ret < 0) ? ret : TRANS_OK_RCVBUFFUL;
        }
      else
        {
          spidev->tx_param.result = (ret < 0) ? ret : TRANS_OK;
        }

      altmdm_sys_setflag(&spidev->tx_param.done_flag, EVENT_TX_DONE);

      /* Yield the CPU so that the upper layer can execute
       * the receiving process.
       */

      nxsig_usleep(YIELD_TASK_NOBUFF);

      break;

    case MODE_TRXHEADERFAILTXREQ:
      spidev->tx_param.result = ret;
      altmdm_sys_setflag(&spidev->tx_param.done_flag, EVENT_TX_DONE);
      break;

    case MODE_RXRESET:
      if (!spidev->is_xferready)
        {
          spidev->is_xferready = true;
          m_info("ready to xfer\n");
          notify_xferready(priv);
        }

      altmdm_pm_notify_reset(priv);
      break;

    case MODE_TRXRESET:
      if (!spidev->is_xferready)
        {
          spidev->is_xferready = true;
          m_info("ready to xfer\n");
          notify_xferready(priv);
        }

      altmdm_pm_notify_reset(priv);
      if (spidev->tx_param.is_bufful)
        {
          spidev->tx_param.is_bufful = 0;
          spidev->tx_param.result = (ret < 0) ? ret : TRANS_OK_RCVBUFFUL;
        }
      else
        {
          spidev->tx_param.result = (ret < 0) ? ret : TRANS_OK;
        }

      altmdm_sys_setflag(&spidev->tx_param.done_flag, EVENT_TX_DONE);
      break;

    case MODE_RXDATANOBUFF:

      /* Yield the CPU so that the upper layer can execute
       * the receiving process.
       */

      nxsig_usleep(YIELD_TASK_NOBUFF);
      break;

    case MODE_TRXHEADERFAILRXREQ:
    case MODE_RXINVALID:
      break;

    default:
      break;
    }
}

/****************************************************************************
 * Name: done_sleep
 *
 * Description:
 *   Notify that sleep request has completed.
 *
 ****************************************************************************/

static void done_sleep(FAR struct altmdm_dev_s *priv, int ret)
{
  FAR struct altmdm_spi_dev_s *spidev = &priv->spidev;

  spidev->sleep_param.result = ret;
  altmdm_sys_setflag(&spidev->sleep_param.done_flag, EVENT_SLEEP_DONE);
}

/****************************************************************************
 * Name: xfer_task_init
 *
 * Description:
 *   Initialize SPI transfer task.
 *
 ****************************************************************************/

static void xfer_task_init(FAR struct altmdm_dev_s *priv)
{
  sigset_t mask;

  sigfillset(&mask);
  nxsig_procmask(SIG_SETMASK, &mask, NULL);

  init_svtimer(priv);
}

/****************************************************************************
 * Name: xfer_task
 *
 * Description:
 *   ALTMDM SPI transfer task.
 *
 ****************************************************************************/

static int xfer_task(int argc, char *argv[])
{
  int ret;
  int sleep_result;
  int res_code;
  uint32_t is_txreq;
  uint32_t is_rxreq;
  uint32_t is_sleepreq;
  uint32_t is_timerexp;
  uint32_t do_sleep;
  uint32_t ptn;
  uint32_t xfer_mode;
  uint32_t modem_state;
  uint32_t is_rcvrready = 0;
  FAR struct altmdm_dev_s *priv = g_privdata;
  FAR struct altmdm_spi_dev_s *spidev = &priv->spidev;

  xfer_task_init(priv);

  while (!spidev->is_not_run)
    {
      ret = altmdm_sys_waitflag(&spidev->xfer_flag, EVENT_TRANS_WAIT,
                                ALTMDM_SYS_FLAG_WMODEOR, &ptn,
                                ALTMDM_SYS_FLAG_TMOFEVR);
      if (ret != 0)
        {
          m_err("wait flag failed:%d.\n", ret);
          continue;
        }

      m_info("ptn:%x.\n", ptn);

      if (ptn & EVENT_REQMASK)
        {
          is_txreq = ptn & EVENT_TXREQ;
          is_rxreq = ptn & EVENT_RXREQ;
          is_sleepreq = ptn & EVENT_SLEEPREQ;
          is_timerexp = ptn & EVENT_SV_TIMER_EXP;
          do_sleep = 0;
          sleep_result = SLEEP_NG;

          /* Sleep transition event received. Check if it can sleep. */

          if (is_sleepreq || is_timerexp)
            {
              /* If data transfer required at the same time, cannot sleep. */

              if (!(is_txreq || is_rxreq))
                {
                  if (altmdm_pm_cansleep(priv))
                    {
                      do_sleep = 1;
                    }
                  else if (is_timerexp)
                    {
                      /* Case where modem spontaneously enters sleep state
                       * and timer is not stopped.
                       */

                      modem_state = altmdm_pm_getinternalstate();
                      if (modem_state == MODEM_PM_INTERNAL_STATE_SLEEP)
                        {
                          stop_svtimer(priv);
                        }
                    }
                }
            }

          if (do_sleep)
            {
              stop_svtimer(priv);

              /* Transfer sleep packet */

              sleep_result = do_xfersleep(priv, 0);
              if (sleep_result == SLEEP_OK)
                {
                  altmdm_pm_sleepmodem(priv);
                }
            }

          if (is_sleepreq)
            {
              /* Send sleep response */

              done_sleep(priv, sleep_result);
            }

          /* Receive data transfer request */

          if (is_txreq || is_rxreq)
            {
              stop_svtimer(priv);

              /* Wakeup modem before data transfer */

              if (is_rxreq)
                {
                  res_code = altmdm_pm_wakeup(priv, NULL);
                }
              else
                {
                  res_code = altmdm_pm_wakeup(priv, wait_receiverready);
                }

              if (res_code == MODEM_PM_WAKEUP_FAIL)
                {
                  done_xfer(priv, MODE_TXDATA, TRANS_WAITRCVRTMO);
                  start_svtimer(priv);
                  continue;
                }
              else if (res_code == MODEM_PM_WAKEUP_DONE)
                {
                  is_rcvrready = 1;
                }

              /* transfer header */

              ret = do_xferheader(priv, is_rxreq, is_txreq, 0, is_rcvrready);

              if (is_rcvrready)
                {
                  is_rcvrready = 0;
                }

              xfer_mode = decide_xfermode(priv, is_rxreq, is_txreq, ret);

              switch (xfer_mode)
                {
                case MODE_RXDATA:
                  ret = do_receivedata(priv);
                  break;

                case MODE_TXDATA:
                  ret = do_senddata(priv);
                  break;

                case MODE_TRXDATA:
                  ret = do_trxdata(priv);
                  break;

                case MODE_RXDATANOBUFF:
                  ret = do_receivedata_nobuff(priv);
                  break;

                case MODE_TRXDATANOBUFF:
                  ret = do_trxdata_norxbuff(priv);
                  break;

                case MODE_RXRESET:
                  ret = do_receivereset(priv);
                  break;

                case MODE_TRXRESET:
                  ret = do_trxreset(priv);
                  break;

                case MODE_RXINVALID:
                  ret = TRANS_RXINVALID;
                  break;

                case MODE_TRXHEADERFAILTXREQ:
                case MODE_TRXHEADERFAILRXREQ:
                  break;

                default:
                  m_err("ERR:%04d Unknown decision of transfer: %d.\n",
                        __LINE__, xfer_mode);
                  break;
                }

              if (is_txreq)
                {
                  DEBUGASSERT(priv->lower);
                  priv->lower->master_request(false);
                }

              m_info("m=%d\n", xfer_mode);
              done_xfer(priv, xfer_mode, ret);
              start_svtimer(priv);
            }
        }

      if (ptn & EVENT_EXIT)
        {
          spidev->is_not_run = true;
        }
    }

  delete_svtimer(priv);
  task_delete(0);

  return 0;
}

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

/****************************************************************************
 * Name: altmdm_spi_gpioreadyisr
 *
 * Description:
 *   Interrupt handler for SLAVE_REQUEST GPIO line.
 *
 ****************************************************************************/

int altmdm_spi_gpioreadyisr(int irq, FAR void *context, FAR void *arg)
{
  FAR struct altmdm_dev_s *priv = g_privdata;
  FAR struct altmdm_spi_dev_s *spidev = &priv->spidev;

  altmdm_sys_setflag(&spidev->xfer_flag, EVENT_RXREQ);

  return 0;
}

/****************************************************************************
 * Name: altmdm_spi_init
 *
 * Description:
 *   Initialize ALTMDM driver.
 *
 ****************************************************************************/

int altmdm_spi_init(FAR struct altmdm_dev_s *priv)
{
  int ret = 0;

  g_privdata = priv;

  /* Initialize modem power management driver */

  altmdm_pm_init(priv);

  memset(&priv->spidev, 0, sizeof(struct altmdm_spi_dev_s));
  priv->spidev.is_not_run = false;
  priv->spidev.is_xferready = false;

  altmdm_sys_initlock(&priv->spidev.tx_param.lock);
  altmdm_sys_initlock(&priv->spidev.rx_param.lock);
  altmdm_sys_initlock(&priv->spidev.sleep_param.lock);

  altmdm_sys_initflag(&priv->spidev.xfer_flag);
  altmdm_sys_initflag(&priv->spidev.xferready_flag);
  altmdm_sys_initflag(&priv->spidev.tx_param.done_flag);
  altmdm_sys_initflag(&priv->spidev.sleep_param.done_flag);

  create_rxbufffifo(priv);

  DEBUGASSERT(priv->lower);

  /* SPI settings */

  SPI_LOCK(priv->spi, true);
  SPI_SETMODE(priv->spi, SPIDEV_MODE0);
  SPI_SETBITS(priv->spi, 8);
  SPI_SETFREQUENCY(priv->spi, priv->lower->spi_maxfreq());
  SPI_LOCK(priv->spi, false);

  priv->lower->sready_irqattach(true, altmdm_spi_gpioreadyisr);

  priv->spidev.task_id = task_create(XFER_TASK_NAME, XFER_TASK_PRI,
                                     XFER_TASK_STKSIZE, xfer_task, NULL);
  if (priv->spidev.task_id == ERROR)
    {
      m_err("Failed to create xfer task\n");
    }

  return ret;
}

/****************************************************************************
 * Name: altmdm_spi_uninit
 *
 * Description:
 *   Uninitialize ALTMDM driver.
 *
 ****************************************************************************/

int altmdm_spi_uninit(FAR struct altmdm_dev_s *priv)
{
  altmdm_sys_setflag(&priv->spidev.xfer_flag, EVENT_EXIT);

  /* check transfer task is deleted or not */

  while (1)
    {
      if (priv->spidev.is_not_run)
        {
          break;
        }

      nxsig_usleep(10);
    }

  altmdm_sys_deletelock(&priv->spidev.tx_param.lock);
  altmdm_sys_deletelock(&priv->spidev.rx_param.lock);
  altmdm_sys_deletelock(&priv->spidev.sleep_param.lock);

  altmdm_sys_deleteflag(&priv->spidev.xfer_flag);
  altmdm_sys_deleteflag(&priv->spidev.tx_param.done_flag);
  altmdm_sys_deleteflag(&priv->spidev.sleep_param.done_flag);

  if (priv->spidev.rx_param.rxbuff != NULL)
    {
      free_rxbuffer(priv, priv->spidev.rx_param.rxbuff);
      priv->spidev.rx_param.rxbuff = NULL;
    }

  destroy_rxbufffifo(priv);

  DEBUGASSERT(priv->lower);
  priv->lower->sready_irqattach(false, NULL);

  /* Uninitialize modem power management driver */

  altmdm_pm_uninit(priv);

  return 0;
}

/****************************************************************************
 * Name: altmdm_spi_enable
 *
 * Description:
 *   Enable ALTMDM SPI driver.
 *
 ****************************************************************************/

int altmdm_spi_enable(FAR struct altmdm_dev_s *priv)
{
  DEBUGASSERT(priv && priv->lower);
  priv->lower->sready_irqenable(true);

  return 0;
}

/****************************************************************************
 * Name: altmdm_spi_disable
 *
 * Description:
 *   Disable ALTMDM SPI driver.
 *
 ****************************************************************************/

int altmdm_spi_disable(FAR struct altmdm_dev_s *priv)
{
  FAR struct altmdm_spi_dev_s *spidev = &priv->spidev;

  DEBUGASSERT(priv->lower);
  priv->lower->sready_irqenable(false);

  spidev->is_xferready = false;

  return 0;
}

/****************************************************************************
 * Name: altmdm_spi_read
 *
 * Description:
 *   ALTMDM SPI driver read method.
 *
 ****************************************************************************/

ssize_t altmdm_spi_read(FAR struct altmdm_dev_s * priv,
                        FAR const char *buffer, size_t readlen)
{
  FAR struct altmdm_spi_dev_s *spidev;
  FAR struct altmdm_spi_rxbuff_s *rbuff;
  ssize_t rsize = readlen;

  /* Check argument */

  if ((priv == NULL) || (buffer == NULL))
    {
      return -EINVAL;
    }

  if (!readlen || readlen > MAX_PKT_SIZE)
    {
      m_err("Invalid read length:%d.\n", readlen);
      return -EINVAL;
    }

  spidev = &priv->spidev;

  altmdm_sys_lock(&spidev->rx_param.lock);

  get_rxbufffifo(priv, &rbuff);
  if (spidev->rx_param.rxabort)
    {
      spidev->rx_param.rxabort = false;
      if (rbuff != NULL)
        {
          m_info("rx buffer discard because of abort.%d\n", __LINE__);
          free_rxbuffer(priv, rbuff);
        }

      rsize = -ECONNABORTED;
    }
  else
    {
      if (rbuff == NULL)
        {
          m_err("get rx buffer failed.\n");
          rsize = -EIO;
        }
      else if (rbuff->rx_size > readlen)
        {
          m_info("get rx buffer.%d\n", __LINE__);
          rsize = readlen;
          memcpy((void *)buffer, rbuff->buff_addr, rsize);
          free_rxbuffer(priv, rbuff);
        }
      else
        {
          m_info("get rx buffer.%d\n", __LINE__);
          rsize = rbuff->rx_size;
          memcpy((void *)buffer, rbuff->buff_addr, rsize);
          free_rxbuffer(priv, rbuff);
        }
    }

  altmdm_sys_unlock(&spidev->rx_param.lock);

  return rsize;
}

/****************************************************************************
 * Name: altmdm_spi_write
 *
 * Description:
 *   ALTMDM SPI driver write method.
 *
 ****************************************************************************/

ssize_t altmdm_spi_write(FAR struct altmdm_dev_s * priv,
                         FAR const char *buffer, size_t witelen)
{
  int ret;
  int remainder;
  uint32_t ptn;
  FAR struct altmdm_spi_dev_s *spidev;
  ssize_t wsize = witelen;

  /* Check argument */

  if ((priv == NULL) || (buffer == NULL))
    {
      return -EINVAL;
    }

  if (!witelen || witelen > MAX_PKT_SIZE)
    {
      m_err("Invalid write length:%d.\n", witelen);
      return -EINVAL;
    }

  spidev = &priv->spidev;

  if (!spidev->is_xferready)
    {
      wait_xferready(priv);
    }

  altmdm_sys_lock(&spidev->tx_param.lock);

again:
  spidev->tx_param.buff_addr = (void *)buffer;
  spidev->tx_param.actual_size = witelen;

  remainder = witelen % UNIT_SIZE;
  if (remainder == 0)
    {
      spidev->tx_param.total_size = spidev->tx_param.actual_size;
    }
  else
    {
      spidev->tx_param.total_size =
        ((spidev->tx_param.actual_size / UNIT_SIZE) + 1) * UNIT_SIZE;
    }

  spidev->tx_param.result = 0;

  altmdm_sys_setflag(&spidev->xfer_flag, EVENT_TXREQ);

  ret = altmdm_sys_waitflag(&spidev->tx_param.done_flag,
                            EVENT_TX_DONE,
                            ALTMDM_SYS_FLAG_WMODEOR,
                            &ptn,
                            WRITE_WAIT_TIMEOUT);
  if (ret != OK)
    {
      m_err("wait failed:%d\n", ret);
      wsize = -ETIME;
    }
  else
    {
      switch (spidev->tx_param.result)
        {
        case TRANS_OK:
        case TRANS_OK_TRXDATANORXBUFF:
          wsize = witelen;
          break;

        case TRANS_RXINVALID:
        case TRANS_WAITRCVRTMO:
          wsize = -EIO;
          break;

        case TRANS_OK_RCVBUFFUL:
          nxsig_usleep(100);
          goto again;
          break;

        default:
          m_err("Unexpected situation. tx result = %d.\n",
                spidev->tx_param.result);
          wsize = -EIO;
          break;
        }

      m_info("%s: tx result: %d.\n", __func__, spidev->tx_param.result);
      m_info("%s: write size: %d.\n", __func__, wsize);
    }

  altmdm_sys_unlock(&spidev->tx_param.lock);

  return wsize;
}

/****************************************************************************
 * Name: spicom_read_abort
 *
 * Description:
 *   Abort the read process.
 *
 ****************************************************************************/

int altmdm_spi_readabort(FAR struct altmdm_dev_s *priv)
{
  /* Check argument */

  if (priv == NULL)
    {
      return -EINVAL;
    }

  abort_get_rxbufffifo(priv);

  return OK;
}

/****************************************************************************
 * Name: altmdm_spi_sleepmodem
 *
 * Description:
 *   Make ALTMDM sleep.
 *
 ****************************************************************************/

int altmdm_spi_sleepmodem(FAR struct altmdm_dev_s *priv)
{
  int ret;
  bool sleep_requested;
  uint32_t ptn;
  FAR struct altmdm_spi_dev_s *spidev = &priv->spidev;

  altmdm_sys_lock(&spidev->sleep_param.lock);
  sleep_requested = spidev->sleep_param.requested;
  if (!sleep_requested)
    {
      spidev->sleep_param.requested = true;
    }

  altmdm_sys_unlock(&spidev->sleep_param.lock);

  if (sleep_requested)
    {
      ret = -EBUSY;
    }
  else
    {
      spidev->sleep_param.result = 0;

      altmdm_sys_setflag(&spidev->xfer_flag, EVENT_SLEEPREQ);

      ret = altmdm_sys_waitflag(&spidev->sleep_param.done_flag,
                                EVENT_SLEEP_DONE, ALTMDM_SYS_FLAG_WMODEOR,
                                &ptn, SREQ_WAIT_TIMEOUT);
      if (ret != OK)
        {
          m_err("wait failed:%d\n", ret);
        }
      else
        {
          ret = spidev->sleep_param.result;
          m_info("%s: sleep result: %d.\n", __func__,
                 spidev->sleep_param.result);
        }

      spidev->sleep_param.requested = false;
    }

  return ret;
}

/****************************************************************************
 * Name: altmdm_spi_setreceiverready
 *
 * Description:
 *   Set receiver ready notification.
 *
 ****************************************************************************/

int altmdm_spi_setreceiverready(FAR struct altmdm_dev_s *priv)
{
  FAR struct altmdm_spi_dev_s *spidev = &priv->spidev;

  altmdm_sys_setflag(&spidev->xfer_flag, EVENT_RXREQ);

  return 0;
}

/****************************************************************************
 * Name: altmdm_spi_isreceiverready
 *
 * Description:
 *   Check already notified or not by altmdm_spi_setreceiverready.
 *
 ****************************************************************************/

int altmdm_spi_isreceiverready(FAR struct altmdm_dev_s *priv)
{
  FAR struct altmdm_spi_dev_s *spidev = &priv->spidev;
  struct altmdm_sys_flagstate_s flag_status;

  altmdm_sys_referflag(&spidev->xfer_flag, &flag_status);
  if (flag_status.flag_pattern & EVENT_RXREQ)
    {
      return 1;
    }

  return 0;
}

/****************************************************************************
 * Name: altmdm_spi_clearreceiverready
 *
 * Description:
 *   Clear receiver ready notification.
 *
 ****************************************************************************/

int altmdm_spi_clearreceiverready(FAR struct altmdm_dev_s *priv)
{
  FAR struct altmdm_spi_dev_s *spidev = &priv->spidev;

  altmdm_sys_clearflag(&spidev->xfer_flag, EVENT_RXREQ);

  return 0;
}

#endif /* CONFIG_MODEM_ALTMDM */