/******************************************************************************
 *     XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS"
 *     SOLELY FOR USE IN DEVELOPING PROGRAMS AND SOLUTIONS FOR
 *     XILINX DEVICES.  BY PROVIDING THIS DESIGN, CODE, OR INFORMATION
 *     AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, APPLICATION
 *     OR STANDARD, XILINX IS MAKING NO REPRESENTATION THAT THIS
 *     IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT,
 *     AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE
 *     FOR YOUR IMPLEMENTATION.  XILINX EXPRESSLY DISCLAIMS ANY
 *     WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE
 *     IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR
 *     REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF
 *     INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *     FOR A PARTICULAR PURPOSE.
 *     
 *     (c) Copyright 2004 Xilinx, Inc.
 *     All rights reserved.
 *****************************************************************************/

/******************************************************************************
 * test_ac97.c
 * Records a buffer of sound from either the Line-In or Mic-In ports of the 
 * ML300 to the AC97 controller and plays it back through the Line-Out port 
 * using the AC97.
 * Author: bf
 *****************************************************************************/

#include <stdio.h>
#include <math.h>

#include "xio.h"
#include "xuartns550_l.h"
#include "sleep.h"
#include "xparameters.h"

#define UART_BASEADDR XPAR_OPB_UART16550_0_BASEADDR
#define UART_CLOCK    XPAR_XUARTNS550_CLOCK_HZ

#if !SIM
#define UART_BAUDRATE 9600                      /* real hardware */
#else
#define UART_BAUDRATE (UART_CLOCK / 16 / 3)     /* simulation */
#endif

/* local prototypes */
volatile int data_in_buf_len;
int data_in_buf[19];
volatile int dummy;

volatile int cur_sound_len;
volatile unsigned char *cur_sound_ptr;

#define no_sounds 4
#define wave_sound 3
#define eat_apple_sound 0
#define dying_sound 1
#define next_level_sound 2

#include "sound_def.h"

unsigned int sound_ptr;
int c;

#define MY_AC97_BASEADDR         0xA6000000
#define AC97_InFIFO              MY_AC97_BASEADDR
#define AC97_OutFIFO             MY_AC97_BASEADDR + 0x4
#define AC97_FIFO_Status         MY_AC97_BASEADDR + 0x8
#define AC97_Control             MY_AC97_BASEADDR + 0xC
#define AC97_RegAddr             MY_AC97_BASEADDR + 0x10
#define AC97_RegRead             MY_AC97_BASEADDR + 0x14
#define AC97_RegWrite            MY_AC97_BASEADDR + 0x18
#define AC97_InFIFO_Full         0x01
#define AC97_InFIFO_Half_Full    0x02
#define AC97_OutFIFO_Full        0x04
#define AC97_OutFIFO_Empty       0x08
#define AC97_Reg_Access_Finished 0x10
#define AC97_CODEC_RDY           0x20
#define AC97_REG_ACCESS          0x40
#define AC97_Enable_In_Intr      0x01

// AC97 CODEC Registers
#define AC97_Reset              0x00
#define AC97_MasterVol          0x02
#define AC97_HeadphoneVol       0x04
#define AC97_MasterVolMono      0x06
#define AC97_Reserved0x08       0x08
#define AC97_PCBeepVol          0x0A
#define AC97_PhoneInVol         0x0C
#define AC97_MicVol             0x0E
#define AC97_LineInVol          0x10
#define AC97_CDVol              0x12
#define AC97_VideoVol           0x14
#define AC97_AuxVol             0x16
#define AC97_PCMOutVol          0x18
#define AC97_RecordSelect       0x1A
#define AC97_RecordGain         0x1C
#define AC97_Reserved0x1E       0x1E
#define AC97_GeneralPurpose     0x20
#define AC97_3DControl          0x22
#define AC97_PowerDown          0x26
#define AC97_ExtendedAudioID    0x28
#define AC97_ExtendedAudioStat  0x2A
#define AC97_PCM_DAC_Rate0      0x78
#define AC97_PCM_DAC_Rate1      0x7A
#define AC97_Reserved0x34       0x34
#define AC97_JackSense          0x72
#define AC97_SerialConfig       0x74
#define AC97_MiscControlBits    0x76
#define AC97_VendorID1          0x7C
#define AC97_VendorID2          0x7E

