//
//     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.
//

/*
 *  Simple slideshow demo with read/write to Compact Flash
 *
 *    Reads images from: "a:\\image01.bmp", "a:\\image02.bmp", "a:\\image03.bmp", ...
 *    Stores images starting at IMAGE_BASEADDR, which by default is 0x12000000
 *    Reads sound from: "a:\\sound.wav"
 *    Stores sound starting at SOUND_BASEADDR, which by default is 0x10000000
 *
*/

#include <xparameters.h>
#include <sysace_stdio.h>
#include "xuartns550_l.h"
#include "xio.h"
#include "sleep.h"
#include "util.h"
#include "xromlcd.h"

// UART Constants
#define UART_BASEADDR   XPAR_OPB_UART16550_0_BASEADDR
#define UART_CLOCK      XPAR_XUARTNS550_CLOCK_HZ
#define UART_BAUDRATE   9600

// AC97 CODEC Constants
#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      0x2C
#define AC97_PCM_DAC_Rate1      0x32
#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  0x1F1F
#define AC97_VolMid  0x1010

// GPIO Addresses
#define GPIO_DATA    0x90000000
#define GPIO_CONTROL 0x90000004

// Demo Parameters
#define SOUND_BASEADDR  0x10000000
#define IMAGE_BASEADDR  0x12000000
#define IMAGE_MAXADDR   0x14000000

/* structs for bitmaps */
typedef struct {
   unsigned short int type;                 /* Magic identifier            */
   unsigned int size;                       /* File size in bytes          */
   unsigned short int reserved1, reserved2;
   unsigned int offset;                     /* Offset to image data, bytes */
} HEADER;

typedef struct {
   unsigned int size;               /* Header size in bytes      */
   int width,height;                /* Width and height of image */
   unsigned short int planes;       /* Number of color planes    */
   unsigned short int bits;         /* Bits per pixel            */
   unsigned int compression;        /* Compression type          */
   unsigned int imagesize;          /* Image size in bytes       */
   int xresolution,yresolution;     /* Pixels per meter          */
   unsigned int ncolors;            /* Number of colors          */
   unsigned int importantcolors;    /* Important colors          */
} INFOHEADER;

/* structs for wave files */
typedef struct {
   unsigned int chunkID;
   unsigned int chunkSize;
   unsigned int format;
} RIFFCHUNK;

typedef struct {
   unsigned int subchunkID;
   unsigned int subchunkSize;
   unsigned short int audioFormat;
   unsigned short int numChannels;
   unsigned int sampleRate;
   unsigned int byteRate;
   unsigned short int blockAlign;
   unsigned short int bitsPerSample;
   unsigned short int extraParamSize;
} FMTCHUNK;

typedef struct {
   unsigned int subchunkID;
   unsigned int subchunkSize;
} DATACHUNK;

/* reads two bytes from file */
static unsigned short ReadUShort(SYSACE_FILE *fptr)
{
  unsigned char b0, b1;
  int numread;
  char readBuffer[2];

  numread = sysace_fread(readBuffer, 1, 2, fptr);

  b0 = readBuffer[0];
  b1 = readBuffer[1];

  return ((b1 << 8) | b0);
}

/* reads four bytes from file in little endian order */
static unsigned int ReadUIntLil(SYSACE_FILE *fptr)
{
  unsigned char b0, b1, b2, b3;
  int numread;
  char readBuffer[4];

  numread = sysace_fread(readBuffer, 1, 4, fptr);

  b0 = readBuffer[0];
  b1 = readBuffer[1];
  b2 = readBuffer[2];
  b3 = readBuffer[3];

  return ((((((b3 << 8) | b2) << 8) | b1) << 8) | b0);
}

/* reads four bytes from file in big endian order */
static unsigned int ReadUIntBig(SYSACE_FILE *fptr)
{
  unsigned char b0, b1, b2, b3;
  int numread;
  char readBuffer[4];

  numread = sysace_fread(readBuffer, 1, 4, fptr);

  b0 = readBuffer[0];
  b1 = readBuffer[1];
  b2 = readBuffer[2];
  b3 = readBuffer[3];

  return ((((((b0 << 8) | b1) << 8) | b2) << 8) | b3);
}

