/**
   \file auoi2smv.c Audio I2S automatic output driver with vol.control for VSOS
   \author Henrik Herranen, VLSI Solution Oy
*/

#include <vo_stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <vstypes.h>
#include <vs1005g.h>
#include <timers.h>
#include <vsos.h>
#include <sysmemory.h>
#include <ringbuf.h>
#include <clockspeed.h>
#include "auxi2smv.h"

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

const struct I2SComb {
  u_int32 sampleRateAndHiBits;
  u_int16 pattern;
} __mem_y i2sComb[] = {
  { 48000L,  I2S_CF_FS48K32B},
  { 96000L,  I2S_CF_FS96K32B},
  {      0,               -1}
};


s_int16 myDebug = 0;



#if 1
  /* The default sample rate may be either 96 or 48 kHz. In any case,
     it must be at least as high as the highest sample rate of the
     playback system. */
#  define DEFAULT_SAMPLE_RATE 96000
#else
#  define DEFAULT_SAMPLE_RATE 48000
#endif

void SrcInterruptVector(void);
void SrcVolInterruptVector(void);
void I2SInterruptVector(void);

#define AUDIO_OUT_BUF_SIZE 16
__align s_int16 __mem_y audioOutBuf[AUDIO_OUT_BUF_SIZE]; /* Note: 32-bit data */

struct AudioOutI2S audioOutI2S;


ioresult AudioClose(void) {
  PERIP(I2S_LEFT_LSB) = 0;
  PERIP(I2S_LEFT) = 0;
  PERIP(I2S_RIGHT_LSB) = 0;
  PERIP(I2S_RIGHT) = 0;
  PERIP(INT_ENABLE0_HP) &= ~(INTF_I2S);
  PERIP(INT_ENABLE1_HP) &= ~(INTF1_SRC);
  PERIP(SRC_CF) = 0;
  PERIP(I2S_CF) = 0;
}

auto ioresult AudioSetRate(register s_int32 sampleRate) {
  struct I2SComb __mem_y *ic = i2sComb;
  s_int16 multiplier;
  sampleRate &= ~0x7F000000; /* Mask away fractional sample rate bits 30:24 */
  if (lockSampleRate) {
    sampleRate = lockSampleRate;
  }
  if (sampleRate >= 50000) {
    sampleRate = 96000;
    multiplier = 2;
  } else {
    sampleRate = 48000;
    multiplier = 1;
  }

  while (ic->sampleRateAndHiBits != sampleRate) {
    if (!ic->sampleRateAndHiBits)
      return S_ERROR;
    ic++;
  }

  Disable();
  PERIP(I2S_CF) = 0;
  audioOutI2S.ao.sampleRate = sampleRate;
  audioOutI2S.ao.flags |= AIN_32BIT;
  PERIP(SRC_CF) = SRC_CF_ENA +
    SRC_CF_FS*((u_int16)(clockSpeed.peripClkHz/(multiplier*(2*48000L)))-1);
  PERIP(I2S_CF) = ic->pattern | (I2S_CF_MASTER | I2S_CF_ENAMCK | I2S_CF_ENA | I2S_CF_32B | I2S_CF_MODE | I2S_CF_INTENA);
  Enable();

  return 0;
}



