/*************************************************************************
 * This demo uses the BALibrary to provide enhanced control of
 * the TGA Pro board.
 * 
 * The latest copy of the BALibrary can be obtained from
 * https://github.com/Blackaddr/BALibrary
 * 
 * This demo will perform a DMA memory test on MEM1 attached
 * to SPI1.
 * 
 * NOTE: SPI MEM1 can be used by a Teensy 3.5/3.6.
 * pins.
 * 
 */
#include <Wire.h>
#include "BALibrary.h"

using namespace BALibrary;

//#define SANITY
#define DMA_SIZE 256

#define SPI_WRITE_CMD 0x2
#define SPI_READ_CMD 0x3
#define SPI_ADDR_2_MASK 0xFF0000
#define SPI_ADDR_2_SHIFT 16
#define SPI_ADDR_1_MASK 0x00FF00
#define SPI_ADDR_1_SHIFT 8
#define SPI_ADDR_0_MASK 0x0000FF
SPISettings memSettings(20000000, MSBFIRST, SPI_MODE0);
const int cs0pin = 15;

BAGpio           gpio;  // access to User LED
BASpiMemoryDMA   spiMem1(SpiDeviceId::SPI_DEVICE1);

bool compareBuffers(uint8_t *a, uint8_t *b, size_t numBytes)
{
  bool pass=true;
  int errorCount = 0;
  for (size_t i=0; i<numBytes; i++) {
    if (a[i] != b[i]) {
      if (errorCount < 10) {
        Serial.print(i); Serial.print(":: a:"); Serial.print(a[i]); Serial.print(" does not match b:"); Serial.println(b[i]);
        pass=false;
        errorCount++;
      } else { return false; }
    }
  }
  return pass;
}

bool compareBuffers16(uint16_t *a, uint16_t *b, size_t numWords)
{
  return compareBuffers(reinterpret_cast<uint8_t *>(a), reinterpret_cast<uint8_t*>(b), sizeof(uint16_t)*numWords);
}

constexpr size_t TEST_END = SPI_MAX_ADDR;

void setup() {

  Serial.begin(57600);
  while (!Serial) {}
  delay(5);

  Serial.println("Enabling SPI, testing MEM1");
  spiMem1.begin();
}


bool spi8BitTest(void) {
  
  size_t spiAddress = 0;
  int spiPhase = 0;
  constexpr uint8_t MASK80 = 0xaa;
  constexpr uint8_t MASK81 = 0x55;
  
  uint8_t src8[DMA_SIZE];
  uint8_t dest8[DMA_SIZE];
  
  // Write to the memory using 8-bit transfers
  Serial.println("\nStarting 8-bit test Write/Read");
  while (spiPhase < 4) {
    spiAddress = 0;
    while (spiAddress < TEST_END) {
      
      // fill the write data buffer
      switch (spiPhase) {
        case 0 :
          for (int i=0; i<DMA_SIZE; i++) {
            src8[i] = ( (spiAddress+i) & 0xff) ^ MASK80;
          }
          break;
        case 1 :
          for (int i=0; i<DMA_SIZE; i++) {
            src8[i] = ((spiAddress+i) & 0xff) ^ MASK81;
          }
          break;
        case 2 :
          for (int i=0; i<DMA_SIZE; i++) {
            src8[i] = ((spiAddress+i) & 0xff) ^ (!MASK80);
          }
          break;
        case 3 :
          for (int i=0; i<DMA_SIZE; i++) {
            src8[i] = ((spiAddress+i) & 0xff) ^ (!MASK81);
          }
          break;
      }      
  
      // Send the DMA transfer
      spiMem1.write(spiAddress, src8, DMA_SIZE);
      // wait until write is done
      while (spiMem1.isWriteBusy()) {}
      spiAddress += DMA_SIZE;    
    }
  
    // Read back the data using 8-bit transfers
    spiAddress = 0;
    while (spiAddress < TEST_END) {
      // generate the golden data
      switch (spiPhase) {
      case 0 :
        for (int i=0; i<DMA_SIZE; i++) {
          src8[i] = ( (spiAddress+i) & 0xff) ^ MASK80;
        }
        break;
      case 1 :
        for (int i=0; i<DMA_SIZE; i++) {
          src8[i] = ((spiAddress+i) & 0xff) ^ MASK81;
        }
        break;
      case 2 :
        for (int i=0; i<DMA_SIZE; i++) {
          src8[i] = ((spiAddress+i) & 0xff) ^ (!MASK80);
        }
        break;
      case 3 :
        for (int i=0; i<DMA_SIZE; i++) {
          src8[i] = ((spiAddress+i) & 0xff) ^ (!MASK81);
        }
        break;
      }
  
      // Read back the DMA data
      spiMem1.read(spiAddress, dest8, DMA_SIZE);
      // wait until read is done
      while (spiMem1.isReadBusy()) {}
      if (!compareBuffers(src8, dest8, DMA_SIZE)) { 
        Serial.println(String("ERROR @") + spiAddress);
        return false; } // stop the test    
      spiAddress += DMA_SIZE; 
    }
    Serial.println(String("Phase ") + spiPhase + String(": 8-bit Write/Read test passed!"));
    spiPhase++;
  }

#ifdef SANITY
  for (int i=0; i<DMA_SIZE; i++) {
    Serial.print(src8[i], HEX); Serial.print("="); Serial.println(dest8[i],HEX);
  }
#endif

  Serial.println("\nStarting 8-bit test Zero/Read");
  // Clear the memory
  spiAddress = 0;
  memset(src8, 0, DMA_SIZE);
  while (spiAddress < SPI_MAX_ADDR) {
      // Send the DMA transfer
      spiMem1.zero(spiAddress, DMA_SIZE);
      // wait until write is done
      while (spiMem1.isWriteBusy()) {}
      spiAddress += DMA_SIZE;     
  }

  // Check the memory is clear
  spiAddress = 0;
  while (spiAddress < SPI_MAX_ADDR) {
    // Read back the DMA data
    spiMem1.read(spiAddress, dest8, DMA_SIZE);
    // wait until read is done
    while (spiMem1.isReadBusy()) {}
    if (!compareBuffers(src8, dest8, DMA_SIZE)) { return false; } // stop the test    
    spiAddress += DMA_SIZE; 
  }
  Serial.println(String(": 8-bit Zero/Read test passed!"));
  return true;
}