/* opens and reads from CF a 640x480 bitmap file, placing it in memory at baseaddr in a format for the tft */
int read_image(char FileName[], int baseaddr)
{
  unsigned char readBuffer[1920];
  SYSACE_FILE *infile;
  int i, j, numread, temp, writeaddr;
  HEADER header;
  INFOHEADER infoheader;

  infile = sysace_fopen(FileName, "r");

  if(infile) {
    xil_printf("Reading file : %s\n\r", FileName);
    XromLCDPrintString ("Loading Slide:  ", &FileName[3]);

    /* Read and check the header */
    header.type = ReadUShort(infile);
    header.size = ReadUIntLil(infile);
    header.reserved1 = ReadUShort(infile);
    header.reserved2 = ReadUShort(infile);
    header.offset = ReadUIntLil(infile);

    infoheader.size = ReadUIntLil(infile);
    infoheader.width = ReadUIntLil(infile);
    infoheader.height = ReadUIntLil(infile);

    infoheader.planes = ReadUShort(infile);
    infoheader.bits = ReadUShort(infile);

    infoheader.compression = ReadUIntLil(infile);
    infoheader.imagesize = ReadUIntLil(infile);
    infoheader.xresolution = ReadUIntLil(infile);
    infoheader.yresolution = ReadUIntLil(infile);
    infoheader.ncolors = ReadUIntLil(infile);
    infoheader.importantcolors = ReadUIntLil(infile);

    /*
    xil_printf("ID is: %d, should be %d\n\r",header.type,'M'*256+'B');
    xil_printf("File size is %d bytes\n\r",header.size);
    xil_printf("Offset to image data is %d bytes\n\r",header.offset);

    xil_printf("Image size = %d x %d\n\r",infoheader.width,infoheader.height);
    xil_printf("Number of color planes is %d\n\r",infoheader.planes);
    xil_printf("Bits per pixel is %d\n\r",infoheader.bits);
    xil_printf("Compression type is %d\n\r",infoheader.compression);
    xil_printf("Number of colors is %d\n\r",infoheader.ncolors);
    xil_printf("Number of required colors is %d\n\r",infoheader.importantcolors);
    */

    /* Process the data */
    for (j=infoheader.height-1;j>=0;j--) {
      numread = sysace_fread(readBuffer, 1, 1920, infile);
      for (i=0;i<infoheader.width;i++) {
	temp = ((((readBuffer[i*3+2] << 8) | readBuffer[(i*3)+1]) << 8) | readBuffer[(i*3)]);
	writeaddr = baseaddr+(j*1024+i)*4;
	XIo_Out32(writeaddr, temp);
      }
    }
    sysace_fclose(infile);
    return 1;
  } else {
    return 0;
  }
}

/* reads a series of images from CF: "image01.bmp", "image02.bmp", "image03.bmp", ... */
/* stores each in memory on consecutive 2MB boundaries starting at IMAGE_BASEADDR up to IMAGE_MAXADDR */
int get_images()
{
  char file[] = "a:\\image01.bmp";
  int baseaddr = IMAGE_BASEADDR;
  int count = 0;

  while(baseaddr < IMAGE_MAXADDR && read_image(file, baseaddr)) {
    if (file[9] == '9') {
      file[8]++;
      file[9] -= 9;
    } else {
      file[9]++;
    }
    baseaddr += 0x200000;
    count++;
  }

  return count;
}

