/**
   \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 <audio.h>
#include <timers.h>
#include <vsos.h>
#include <sysmemory.h>
#include <ringbuf.h>
#include <aucommon.h>
#include <exec.h>
#include <clockspeed.h>
#include <imem.h>
#include "auodac.h"

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

/* These definitions are missing up to VSOS 3.57.
   In VS1005g DAC_MODE_3MUAD and DAC_MODE_96K live in bits 12 and 11,
   respectively, in register DAC_MTEST. */
#ifndef DAC_MODE

#define DAC_MODE         0xFED7
#define DAC_MODE_REG_BITS 13
 
#define DAC_MODE_3MUAD_B     12
#define DAC_MODE_96K_B       11
#define DAC_MODE_SRCADD_B     4
#define DAC_MODE_UNUSED_B     3
#define DAC_MODE_SRCCNT_B     0

#define DAC_MODE_3MUAD       (1<<12)
#define DAC_MODE_96K         (1<<11)
#define DAC_MODE_SRCADD      (1<< 4)
#define DAC_MODE_SRCADD_MASK (127<<DAC_MODE_SRCADD_B)
#define DAC_MODE_UNUSED      (1<< 3)
#define DAC_MODE_SRCCNT      (1<< 0)
#define DAC_MODE_SRCCNT_MASK (7<<DAC_MODE_SRCCNT_B)

#endif /* !DAC_MODE */

/* Bit DAC_MODE_96K lives in a different address in VS1005g and VS1005h,
   but in both registers they live in bit 11. */
int dacModeRegAddress = DAC_MODE;
int isVS1005g = 0;
void DacInterruptVector(void);
extern u_int32 auxi;

struct AudioOut audioOut = {
  NULL
};

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)
    return 0;
  return fr & ~3;
}

struct FractRate {
  int highLimit;
  u_int16 regVal;
} const __mem_y fractRate[] = {
  { 16, 0x0000}, /* 0/1 -> val 0.000000, limit 0.062500, 0b */
  { 34, 0x0017}, /* 1/8 -> val 0.125000, limit 0.133929, 0b0000001 */
  { 40, 0x0016}, /* 1/7 -> val 0.142857, limit 0.154762, 0b000001 */
  { 47, 0x0015}, /* 1/6 -> val 0.166667, limit 0.183333, 0b00001 */
  { 58, 0x0014}, /* 1/5 -> val 0.200000, limit 0.225000, 0b0001 */
  { 69, 0x0117}, /* 2/8 -> val 0.250000, limit 0.267857, 0b0010001 */
  { 79, 0x0096}, /* 2/7 -> val 0.285714, limit 0.309524, 0b001001 */
  { 91, 0x0095}, /* 2/6 -> val 0.333333, limit 0.354167, 0b01001 */
  { 99, 0x0257}, /* 3/8 -> val 0.375000, limit 0.387500, 0b0100101 */
  {106, 0x0054}, /* 2/5 -> val 0.400000, limit 0.414286, 0b0101 */
  {119, 0x0156}, /* 3/7 -> val 0.428571, limit 0.464286, 0b010101 */
  {137, 0x0155}, /* 3/6 -> val 0.500000, limit 0.535714, 0b10101 */
  {150, 0x02b6}, /* 4/7 -> val 0.571429, limit 0.585714, 0b101011 */
  {157, 0x00b4}, /* 3/5 -> val 0.600000, limit 0.612500, 0b1011 */
  {165, 0x05b7}, /* 5/8 -> val 0.625000, limit 0.645833, 0b1011011 */
  {177, 0x01b5}, /* 4/6 -> val 0.666667, limit 0.690476, 0b11011 */
  {187, 0x0376}, /* 5/7 -> val 0.714286, limit 0.732143, 0b110111 */
  {198, 0x0777}, /* 6/8 -> val 0.750000, limit 0.775000, 0b1110111 */
  {209, 0x00f4}, /* 4/5 -> val 0.800000, limit 0.816667, 0b1111 */
  {216, 0x01f5}, /* 5/6 -> val 0.833333, limit 0.845238, 0b11111 */
  {222, 0x03f6}, /* 6/7 -> val 0.857143, limit 0.866071, 0b111111 */
  {256, 0x07f7}, /* 7/8 -> val 0.875000, limit 0.937500, 0b1111111 */
};

