/***********************************************************************
 * $Id::$
 *
 * Project: Embedded Artists LPC3141 Board
 *
 * Description:
 *     This file contains driver support for various VAL3150 board
 *     functions.
 *
 ***********************************************************************
 * Software that is described herein is for illustrative purposes only
 * which provides customers with programming information regarding the
 * products. This software is supplied "AS IS" without any warranties.
 * NXP Semiconductors assumes no responsibility or liability for the
 * use of the software, conveys no license or title under any patent,
 * copyright, or mask work right to the product. NXP Semiconductors
 * reserves the right to make changes in the software without
 * notification. NXP Semiconductors also make no representation or
 * warranty that such application will be suitable for the specified
 * use without further testing or modification.
 **********************************************************************/

#include "ea3141_board.h"
#include "lpc313x_spi_driver.h"
#include "lpc313x_timer_driver.h"
#include "lpc313x_ioconf_driver.h"

/* Manufacturer IDs */
#define MANFID_ATMEL    0x1F
#define MANFID_SPANSION    0x01


/* Common Commands */

#define CMD_RDID        0x9F        /* read device ID */
#define CMD_FAST_READ   0x0B        /* read data (extra command setup time allows higher SPI clock) */
#define CMD_RES         0xAB        /* release from deep power down */

/* Atmel commands and status values   */

#define AT_CMD_RDSR        0xD7        /* read status register */
#define AT_CMD_PP       0x82        /* Page Program Through Buffer */
#define AT_CMD_BE          0x50        /* block erase */
#define AT_CMD_SE          0x7C        /* sector erase */

/* status register defines */
#define STATUS_RDY      (1 << 7)
#define STATUS_COMP     (1 << 6)
#define STATUS_PROTECT  (1 << 1)
#define STATUS_P512     (1 << 0)

/* Spansion commands and status values*/

#define SP_CMD_RDSR   0x05     /* read status register */
#define SP_CMD_PP     0x02     /* Page Program  */
#define SP_CMD_WREN   0x06     /* Write enable  */
#define SP_ERASE_P4E  0x20     /* 4KB erase     */
#define SP_CMD_SE     0xD8     /* sector erase     */
#define SP_CMD_RCR    0x35     /* read configuration register */

#define SP_STATUS_SRWD   (1 << 7)
#define SP_STATUS_P_ERR  (1 << 6)
#define SP_STATUS_E_ERR  (1 << 5)
#define SP_STATUS_WEL    (1 << 1)
#define SP_STATUS_WIP    (1 << 0)


#define CMD_DP          0xB9        /* deep power down */

#define DUMMY_BYTE		  0xFF


#define PAGE_528       (528)
#define PAGE_512       (512)
#define PAGE_256       (256)

#define BLOCK_SIZE(pg_sz)      (16 * (pg_sz))
#define SECTOR_SIZE(pg_sz)     (32 * (pg_sz))


/****************************************************************************
****************************************************************************/

typedef struct
{
  INT_32 spi_id;
  UNS_32 spi_slave_id;
  UNS_32 page_size;
  UNS_8  manufacturer_id;
  UNS_8  device_type;
  UNS_8  device_capacity;
  UNS_8 _pad;

} SPINOR_INFO_T;

static SPINOR_INFO_T g_spinor;

/***********************************************************************
 * Local variables
 **********************************************************************/

/* NOR flash manufacturer ID */
static UNS_32 norManufId = 0;


/***********************************************************************
 * Private functions
 **********************************************************************/
void spinor_cs_low(void)
{
  gpio_set_outpin_low(IOCONF_SPI, 4);
}

void spinor_cs_high(void)
{
  gpio_set_outpin_high(IOCONF_SPI, 4);
}

/***********************************************************************
 *
 * Function: spinor_xfer
 *
 * Purpose: Transfer data to/from the serial EEPROM
 *
 * Processing:
 *     See function.
 *
 * Parameters:
 *     out   : Output data
 *     in    : Input data
 *     bytes : Number of bytes to send/receive
 *
 * Outputs: None
 *
 * Returns: TRUE if the byte was transferred
 *
 * Notes: Do not use this function to transfer more than 8 bytes!
 *
 **********************************************************************/
static void spinor_xfer(UNS_8 *out,
                        UNS_8 *in,
                        int bytes)
{
  INT_32 rbytes = 0;
  volatile UNS_32 status;

  /* flush the FFOS */
  spi_ioctl(g_spinor.spi_id, SPI_FLUSH_RX_FIFO, 0);
  /* write the commands */
  spi_write(g_spinor.spi_id, out, bytes);
  /* wait till SPI is not busy */
  do
  {
    status = spi_ioctl(g_spinor.spi_id, SPI_GET_STATUS, SPI_FIFO_ST);
  }
  while ((status & SPI_ST_TX_EMPTY) != SPI_ST_TX_EMPTY);

  while (rbytes < bytes)
  {
    rbytes += spi_read(g_spinor.spi_id, &in [rbytes], 1);
  }
}


