/*****************************************************************************
 *                                                                           *
 * Copyright (c) 2006, 2009, 2010 Rowley Associates Limited.                 *
 *                                                                           *
 * This file may be distributed under the terms of the License Agreement     *
 * provided with this software.                                              *
 *                                                                           *
 * THIS FILE IS PROVIDED AS IS WITH NO WARRANTY OF ANY KIND, INCLUDING THE   *
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. *
 *****************************************************************************/
#if defined(AT91RM9200)
#include <targets/AT91RM9200.h>
#define SPI_(S) SPI_##S
#elif defined(AT91SAM9260)
#include <targets/AT91SAM9260.h>
#define SPI_(S) SPI0_##S
#elif defined(AT91SAM9G20)
#include <targets/AT91SAM9G20.h>
#define SPI_(S) SPI0_##S
#elif defined(AT91SAM9261)
#include <targets/AT91SAM9261.h>
#define SPI_(S) SPI0_##S
#elif defined(AT91SAM9263)
#include <targets/AT91SAM9263.h>
#define SPI_(S) SPI0_##S
#elif defined(AT91SAM9RL64)
#include <targets/AT91SAM9RL64.h>
#define SPI_(S) SPI_##S
#elif defined(AT91SAM9G45)
#include <targets/AT91SAM9G45.h>
#define SPI_(S) SPI0_##S
#elif defined(AT91SAM9G10)
#include <targets/AT91SAM9G10.h>
#define SPI_(S) SPI0_##S
#endif
#include "spi0df.h"

#define DB_READ_PAGE 0xD2
#define DB_CONTINUOUS_ARRAY_READ 0xE8	
#define DB_STATUS 0xD7
#define DB_BUF1_WRITE 0x84
#define DB_BUF1_PAGE_ERASE_PGM 0x83
#define DB_PAGE_PGM_BUF1 0x82
#define DB_PAGE_ERASE 0x81
#define DB_BUF1_PAGE_PGM 0x88

#define AT45DB011D 0x0C // 1M
#define AT45DB021D 0x14 // 2M
#define AT45DB041D 0x1C // 4M
#define AT45DB081D 0x24 // 8M
#define AT45DB161D 0x2C // 16M
#define AT45DB321D 0x34 // 34M
#define AT45DB642D 0x3C // 64M
#define AT45DB1282 0x10 // 128M
#define AT45DB2562 0x18 // 256M
#define AT45DB5122 0x20 // 512M


#define MASTER_CLOCK_FREQ (100000000) // 100 Mhz
#define SPI_CLOCK         (8000000)   // 8 Mhz

#define DATAFLASH_SCBR_VAL (MASTER_CLOCK_FREQ / SPI_CLOCK)
#define DATAFLASH_DLYBS_VAL 25 // 25/100000000 = 250 ns
#define DATAFLASH_DLYBCT_VAL 1 // 32/100000000 = 320 ns

// Chip Select 0 : NPCS0 %1110
#define AT91C_SPI_PCS0_DATAFLASH 0xE
// Chip Select 1 : NPCS1 %1101
#define AT91C_SPI_PCS1_DATAFLASH 0xD