/* opens and reads from CF a wave file named "sound.wav" */
/* file is assumed to have only RIFF, FMT, and DATA chunks */
void read_sound()
{
  unsigned char readBuffer[2048];
  SYSACE_FILE *infile;
  char FileName[] = "a:\\sound.wav";
  int i, numread, temp;
  int writeaddr = SOUND_BASEADDR;
  RIFFCHUNK riffchunk;
  FMTCHUNK fmtchunk;
  DATACHUNK datachunk;

  infile = sysace_fopen(FileName, "r");

  if(infile) {
    xil_printf("Reading file : %s\n\r", FileName);
    XromLCDPrintString ("Loading Audio:  ", "                ");
    XromLCDPrintString ("Loading Audio:  ", &FileName[3]);

    /* Read the RIFF chunk */
    riffchunk.chunkID = ReadUIntBig(infile);
    riffchunk.chunkSize = ReadUIntLil(infile);
    riffchunk.format = ReadUIntBig(infile);

    /* Read the FMT chunk */
    fmtchunk.subchunkID = ReadUIntBig(infile);
    fmtchunk.subchunkSize = ReadUIntLil(infile);
    fmtchunk.audioFormat = ReadUShort(infile);
    fmtchunk.numChannels = ReadUShort(infile);
    fmtchunk.sampleRate = ReadUIntLil(infile);
    fmtchunk.byteRate = ReadUIntLil(infile);
    fmtchunk.blockAlign = ReadUShort(infile);
    fmtchunk.bitsPerSample = ReadUShort(infile);

    /* Read the DATA chunk */
    datachunk.subchunkID = ReadUIntBig(infile);
    datachunk.subchunkSize = ReadUIntLil(infile);

    /*
    xil_printf("ChunkID is: %x, should be %x\n\r",riffchunk.chunkID,0x52494646);
    xil_printf("ChunkSize is %d bytes\n\r",riffchunk.chunkSize);
    xil_printf("Format is: %x, should be %x\n\r",riffchunk.format,0x57415645);
    xil_printf("SubchunkID is: %x, should be %x\n\r",fmtchunk.subchunkID,0x666d7420);
    xil_printf("SubchunkSize is %d bytes\n\r",fmtchunk.subchunkSize);
    xil_printf("SubchunkID is: %x, should be %x\n\r",datachunk.subchunkID,0x64617461);
    xil_printf("SubchunkSize is %d bytes\n\r",datachunk.subchunkSize);
    */

    XIo_Out32(writeaddr, datachunk.subchunkSize);
    writeaddr += 4;

    do {
      numread = sysace_fread(readBuffer, 1, 2048, infile);
      for (i=0;i<numread;i+=4) {
	temp = ((((((readBuffer[i+3] << 8) | readBuffer[i+2]) << 8) | readBuffer[i+1]) << 8) | readBuffer[i]);
        XIo_Out32(writeaddr, temp);
        writeaddr+=4;
      }
    } while(numread);

    sysace_fclose(infile);
  }
}

/* write an AC97 register */
void WriteAC97Reg(int reg_addr, int value) {
  XIo_Out32 (AC97_RegWrite, value);
  XIo_Out32 (AC97_RegAddr, reg_addr);
  usleep (10000);
}

/* read an AC97 register */
int ReadAC97Reg(int reg_addr) {
  XIo_Out32 (AC97_RegAddr, reg_addr | 0x80);
  usleep (10000);
  return XIo_In32(AC97_RegRead);
}

/* initialize AC97 */
void init_sound() {

  // 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)) {};

  // turn on external amp
  WriteAC97Reg(AC97_JackSense,0x3F00); // set Jack Sense Pins

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

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

  // Turn back on power to ADC and DAC
  WriteAC97Reg(AC97_PowerDown,0x0000);
  usleep (100000);

  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);

  // bypass 3D
  // turn off digital loopback
  WriteAC97Reg(AC97_GeneralPurpose,0x8000);

  WriteAC97Reg(AC97_SerialConfig,0x7000);

  WriteAC97Reg(AC97_MasterVol,     AC97_VolMin);
  WriteAC97Reg(AC97_HeadphoneVol,  AC97_VolMin);
  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,     AC97_VolMax);

  WriteAC97Reg(AC97_RecordSelect,  0x0404);
  WriteAC97Reg(AC97_MicVol,        AC97_VolMute);
  WriteAC97Reg(AC97_LineInVol,     AC97_VolMute);

  // setup record gain for mute
  WriteAC97Reg(AC97_RecordGain, AC97_VolMute);
}