static void read_device_id(UNS_8 *device_id)
{
  UNS_8 datao [4];

  datao[0] = CMD_RDID;
  datao[1] = DUMMY_BYTE;
  datao[2] = DUMMY_BYTE;
  datao[3] = DUMMY_BYTE;

  /* 3 bytes: manufacturer id, device type, device capacity */
  spinor_cs_low();
  spinor_xfer(datao, device_id, 4);
  spinor_cs_high();
}


static void simple_command(UNS_8 command_byte)
{
  UNS_8 dummy;
  /* should we wait for command to txfer */
  spinor_cs_low();
  spinor_xfer(&command_byte, &dummy, 1);
  spinor_cs_high();
}

static void spinor_enter_deep_power_down(void)
{
  simple_command(CMD_DP);
}

static void exit_deep_power_down(void)
{
  simple_command(CMD_RES);
  /* max tEDPD in Atmel AT45DB161D-SU datasheet is given as 35 usec */
  timer_wait_us(NULL, 50);
}

static void read_status_register(UNS_32 *status)
{
  UNS_8 datai [2], datao [2];

  if (norManufId ==  MANFID_ATMEL) {
    datao[0] = AT_CMD_RDSR;
  }
  else {
    datao[0] = SP_CMD_RDSR;
  }

  datao[1] = DUMMY_BYTE;
  spinor_cs_low();
  spinor_xfer(datao, datai, 2);
  spinor_cs_high();
  *status = datai[1];
}

static void poll_is_busy(void)
{
  UNS_32 status;

  do
  {
    timer_wait_us(NULL, 10);
    read_status_register(&status);
  }
  while ((norManufId == MANFID_ATMEL && (status & STATUS_RDY) == 0)
    || (norManufId == MANFID_SPANSION && (status & SP_STATUS_WIP) != 0));
}

static void atmel_write_cfg_reg(void)
{
  UNS_8 datai [4], datao [4];

  /* 4 bytes magic sequence to program the device to 512 page device */
  datao[0] = 0x3D;
  datao[1] = 0x2A;
  datao[2] = 0x80;
  datao[3] = 0xA6;

  spinor_cs_low();
  spinor_xfer(datao, datai, 4);
  spinor_cs_high();
}

static void atmel_erase(UNS_8 cmd, UNS_32 offset)
{
  UNS_8 datai [4], datao [4];

  datao[0] = cmd;
  datao[1] = (offset >> 16) & 0xFF; /* 24bit address upper byte */
  datao[2] = (offset >>  8) & 0xFF; /* 24bit address middle byte */
  datao[3] = (offset >>  0) & 0xFF; /* 24bit address least significant byte */

  spinor_cs_low();
  spinor_xfer(datao, datai, 4);
  spinor_cs_high();

  poll_is_busy();  
}

static void atmel_erase_block(UNS_32 offset)
{
  atmel_erase(AT_CMD_BE, offset);
}

static void atmel_erase_sector(UNS_32 offset)
{
  atmel_erase(AT_CMD_SE, offset);  
}
 
static void atmel_page_program(UNS_32 offset, UNS_8 *src, UNS_32 bytecount)
{
  UNS_8 datai [64], datao [4];
  UNS_32 count, status;

  /* assuming sectors are not protected. */

  datao[0] = AT_CMD_PP;
  datao[1] = (offset >> 16) & 0xFF; /* 24bit address upper byte */
  datao[2] = (offset >>  8) & 0xFF; /* 24bit address middle byte */
  datao[3] = (offset >>  0) & 0xFF; /* 24bit address least significant byte */
  spinor_cs_low();

  spinor_xfer(datao, datai, 4);

  while (bytecount > 0)
  {
    count = spi_write(g_spinor.spi_id, src, bytecount);
    bytecount -= count;
    src += count;
  }
  /* wait for Tx FIFO to empty */
  do
  {
    status = spi_ioctl(g_spinor.spi_id, SPI_GET_STATUS, SPI_FIFO_ST);
  }
  while ((status & SPI_ST_TX_EMPTY) != SPI_ST_TX_EMPTY);


  /* flush RX FIFO */
  spi_ioctl(g_spinor.spi_id, SPI_FLUSH_RX_FIFO, 0);
  spinor_cs_high();

  poll_is_busy();
}