// Volume Constants
#define AC97_VolMute 0x8000
#define AC97_VolMax  0x0000
#define AC97_VolMin  0x3F3F
#define AC97_VolMid  0x1010

void WriteAC97Reg( int reg_addr, int value) {
  XIo_Out32 (AC97_RegWrite, value);
  XIo_Out32 (AC97_RegAddr, reg_addr);
//  while ((XIo_In32(AC97_FIFO_Status) & AC97_Reg_Access_Finished) == 0);
  usleep (10000);
}

int ReadAC97Reg( int reg_addr) {
  XIo_Out32 (AC97_RegAddr, reg_addr | 0x80);
//  while ((XIo_In32(AC97_FIFO_Status) & AC97_Reg_Access_Finished) == 0);
  usleep (10000);
  return XIo_In32(AC97_RegRead);
}

void init_sound() {
  int j;

  // reset all reg's to known states (cause MUTE to all)
  WriteAC97Reg(AC97_Reset,0);
  usleep (1000);
  while (!(XIo_In32(AC97_FIFO_Status) & AC97_CODEC_RDY)) {};

  xil_printf ("PowerDown Reg State (Should be 0x000F) = %x \n\r", ReadAC97Reg (AC97_PowerDown));

  // turn on external amp
  xil_printf ("Turning on External Power Amp \n\r");
  WriteAC97Reg(AC97_JackSense,0x3F00); // set Jack Sense Pins

  // powerdown DAC and ADC temporarily
  WriteAC97Reg(AC97_PowerDown,0x0300);
  usleep (1000000);

  // clear screen
//  for (j = 0x0fe00000; j < 0x0fffff00; j = j +4)
//     XIo_Out32 (j, 0);

  xil_printf ("\n\r\n\rSound loopback. Do you want to loopback Line in or Mic (L,M)\n\r");
  do
  {
    c = getchar();
  }  while (((char) c != 'L') & ((char) c != 'l') & ((char) c != 'M') & ((char) c != 'm'));

  WriteAC97Reg(AC97_ExtendedAudioStat,1);  // Enabling VRA mode
  WriteAC97Reg(AC97_PCM_DAC_Rate1, 48000); // sampling rate PLAY
  WriteAC97Reg(AC97_PCM_DAC_Rate0, 48000); // sampling rate rec

//  WriteAC97Reg(AC97_ExtendedAudioStat,1);      // Enabling VRA mode
//  WriteAC97Reg(AC97_PCM_DAC_Rate1, 10000); // sampling rate PLAY
//  WriteAC97Reg(AC97_PCM_DAC_Rate0, 10000); // sampling rate rec

  xil_printf ("DAC sample rate= %d hz\n\r", ReadAC97Reg (AC97_PCM_DAC_Rate1));
  xil_printf ("ADC sample rate= %d hz\n\r", ReadAC97Reg (AC97_PCM_DAC_Rate0));
  xil_printf ("vendor id 1= %x\n\r", ReadAC97Reg (AC97_VendorID1));
  xil_printf ("vendor id 2= %x\n\r", ReadAC97Reg (AC97_VendorID2));

  // Turn back on power to ADC and DAC
  WriteAC97Reg(AC97_PowerDown,0x0000);
  usleep (100000);
  xil_printf ("PowerDown Reg State (Should be 0x000F) = %x \n\r", ReadAC97Reg (AC97_PowerDown));

  xil_printf ("Misc Control Bits = %x\n\r", ReadAC97Reg (AC97_MiscControlBits));


  XIo_Out32(AC97_Control, 0x00000003); // clear FIFOs

  XIo_Out32(AC97_InFIFO, 0);
  XIo_Out32(AC97_InFIFO, 0);
  XIo_Out32(AC97_InFIFO, 0);
  XIo_Out32(AC97_InFIFO, 0);
  XIo_Out32(AC97_InFIFO, 0);
  XIo_Out32(AC97_InFIFO, 0);
  XIo_Out32(AC97_InFIFO, 0);
  XIo_Out32(AC97_InFIFO, 0);
  XIo_Out32(AC97_InFIFO, 0);
  XIo_Out32(AC97_InFIFO, 0);
  XIo_Out32(AC97_InFIFO, 0);
  XIo_Out32(AC97_InFIFO, 0);
  XIo_Out32(AC97_InFIFO, 0);
  XIo_Out32(AC97_InFIFO, 0);
  XIo_Out32(AC97_InFIFO, 0);
  XIo_Out32(AC97_InFIFO, 0);

  // turn off digital loopback
  WriteAC97Reg(AC97_GeneralPurpose,0x0000);
  xil_printf ("General Purpose reg state = %x\n\r", ReadAC97Reg (AC97_GeneralPurpose));

  WriteAC97Reg(AC97_SerialConfig,0x7000);
  xil_printf ("config reg state = %x\n\r", ReadAC97Reg (AC97_SerialConfig));

//  WriteAC97Reg(AC97_MasterVol,     0x0F0F);
//  WriteAC97Reg(AC97_HeadphoneVol,  0x0808);
//  WriteAC97Reg(AC97_MasterVolMono, 0x000F);
  WriteAC97Reg(AC97_MasterVol,     AC97_VolMax);
  WriteAC97Reg(AC97_HeadphoneVol,  AC97_VolMax);
  WriteAC97Reg(AC97_MasterVolMono, AC97_VolMax);
  WriteAC97Reg(AC97_PCBeepVol,     AC97_VolMute);
  WriteAC97Reg(AC97_PhoneInVol,    AC97_VolMute);
  WriteAC97Reg(AC97_CDVol,         AC97_VolMute);
  WriteAC97Reg(AC97_VideoVol,      AC97_VolMute);
  WriteAC97Reg(AC97_AuxVol,        AC97_VolMute);
//  WriteAC97Reg(AC97_PCMOutVol,     0x0F0F);
  WriteAC97Reg(AC97_PCMOutVol,     AC97_VolMax);

  if (((char) c == 'M') | ((char) c == 'm')) {
    WriteAC97Reg(AC97_RecordSelect,  0x0000);
    WriteAC97Reg(AC97_MicVol,        0x0040);
    WriteAC97Reg(AC97_LineInVol,     AC97_VolMute);
  } else {
    WriteAC97Reg(AC97_RecordSelect,  0x0404);
    WriteAC97Reg(AC97_MicVol,        AC97_VolMute);
    WriteAC97Reg(AC97_LineInVol,     AC97_VolMax);
  }
  // setup record gain for +1.5db
  WriteAC97Reg(AC97_RecordGain,0x0101);

}

