/****************************************************************************
 * arch/arm/src/calypso/calypso_spi.c
 * SPI driver for TI Calypso
 *
 *   Copyright (C) 2010 Harald Welte <laforge@gnumonks.org>
 *   Copyright (C) 2011 Stefan Richter <ichgeh@l--putt.de>
 *
 * Part of this source code is derivated from Osmocom-BB project and was
 * relicensed as BSD with permission from original authors.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

#include <nuttx/config.h>
#include <nuttx/spi/spi.h>

#include <unistd.h>
#include <semaphore.h>
#include <debug.h>
#include <errno.h>

#include "up_arch.h"
#include "calypso_spi.h"

#warning "MOST OF SPI API IS INCOMPLETE! (Wrapper around Osmocom driver)"
extern void spi_init(void);
extern int spi_xfer(uint8_t dev_idx, uint8_t bitlen, const void *dout, void *din);

#ifndef CONFIG_SPI_EXCHANGE
#error "Calypso HW only supports exchange. Enable CONFIG_SPI_EXCHANGE!"
#endif

struct calypso_spidev_s
{
  struct spi_dev_s spidev;  /* External driver interface */
  int              nbits;   /* Number of transfered bits */

#ifndef CONFIG_SPI_OWNBUS
  sem_t            exclsem; /* Mutual exclusion of devices */
#endif
};

/* STUBS! */

#ifndef CONFIG_SPI_OWNBUS
static int spi_lock(FAR struct spi_dev_s *dev, bool lock)
{
  return -ENOSYS;
}
#endif

static void spi_select(FAR struct spi_dev_s *dev, enum spi_dev_e devid,
                       bool selected)
{
}

static uint32_t spi_setfrequency(FAR struct spi_dev_s *dev, uint32_t frequency)
{
  return frequency;
}

static void spi_setmode(FAR struct spi_dev_s *dev, enum spi_mode_e mode)
{
}

/* Osmocom wrapper */

static void spi_setbits(FAR struct spi_dev_s *dev, int nbits)
{
  ((FAR struct calypso_spidev_s *)dev)->nbits = nbits;
}

static void spi_exchange(FAR struct spi_dev_s *dev, FAR const void *txbuffer,
                         FAR void *rxbuffer, size_t nwords)
{
  FAR struct calypso_spidev_s *priv = (FAR struct calypso_spidev_s *)dev;
  size_t i;

  for (i = 0; i < nwords; i++)
    {
      spi_xfer(0, priv->nbits, txbuffer + i, rxbuffer + i);
    }
}

static uint16_t spi_send(FAR struct spi_dev_s *dev, uint16_t wd)
{
  uint16_t buf = wd;
  spi_exchange(dev, &buf, &buf, 1);
  return buf;
}

static const struct spi_ops_s g_spiops =
{
#ifndef CONFIG_SPI_OWNBUS
  .lock              = spi_lock,
#endif
  .select            = spi_select,
  .setfrequency      = spi_setfrequency,
  .setmode           = spi_setmode,
  .setbits           = spi_setbits,
  .status            = 0,
#ifdef CONFIG_SPI_CMDDATA
  .cmddata           = ,
#endif
  .send              = spi_send,
#ifdef CONFIG_SPI_EXCHANGE
  .exchange          = spi_exchange,
#else
  .sndblock          = spi_sndblock,
  .recvblock         = spi_recvblock,
#endif
  .registercallback  = 0,
};

static struct calypso_spidev_s g_spidev =
{
  .spidev  = { &g_spiops },
  .nbits  = 0,
};

void spi_init(void)
{
  putreg16(SPI_SET1_EN_CLK | SPI_SET1_WR_IRQ_DIS | SPI_SET1_RDWR_IRQ_DIS,
           SPI_REG(REG_SET1));

  putreg16(0x0001, SPI_REG(REG_SET2));
}

int spi_xfer(uint8_t dev_idx, uint8_t bitlen, const void *dout, void *din)
{
  uint8_t bytes_per_xfer;
  uint8_t reg_status, reg_ctrl = 0;
  uint32_t tmp;

  if (bitlen == 0)
    {
      return 0;
    }

  if (bitlen > 32)
    {
      return -1;
    }

  if (dev_idx > 4)
    {
      return -1;
    }

  bytes_per_xfer = bitlen / 8;
  if (bitlen % 8)
    {
      bytes_per_xfer ++;
    }

  reg_ctrl |= (bitlen - 1) << SPI_CTRL_NB_SHIFT;
  reg_ctrl |= (dev_idx & 0x7) << SPI_CTRL_AD_SHIFT;

  if (bitlen <= 8)
    {
      tmp = *(uint8_t *)dout;
      tmp <<= 24 + (8-bitlen);  /* align to MSB */
    }
  else if (bitlen <= 16)
    {
      tmp = *(uint16_t *)dout;
      tmp <<= 16 + (16-bitlen);  /* align to MSB */
    }
  else
    {
      tmp = *(uint32_t *)dout;
      tmp <<= (32-bitlen);    /* align to MSB */
    }

  dbg("spi_xfer(dev_idx=%u, bitlen=%u, data_out=0x%08x): ",
      dev_idx, bitlen, tmp);

  /* fill transmit registers */

  putreg16(tmp >> 16, SPI_REG(REG_TX_MSB));
  putreg16(tmp & 0xffff, SPI_REG(REG_TX_LSB));

  /* initiate transfer */

  if (din)
    {
      reg_ctrl |= SPI_CTRL_RDWR;
    }
  else
    {
      reg_ctrl |= SPI_CTRL_WR;
    }

  putreg16(reg_ctrl, SPI_REG(REG_CTRL));
  dbg("reg_ctrl=0x%04x ", reg_ctrl);

  /* wait until the transfer is complete */

  while (1)
    {
      reg_status = getreg16(SPI_REG(REG_STATUS));
      dbg("status=0x%04x ", reg_status);
      if (din && (reg_status & SPI_STATUS_RE))
        {
          break;
        }
      else if (reg_status & SPI_STATUS_WE)
        {
          break;
        }
    }

  /* FIXME: calibrate how much delay we really need (seven 13MHz cycles) */

  usleep(1000);

  if (din)
    {
      tmp = getreg16(SPI_REG(REG_RX_MSB)) << 16;
      tmp |= getreg16(SPI_REG(REG_RX_LSB));
      dbg("data_in=0x%08x ", tmp);

      if (bitlen <= 8)
        {
          *(uint8_t *)din = tmp & 0xff;
        }
      else if (bitlen <= 16)
        {
          *(uint16_t *)din = tmp & 0xffff;
        }
      else
        {
          *(uint32_t *)din = tmp;
        }
    }

  dbg("\n");

  return 0;
}

FAR struct spi_dev_s *up_spiinitialize(int port)
{
  switch (port)
    {
    case 0:    /* SPI master device */
      spi_init();
      return (FAR struct spi_dev_s *)&g_spidev;

    case 1:    /* uWire device */
      return NULL;

    default:
      return NULL;
    }
}