static void spansion_read_conf_register(UNS_32 *status)
{
  UNS_8 datai [2], datao [2];


  datao[0] = SP_CMD_RCR;
  datao[1] = DUMMY_BYTE;

  spinor_cs_low();
  spinor_xfer(datao, datai, 2);
  spinor_cs_high();                                    

  *status = datai[1];
}

static void spansion_wren(void)
{
  UNS_8 datai [1], datao [1];

  datao[0] = SP_CMD_WREN;

  spinor_cs_low();
  spinor_xfer(datao, datai, 1);
  spinor_cs_high();

  timer_wait_us(NULL, 50);  
}

static void spansion_page_program(UNS_32 offset, UNS_8 *src, UNS_32 bytecount)
{
  UNS_8 datai [64], datao [4];
  UNS_32 count, status;

  /* assuming sectors are not protected. */

  datao[0] = SP_CMD_PP;
  datao[1] = (offset >> 16) & 0xFF; /* 24bit address upper byte */
  datao[2] = (offset >>  8) & 0xFF; /* 24bit address middle byte */
  datao[3] = (offset >>  0) & 0xFF; /* 24bit address least significant byte */
  spinor_cs_low();

  spinor_xfer(datao, datai, 4);

  while (bytecount > 0)
  {
    count = spi_write(g_spinor.spi_id, src, bytecount);
    bytecount -= count;
    src += count;
  }
  /* wait for Tx FIFO to empty */
  do
  {
    status = spi_ioctl(g_spinor.spi_id, SPI_GET_STATUS, SPI_FIFO_ST);
  }
  while ((status & SPI_ST_TX_EMPTY) != SPI_ST_TX_EMPTY);


  /* flush RX FIFO */
  spi_ioctl(g_spinor.spi_id, SPI_FLUSH_RX_FIFO, 0);
  spinor_cs_high();

  poll_is_busy();
}


static void spansion_erase(UNS_8 cmd, UNS_32 offset)
{
  UNS_8 datao [4];
  UNS_8 datai [4];

  datao[0] = cmd;
  datao[1] = (offset >> 16) & 0xFF; /* 24bit address upper byte */
  datao[2] = (offset >>  8) & 0xFF; /* 24bit address middle byte */
  datao[3] = (offset >>  0) & 0xFF; /* 24bit address least significant byte */

  spinor_cs_low();
  spinor_xfer(datao, datai, 4);
  spinor_cs_high();                                    

  poll_is_busy();
}

static void spansion_erase_block(UNS_32 offset)
{
  spansion_erase(SP_ERASE_P4E, offset);
}

static void spansion_erase_sector(UNS_32 offset)
{
  spansion_erase(SP_CMD_SE, offset);
}


/***********************************************************************
 * Public functions
 **********************************************************************/

UNS_32 spinor_get_id(void)
{
  return (g_spinor.manufacturer_id << 8|g_spinor.device_capacity) ;
}

/***********************************************************************
 *
 * Function: spinor_init
 *
 * Purpose: Initializes basic board functions
 *
 * Processing:
 *     Does nothing
 *
 * Parameters: None
 *
 * Outputs: None
 *
 * Returns: Nothing
 *
 * Notes: None
 *
 **********************************************************************/