bool spi16BitTest(void) {
  size_t spiAddress = 0;
  int spiPhase = 0;
  constexpr uint16_t MASK160 = 0xaaaa;
  constexpr uint16_t MASK161 = 0x5555;
  constexpr int NUM_DATA = DMA_SIZE / sizeof(uint16_t);
  
  uint16_t src16[NUM_DATA];
  uint16_t dest16[NUM_DATA];

  // Write to the memory using 16-bit transfers
  Serial.println("\nStarting 16-bit test");
  while (spiPhase < 4) {
    spiAddress = 0;
    while (spiAddress < SPI_MAX_ADDR) {
      
      // fill the write data buffer
      switch (spiPhase) {
        case 0 :
          for (int i=0; i<NUM_DATA; i++) {
            src16[i] = ( (spiAddress+i) & 0xffff) ^ MASK160;
          }
          break;
        case 1 :
          for (int i=0; i<NUM_DATA; i++) {
            src16[i] = ((spiAddress+i) & 0xffff) ^ MASK161;
          }
          break;
        case 2 :
          for (int i=0; i<NUM_DATA; i++) {
            src16[i] = ((spiAddress+i) & 0xffff) ^ (!MASK160);
          }
          break;
        case 3 :
          for (int i=0; i<NUM_DATA; i++) {
            src16[i] = ((spiAddress+i) & 0xffff) ^ (!MASK161);
          }
          break;
      }
  
      // Send the DMA transfer
      spiMem1.write16(spiAddress, src16, NUM_DATA);
      // wait until write is done
      while (spiMem1.isWriteBusy()) {}
      spiAddress += DMA_SIZE;    
    }
  
    // Read back the data using 8-bit transfers
    spiAddress = 0;
    while (spiAddress < SPI_MAX_ADDR) {
      // generate the golden data
      switch (spiPhase) {
      case 0 :
        for (int i=0; i<NUM_DATA; i++) {
          src16[i] = ( (spiAddress+i) & 0xffff) ^ MASK160;
        }
        break;
      case 1 :
        for (int i=0; i<NUM_DATA; i++) {
          src16[i] = ((spiAddress+i) & 0xffff) ^ MASK161;
        }
        break;
      case 2 :
        for (int i=0; i<NUM_DATA; i++) {
          src16[i] = ((spiAddress+i) & 0xffff) ^ (!MASK160);
        }
        break;
      case 3 :
        for (int i=0; i<NUM_DATA; i++) {
          src16[i] = ((spiAddress+i) & 0xffff) ^ (!MASK161);
        }
        break;
      }
  
      // Read back the DMA data
      spiMem1.read16(spiAddress, dest16, NUM_DATA);
      // wait until read is done
      while (spiMem1.isReadBusy()) {}
      if (!compareBuffers16(src16, dest16, NUM_DATA)) { 
        Serial.print("ERROR @"); Serial.println(spiAddress, HEX);
        return false; } // stop the test    
      spiAddress += DMA_SIZE; 
    }
    Serial.println(String("Phase ") + spiPhase + String(": 16-bit test passed!"));
    spiPhase++;
  }
#ifdef SANITY
  for (int i=0; i<NUM_DATA; i++) {
   Serial.print(src16[i], HEX); Serial.print("="); Serial.println(dest16[i],HEX);
  }
#endif

  Serial.println("\nStarting 16-bit test Zero/Read");
  // Clear the memory
  spiAddress = 0;
  memset(src16, 0, DMA_SIZE);
  while (spiAddress < SPI_MAX_ADDR) {
      // Send the DMA transfer
      spiMem1.zero16(spiAddress, NUM_DATA);
      // wait until write is done
      while (spiMem1.isWriteBusy()) {}
      spiAddress += DMA_SIZE;     
  }

  // Check the memory is clear
  spiAddress = 0;
  while (spiAddress < SPI_MAX_ADDR) {
    // Read back the DMA data
    spiMem1.read16(spiAddress, dest16, NUM_DATA);
    // wait until read is done
    while (spiMem1.isReadBusy()) {}
    if (!compareBuffers16(src16, dest16, NUM_DATA)) { return false; } // stop the test    
    spiAddress += DMA_SIZE; 
  }
  Serial.println(String(": 16-bit Zero/Read test passed!"));
  return true;
}

void loop() {

  spi8BitTest();
  spi16BitTest();
  Serial.println("\nTEST DONE!");
  while(true) {}

}