/**
   \file bfispis.c SPI slave mode input driver for VSOS
   \author Henrik Herranen, VLSI Solution Oy
*/

#include <vo_stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <math.h>
#include <vstypes.h>
#include <vs1005h.h>
#include <timers.h>
#include <vsos.h>
#include <sysmemory.h>
#include <ringbuf.h>
#include <apploader.h>
#include <clockspeed.h>
#include <imem.h>
#include <ringbuf.h>
#include <vo_gpio.h>
#include "bfxspi.h"
#include "simpledevice.h"

#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))


extern u_int16 IntSpi1Vector;

struct AudioIn audioIn = {
  NULL
};

void IntSpi1C(void) {
  int quit = 0;
  while (PERIP(SPI1_STATUS) & SPI_ST_RXFULL) {
    s_int16 __mem_y *nextWr = ringmodY(audioIn.wr, audioIn.modifier);
    if (nextWr == audioIn.wr) {
      audioIn.overflow++;
      //      PERIP(UART_DATA) = 'o';
    } else {
      *audioIn.wr = PERIP(SPI1_DATA);
      audioIn.wr = nextWr;
      //      PERIP(UART_DATA) = 'w';
    }
    audioIn.sampleCounter++;
  }
}

auto u_int16 AudioInFill(void) {
  s_int16 fi = audioIn.wr - audioIn.rd;
  if (fi < 0)
    fi += audioIn.bufSize;
  return fi;
}

ioresult AllocateAudioBuffer(register struct AudioIn *a,
			     register u_int16 bufSize) {
  ioresult res = S_OK;
  Disable();
  if (a->bufSize) {
    FreeMemY(a->buf, a->bufSize);
  }
  bufSize &= ~1;
  if (!(a->buf = AllocMemY(bufSize, CeilPow2(bufSize)))) {
    static u_int16 __mem_y panicBuffer[4];
    a->buf = panicBuffer;
    a->bufSize = 0;
    a->modifier = MAKEMODF(4);
    res = S_ERROR;
  } else {
    memsetY(a->buf, 0, bufSize);
    a->bufSize = bufSize;
    a->modifier = MAKEMODF(bufSize);
  }
  a->wr = a->rd = (__mem_y s_int16 *)(a->buf);
  Enable();
  return res;
}


const char *DevSpiIdentify(register __i0 void *self, char *buf, u_int16 bufSize) {
  const char *s = "BfISpiS";
  if (bufSize) {
    strncpy(buf, s, bufSize);
    buf[bufSize-1] = '\0';
  }
  return s;
}

ioresult DevSpiOpen(register __i0 SIMPLE_FILE *self, const char *name, const char *mode) { ///< Start device, populate descriptor, find and start filesystem.
  self->pos = 0;
  return S_OK;
}

ioresult DevSpiClose(register __i0 SIMPLE_FILE *self) { ///< Flush, Stop, Clean and Free the descriptor of the device
  return S_OK;
}