STATUS spinor_init(INT_32 spi_id)
{
  UNS_8 device_id[4];
  SPI_SLAVE_CONFIG_T cfg;
  UNS_32 temp;

  /* de-select the device by pulling the CS high. */
  gpio_set_outpin_high(IOCONF_SPI, 4);

  g_spinor.spi_id = spi_id;
  /* on VAL3150 SPI NOR is connected to chip select 0 */
  g_spinor.spi_slave_id = SPI_NOR_SLAVE_ID;

  /* configure the slave setting with NOR flash timings */
  cfg.sid = (SPI_SLAVE_ID_T)SPI_NOR_SLAVE_ID;
  cfg.databits = 8;
  cfg.words = SPI_FIFO_DEPTH;
  cfg.mode = SPI_MODE0;
  cfg.cs_high = 0;
  cfg.pp_delay = 0;
  cfg.inter_delay = 0;
  cfg.clk = 10000000;
  spi_ioctl(g_spinor.spi_id, SPI_SLAVE_CONFIG, (INT_32)&cfg);

  spi_ioctl(g_spinor.spi_id, SPI_SET_ACTIVE_SLAVE, SPI_NOR_SLAVE_ID);
  /* make sure SPI module is enabled */
  spi_ioctl(g_spinor.spi_id, SPI_ENABLE, 1);

  exit_deep_power_down();
  read_device_id(device_id);

  g_spinor.manufacturer_id = device_id[1];
  g_spinor.device_capacity = device_id[2];
  g_spinor.device_type = device_id[3];

  /* return if it is not ATMEL device */
  if (device_id[1] ==  MANFID_ATMEL) {
    norManufId = MANFID_ATMEL;
    g_spinor.page_size = PAGE_512; 

    do
    {
      read_status_register(&temp);
      temp = temp;
    }
    while ((temp & STATUS_RDY) == 0);

    if ((temp & STATUS_P512) == 0) {
      /* NOT configured to 512 byte mode */
      g_spinor.page_size = PAGE_528;
    }

  }
  else if (device_id[1] ==  MANFID_SPANSION) {
    norManufId = MANFID_SPANSION;
    g_spinor.page_size = PAGE_256;     

    do
    {
      read_status_register(&temp);
      temp = temp;
    }
    while ((temp & SP_STATUS_WIP) != 0);


    spansion_read_conf_register(&temp);
  } 
  else {
    return _ERROR;
  }

  poll_is_busy();

  return g_spinor.page_size;

#if 0
  /* return if it is not ATMEL device */
  if (g_spinor.manufacturer_id !=  MANFID_ATMEL)
    return _ERROR;


  do
  {
    spinor_read_status_register(&temp);
  }
  while ((temp & STATUS_RDY) == 0);


  /* Check if the device is configured as 512 page device.
     If not program it to be 512 page device.
  */
  if ((temp & STATUS_P512) == 0)
  {
    g_spinor.page_size = PAGE_528;
    /* program device to 512 mode. needs a power cycle after this.*/
    spinor_write_cfg_reg();
    return _ERROR;
  }
  else
  {
    g_spinor.page_size = PAGE_512;
  }

  spinor_enter_deep_power_down();

  return g_spinor.page_size;
#endif
}

/***********************************************************************
 *
 * Function: spinor_setTo512Mode
 *
 * Purpose: Change to 512 byte mode (only valid for Atmel NOR flash)
 *
 * Parameters: None
 *
 * Outputs: None
 *
 * Returns: None
 *
 * Notes: None
 *
 **********************************************************************/
void spinor_setTo512Mode(void)
{
  /* only valid for the Atmel NOR flash */
  if (norManufId ==  MANFID_ATMEL) {
    atmel_write_cfg_reg();
  }  
}

STATUS spinor_read(UNS_8 *dst, UNS_32 offset, UNS_32 bytecount)
{
  UNS_8 datai [5], datao [5];
  INT_32 count, rbytes;
  volatile UNS_32 status = 0;

  if (g_spinor.spi_id == 0)
    return _ERROR;

  spi_ioctl(g_spinor.spi_id, SPI_SET_ACTIVE_SLAVE, SPI_NOR_SLAVE_ID);
  exit_deep_power_down();

  datao[0] = (CMD_FAST_READ);          /* FAST_READ is safe at all supported SPI speeds... */
  datao[1] = ((offset >> 16) & 0xFF);  /* 24bit address upper byte */
  datao[2] = ((offset >>  8) & 0xFF);  /* 24bit address middle byte */
  datao[3] = ((offset >>  0) & 0xFF);  /* 24bit address least significant byte */
  datao[4] = (0);                      /* dummy byte */

  spinor_cs_low();
  gpio_set_outpin_low(IOCONF_SPI, 4);
  spinor_xfer(datao, datai, 5);

  /* wait until both FIFOs are empty */
  status = 0;
  while ((status & (SPI_ST_RX_EMPTY | SPI_ST_TX_EMPTY)) !=
         (SPI_ST_RX_EMPTY | SPI_ST_TX_EMPTY))
  {
    status = spi_ioctl(g_spinor.spi_id, SPI_GET_STATUS, SPI_FIFO_ST);
  }

  while (bytecount > 0)
  {
    count = spi_write(g_spinor.spi_id, dst, bytecount);
    status = 0;
    while ((status & SPI_ST_TX_EMPTY) != SPI_ST_TX_EMPTY)
    {
      status = spi_ioctl(g_spinor.spi_id, SPI_GET_STATUS, SPI_FIFO_ST);
    }

    rbytes = 0;
    while (count > 0)
    {
      rbytes = spi_read(g_spinor.spi_id, dst, count);
      count -= rbytes;
      dst += rbytes;
      bytecount -= rbytes;
    }
  }
  spinor_cs_high();

  gpio_set_outpin_high(IOCONF_SPI, 4);

  spinor_enter_deep_power_down();

  return _NO_ERROR;
}


