/**
   \file audiofs.c Audio Filesystem 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"

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


extern u_int16 IntSpi1Vector;

struct AudioOut audioOut = {
  NULL
};


void IntSpi1C(void) {
  int quit = 0;
  while (!(PERIP(SPI1_STATUS) & SPI_ST_TXFULL) && !quit) {
    if (audioOut.rd != audioOut.wr) {
      PERIP(SPI1_DATA) = *audioOut.rd;
      audioOut.rd = ringmodY(audioOut.rd, audioOut.modifier);
      audioOut.sampleCounter++;
    } else {
      quit = 1;
    }
  }
}

auto u_int16 AudioOutFree(void) {
  s_int16 fi = audioOut.wr - audioOut.rd;
  s_int16 fr;
  if (fi < 0)
    fi += audioOut.bufSize;
  fr = audioOut.bufSize - fi - 1;
  if (fr < 0) fr = 0;
  return fr;
}

ioresult AllocateAudioBuffer(register struct AudioIn *a,
			     register u_int16 bufSize) {
  ioresult res = S_OK;
  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 = "BfOSpiM";
  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;
#if 0
  printf("Open(%p, %s, %s)\n", self, name, mode);
#endif
  return S_OK;
}

ioresult DevSpiClose(register __i0 SIMPLE_FILE *self) { ///< Flush, Stop, Clean and Free the descriptor of the device
#if 0
  printf("Close(%p)\n", self);
#endif
  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
    audioOut.wr = audioOut.rd = audioOut.buf;
    audioOut.flags = 0;
    if (AllocateAudioBuffer(&audioOut, 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) = 0x1f<<SPI_CC_CLKDIV_B; /* 0xff<<SPI_CC_CLKDIV_B slowest */
    PERIP(SPI1_CF) = /*SPI_CF_SRESET|*/SPI_CF_MASTER|(8-1)*SPI_CF_DLEN|SPI_CF_FSIDLE;
    break;

#if 0
  case IOCTL_AUDIO_GET_ORATE:
    *((u_int32 *)argp) = 48000L;
    break;

  case IOCTL_AUDIO_GET_BITS:
    return (audioOut.flags & (AIN_32BIT)) ? 32 : 16;
    break;

  case IOCTL_AUDIO_SET_BITS:
    res = AudioSetHiBits((u_int16)argp > 16);
    break;
#endif
    
  case IOCTL_AUDIO_GET_OUTPUT_BUFFER_SIZE:
    return audioOut.bufSize;
    break;

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

  case IOCTL_AUDIO_GET_OUTPUT_BUFFER_FREE:
    return AudioOutFree();
    break;

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

  case IOCTL_AUDIO_GET_UNDERFLOWS:
    Disable();
    *((u_int32 *)argp) = audioOut.underflow;
    Enable();
    break;

  default:
    res = S_ERROR;
    break;
  }

  if (audioOut.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
  printf("Read(%p, %p, %x, %x)\n", self, buf, destinationIndex, bytes);
  return bytes;
}

u_int16 DevSpiWrite(register __i0 SIMPLE_FILE *self, u_int16 *buf, u_int16 sourceIndex, u_int16 bytes) { ///< Write bytes from *buf at byte index
  u_int16 bytesLeft = bytes;
#if 0
  printf("Write(%p, %p, %x, %x), wr=%p, rd=%p  buf %x ->",
	 self, buf, sourceIndex, bytes, audioOut.wr, audioOut.rd, buf);
#endif
  
  while (bytesLeft) {
    u_int16 toWriteBytes = MIN(bytesLeft, 16);
    static u_int16 tmpBuf[16];

    MemCopyPackedBigEndian(tmpBuf, 16, buf, sourceIndex, toWriteBytes);
    memcpyunpack(tmpBuf, tmpBuf+8, toWriteBytes);
    while (AudioOutFree() < toWriteBytes) {
      Delay(1);
    }
    audioOut.wr =
      RING_XY_D(ringcpyXY(audioOut.wr, audioOut.modifier, tmpBuf, 1, toWriteBytes));
#if 0
    {
      int i;
      for (i=0; i<toWriteBytes; i++) {
	printf(" %02x", tmpBuf[i]);
      }
      printf("\n");
    }
#endif
    
    bytesLeft -= toWriteBytes;
    sourceIndex += toWriteBytes;
  }
#if 0
  printf("\n");
#endif
  
  if (!(PERIP(SPI1_STATUS) & (SPI_ST_TXFIFOFULL|SPI_ST_TXFULL|SPI_ST_TXRUNNING))) {
    Disable();
    IntSpi1C();
    Enable();
  }
  
  return bytes;
}

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