/* infinite loop that fills fifos and handles button pushing */
/* sound file is assumed to be 44.1 kH, 16 bit/sample, stereo */
void play_sound(Xuint32 baseaddr, Xuint32 count) {
  unsigned int i, j;
  unsigned int data, length, temp;
  unsigned int tftptr, volume;
  Xboolean button_n, button_e, button_s, button_w, button_c;
  Xboolean past_button_n, past_button_e, past_button_s, past_button_w, past_button_c;
  Xboolean paused = 0;

  length = XIo_In32(baseaddr);

  WriteAC97Reg(AC97_RecordGain,  AC97_VolMute);
  WriteAC97Reg(AC97_PowerDown,   0x0100);
  WriteAC97Reg(AC97_LineInVol,   AC97_VolMute);

  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);

  tftptr = IMAGE_BASEADDR;
  volume = AC97_VolMin;
  past_button_n = 0;
  past_button_e = 0;
  past_button_s = 0;
  past_button_w = 0;
  past_button_c = 0;
  i = 4;
  j = 0;
  while (1) {
    while (XIo_In32(AC97_FIFO_Status) & AC97_InFIFO_Half_Full) {};

    // fill fifo
    data = XIo_In32(baseaddr + i);
    XIo_Out32(AC97_InFIFO, (data >> 16) & 0x0000FFFF);
    XIo_Out32(AC97_InFIFO, data & 0x0000FFFF);
    i += 4;

    data = XIo_In32(baseaddr + i);
    XIo_Out32(AC97_InFIFO, (data >> 16) & 0x0000FFFF);
    XIo_Out32(AC97_InFIFO, data & 0x0000FFFF);
    i += 4;

    data = XIo_In32(baseaddr + i);
    XIo_Out32(AC97_InFIFO, (data >> 16) & 0x0000FFFF);
    XIo_Out32(AC97_InFIFO, data & 0x0000FFFF);
    i += 4;

    data = XIo_In32(baseaddr + i);
    XIo_Out32(AC97_InFIFO, (data >> 16) & 0x0000FFFF);
    XIo_Out32(AC97_InFIFO, data & 0x0000FFFF);
    i += 4;
    j += 4;
    if (i > length) i = 4;

    // auto advance tft
    if ((j % 524288 == 0) && (!paused)) {
      if (tftptr == IMAGE_BASEADDR+0x200000*(count-1)) tftptr = IMAGE_BASEADDR;
	else tftptr += 0x200000;
	XIo_Out32(0xD0000200, tftptr);
    }

    // check for button pushes
    if (j % 1024 == 0) {
      temp = XIo_In32(GPIO_DATA);
      button_n = (temp >> 13) & (0x00000001);
      button_e = (temp >> 12) & (0x00000001);
      button_s = (temp >> 11) & (0x00000001);
      button_w = (temp >> 10) & (0x00000001);
      button_c = (temp >> 9)  & (0x00000001);

      if (button_n && !past_button_n) {
	if (volume == AC97_VolMute) volume = AC97_VolMin;
	else if (volume != AC97_VolMax) volume -= 0x0101;
	WriteAC97Reg(AC97_MasterVol,     volume);
	WriteAC97Reg(AC97_HeadphoneVol,  volume);
      }
      if (button_s && !past_button_s) {
	if (volume == AC97_VolMin | volume == AC97_VolMute) volume = AC97_VolMute;
	else volume += 0x0101;
	WriteAC97Reg(AC97_MasterVol,     volume);
	WriteAC97Reg(AC97_HeadphoneVol,  volume);
      }
      if (button_e && !past_button_e) {
	if (tftptr == IMAGE_BASEADDR+0x200000*(count-1)) tftptr = IMAGE_BASEADDR;
	else tftptr += 0x200000;
	XIo_Out32(0xD0000200, tftptr);
      }
      if (button_w && !past_button_w) {
	if (tftptr == IMAGE_BASEADDR) tftptr = IMAGE_BASEADDR+0x200000*(count-1);
	else tftptr -= 0x200000;
	XIo_Out32(0xD0000200, tftptr);
      }
      if (button_c && !past_button_c) {
      paused = !paused;
      if (paused) XromLCDPrintString ("Slideshow Paused", "Resume= Button C");
      else XromLCDPrintString ("Slideshow Ready ", "VGA+Audio Active");
      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);
      }

      past_button_n = button_n;
      past_button_e = button_e;
      past_button_s = button_s;
      past_button_w = button_w;
      past_button_c = button_c;
    }
  }
}

int main()
{
  int count;

  /* initialize UART */
  XUartNs550_SetBaud(UART_BASEADDR, UART_CLOCK, UART_BAUDRATE);
  XUartNs550_mSetLineControlReg(UART_BASEADDR, XUN_LCR_8_DATA_BITS);

  /* initialize GPIO */
  XIo_Out32(GPIO_DATA, 0x00000000);
  XIo_Out32(GPIO_CONTROL, 0xFFFFFE00);

  /* print directions to LCD screen */
  XromLCDOn();
  XromLCDInit();

  /* get images and sound */
  print("\nProgram running.\n\r");
  count = get_images();
  read_sound();
  print("\n\rReads done\n\r");
  XromLCDPrintString ("Slideshow Ready ", "VGA+Audio Active");

  /* set TFT pointer */
  XIo_Out32(0xD0000200, IMAGE_BASEADDR);

  /* initialize and play sound */
  init_sound();
  play_sound(SOUND_BASEADDR, count); // infinite loop

  return 0;
}
