/*
** Because of the massive number of interrupts the Aura "soundcard" needs,
** this driver does some *very* ugly things:
**  1) It requires both CIA-B timers.
**  2) It takes complete control over the level 6 interrupt. Make sure
**     nobody else is using it!
**
**  We use Timer A of CIA-B.
**
*/


#include <exec/exec.h>
#include <dos/dos.h>
#include <exec/interrupts.h>
#include <hardware/cia.h>
#include <resources/cia.h>

#include <proto/cia.h>
#include <proto/exec.h>
#include <proto/utility.h>
#include <proto/ahi_sub.h>

#include <devices/ahi.h>
#include <libraries/ahi_sub.h>

#include "aura.h"

#define EQ ==
#define dd ((struct aura *) AudioCtrl->ahiac_DriverData)

#define RECBUFFERSIZE  2048   // in samples

#define STARTA_OR  CIACRAF_START
#define STOPA_AND  CIACRAF_TODIN | CIACRAF_PBON | CIACRAF_OUTMODE | CIACRAF_SPMODE
#define STOPB_AND  CIACRBF_ALARM | CIACRBF_PBON | CIACRBF_OUTMODE

extern void KPrintF(char *fmt,...);

extern char __far _LibID[];
extern char __far _LibName[];

extern void __asm InstallUglyInterruptHack(register __a1 void (* )(), register __a2 struct aura *);
extern void __asm UninstallUglyInterruptHack(register __a3 struct aura *);
extern void __asm DummyFunc(void);
extern void __asm DummyInt(void);
extern void __asm PlayIntMono(void);
extern void __asm RecordIntMono(void);
extern void __asm PlayAndRecordIntMono(void);
extern void __asm PlayIntStereo(void);
extern void __asm RecordIntStereo(void);
extern void __asm PlayAndRecordIntStereo(void);
extern void __asm SoftFunc(void);

struct ExecBase *SysBase;

const static void *IntCodeTable[] =
{
  DummyInt,
  PlayIntMono,
  RecordIntMono,
  PlayAndRecordIntMono,

  DummyInt,
  PlayIntStereo,
  RecordIntStereo,
  PlayAndRecordIntStereo

};

#define FREQUENCIES 23
const static ULONG frequency[FREQUENCIES] =
{
  4410,         // CD/10
  4800,         // DAT/10
  5513,         // CD/8
  6000,         // DAT/8
  7350,         // CD/6
  8000,         // µ- and A-Law, DAT/6
  9600,         // DAT/5
	11025,        // CD/4
  12000,        // DAT/4
	14700,        // CD/3
  16000,        // DAT/3
	17640,        // CD/2.5
  18900,
	19200,        // DAT/2.5
  22050,        // CD/2
	24000,        // DAT/2
  27429,
	29400,        // CD/1.5
  32000,        // DAT/1.5
	33075,
  37800,
	44100,        // CD
  48000         // DAT
};

struct Library        *UtilityBase = NULL;
struct Library        *AHIsubBase = NULL;
struct CIA            *ciab = (struct CIA *)0xbfd000;
struct CIABase        *ciabbase = NULL;

BOOL InUse = FALSE;

int  __saveds __asm __UserLibInit (register __a6 struct Library *libbase)
{
  SysBase = *(struct ExecBase **)4;
  AHIsubBase=libbase;

  if( ! (UtilityBase=OpenLibrary("utility.library",37)))
  {
    Alert(AN_Unknown|AG_OpenLib|AO_UtilityLib);
    return 1;
  }

  if( ! (ciabbase = OpenResource(CIABNAME)))
  {
    return 1;
  }

  return 0;
}

void __saveds __asm __UserLibCleanup (register __a6 struct Library *libbase)
{
  if(UtilityBase)   { CloseLibrary(UtilityBase); UtilityBase=NULL; }
}