void rec_sound() {
  //int i;
  int j;
  xil_printf ("Record Start\n\r");
  sound_ptr = 0x0fe00000;
  XIo_Out32(AC97_Control, 0x00000003); // clear FIFOs
/*
  for (i = 0x0fe00000; i < 0x0fffff00; i = i + 8) {
    switch ((i/8) % 22) {
       case 0: j = 0 ; break;
       case 1: j = 9232; break;
       case 2: j = 17715; break;
       case 3: j = 24764; break;
       case 4: j = 29806; break;
       case 5: j = 32434; break;
       case 6: j = 32435; break;
       case 7: j = 29808; break;
       case 8: j = 24766; break;
       case 9: j = 17718; break;
       case 10: j = 9234; break;
       case 11: j = 3; break;
       case 12: j = -9229; break;
       case 13: j = -17713; break;
       case 14: j = -24762; break;
       case 15: j = -29805; break;
       case 16: j = -32434; break;
       case 17: j = -32435; break;
       case 18: j = -29809; break;
       case 19: j = -24768; break;
       case 20: j = -17720; break;
       case 21: j = -9237; break;
     }
//    switch ((i/8) % 2) {
//       case 0: j = 0x5555 ; break;
//       case 1: j = 0x7777 ; break;
//     }
     j = 0;
     XIo_Out32 (i, j);
     XIo_Out32 (i+4, j);
   }
*/

  while (sound_ptr < 0x0fffff00) {
      if (XIo_In32(AC97_FIFO_Status) & 0x0080) xil_printf ("record overrun\n\r");
      while (XIo_In32(AC97_FIFO_Status) & AC97_OutFIFO_Empty) {};
      j = XIo_In32(AC97_OutFIFO);
      XIo_Out32(sound_ptr, j);
      sound_ptr = sound_ptr + 4;
      while (XIo_In32(AC97_FIFO_Status) & AC97_OutFIFO_Empty) {};
      j = XIo_In32(AC97_OutFIFO);
      XIo_Out32(sound_ptr, j);
      sound_ptr = sound_ptr + 4;
  }

}