void
SPI0DataFlashInit(void)
{
  volatile int i;
#if (defined(AT91SAM9260)||defined(AT91SAM9G20)) && defined(NPCS1)
  // select peripheral A on IO lines PA0-PA2 (SPIO_MISO,SPI0_MOSI,SPI0_SPCK)
  PIOA_PDR = 0x7;
  PIOA_ASR = 0x7;
  // select peripheral B on IO lines PC11 (SPI0_NPCS1)
  PIOC_PDR = (1<<11);
  PIOC_BSR = (1<<11);
  // enable PIOA/PIOC and SPIO
  PMC_PCER |= (1<<2)|(1<<4)|(1<<12);
#elif defined(AT91SAM9263)
  // select peripheral B on IO lines PA0-PA2,PA5 (SPI0_MISO,SPI0_MOSI,SPI0_SPCK,SPI0_NPCS0)
  PIOA_PDR = (1<<5)|0x7;
  PIOA_BSR = (1<<5)|0x7;
  // enable PIOB and SPI0
  PMC_PCER |= (1<<3)|(1<<14);
#elif defined(AT91SAM9RL64)
  // select peripheral A on IO lines PA25-PA28 (SPI_MISO,SPI_MOSI,SPI_SPCK,SPI_NPCS)
  PIOA_PDR = 0xF<<25;
  PIOA_BSR = 0xF<<25;
  // enable PIOA and SPI
  PMC_PCER |= (PMC_PCER_PIOA|PMC_PCER_SPI);
#elif defined(AT91SAM9G45)
  // select peripheral A on IO lines PB0-PB3 (SPI0_MISO,SPI0_MOSI,SPI0_SPCK,SPI0_NPCS0)
  PIOB_PDR = 0xF;
  PIOB_ASR = 0xF;
  // enable PIOB and SPI0
  PMC_PCER |= (PMC_PCER_PIOB|PMC_PCER_SPI0);
#else
  // select peripheral A on IO lines PA0-PA3 (SPIO_MISO,SPI0_MOSI,SPI0_SPCK,SPI0_NPCS0)
  PIOA_PDR = 0xF;
  PIOA_ASR = 0xF;
  // enable PIOA and SPIO
#if defined(AT91RM9200)
  PMC_PCER |= (1<<2)|(1<<13);
#else
  PMC_PCER |= (1<<2)|(1<<12);
#endif
#endif

  // reset SPIO
  SPI_(CR) = SPI_(CR_SWRST);
  // Configure SPI0 in Master Mode with No CS selected
  SPI_(MR) = SPI_(MR_MSTR) | SPI_(MR_MODFDIS) | (0xf<<SPI_(MR_PCS_BIT));
#if (defined(AT91SAM9260)||defined(AT91SAM9G20)) && defined(NPCS1)
  // Configure CS1
  SPI_(CSR1) = SPI_(CSR1_CPOL) | (DATAFLASH_SCBR_VAL << SPI_(CSR1_SCBR_BIT)) | (DATAFLASH_DLYBS_VAL << SPI_(CSR1_DLYBS_BIT)) | (DATAFLASH_DLYBCT_VAL << SPI_(CSR1_DLYBCT_BIT));
#else
  // Configure CS0
  SPI_(CSR0) = SPI_(CSR0_CPOL) | (DATAFLASH_SCBR_VAL << SPI_(CSR0_SCBR_BIT)) | (DATAFLASH_DLYBS_VAL << SPI_(CSR0_DLYBS_BIT)) | (DATAFLASH_DLYBCT_VAL << SPI_(CSR0_DLYBCT_BIT));
#endif
  // Choose CSx
  SPI_(MR) &= 0xFFF0FFFF;
#if (defined(AT91SAM9260)||defined(AT91SAM9G20)) && defined(NPCS1)
  SPI_(MR) |= AT91C_SPI_PCS1_DATAFLASH << SPI_(MR_PCS_BIT);
#else
  SPI_(MR) |= AT91C_SPI_PCS0_DATAFLASH << SPI_(MR_PCS_BIT);
#endif
  // SPI_Enable
  SPI_(CR) = SPI_(CR_SPIEN);
  // Wait loop
  for (i=0; i<100000; i++);
  SPI_(SR);
  SPI_(RDR);
}

static void
SPI0DataFlashSendCommandAndData(unsigned commandlen, unsigned char *command, unsigned datalen, unsigned char *data)
{
  SPI_(PTCR) = SPI_(PTCR_RXTDIS); 
  SPI_(RPR) = (unsigned)command;
  SPI_(RCR) = commandlen;           
  SPI_(RNPR) = (unsigned)data;
  SPI_(RNCR) = datalen;             

  SPI_(PTCR) = SPI_(PTCR_TXTDIS);
  SPI_(TPR) = (unsigned)command;
  SPI_(TCR) = commandlen;           
  SPI_(TNPR) = (unsigned)data;
  SPI_(TNCR) = datalen;    

  SPI_(PTCR) = SPI_(PTCR_RXTEN);
  SPI_(PTCR) = SPI_(PTCR_TXTEN);  

  while (!(SPI_(SR) & SPI_(SR_RXBUFF)));

  SPI_(PTCR) = SPI_(PTCR_TXTDIS);
  SPI_(PTCR) = SPI_(PTCR_RXTDIS); 
}

static unsigned 
SPI0DataFlashSendStatusCommand()
{
  unsigned char command[2] = { DB_STATUS };
  SPI0DataFlashSendCommandAndData(2, command, 0, 0);
  return command[1];
}

unsigned SPI0DataFlashNumPages, SPI0DataFlashPageSize;
static unsigned pageOffset;