ULONG __asm __saveds intAHIsub_AllocAudio(
    register __a1 struct TagItem *tagList,
    register __a2 struct AHIAudioCtrlDrv *AudioCtrl )
{

  // Only one user at the time!

  Forbid();
  if(InUse)
  {
    Permit();
    return AHISF_ERROR;
  }
  InUse = TRUE;
  Permit();


  if(AudioCtrl->ahiac_DriverData=AllocVec(sizeof(struct aura),MEMF_PUBLIC|MEMF_CLEAR))
  {

    dd->a_CIAperiod=SysBase->ex_EClockFrequency*2/AudioCtrl->ahiac_MixFreq;
    AudioCtrl->ahiac_MixFreq=SysBase->ex_EClockFrequency/dd->a_CIAperiod/2;

    if(dd->a_AuraInt=AllocVec(sizeof(struct Interrupt),MEMF_PUBLIC|MEMF_ANY|MEMF_CLEAR))
    {
      dd->a_AuraInt->is_Node.ln_Type=NT_INTERRUPT;
      dd->a_AuraInt->is_Node.ln_Name=_LibName;
      dd->a_AuraInt->is_Data=AudioCtrl;
      // It will never be called anyway...
      dd->a_AuraInt->is_Code=(void (* )()) DummyFunc;

      // Allocate Timer A
      if( ! (AddICRVector((struct Library *) ciabbase,CIAICRB_TA,dd->a_AuraInt)))
      {
        dd->a_GotTimerA = TRUE;

        // Allocate Timer B
        if ( ! (AddICRVector((struct Library *)ciabbase,CIAICRB_TB,dd->a_AuraInt)))
        {
          dd->a_GotTimerB = TRUE;

          // Stop both timers, set 02 pulse count-down mode, set continuous mode
          Disable();
          ciab->ciacra &= STOPA_AND;
          ciab->ciacrb &= STOPB_AND;
          Enable();

          InstallUglyInterruptHack(DummyInt, (struct aura *)AudioCtrl->ahiac_DriverData);

          if(dd->a_SoftInt = AllocVec(
              sizeof(struct Interrupt),MEMF_PUBLIC|MEMF_ANY|MEMF_CLEAR))
          {
            dd->a_SoftInt->is_Node.ln_Type=NT_INTERRUPT;
            dd->a_SoftInt->is_Node.ln_Name=_LibName;
            dd->a_SoftInt->is_Data=AudioCtrl;
            dd->a_SoftInt->is_Code=(void (* )()) SoftFunc;

            if(TRUE)         // FIXIT: Check if hardware is present...
            {
              return AHISF_KNOWSTEREO|AHISF_CANRECORD|AHISF_MIXING|AHISF_TIMING;
            }
          }
        }
      }
    }
  }
  return AHISF_ERROR;
}

void __asm __saveds intAHIsub_FreeAudio(
    register __a2 struct AHIAudioCtrlDrv *AudioCtrl )
{
  if(AudioCtrl->ahiac_DriverData)
  {
    if(dd->a_GotTimerA && dd->a_GotTimerB)
    {
      UninstallUglyInterruptHack((struct aura *)AudioCtrl->ahiac_DriverData);

      if(dd->a_GotTimerA)
      {
        RemICRVector((struct Library *)ciabbase,CIAICRB_TA,dd->a_AuraInt);
      }
      if(dd->a_GotTimerB)
      {
        RemICRVector((struct Library *)ciabbase,CIAICRB_TB,dd->a_AuraInt);
      }
    }
    FreeVec(dd->a_SoftInt);
    FreeVec(dd->a_AuraInt);
    FreeVec(AudioCtrl->ahiac_DriverData);
    AudioCtrl->ahiac_DriverData=NULL;
  }

  InUse = FALSE;
}