void play_sound(){
  int i;
  int j;
  xil_printf ("Play Start\n\r");
  sound_ptr = 0x0fe00000;
  WriteAC97Reg(AC97_RecordGain,  AC97_VolMute);
  WriteAC97Reg(AC97_PowerDown,   0x0100);
  WriteAC97Reg(AC97_LineInVol,   AC97_VolMute);
  i = 0;
  do {
    XIo_Out32(AC97_Control, 0x00000003); // clear FIFOs
    XIo_Out32(AC97_InFIFO, 0);
    XIo_Out32(AC97_InFIFO, 0);
    XIo_Out32(AC97_InFIFO, 0);
    XIo_Out32(AC97_InFIFO, 0);
    XIo_Out32(AC97_InFIFO, 0);
    XIo_Out32(AC97_InFIFO, 0);
    XIo_Out32(AC97_InFIFO, 0);
    XIo_Out32(AC97_InFIFO, 0);
  } while (XIo_In32(AC97_FIFO_Status) & 0x0040);

  while (1) {
      if (XIo_In32(AC97_FIFO_Status) & 0x0040) xil_printf ("play underrun\n\r");
      while (XIo_In32(AC97_FIFO_Status) & AC97_InFIFO_Half_Full) {};
//      while ((XIo_In32(AC97_FIFO_Status) & AC97_InFIFO_Empty) == 0) {};


    switch ((i) % 22) {
       case 0: j = 0 ; break;
       case 1: j = 9232; break;
       case 2: j = 17715; break;
       case 3: j = 24764; break;
       case 4: j = 29806; break;
       case 5: j = 32434; break;
       case 6: j = 32435; break;
       case 7: j = 29808; break;
       case 8: j = 24766; break;
       case 9: j = 17718; break;
       case 10: j = 9234; break;
       case 11: j = 3; break;
       case 12: j = -9229; break;
       case 13: j = -17713; break;
       case 14: j = -24762; break;
       case 15: j = -29805; break;
       case 16: j = -32434; break;
       case 17: j = -32435; break;
       case 18: j = -29809; break;
       case 19: j = -24768; break;
       case 20: j = -17720; break;
       case 21: j = -9237; break;
     }
   i = i+1;

//      j = XIo_In32(sound_ptr);
//      sound_ptr = sound_ptr + 4;
      XIo_Out32(AC97_InFIFO, j);
//      while (XIo_In32(AC97_FIFO_Status) & AC97_InFIFO_Half_Full) {};
//      j = XIo_In32(sound_ptr);
//      sound_ptr = sound_ptr + 4;
      XIo_Out32(AC97_InFIFO, j);
//      if (sound_ptr > 0x0fffff00) sound_ptr = 0x0fe00000;
  }
}

int main ()
{
  XUartNs550_SetBaud(UART_BASEADDR, UART_CLOCK, UART_BAUDRATE);
  XUartNs550_mSetLineControlReg(UART_BASEADDR, XUN_LCR_8_DATA_BITS);
  init_sound();
//  rec_sound();
  while (1) {
    play_sound();
  }
  return 0;
}
