/****************************************************************************
 * drivers/modem/alt1250/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 <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#include <nuttx/modem/alt1250.h>

#include "altmdm_spi.h"

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

void altmdm_spipkt_init(FAR altmdm_spipkt_t *pkt)
{
  pkt->header = 0;
  pkt->buffer = NULL;
  pkt->buff_size = 0;
}

void altmdm_set_spipkt_rxbuffer(FAR altmdm_spipkt_t *pkt, FAR void *buf,
  uint16_t sz)
{
  pkt->buffer = buf;
  pkt->buff_size = sz;
}

void altmdm_set_spipkt_txbuffer(FAR altmdm_spipkt_t *pkt, FAR void *buf,
  uint16_t sz)
{
  uint16_t total_sz;

  if (sz <= ALTSPI_MAX_PKTSIZE)
    {
      sz &= ALTSPI_PKT_SIZEMASK;
      total_sz = (sz + (ALTSPI_PKT_WORDSIZE - 1)) &
        ~(ALTSPI_PKT_WORDSIZE - 1);
      pkt->header = (total_sz << ALTSPI_PKT_TOTALSIZE_POS) + sz;
      pkt->buffer = buf;
      pkt->buff_size = sz;
    }
}

void altmdm_overwrite_body_size(FAR altmdm_spipkt_t *pkt, uint16_t sz)
{
  uint16_t total_sz;

  if (sz <= ALTSPI_MAX_PKTSIZE)
    {
      sz &= ALTSPI_PKT_SIZEMASK;
      total_sz = (sz + (ALTSPI_PKT_WORDSIZE - 1)) &
        ~(ALTSPI_PKT_WORDSIZE - 1);
      pkt->header = (pkt->header & ALTSPI_STATUSMASK) |
        ((total_sz << ALTSPI_PKT_TOTALSIZE_POS) + sz);
    }
}

void altmdm_set_sleeppkt(FAR altmdm_spipkt_t *pkt)
{
  altmdm_set_spipkt_txbuffer(pkt, NULL, 0);
  pkt->header |= ALTSPI_SLEEP_BIT;
}

void altmdm_set_retrypkt(FAR altmdm_spipkt_t *pkt)
{
  pkt->header |= ALTSPI_BUFFFULL_BIT;
}

bool altmdm_is_valid_spipkt_header(FAR altmdm_spipkt_t *pkt)
{
  uint16_t actual_size;
  uint16_t total_size;

  actual_size = pkt_actual_size(pkt);
  total_size = pkt_total_size(pkt);

  return ((actual_size <= ALTSPI_MAX_PKTSIZE) &&
    (actual_size <= pkt->buff_size) &&
    (((actual_size + (ALTSPI_PKT_WORDSIZE - 1)) & ~(ALTSPI_PKT_WORDSIZE - 1))
     == total_size));
}

bool altmdm_is_sleeppkt_ok(FAR altmdm_spipkt_t *pkt)
{
  return (strncmp(pkt->buffer, "OKOK", 4) == 0);
}

void altmdm_do_hdr_transaction(FAR struct spi_dev_s *spidev,
  FAR const struct alt1250_lower_s *lower, FAR altmdm_spipkt_t *tx_pkt,
  FAR altmdm_spipkt_t *rx_pkt)
{
  /* Alt1250 is network endian, so swap tx header value. */

  tx_pkt->header = htonl(tx_pkt->header);

  SPI_EXCHANGE(spidev, &tx_pkt->header, &rx_pkt->header, ALTSPI_PKT_HDRSIZE);

  /* Get correct endian values after exchanged */

  rx_pkt->header = ntohl(rx_pkt->header);
  tx_pkt->header = ntohl(tx_pkt->header);

  m_info("Header TRX done. rx: 0x%08lx, tx: 0x%08lx\n",
    rx_pkt->header, tx_pkt->header);
}

void altmdm_do_body_transaction(FAR struct spi_dev_s *spidev,
  FAR const struct alt1250_lower_s *lower, FAR altmdm_spipkt_t *tx_pkt,
  FAR altmdm_spipkt_t *rx_pkt)
{
  size_t exchange_size;

  /* Larger size is exchange size */

  exchange_size = pkt_total_size(tx_pkt) > pkt_total_size(rx_pkt)
    ? pkt_total_size(tx_pkt) : pkt_total_size(rx_pkt);

  SPI_EXCHANGE(spidev, tx_pkt->buffer, rx_pkt->buffer, exchange_size);

  m_info("Body TRX done. ex size: %d, rx size: %d/%d, tx size: %d/%d\n",
    exchange_size, pkt_actual_size(rx_pkt), pkt_total_size(rx_pkt),
    pkt_actual_size(tx_pkt), pkt_total_size(tx_pkt));
}