auto void MySetRate(register __reg_c u_int32 rate, register s_int16 shRight) {
  u_int32 f;

  /* Fractional conversion:
     rate(30:24) -> rate( 6:0)
     rate(23: 0) -> rate(30:7) */
  rate = (((rate & ~0x7f000000)<< 7) | (rate >> 25)) >> shRight;

  /* The calculated value is DAC_SRC << 8.
     We fill use the 8 fractional bits to determine how to round the
     integer part (VS1005g), or how to get optimal fractional register
     values (VS1005h) */
  f = (u_int32)((131072.0*8.0*256.0) * rate / clockSpeed.peripClkHz + 0.5);

  /* If sample rate couldn't be represented accurately with DAC_SRC_L and
     DAC_SRC_H, then calculate best fractional additional value for
     register DAC_MODE. This makes it possible to playback exactly at
     8 kHz with VS1005h. VS1005g is missing the DAC_MODE register
     so this method doesn't work on VS1005g. */
  if (isVS1005g) {
    /* Rounding is the best thing we can do with VS1005g. */
    f += 128;
  } else {
    /* With VS1005h, find best fractional adder. */
    u_int16 regVal = 0;
    u_int16 ff = (u_int16)f&0xFF;

    if (ff >= 240) {
      /* Fractional part was so high that closest rounding is to next
	 integer value. */
      f += 256;
    } else {
      const struct FractRate __mem_y *fr = fractRate;
      while (ff > fr->highLimit) {
	fr++;
	}
      regVal = fr->regVal;
    }
    PERIP(DAC_MODE) = PERIP(DAC_MODE) &
      ~(DAC_MODE_SRCADD_MASK|DAC_MODE_SRCCNT_MASK) | regVal;
  }

  PERIP32(DAC_SRCL) = f>>8;
}


auto ioresult AudioSetRateAndBits(register s_int32 sampleRate,
				  register u_int16 hiBits) {
  u_int16 useHighRate = PERIP(dacModeRegAddress) & DAC_MODE_96K;
  u_int32 intSampleRate = sampleRate & ~0x7f000000;

  if (intSampleRate > 97500) {
    sampleRate = 97500;
  }
  audioOut.sampleRate = intSampleRate;
  Forbid();
  if (useHighRate) {
    if (intSampleRate > 95900) {
      MySetRate(sampleRate, 1);
    } else {
      PERIP(DAC_SRCH) = 0;
      PERIP(DAC_SRCL) = 0;
      PERIP(dacModeRegAddress) &= ~DAC_MODE_96K;
      MySetRate(sampleRate, 0);
    }
  } else {
    if (intSampleRate < 96000) {
      MySetRate(sampleRate, 0);
    } else {
      PERIP(DAC_SRCH) = 0;
      PERIP(DAC_SRCL) = 0;
      PERIP(dacModeRegAddress) |= DAC_MODE_96K;
      MySetRate(sampleRate, 1);
    }
  }
  Permit();


  /* If 32-bit status has changed */
  if ((hiBits ? AIN_32BIT : 0) ^ (audioOut.flags & AIN_32BIT)) {
    memsetY(audioOut.buf, 0, audioOut.bufSize);
    Disable();
    audioOut.wr = audioOut.rd = audioOut.buf;
    audioOut.flags ^= AIN_32BIT;
    Enable();
  }

  return 0;
}



ioresult AudioClose(void) {
  PERIP(DAC_LEFT) = 0;
  PERIP(DAC_LEFT_LSB) = 0;
  PERIP(DAC_RIGHT) = 0;
  PERIP(DAC_RIGHT_LSB) = 0;
  PERIP(INT_ENABLE0_HP) &= ~INTF_DAC;

  if (audioOut.bufSize) {
    FreeMemY(audioOut.buf, audioOut.bufSize);
    audioOut.bufSize = NULL;
  }
}