auto ioresult AudioSetVolume(register u_int16 volume) {
  ioresult res = S_OK;

  audioOutI2S.volVal = volume;
  if (volume == 256) {
    WriteIMem((void *)(0x20 + INTV_SRC), ReadIMem((void *)SrcInterruptVector));  
    audioOutI2S.volMult[0] = audioOutI2S.volMult[1] = 4096;
  } else {
    WriteIMem((void *)(0x20 + INTV_SRC), ReadIMem((void *)SrcVolInterruptVector));  
    if (volume <= 256-48) {
      audioOutI2S.volMult[0] = audioOutI2S.volMult[1] = 65535;
    } else if (volume >= 511) {
      audioOutI2S.volMult[0] = audioOutI2S.volMult[1] = 0;
    } else {
      static const u_int16 __mem_y volTab[12] =
	{61858, 58386, 55109, 52016, 49097, 46341,
	 43740, 41285, 38968, 36781, 34716, 32768};
      u_int16 tmpVol = volume-(256-48+1);
      audioOutI2S.volMult[0] = audioOutI2S.volMult[1] =
	volTab[tmpVol%12] >> (tmpVol/12);
    }
  }

  return res;
}


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

  switch (request) {
#if 0
  case IOCTL_START_FRAME:			
    break;
		
  case IOCTL_END_FRAME:
    break;
#endif

  case IOCTL_RESTART:
    Disable();
    /* Set to Perip Mode  I2S_DI    I2S_DO    I2S_BCK   I2S_FRM   I2S_12M */
    PERIP(GPIO1_MODE) |= ((1<<10) | (1<<11) | (1<<12) | (1<<13) | (1<<14));
    AudioSetRate(96000);
    audioFile.flags = __MASK_PRESENT | __MASK_OPEN | __MASK_CHARACTER_DEVICE |
      __MASK_FILE |
      __MASK_READABLE | __MASK_WRITABLE;
    PERIP(INT_ENABLE0_LP) &= ~(INTF_I2S);
    PERIP(INT_ENABLE0_HP) &= ~(INTF_I2S);
    PERIP(INT_ENABLE1_LP) &= ~(INTF1_SRC);
    PERIP(INT_ENABLE1_HP) &= ~(INTF1_SRC);
    WriteIMem((void *)(0x20 + INTV_I2S), ReadIMem((void *)I2SInterruptVector));  
    AudioSetRate(DEFAULT_SAMPLE_RATE);
    AudioSetVolume(0x100);
    audioOutI2S.ao.buf = audioOutBuf;
    audioOutI2S.ao.bufSize = AUDIO_OUT_BUF_SIZE;
    audioOutI2S.ao.modifier = MAKEMODF(AUDIO_OUT_BUF_SIZE);
    audioOutI2S.ao.wr = audioOutI2S.ao.rd =
      (__mem_y s_int16 *)(audioOutI2S.ao.buf);

    PERIP(INT_ENABLE0_HP) |= (INTF_I2S);
    PERIP(INT_ENABLE1_HP) |= (INTF1_SRC);

    Enable();
    break;

  case IOCTL_AUDIO_GET_ORATE:
    *((u_int32 *)argp) = audioOutI2S.ao.sampleRate;
    break;

  case IOCTL_AUDIO_SET_ORATE:
  case IOCTL_AUDIO_SET_RATE_AND_BITS:
    res = AudioSetRate(labs(*((s_int32 *)argp)));
    break;

  case IOCTL_AUDIO_GET_VOLUME:
    res = audioOutI2S.volVal;
    break;

  case IOCTL_AUDIO_SET_VOLUME:
    res = AudioSetVolume((int)argp);
    break;

  default:
    res = S_ERROR;
    break;
  }

  return res;
}


char *Identify(register __i0 void *self, char *buf, u_int16 bufSize) {
  strncpy(buf, "AUOI2SMV", bufSize);
  buf[bufSize-1] = '\0';
  return buf;
}

FILEOPS audioFileOps = {
  NULL, /* Open() */
  NULL, /* Close() */
  AudioIoctl, /* Ioctl() */
  CommonOkResultFunction, /* Read() */
  CommonOkResultFunction /* Write() */
};

SIMPLE_FILE audioFile = {
  __MASK_PRESENT|__MASK_OPEN|__MASK_WRITABLE|__MASK_READABLE|__MASK_FILE, /* Flags */
  Identify, /* Identify() */
  &audioFileOps,
  0, /* pos */
  0, /* ungetc_buffer */
  NULL, /* dev */
};
