// Program for flashing the boot area of an SPI flash

#include <vs1000.h>
#include <minifat.h>
#include <vsNand.h>
extern __y u_int16 mallocAreaY[]; /* for ramdisk */
extern u_int16 mallocAreaX[];     /* for ramboot */
extern struct FsNandPhys fsNandPhys;
extern struct FsPhysical *ph;



#define SPI_EEPROM_COMMAND_WRITE_ENABLE  0x06
#define SPI_EEPROM_COMMAND_WRITE_DISABLE  0x04
#define SPI_EEPROM_COMMAND_READ_STATUS_REGISTER  0x05
#define SPI_EEPROM_COMMAND_WRITE_STATUS_REGISTER  0x01
#define SPI_EEPROM_COMMAND_READ  0x03
#define SPI_EEPROM_COMMAND_WRITE 0x02
#define SPI_EEPROM_COMMAND_CLEAR_ERROR_FLAGS 0x30
#define SPI_EEPROM_COMMAND_ERASE_BLOCK 0xD8
#define SPI_EEPROM_COMMAND_ERASE_SECTOR 0x20
#define SPI_EEPROM_COMMAND_ERASE_CHIP 0xC7

//macro to set SPI to MASTER; 8BIT; FSYNC Idle => xCS high
#define SPI_MASTER_8BIT_CSHI   PERIP(SPI0_CONFIG) = \
        SPI_CF_MASTER | SPI_CF_DLEN8 | SPI_CF_FSIDLE1

//macro to set SPI to MASTER; 8BIT; FSYNC not Idle => xCS low
#define SPI_MASTER_8BIT_CSLO   PERIP(SPI0_CONFIG) = \
        SPI_CF_MASTER | SPI_CF_DLEN8 | SPI_CF_FSIDLE0

//macro to set SPI to MASTER; 16BIT; FSYNC not Idle => xCS low
#define SPI_MASTER_16BIT_CSLO  PERIP(SPI0_CONFIG) = \
        SPI_CF_MASTER | SPI_CF_DLEN16 | SPI_CF_FSIDLE0

void SingleCycleCommand(u_int16 cmd){
  SPI_MASTER_8BIT_CSHI; 
  SPI_MASTER_8BIT_CSLO;
  SpiSendReceive(cmd);
  SPI_MASTER_8BIT_CSHI;
}

/// Wait for not_busy (status[0] = 0) and return status
u_int16 SpiWaitStatus(void) {
  u_int16 status;
  SPI_MASTER_8BIT_CSHI;
  SPI_MASTER_8BIT_CSLO;
  SpiSendReceive(SPI_EEPROM_COMMAND_READ_STATUS_REGISTER);
  do {
    status = SpiSendReceive(0);
  }    
  while (status & 0x01);
  
    ; //Wait until ready
  SPI_MASTER_8BIT_CSHI;
  
  return status;
}


void EeUnprotect(){
  SingleCycleCommand(SPI_EEPROM_COMMAND_WRITE_ENABLE);

  SPI_MASTER_8BIT_CSLO;
  SpiSendReceive(SPI_EEPROM_COMMAND_WRITE_STATUS_REGISTER);
  SpiSendReceive(0x02); //Sector Protections Off
  SPI_MASTER_8BIT_CSHI;
  SpiWaitStatus();


  SingleCycleCommand(SPI_EEPROM_COMMAND_WRITE_ENABLE);
}

/// Erase one erasable block (64K) 
void SpiEraseBlock(u_int16 blockn_inside_sector){
  SingleCycleCommand(SPI_EEPROM_COMMAND_WRITE_ENABLE);  
  SPI_MASTER_8BIT_CSLO;
  SpiSendReceive(SPI_EEPROM_COMMAND_WRITE_STATUS_REGISTER);
  SpiSendReceive(0x02); //Sector Protections Off
  SPI_MASTER_8BIT_CSHI;
  SpiWaitStatus();
  SingleCycleCommand(SPI_EEPROM_COMMAND_WRITE_ENABLE);  
  SPI_MASTER_8BIT_CSLO;
  SpiSendReceive(SPI_EEPROM_COMMAND_ERASE_BLOCK);
  SpiSendReceive(blockn_inside_sector>>7);       
  SpiSendReceive((blockn_inside_sector<<1)&0xff);
  SpiSendReceive(0);              
  SPI_MASTER_8BIT_CSHI;
  SpiWaitStatus();
}


// Program one page of EEPROM. Does not erase page.
// \param startAddress start address of page in bytes
// \param dptr pointer to data block
void EeProgramPage(u_int32 startAddress, u_int16 *dptr){
  SingleCycleCommand(SPI_EEPROM_COMMAND_WRITE_ENABLE);
  SingleCycleCommand(SPI_EEPROM_COMMAND_CLEAR_ERROR_FLAGS);

  EeUnprotect();

  SPI_MASTER_16BIT_CSLO;
  SpiSendReceive(SPI_EEPROM_COMMAND_WRITE<<8|((startAddress >> 16) & 0xffff));
  SpiSendReceive(startAddress & 0xffff); 
  {
    u_int16 n;
    for (n=0; n<128; n++){
      SpiSendReceive((*dptr++));
    }
  }
  SPI_MASTER_8BIT_CSHI;
  SpiWaitStatus(); 
}

void InitSpi(u_int16 clockDivider){
  SPI_MASTER_8BIT_CSHI;
  PERIP(SPI0_FSYNC) = 0;
  PERIP(SPI0_CLKCONFIG) = SPI_CC_CLKDIV * (clockDivider-1);
  PERIP(GPIO1_MODE) |= 0x1f; /* enable SPI pins */
}


void main(void) {
  register u_int16 j = 0;
  u_int16 writePos = 0;
  
  InitSpi(2);

  SpiEraseBlock(0); //Erase the boot block
  
  if (InitFileSystem() == 0) { // Reinitialize file system
    
    static const u_int32 bootFiles[] = { FAT_MKID('I','M','G'), 0 };
    minifatInfo.supportedSuffixes = bootFiles; // Only read .IMG files
    
    if (OpenFile(0) < 0) { // Open first .IMG file on ramdisk
      while (j = ReadFile(mallocAreaX, 0, 256)) {
	EeProgramPage(writePos, mallocAreaX);
	writePos += 256;
      }
      {
	register u_int16 *g = (u_int16*) g_yprev0; // get ptr to sintest params
	*g++ = 4; *g++ = 44100; *g++ = 0x4040; *g++ = 300; *g++ = 200;
	SinTest(); //Exit to SinTest: Play weird tone at low volume
      }      
    }
  }

  {
    register u_int16 *g = (u_int16*) g_yprev0; // get ptr to sintest params
    *g++ = 4; *g++ = 44100; *g++ = 0x4040; *g++ = 120; *g++ = 190;
    SinTest(); //Exit to SinTest: Play weird tone at low volume
  }

}