/*
    Very crude write support...
    Assumes all sectors are 64k bytes, does not verify address is within
    bounds of device, etc, etc.

    Currently, write support is meant for simple testing only... !!
*/
STATUS spinor_write(UNS_8 *src, UNS_32 offset, UNS_32 bytecount)
{
  UNS_32 offset_end;


  if ((g_spinor.spi_id == 0) ||
      ((offset % g_spinor.page_size) != 0)) /* error if address is not sector aligned */
  {
    return _ERROR;
  }

  spi_ioctl(g_spinor.spi_id, SPI_SET_ACTIVE_SLAVE, SPI_NOR_SLAVE_ID);
  exit_deep_power_down();

  offset_end = (offset + bytecount);              /* end offset of entire write operation */

  while (offset < offset_end)
  {
    /* No need to erase since we use "Main Memory Page Program Through Buffer" in this driver.
    */
    if(norManufId == MANFID_ATMEL) {
      atmel_page_program(offset, src, g_spinor.page_size);
    }
    else {
      spansion_page_program(offset, src, g_spinor.page_size);
    }

    src += g_spinor.page_size;
    offset += g_spinor.page_size;
  }

  spinor_enter_deep_power_down();

  return _NO_ERROR;
}

/***********************************************************************
 *
 * Function: spinor_block_erase
 *
 * Purpose: Erase a block (4KB)
 *
 * Parameters:
 *     offset : block offset in the flash
 *
 * Outputs: None
 *
 * Returns: <0 if an error occurs
 *
 * Notes: None
 *
 **********************************************************************/
STATUS spinor_block_erase(UNS_32 offset) 
{
  if ((g_spinor.spi_id == 0))
  {
    return _ERROR;
  }

  exit_deep_power_down();

  poll_is_busy();

  if(norManufId == MANFID_ATMEL) {
    atmel_erase_block(offset);
  } else {
    spansion_wren();
    spansion_erase_block(offset);
  }

  return _NO_ERROR;

}

/***********************************************************************
 *
 * Function: spinor_sector_erase
 *
 * Purpose: Erase a block (64KB)
 *
 * Parameters:
 *     offset : sector offset in the flash
 *
 * Outputs: None
 *
 * Returns: <0 if an error occurs
 *
 * Notes: None
 *
 **********************************************************************/
STATUS spinor_sector_erase(UNS_32 offset) 
{
  if ((g_spinor.spi_id == 0))
  {
    return _ERROR;
  }

  exit_deep_power_down();

  poll_is_busy();

  if(norManufId == MANFID_ATMEL) {
    atmel_erase_sector(offset);
  } else {
    spansion_wren();
    spansion_erase_sector(offset);
  }

  return _NO_ERROR;
}


STATUS spinor_erase_from_beginning(UNS_32 bytecount)
{
  UNS_32 offset = 0;

  if (g_spinor.spi_id == 0)
    return _ERROR;

  spi_ioctl(g_spinor.spi_id, SPI_SET_ACTIVE_SLAVE, SPI_NOR_SLAVE_ID);
  exit_deep_power_down();

  while (offset < bytecount)
  {
    spinor_sector_erase(offset);
    /* check if it is first sector */
    if (offset < SECTOR_SIZE(g_spinor.page_size))
    {
      offset += SECTOR_SIZE(g_spinor.page_size) / 2;
    }
    else
    {
      offset += SECTOR_SIZE(g_spinor.page_size);
    }
  }

  spinor_enter_deep_power_down();

  return _NO_ERROR;
}


STATUS spinor_erase_sector(UNS_32 sectorIndex)
{
  UNS_32 offset;

  if (g_spinor.spi_id == 0)
    return _ERROR;

  spi_ioctl(g_spinor.spi_id, SPI_SET_ACTIVE_SLAVE, SPI_NOR_SLAVE_ID);
  exit_deep_power_down();

  offset = sectorIndex * SECTOR_SIZE(g_spinor.page_size);

  spinor_sector_erase(offset);

//  spinor_enter_deep_power_down();

  return _NO_ERROR;
}

INT_32 spinor_write_page(UNS_8 *src, UNS_32 offset)
{
  if ((g_spinor.spi_id == 0) ||
      ((offset % g_spinor.page_size) != 0)) /* error if address is not sector aligned */
  {
    return 0;
  }

  spi_ioctl(g_spinor.spi_id, SPI_SET_ACTIVE_SLAVE, SPI_NOR_SLAVE_ID);
  exit_deep_power_down();



  if(norManufId == MANFID_ATMEL) {
    atmel_page_program(offset, src, g_spinor.page_size);
  } else {
    spansion_wren();
    spansion_page_program(offset, src, g_spinor.page_size);
  }

  return g_spinor.page_size;

}