ioresult AllocateAudioBuffer(register struct AudioOut *a,
			     register u_int16 bufSize) {
  ioresult res = S_OK;
#if 1
  Disable();
#endif
#if 0
  printf("#1 bs=%d, a->bs=%d\n", bufSize, a->bufSize); Delay(10);
#endif
  if (a->bufSize) {
    FreeMemY(a->buf, a->bufSize);
  }
  if (bufSize == -1) {
#if 0
    printf("BIG BUFFER\n");
    Delay(10);
#endif
    a->buf = (u_int16 __mem_y *)0x7000;
    a->bufSize = 4096;
    memsetY(a->buf, 0, 4096);
    a->modifier = MAKEMODF(4096);
  } else if (!(a->buf = AllocMemY(bufSize, bufSize))) {
    static u_int16 __mem_y panicBuffer[4];
#if 0
    printf("NORMAL BUFFER FAIL %d\n", bufSize);
    Delay(10);
#endif
    a->buf = panicBuffer;
    a->bufSize = 0;
    a->modifier = MAKEMODF(4);
    res = S_ERROR;
  } else {
#if 0
    printf("NORMAL BUFFER OK %d\n", bufSize);
    Delay(10);
#endif
    memsetY(a->buf, 0, bufSize);
    a->bufSize = bufSize;
    a->modifier = MAKEMODF(bufSize);
  }
  a->wr = a->rd = (__mem_y s_int16 *)(a->buf);
#if 1
  Enable();
#endif
  return res;
}

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

#if 0
  printf("AUODAC Ioctl(%p, %d, %p)\n", self, request, argp);
#endif

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

  case IOCTL_RESTART:
    PERIP(INT_ENABLE0_LP) &= ~INTF_DAC;
    PERIP(INT_ENABLE0_HP) &= ~INTF_DAC;
    PERIP(ANA_CF1) |= ANA_CF1_DRV_ENA;
    if (ReadIMem(0xFFFF) == ROM_ID_VS1005G) {
      isVS1005g = 1;
      dacModeRegAddress = DAC_MTEST;
    }
    WriteIMem(0x20 + INTV_DAC, ReadIMem((u_int16)DacInterruptVector));  
    AudioSetRateAndBits(48000L, 0);
    if (AllocateAudioBuffer(&audioOut, ((s_int16)argp == -1) ? -1 : 512)) {
      res = S_ERROR;
    }
    break;

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

  case IOCTL_AUDIO_SET_ORATE:
    res = AudioSetRateAndBits(*((u_int32 *)argp), audioOut.flags & AIN_32BIT);
    break;

  case IOCTL_AUDIO_SET_RATE_AND_BITS:
    {
      s_int32 rate = *((u_int32 *)argp);
      s_int16 hiBits = (rate < 0);
      rate = labs(rate);
      res = AudioSetRateAndBits(rate, hiBits);
    }
    break;

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

  case IOCTL_AUDIO_SET_BITS:
    res = AudioSetRateAndBits(audioOut.sampleRate, (u_int16)argp > 16);
    break;

  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_VOLUME:
    return (volumeReg & 0xFF) + 256;
    break;

  case IOCTL_AUDIO_SET_VOLUME:
    {
      s_int16 t = (s_int16)argp - 256;
      if (t < 0) {
	t = 0;
      } else if (t > 255) {
	t = 255;
      }
      volumeReg = t * 0x0101;
      SetVolume();
    }
    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) {
    audioFile.flags = __MASK_PRESENT | __MASK_OPEN | __MASK_CHARACTER_DEVICE |
      __MASK_FILE |
      /*__MASK_READABLE |*/ __MASK_WRITABLE;
    PERIP(INT_ENABLE0_HP) |= INTF_DAC;
  } else {
    audioFile.flags = __MASK_PRESENT | __MASK_OPEN | __MASK_CHARACTER_DEVICE |
      __MASK_FILE;
    PERIP(INT_ENABLE0_HP) &= ~INTF_DAC;
  }

  return res;
}



u_int16 AudioWrite(register __i0 VO_FILE *self, u_int16 *buf,
		   u_int16 sourceIndex, u_int16 bytes) {
  u_int16 left = bytes>>1;
  if (sourceIndex || (bytes&((audioOut.flags & AIN_32BIT) ? 7 : 3))) {
    return 0;
  }
  while (left) {
    u_int16 toWrite = MIN(left, 16);
    while (AudioOutFree() < toWrite) {
      Delay(1);
    }
    audioOut.wr =
      RING_XY_D(ringcpyXY(audioOut.wr, audioOut.modifier, buf, 1, toWrite));
    buf += toWrite;
    left -= toWrite;
  }
  return bytes;
}

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

FILEOPS audioFileOps = {
  NULL, /* Open() */
  NULL, /* Close() */
  AudioIoctl, /* Ioctl() */
  CommonOkResultFunction, /* Read() */
  AudioWrite /* 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 */
};