ULONG __asm __saveds intAHIsub_Start(
    register __d0 ULONG Flags,
    register __a2 struct AHIAudioCtrlDrv *AudioCtrl )
{

  if(Flags & AHISF_PLAY)
  {
    AHIsub_Stop(AHISF_PLAY,AudioCtrl);

    if(!(dd->a_MixBuffer1=AllocVec(AudioCtrl->ahiac_BuffSize,MEMF_PUBLIC|MEMF_CLEAR|MEMF_ANY)))
      return AHIE_NOMEM;

    if(!(dd->a_MixBuffer2=AllocVec(AudioCtrl->ahiac_BuffSize,MEMF_PUBLIC|MEMF_CLEAR|MEMF_ANY)))
      return AHIE_NOMEM;

    dd->a_Status |= STATUS_PLAY;

    switch(AudioCtrl->ahiac_BuffType)
    {
      case AHIST_M16S:
        dd->a_Status &= ~STATUS_STEREO;
        break;
      case AHIST_S16S:
        dd->a_Status |= STATUS_STEREO;
        break;
      default:
        return AHIE_BADSAMPLETYPE;
    }
    InstallUglyInterruptHack((void (* )())IntCodeTable[dd->a_Status],
        (struct aura *)AudioCtrl->ahiac_DriverData);

/*
    KPrintF("DummyInt: 0x%08lx\n",DummyInt);
    KPrintF("PlayIntMono: 0x%08lx\n",PlayIntMono);
    KPrintF("RecordIntMono: 0x%08lx\n",RecordIntMono);
    KPrintF("PlayAndRecordIntMono: 0x%08lx\n",PlayAndRecordIntMono);

    KPrintF("DummyInt: 0x%08lx\n",DummyInt);
    KPrintF("PlayIntStereo: 0x%08lx\n",PlayIntStereo);
    KPrintF("RecordIntStereo: 0x%08lx\n",RecordIntStereo);
    KPrintF("PlayAndRecordIntStereo: 0x%08lx\n",PlayAndRecordIntStereo);

    KPrintF("Code: 0x%08lx\n",IntCodeTable[dd->a_Status]);
*/
// Start the interval timer - we will start the counter after
// writing the low, and high byte counter values

    ciab->ciatalo = dd->a_CIAperiod & 0xff;
    ciab->ciatahi = dd->a_CIAperiod>>8;

// Turn on start bit

    Disable();
    ciab->ciacra |= STARTA_OR;
    Enable();
  }

  if(Flags & AHISF_RECORD)
  {
    if(!(dd->a_RecBuffer1=AllocVec(RECBUFFERSIZE<<2,MEMF_ANY)))
      return AHIE_NOMEM;
    if(!(dd->a_RecBuffer2=AllocVec(RECBUFFERSIZE<<2,MEMF_ANY)))
      return AHIE_NOMEM;
    if(!(dd->a_RecMessage=AllocVec(sizeof(struct AHIRecordMessage),MEMF_ANY|MEMF_CLEAR)))
      return AHIE_NOMEM;

    dd->a_RecMessage->ahirm_Type=AHIST_S16S;

    dd->a_Status |= STATUS_RECORD;
    InstallUglyInterruptHack((void (* )())IntCodeTable[dd->a_Status],
        (struct aura *)AudioCtrl->ahiac_DriverData);

// Start the interval timer - we will start the counter after
// writing the low, and high byte counter values

    ciab->ciatalo = dd->a_CIAperiod & 0xff;
    ciab->ciatahi = dd->a_CIAperiod>>8;

// Turn on start bit

    Disable();
    ciab->ciacra |= STARTA_OR;
    Enable();
  }

  return AHIE_OK;
}

void __asm __saveds __interrupt intAHIsub_Update(
    register __d0 ULONG Flags,
    register __a2 struct AHIAudioCtrlDrv *AudioCtrl )
{
}