IOCTL_RESULT DevSpiIoctl(register __i0 SIMPLE_FILE *self, s_int16 request,
			 IOCTL_ARGUMENT argp) {
  ioresult res = S_OK;

#if 0
  printf("Ioctl(%p, %x, %x)\n", self, request, argp);
#endif
  
  switch (request) {
#if 0
  case IOCTL_START_FRAME:			
    break;
		
  case IOCTL_END_FRAME:
    break;
#endif

  case IOCTL_RESTART:
#if 0
    PERIP(SP_TX_CF) = 0;
    PERIP(INT_ENABLE1_LP) &= ~(INTF1_STX);
    PERIP(INT_ENABLE1_HP) &= ~(INTF1_STX);
    /* Set to Perip Mode */
    PERIP(GPIO0_MODE) |= GPIO0_SPDIF_IN | GPIO0_SPDIF_OUT;
    WriteIMem(0x20 + INTV_STX, ReadIMem((u_int16)StxInterruptVector));
    PERIP(SP_TX_CHST0) = 0;
    PERIP(SP_TX_CHST1) = 2*SP_TX_CHST1_CLKA | (SP_TX_CHST1_CH * 0b0011) |
      (SP_TX_CHST1_FS * 0b0000);
    PERIP(SP_TX_CHST2) = 0;
    PERIP(SP_TX_CF) = (u_int16)(clockSpeed.cpuClkHz/6144000)*SP_TX_CF_CLKDIV |
      SP_TX_CF_SND | SP_TX_CF_IE;
    AudioSetHiBits(0);
#endif
    audioIn.wr = audioIn.rd = audioIn.buf;
    audioIn.flags = 0;
    if (AllocateAudioBuffer(&audioIn, 2048)) {
      res = S_ERROR;
    }
    WriteIMem(0x20+INTV_SPI1, ReadIMem((int)(&IntSpi1Vector)));
    GpioSetAsPeripheral(0x14); // XCS1
    GpioSetAsPeripheral(0x15); // SCLK1
    GpioSetAsPeripheral(0x16); // MISO1
    GpioSetAsPeripheral(0x17); // MOSI1
    PERIP(INT_ENABLE0_LP) |= INTF_SPI1;
    PERIP(INT_ENABLE0_HP) &= ~INTF_SPI1;
    PERIP(SPI1_FSYNC) = 0;
    PERIP(SPI1_DEFAULT) = 0;
    PERIP(SPI1_CLKCF) = 0xff<<SPI_CC_CLKDIV_B; /* 1ff = slowest */
#if 0
    PERIP(SPI1_CF) = /*SPI_CF_SRESET|*/SPI_CF_MASTER|15*SPI_CF_DLEN|SPI_CF_FSIDLE;
#else
    PERIP(SPI1_CF) = 0*SPI_CF_XCSMODE|(8-1)*SPI_CF_DLEN|SPI_CF_RXFIFO_ENA;
#endif
    break;

  case IOCTL_AUDIO_GET_INPUT_BUFFER_SIZE:
    return audioIn.bufSize;
    break;

  case IOCTL_AUDIO_SET_INPUT_BUFFER_SIZE:
    if (AllocateAudioBuffer(&audioIn, (u_int16)argp)) {
      res = S_ERROR;
    }
    break;

  case IOCTL_AUDIO_GET_INPUT_BUFFER_FILL:
    return AudioInFill();
    break;

  case IOCTL_AUDIO_GET_SAMPLE_COUNTER:
    Disable();
    *((u_int32 *)argp) = audioIn.sampleCounter;
    Enable();
    break;

  case IOCTL_AUDIO_GET_OVERFLOWS:
    Disable();
    *((u_int32 *)argp) = audioIn.overflow;
    Enable();
    break;

  default:
    res = S_ERROR;
    break;
  }

  if (audioIn.bufSize) {
    self->flags = __MASK_PRESENT | __MASK_OPEN | __MASK_CHARACTER_DEVICE |
      __MASK_READABLE | __MASK_WRITABLE;
    PERIP(INT_ENABLE1_HP) |= INTF_SPI1;
  } else {
    self->flags = __MASK_PRESENT | __MASK_OPEN | __MASK_CHARACTER_DEVICE;
    PERIP(INT_ENABLE1_HP) &= ~(INTF_SPI1);
  }

  return res;
}

u_int16 DevSpiRead(register __i0 SIMPLE_FILE *self, u_int16 *buf, u_int16 destinationIndex, u_int16 bytes) { ///< Read bytes to *buf, at byte index
  u_int16 bytesRead = 0;
  /* Only whole samples are allowed. This is not a character device driver. */
#if 0
  printf("Read(%p, %p, %x, %x), rd %p, wr %p\n",
	 self, buf, destinationIndex, bytes, audioIn.rd, audioIn.wr);
#endif

  while (bytes) {
    u_int16 wantBytes = MIN(bytes, 16), toReadBytes;
    u_int16 fill = AudioInFill();
    static u_int16 tmpBuf[16];
    
#if 0
    if (ioctl(stdin, IOCTL_TEST, NULL) > 0) {
      int cmd = fgetc(stdin);
      printf("%c", cmd);
    }
#endif
    
    toReadBytes = MIN(fill, wantBytes);
    audioIn.rd =
      RING_YX_S(ringcpyYX(tmpBuf, 1, audioIn.rd, audioIn.modifier, toReadBytes));
#if 0
    {
      int i;
      for (i=0; i<toReadBytes; i++) {
	printf(" %02x", tmpBuf[i]);
      }
      printf("\n");
    }

#endif
    
    memcpypack(tmpBuf, tmpBuf, toReadBytes);
    MemCopyPackedBigEndian(buf, destinationIndex, tmpBuf, 0, toReadBytes);
      
    bytes -= toReadBytes;
    bytesRead += toReadBytes;
    destinationIndex += toReadBytes;
#if 0
    if (toReadBytes < wantBytes) {
      /* There was too little content in audio buffer so we return what
	 we got this far. */
      bytes = 0;
    }
#endif
  }
#if 0
  printf(" -> %d\n", bytesRead);
#endif
  return bytesRead;
}

u_int16 DevSpiWrite(register __i0 SIMPLE_FILE *self, u_int16 *buf, u_int16 sourceIndex, u_int16 bytes) { ///< Write bytes from *buf at byte index
  return 0;
}

const SIMPLE_DEVICE spiDevice = {
  __MASK_PRESENT | __MASK_CHARACTER_DEVICE | __MASK_READABLE | __MASK_OPEN,
  DevSpiIdentify,
  DevSpiOpen,
  DevSpiClose,
  DevSpiIoctl,
  DevSpiRead,
  DevSpiWrite
};