unsigned
SPI0DataFlashIdentify(void)
{
  switch (SPI0DataFlashSendStatusCommand() & 0x3F)
    {
      case AT45DB011D:
        SPI0DataFlashNumPages = 512;
        SPI0DataFlashPageSize = 264;
        pageOffset = 9;
        break;
      
      case AT45DB021D:
        SPI0DataFlashNumPages = 1024;
        SPI0DataFlashPageSize = 264;
        pageOffset = 9;
        break;
      
      case AT45DB041D:
        SPI0DataFlashNumPages = 2048;
        SPI0DataFlashPageSize = 264;
        pageOffset = 9;
        break;
      
      case AT45DB081D:
        SPI0DataFlashNumPages = 4096;
        SPI0DataFlashPageSize = 264;
        pageOffset = 9;
        break;
      
      case AT45DB161D:
        SPI0DataFlashNumPages = 4096;
        SPI0DataFlashPageSize = 528;
        pageOffset = 10;
        break;
      
      case AT45DB321D:
        SPI0DataFlashNumPages = 8192;
        SPI0DataFlashPageSize = 528;
        pageOffset = 10;
        break;
      
      case AT45DB642D:
        SPI0DataFlashNumPages = 8192;
        SPI0DataFlashPageSize = 1056;
        pageOffset = 11;
        break;
      
      case AT45DB1282:
        SPI0DataFlashNumPages = 16384;
        SPI0DataFlashPageSize = 1056;
        pageOffset = 11;
        break;
      
      case AT45DB2562:
        SPI0DataFlashNumPages = 16384;
        SPI0DataFlashPageSize = 2112;
        pageOffset = 12;
        break;
      
      case AT45DB5122:
        SPI0DataFlashNumPages = 32768;
        SPI0DataFlashPageSize = 2112;
        pageOffset = 12;
        break;
      
      default:
        return 0;
    }
  return 1;
}

static unsigned
SPI0DataFlashSendCommand(unsigned commandByte, unsigned flashPageNumber, unsigned datalen, unsigned char *data)
{
  unsigned timeout;
  unsigned address = flashPageNumber << pageOffset;
  unsigned char command[9];
  unsigned commandlen = 0;
  command[commandlen++] = commandByte;
  if (SPI0DataFlashNumPages >= 16384)   
    command[commandlen++] = address >> 24;
  command[commandlen++] = address >> 16;
  command[commandlen++] = address >> 8;
  if (SPI0DataFlashNumPages < 16384 || (commandByte != DB_CONTINUOUS_ARRAY_READ && commandByte != DB_READ_PAGE))
    command[commandlen++] = address;
  if (commandByte == DB_CONTINUOUS_ARRAY_READ || commandByte == DB_READ_PAGE)
    commandlen += 4;
  SPI0DataFlashSendCommandAndData(commandlen, command, datalen, data);
  for (timeout=0; timeout < 10000000; timeout++)
    if (SPI0DataFlashSendStatusCommand() & 0x80)
      return 1;
  return 0;
}

unsigned 
SPI0DataFlashErasePage(unsigned flashPageNumber)
{
  return SPI0DataFlashSendCommand(DB_PAGE_ERASE, flashPageNumber, 0, 0);
}

unsigned
SPI0DataFlashWritePage(unsigned flashPageNumber, unsigned char *buffer)
{
  return SPI0DataFlashSendCommand(DB_BUF1_WRITE, 0, SPI0DataFlashPageSize, buffer) &&
         SPI0DataFlashSendCommand(DB_BUF1_PAGE_ERASE_PGM, flashPageNumber, 0, 0);
}

unsigned
SPI0DataFlashReadPage(unsigned flashPageNumber, unsigned char *buffer)
{
  return SPI0DataFlashSendCommand(DB_READ_PAGE, flashPageNumber, SPI0DataFlashPageSize, buffer);
}

unsigned 
SPI0DataFlashReadBytes(unsigned flashPageNumber, unsigned offset, unsigned n, unsigned char *buffer)
{
  unsigned timeout;
  unsigned address = (flashPageNumber << pageOffset) + offset;
  unsigned char command[9];
  unsigned commandlen = 0;
  command[commandlen++] = DB_CONTINUOUS_ARRAY_READ;
  if (SPI0DataFlashNumPages >= 16384)   
    command[commandlen++] = address >> 24;
  command[commandlen++] = address >> 16;
  command[commandlen++] = address >> 8;
  if (SPI0DataFlashNumPages < 16384)
    command[commandlen++] = address; 
  commandlen += 4;
  SPI0DataFlashSendCommandAndData(commandlen, command, n, buffer);
  for (timeout=0; timeout < 10000000; timeout++)
    if (SPI0DataFlashSendStatusCommand() & 0x80)
      return 1;
  return 0;
}