void __asm __saveds intAHIsub_Stop(
    register __d0 ULONG Flags,
    register __a2 struct AHIAudioCtrlDrv *AudioCtrl )
{
  if(Flags & AHISF_PLAY)
  {
    dd->a_Status &= ~STATUS_PLAY;
    InstallUglyInterruptHack((void (* )())IntCodeTable[dd->a_Status],
        (struct aura *)AudioCtrl->ahiac_DriverData);

    FreeVec(dd->a_MixBuffer1);
    dd->a_MixBuffer1=NULL;
    FreeVec(dd->a_MixBuffer2);
    dd->a_MixBuffer2=NULL;
  }

  if(Flags & AHISF_RECORD)
  {
    dd->a_Status &= ~STATUS_RECORD;
    InstallUglyInterruptHack((void (* )())IntCodeTable[dd->a_Status],
        (struct aura *)AudioCtrl->ahiac_DriverData);

    FreeVec(dd->a_RecBuffer1);
    dd->a_RecBuffer1=NULL;
    FreeVec(dd->a_RecBuffer2);
    dd->a_RecBuffer2=NULL;
    FreeVec(dd->a_RecMessage);
    dd->a_RecMessage=NULL;
  }

  if(!(dd->a_Status & (STATUS_PLAY | STATUS_RECORD)))
  {
    // Stop timer, set 02 pulse count-down mode, set continuous mode
    Disable();
    ciab->ciacra &= STOPA_AND;
    Enable();
  }
}



LONG __asm __saveds intAHIsub_GetAttr(
    register __d0 ULONG Attribute,
    register __d1 LONG Argument,
    register __d2 LONG Default,
    register __a1 struct TagItem *tagList,
    register __a2 struct AHIAudioCtrlDrv *AudioCtrl)
{
  switch(Attribute)
  {
    case AHIDB_Bits:
      return 12;
    case AHIDB_Frequencies:
      return FREQUENCIES;
    case AHIDB_Frequency: // Index->Frequency
      return (LONG) frequency[Argument];
    case AHIDB_Index: // Frequency->Index
    {
      int i;
      
      if(Argument<=frequency[0])
        return 0;
      if(Argument>=frequency[FREQUENCIES-1])
        return FREQUENCIES-1;
      for(i=1;i<FREQUENCIES;i++)
        if(frequency[i]>Argument)
        {
          if( (Argument-frequency[i-1]) < (frequency[i]-Argument) )
            return i-1;
          else
            return i;
        }
      return 0;  // Will not happen
    }
    case AHIDB_Author:
      return (LONG) "Martin 'Leviticus' Blom";
    case AHIDB_Copyright:
      return (LONG) "Public Domain";
    case AHIDB_Version:
      return (LONG) _LibID;
    case AHIDB_Annotation:
      return (LONG) "This driver plays some very nasty tricks with the\n"
                    "Level 6 interrupt, and will not work unless it can\n"
                    "get full control over both timer A and B.";
      break;
    case AHIDB_Record:
      return TRUE;
    case AHIDB_FullDuplex:
      return TRUE;
    case AHIDB_Realtime:
      return TRUE;
    case AHIDB_MaxRecordSamples:
      return RECBUFFERSIZE;
    case AHIDB_MinMonitorVolume:
    case AHIDB_MaxMonitorVolume:
      return 0x00000;
    case AHIDB_MinInputGain:
    case AHIDB_MaxInputGain:
    case AHIDB_MinOutputVolume:
    case AHIDB_MaxOutputVolume:
      return 0x10000;
    case AHIDB_Inputs:
    case AHIDB_Outputs:
      return 1;
    case AHIDB_Input:
    case AHIDB_Output:
      return (LONG) "Line";
    default:
      return Default;
  }
}

LONG __asm __saveds __interrupt intAHIsub_HardwareControl(
    register __d0 ULONG attribute,
    register __d1 LONG argument,
    register __a2 struct AHIAudioCtrlDrv *AudioCtrl )
{
  switch (attribute)
  {
    case AHIC_MonitorVolume_Query:
      return 0;
    case AHIC_InputGain_Query:
    case AHIC_OutputVolume_Query:
      return 0x10000;
    case AHIC_Input_Query:
      return 0;                       // There is only one input
    case AHIC_Output_Query:
      return 0;                       // There is only one output
  }
  return FALSE;
}
