/* -*- c++ -*-
 *
 * SOCLIB_LGPL_HEADER_BEGIN
 * 
 * This file is part of SoCLib, GNU LGPLv2.1.
 * 
 * SoCLib is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation; version 2.1 of the License.
 * 
 * SoCLib is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with SoCLib; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * SOCLIB_LGPL_HEADER_END
 *
 * Sebastien Cerdan <sebcerdan@gmail.com>, 2014
 */

#include <stdint.h>
#include "register.h"
#include "vci_spi_ftdi.h"


namespace soclib { namespace caba {

#define CTRL_RST_VALUE 0x7
#define MAX_WORD_SIZE 0x20
#define STATUS_RST (SOCLIB_SPI_STATUS_TXEMPTY|SOCLIB_SPI_IRQPEND_RXEMPTY|SOCLIB_SPI_IRQPEND_TXHALF)

#define tmpl(t) template<typename vci_param> t VciSpiFtdi<vci_param>

tmpl(bool)::on_write(int seg, typename vci_param::addr_t addr, typename vci_param::data_t data, int be)
{
  switch ((int)addr) {
    case SOCLIB_SPI_CTRL_ADDR:
      _ctrl = data;
      _wsize = 1 << (SOCLIB_SPI_CTRL_WSIZE_GET(data) + 1);
      return true;
    case SOCLIB_SPI_FIFO_ADDR:
      if (_txb->full())
        {
          std::cerr << name() << ": tx buffer overflow\n";
          break;
        }
      _txb->simple_put(data);
      return true;
    case SOCLIB_SPI_GPOUT_ADDR:
      s_gpout = data;
      return true;
    case SOCLIB_SPI_GPIN_ADDR:
      _gpirqm = data;
      return true;
    case SOCLIB_SPI_CSTGL_ADDR:
      _cstgl = data;
      return true;
    case SOCLIB_SPI_TLEN_ADDR:
      s_gpout = s_gpout ^_cstgl;
      s_tlen = data;
      return true;
    case SOCLIB_SPI_CLKDIV_ADDR:
      if (_ctx)
	ftdi_set_baudrate(_ctx, 3000000 / (data + 1));
      return true;
    case SOCLIB_SPI_IRQMASK_ADDR:
      _irqmsk = data;
      return true;
    case SOCLIB_SPI_GPIRQ_POL_ADDR:
      _gpirqpol = data;
      return true;
    case SOCLIB_SPI_GPIRQ_MODE_ADDR:
      _gpirqmode = data;
      return true;
    };
  return false;
}

tmpl(bool)::on_read(int seg, typename vci_param::addr_t addr, typename vci_param::data_t &data)
{
  switch ((int)addr) {
    case SOCLIB_SPI_CTRL_ADDR:
      data = _ctrl;
      return true;
    case SOCLIB_SPI_FIFO_ADDR:
      if (_rxb->empty())
        {
          std::cerr << name() << ": rx buffer underflow\n";
	  data = 0;
        }
      else
	{
	  data = _rxb->read();
	  _rxb->simple_get();
	}
      return true;
    case SOCLIB_SPI_CONFIG_ADDR:
      data = (__builtin_ctz(_fsize) - 1) | SOCLIB_SPI_CONFIG_WSIZE(MAX_WORD_SIZE - 1) |
             SOCLIB_SPI_CONFIG_GPINCNT(_ngpin - 1) | SOCLIB_SPI_CONFIG_GPOUTCNT(_ngpout - 1);
      return true;
    case SOCLIB_SPI_GPIN_ADDR:
      data = s_gpin & ((1 << _ngpin) - 1);
      return true;
    case SOCLIB_SPI_GPOUT_ADDR:
      data = s_gpout;
      return true;
    case SOCLIB_SPI_CSTGL_ADDR:
      data = _cstgl;
      return true;
    case SOCLIB_SPI_TLEN_ADDR:
      data = s_tlen;
      return true;
    case SOCLIB_SPI_IRQMASK_ADDR:
      data = _irqmsk;
      return true;
    case SOCLIB_SPI_IRQPEND_ADDR:
      data = _irqpend;
      if (_gpirqpend)
	_gpirqpend &= ~(1 << SOCLIB_SPI_IRQPEND_GPIRQN_GET(_irqpend));
      _irqpend = 0;
      return true;
    case SOCLIB_SPI_STATUS_ADDR:
      data = s_status;
      return true;
    case SOCLIB_SPI_GPIRQ_POL_ADDR:
      data = _gpirqpol;
      return true;
    case SOCLIB_SPI_GPIRQ_MODE_ADDR:
      data = _gpirqmode;
      return true;
    };
  return false;
}

tmpl(uint32_t)::format_data(uint32_t data)
{
  uint32_t v = data;

  if (SOCLIB_SPI_CTRL_LSBF_GET(_ctrl))
    {
      v = ((v >> 16) & 0x0000FFFF) | ((v << 16) & 0xFFFF0000);
      v = ((v >> 8 ) & 0x00FF00FF) | ((v << 8 ) & 0xFF00FF00);
      v = ((v >> 4 ) & 0x0F0F0F0F) | ((v << 4 ) & 0xF0F0F0F0);
      v = ((v >> 2 ) & 0x33333333) | ((v << 2 ) & 0xCCCCCCCC);
      v = ((v >> 1 ) & 0x55555555) | ((v << 1 ) & 0xAAAAAAAA);
      v >>= 8 * sizeof(v) - SOCLIB_SPI_CTRL_WSIZE_GET(_ctrl);
    }

   if (SOCLIB_SPI_CTRL_DPOL_GET(_ctrl))
     v = ~v;

   v &= (1 << SOCLIB_SPI_CTRL_WSIZE_GET(_ctrl)) - 1;

   return v;
}

tmpl(void)::transition()
{
  if (!p_resetn || SOCLIB_SPI_CTRL_RESET_GET(_ctrl)) {
        m_vci_target_fsm.reset();
        _txb->init();
        _rxb->init();
	_ctrl = CTRL_RST_VALUE;
	s_status = STATUS_RST;
	_cstgl = 0;
	s_tlen = 0;
	_irqpend = 0;
        _irqmsk = 0;
        _gpirqpend = 0;
        _gpirqm = 0;
        _gpirqmode = 0;
	_gpirqpol = 0;
	return;
  }

  if (SOCLIB_SPI_CTRL_NORX_GET(_ctrl) && !_rxb->empty())
    _rxb->init();

  uint8_t clk = (SOCLIB_SPI_CTRL_CKPOL_GET(_ctrl) ^ 
                 SOCLIB_SPI_CTRL_CKPHA_GET(_ctrl)) & 1;

  unsigned int i;

  if (!_txb->empty())
    {
      uint32_t bitcnt = SOCLIB_SPI_CTRL_WSIZE_GET(_ctrl);
      uint8_t x[2 * bitcnt + 1];

      if (_ftdi_tx_size >= sizeof(x))
        {
          uint32_t data = _txb->read();
          uint32_t wbuffer = format_data(data);

          for (i = 0; i < 2 * bitcnt; i += 2)
            {
              x[i] = ((_last_out & SOCLIB_SPI_FTDI_GPIO_MASK) |
                      (clk << SOCLIB_SPI_FTDI_SCLK) |
                      ((wbuffer >> (bitcnt - 1)) & 1) << SOCLIB_SPI_FTDI_MOSI);

              x[i + 1] = x[i] ^ (1 << SOCLIB_SPI_FTDI_SCLK);  
              wbuffer <<= 1;
            }

          _last_out = x[i] = x[i-1] ^ (1 << SOCLIB_SPI_FTDI_SCLK); 

          if (_ctx)
            ftdi_write_data(_ctx, x, sizeof(x));
          _ftdi_tx_size -= sizeof(x);
          _ftdi_tx.push_back(bitcnt);
          _ftdi_rx_cycles = _ftdi_wait;

          _txb->simple_get();
        }
    }

  if (_ftdi_rx_cycles > 0)
    _ftdi_rx_cycles--;

  if (_ftdi_rx_cycles == 0 && !_ftdi_tx.empty() && !_rxb->full())
    {
      uint32_t bitcnt = _ftdi_tx.front();
      uint8_t x[2 * bitcnt + 1];

      _ftdi_tx.pop_front();

      if (_ctx)
        ftdi_read_data(_ctx, x, sizeof(x));
      _ftdi_tx_size += sizeof(x);
      assert(_ftdi_tx_size <= 128);

      uint32_t rbuffer = 0;
      for (i = 0; i < 2 * bitcnt; i += 2)
        rbuffer = (rbuffer << 1) | ((x[i ^ clk ^ 1] >> SOCLIB_SPI_FTDI_MISO) & 1);
      _last_in = x[i];

      _rxb->simple_put(format_data(rbuffer));

      s_tlen = s_tlen - 1;
      if (s_tlen == 1)
	{
	  s_gpout = _cstgl ^ s_gpout;
	  _irqpend |= SOCLIB_SPI_IRQPEND_DONE;
	}
      _poll_cycles = _poll_top;
    }

  m_vci_target_fsm.transition();

  /* gpinput */
  if (_poll_cycles == 0) 
    {
      if (_ctx)
	ftdi_read_pins(_ctx, &_last_in);
      _poll_cycles = _poll_top;
    }
  else 
    _poll_cycles--;

  uint32_t pgpin = (_last_in >> SOCLIB_SPI_FTDI_GPIO) & ((1 << _ngpin) - 1);

  s_gpin.write(pgpin);

  uint32_t pol = pgpin ^ _gpirqpol;

  /* edge irqs */
  _gpirqpend |= ((s_gpin ^ pgpin) & ~_gpirqmode & pol) & _gpirqm;

  /* level irqs */
  uint32_t gpirq = (_gpirqpend | (pol & _gpirqmode & _gpirqm));

  if (gpirq)
    {
      _irqpend |= SOCLIB_SPI_IRQPEND_GPIRQ;
      SOCLIB_SPI_IRQPEND_GPIRQN_SET(_irqpend, __builtin_ctz(gpirq));
    }
  else
    {
      _irqpend &= ~SOCLIB_SPI_IRQPEND_GPIRQ;
      SOCLIB_SPI_IRQPEND_GPIRQN_SET(_irqpend, 0);
    }

  /* Interrupt status */

  uint32_t status = 0;

  if (_txb->full())
    {
      status |= SOCLIB_SPI_STATUS_TXFULL;
      if (!(s_status & SOCLIB_SPI_STATUS_TXFULL))
        _irqpend |= SOCLIB_SPI_IRQPEND_TXFULL;
    }
  if (_txb->empty())
    {
      status |= SOCLIB_SPI_STATUS_TXEMPTY;
      if (!(s_status & SOCLIB_SPI_STATUS_TXEMPTY))
        _irqpend |= SOCLIB_SPI_IRQPEND_TXEMPTY;
    }
  if (_txb->filled_status() <= _fsize/2)
    {
      status |= SOCLIB_SPI_STATUS_TXHALF;
      if (!(s_status & SOCLIB_SPI_STATUS_TXHALF))
        _irqpend |= SOCLIB_SPI_IRQPEND_TXHALF;
    }
  if (_rxb->full())
    {
      status |= SOCLIB_SPI_STATUS_RXFULL;
      if (!(s_status & SOCLIB_SPI_STATUS_RXFULL))
        _irqpend |= SOCLIB_SPI_IRQPEND_RXFULL;
    }
  if (_rxb->empty())
    {
      status |=  SOCLIB_SPI_STATUS_RXEMPTY;
      if (!(s_status & SOCLIB_SPI_STATUS_RXEMPTY))
        _irqpend |= SOCLIB_SPI_IRQPEND_RXEMPTY;
    }
  if (_rxb->filled_status() >= _fsize/2)
    {
      status |= SOCLIB_SPI_STATUS_RXHALF;
      if (!(s_status & SOCLIB_SPI_STATUS_RXHALF))
        _irqpend |= SOCLIB_SPI_IRQPEND_RXHALF;
    }

  s_status.write(status);
}

tmpl(void)::genMoore()
{
  m_vci_target_fsm.genMoore();

  p_irq = false;
  if (_irqmsk & _irqpend)
    p_irq = true;

  uint8_t out = (s_gpout.read() & ((1 << _ngpout) - 1)) << (SOCLIB_SPI_FTDI_GPIO + _ngpin);

  if ((_last_out & SOCLIB_SPI_FTDI_GPIO_MASK) != out)
    {
      _last_out = (_last_out & SOCLIB_SPI_FTDI_SPI_MASK) | (out & SOCLIB_SPI_FTDI_GPIO_MASK);
      if (_ctx)
	{
	  ftdi_write_data(_ctx, &_last_out, 1);
	  ftdi_read_data(_ctx, &_last_in, 1);
	}
    }
}

tmpl(/**/)::VciSpiFtdi(
    sc_module_name name,
    size_t fsize,
    size_t ngpin,
    size_t ngpout,
    const MappingTable &mt,
    const IntTab &tgtid,
    uint16_t usb_vid, uint16_t usb_pid):
      caba::BaseModule(name),
      m_vci_target_fsm(p_vci_target, mt.getSegmentList(tgtid)),
      p_clk("clk"),
      p_resetn("resetn"),
      p_irq("irq"),
      p_vci_target("vci_target")
{
  _fsize = fsize;
  _ngpin = ngpin;
  _ngpout = ngpout;
  _ctrl = CTRL_RST_VALUE;

  _poll_top = 1 << 15;
  _ftdi_wait = 1024;

  assert(fsize >= 2 && fsize <= 256);
  assert((fsize & (fsize - 1)) == 0);
  assert(ngpin + ngpout <= 8 - SOCLIB_SPI_FTDI_GPIO);

  _txb = new GenericFifo<uint32_t>("tx_fifo", fsize);
  _rxb = new GenericFifo<uint32_t>("rx_fifo", fsize);

  _ctx = ftdi_new();
  _last_in = _last_out = 0;

  uint8_t mask_out = (((1 << ngpout) - 1) << (ngpin + SOCLIB_SPI_FTDI_GPIO))|
                    (1 << SOCLIB_SPI_FTDI_MOSI) | (1 << SOCLIB_SPI_FTDI_SCLK);

  if (_ctx) 
    {
      if (ftdi_usb_open(_ctx, usb_vid, usb_pid)) {
          ftdi_free(_ctx);
          _ctx = 0;
      } else {
        ftdi_usb_reset(_ctx);
        ftdi_set_bitmode(_ctx, mask_out, BITMODE_SYNCBB);
        ftdi_set_baudrate(_ctx, 100000);
      }
    }

  _ftdi_tx_size = 128;
  _ftdi_rx_cycles = 0;
  _poll_cycles = 0;

  if (!_ctx)
      std::cerr << "VciSpiFtdi: unable to open USB device" << std::endl;

  m_vci_target_fsm.on_read_write(on_read, on_write);

  SC_METHOD(transition);
  dont_initialize();
  sensitive << p_clk.pos();

  SC_METHOD(genMoore);
  dont_initialize();
  sensitive << p_clk.neg();
}

tmpl(/**/)::~VciSpiFtdi()
{
  if (_ctx)
    {
      ftdi_usb_close(_ctx);
      ftdi_free(_ctx);
    }
}

}}


