/*****************************************************************************
 *
 *   Copyright(C) 2011, Embedded Artists AB
 *   All rights reserved.
 *
 ******************************************************************************
 * 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.
 * Embedded Artists AB 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. Embedded Artists AB
 * reserves the right to make changes in the software without
 * notification. Embedded Artists AB also make no representation or
 * warranty that such application will be suitable for the specified
 * use without further testing or modification.
 *****************************************************************************/


/******************************************************************************
 * Includes
 *****************************************************************************/
#include "../board.h"

#if BOARD == BOARD_EA4357

#include "lpc_types.h"
#include "lpc43xx_scu.h"
#include "lpc43xx_timer.h"
#include "nand.h"


/******************************************************************************
 * Defines and typedefs
 *****************************************************************************/

#define K9F1G_CLE   ((volatile uint8_t *)0x1D100000)
#define K9F1G_ALE   ((volatile uint8_t *)0x1D080000)
#define K9F1G_DATA  ((volatile uint8_t *)0x1D000000)

#define K9FXX_WAIT()

#define K9FXX_READ_1            0x00                
#define K9FXX_READ_2            0x30                

#define K9FXX_SET_ADDR_A        0x00                
#define K9FXX_SET_ADDR_B        0x01                
#define K9FXX_SET_ADDR_C        0x50                
#define K9FXX_READ_ID           0x90                
#define K9FXX_RESET             0xff                
#define K9FXX_BLOCK_PROGRAM_1   0x80                
#define K9FXX_BLOCK_PROGRAM_2   0x10                
#define K9FXX_BLOCK_ERASE_1     0x60                
#define K9FXX_BLOCK_ERASE_2     0xd0                
#define K9FXX_READ_STATUS       0x70                
#define K9FXX_BUSY              (1 << 6)            
#define K9FXX_OK                (1 << 0)   

#define ID_MARKER_CODE (0xEC)
#define ID_SAMSUNG     (0xF1)

#define ID_PAGE_SZ_1KB (0x00)
#define ID_PAGE_SZ_2KB (0x01)
#define ID_PAGE_SZ_4KB (0x02)
#define ID_PAGE_SZ_8KB (0x03)

#define ID_BLOCK_SZ_64KB  (0x00)
#define ID_BLOCK_SZ_128KB (0x01)
#define ID_BLOCK_SZ_256KB (0x02)
#define ID_BLOCK_SZ_512KB (0x03)

#define ID_PAGE_SZ_1KB (0x00)
#define ID_PAGE_SZ_2KB (0x01)
#define ID_PAGE_SZ_4KB (0x02)
#define ID_PAGE_SZ_8KB (0x03)

#define ID_REDUND_SZ_8  (0x00)
#define ID_REDUND_SZ_16 (0x01)



/* This macro could be changed to check the ready pin */
#define WAIT_READY() (TIM_Waitus(25))
         

/******************************************************************************
 * External global variables
 *****************************************************************************/

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

static uint32_t pageSize   = 0;
static uint32_t blockSize  = 0;
static uint32_t reduntSize = 0;


/******************************************************************************
 * Local Functions
 *****************************************************************************/

static void pinConfig(void)
{
	/* Set up EMC pin */
	scu_pinmux(	 2	,	 9	,	MD_PLN_FAST	,	3	);//A0
	scu_pinmux(	 2	,	10	,	MD_PLN_FAST	,	3	);//A1
	scu_pinmux(	 2	,	11	,	MD_PLN_FAST	,	3	);//A2
	scu_pinmux(	 2	,	12	,	MD_PLN_FAST	,	3	);//A3
	scu_pinmux(	 2	,	13	,	MD_PLN_FAST	,	3	);//A4
	scu_pinmux(	 1	,	 0	,	MD_PLN_FAST	,	2	);//A5
	scu_pinmux(	 1	,	 1	,	MD_PLN_FAST	,	2	);//A6
	scu_pinmux(	 1	,	 2	,	MD_PLN_FAST	,	2	);//A7
	scu_pinmux(	 2	,	 8	,	MD_PLN_FAST	,	3	);//A8
	scu_pinmux(	 2	,	 7	,	MD_PLN_FAST	,	3	);//A9
	scu_pinmux(	 2	,	 6	,	MD_PLN_FAST	,	2	);//A10
	scu_pinmux(	 2	,	 2	,	MD_PLN_FAST	,	2	);//A11
	scu_pinmux(	 2	,	 1	,	MD_PLN_FAST	,	2	);//A12
	scu_pinmux(	 2	,	 0	,	MD_PLN_FAST	,	2	);//A13
	scu_pinmux(	 6	,	 8	,	MD_PLN_FAST	,	1	);//A14
	scu_pinmux(	 6	,	 7	,	MD_PLN_FAST	,	1	);//A15
	scu_pinmux(	13	,	16	,	MD_PLN_FAST	,	2	);//A16
	scu_pinmux(	13	,	15	,	MD_PLN_FAST	,	2	);//A17
	scu_pinmux(	14	,	 0	,	MD_PLN_FAST	,	3	);//A18
	scu_pinmux(	14	,	 1	,	MD_PLN_FAST	,	3	);//A19
	scu_pinmux(	14	,	 2	,	MD_PLN_FAST	,	3	);//A20
	scu_pinmux(	14	,	 3	,	MD_PLN_FAST	,	3	);//A21
	scu_pinmux(	14	,	 4	,	MD_PLN_FAST	,	3	);//A22
	scu_pinmux(	10	,	 4	,	MD_PLN_FAST	,	3	);//A23
	
	scu_pinmux(	 1	,	 7	,	MD_PLN_FAST	,	3	);//D0
	scu_pinmux(	 1	,	 8	,	MD_PLN_FAST	,	3	);//D1
	scu_pinmux(	 1	,	 9	,	MD_PLN_FAST	,	3	);//D2
	scu_pinmux(	 1	,	10	,	MD_PLN_FAST	,	3	);//D3
	scu_pinmux(	 1	,	11	,	MD_PLN_FAST	,	3	);//D4
	scu_pinmux(	 1	,	12	,	MD_PLN_FAST	,	3	);//D5
	scu_pinmux(	 1	,	13	,	MD_PLN_FAST	,	3	);//D6
	scu_pinmux(	 1	,	14	,	MD_PLN_FAST	,	3	);//D7
	scu_pinmux(	 5	,	 4	,	MD_PLN_FAST	,	2	);//D8
	scu_pinmux(	 5	,	 5	,	MD_PLN_FAST	,	2	);//D9
	scu_pinmux(	 5	,	 6	,	MD_PLN_FAST	,	2	);//D10
	scu_pinmux(	 5	,	 7	,	MD_PLN_FAST	,	2	);//D11
	scu_pinmux(	 5	,	 0	,	MD_PLN_FAST	,	2	);//D12
	scu_pinmux(	 5	,	 1	,	MD_PLN_FAST	,	2	);//D13
	scu_pinmux(	 5	,	 2	,	MD_PLN_FAST	,	2	);//D14
	scu_pinmux(	 5	,	 3	,	MD_PLN_FAST	,	2	);//D15
	scu_pinmux(	13	,	 2	,	MD_PLN_FAST	,	2	);//D16
	scu_pinmux(	13	,	 3	,	MD_PLN_FAST	,	2	);//D17
	scu_pinmux(	13	,	 4	,	MD_PLN_FAST	,	2	);//D18
	scu_pinmux(	13	,	 5	,	MD_PLN_FAST	,	2	);//D19
	scu_pinmux(	13	,	 6	,	MD_PLN_FAST	,	2	);//D20
	scu_pinmux(	13	,	 7	,	MD_PLN_FAST	,	2	);//D21
	scu_pinmux(	13	,	 8	,	MD_PLN_FAST	,	2	);//D22
	scu_pinmux(	13	,	 9	,	MD_PLN_FAST	,	2	);//D23
	scu_pinmux(	14	,	 5	,	MD_PLN_FAST	,	3	);//D24
	scu_pinmux(	14	,	 6	,	MD_PLN_FAST	,	3	);//D25
	scu_pinmux(	14	,	 7	,	MD_PLN_FAST	,	3	);//D26
	scu_pinmux(	14	,	 8	,	MD_PLN_FAST	,	3	);//D27
	scu_pinmux(	14	,	 9	,	MD_PLN_FAST	,	3	);//D28
	scu_pinmux(	14	,	10	,	MD_PLN_FAST	,	3	);//D29
	scu_pinmux(	14	,	11	,	MD_PLN_FAST	,	3	);//D30
	scu_pinmux(	14	,	12	,	MD_PLN_FAST	,	3	);//D31
		
	scu_pinmux(	 1	,	 3	,	MD_PLN_FAST	,	3	);//OE
	scu_pinmux(	 1	,	 6	,	MD_PLN_FAST	,	3	);//WE
	
	scu_pinmux(	 1	,	 4	,	MD_PLN_FAST	,	3	);//BLS0
	scu_pinmux(	 6	,	 6	,	MD_PLN_FAST	,	1	);//BLS1	
	scu_pinmux(	13	,	13	,	MD_PLN_FAST	,	2	);//BLS2
	scu_pinmux(	13	,	10	,	MD_PLN_FAST	,	2	);//BLS3
	
	scu_pinmux(	 1	,	 5	,	MD_PLN_FAST	,	3	);//CS0	
	scu_pinmux(	 6	,	 3	,	MD_PLN_FAST	,	3	);//CS1
	scu_pinmux(	13	,	12	,	MD_PLN_FAST	,	2	);//CS2
	scu_pinmux(	13	,	11	,	MD_PLN_FAST	,	2	);//CS3
}


static uint32_t nandReadId(void)
{
  uint8_t a, b, c, d;
  volatile uint8_t *pCLE;
  volatile uint8_t *pALE;
  volatile uint8_t *pData;
  
  pCLE  = K9F1G_CLE;
  pALE  = K9F1G_ALE;
  pData = K9F1G_DATA;
    
  *pCLE = K9FXX_READ_ID;
  *pALE = 0;
    
  a = *pData;
  b = *pData;
  c = *pData;
  d = *pData;
  
    
  return (a << 24) | (b << 16) | (c << 8) | d;
}

static uint8_t nandStatus(void)
{
  uint8_t status = 0;
  volatile uint8_t *pCLE;
  volatile uint8_t *pALE;
  volatile uint8_t *pData;
  
  pCLE  = K9F1G_CLE;
  pALE  = K9F1G_ALE;
  pData = K9F1G_DATA;
    
  *pCLE = K9FXX_READ_STATUS;
  *pALE = 0;
    
  status = *pData;
      
  /* remove bits not used */
  return (status & 0xC1);  
}

static void nandWaitReady(void) 
{
  while( !(nandStatus() & (1<<6)) );
}

/******************************************************************************
 * Public Functions
 *****************************************************************************/



/******************************************************************************
 *
 * Description:
 *    Initialize the NAND Flash
 *
 * Returns:
 *    TRUE if initialization successful; otherwise FALSE
 *
 *****************************************************************************/
uint32_t nand_init (void)
{
  uint32_t nandId = 0;
  TIM_TIMERCFG_Type timerCfg;

//  LPC_SC->PCONP     |= 0x00000800;
  LPC_EMC->CONTROL   = 0x00000001;
  LPC_EMC->CONFIG    = 0x00000000;

  pinConfig();

  TIM_ConfigStructInit(TIM_TIMER_MODE, &timerCfg);
  TIM_Init(LPC_TIMER0, TIM_TIMER_MODE, &timerCfg);

  LPC_EMC->STATICCONFIG1   = 0x00000080;

  LPC_EMC->STATICWAITWEN1  = 0x00000002; 
  LPC_EMC->STATICWAITOEN1  = 0x00000002; 
  LPC_EMC->STATICWAITRD1   = 0x00000008; 
  LPC_EMC->STATICWAITPAG1  = 0x0000001f; 
  LPC_EMC->STATICWAITWR1   = 0x00000008; 
  LPC_EMC->STATICWAITTURN1 = 0x0000000f;

  nandId = nandReadId();

  if ((nandId & 0xffff0000) != 
    (((uint32_t)(ID_MARKER_CODE) << 24) | ID_SAMSUNG << 16)) {
    /* unknown NAND chip */
    return FALSE;
  }

  pageSize   = 1024 * (1 << (nandId & 0x03));  
  blockSize  = 64*1024 * (1 << ((nandId>>4) & 0x03));
  reduntSize = 8 * (1 << ((nandId >> 1) & 0x1));

                         
  return TRUE;
}

/******************************************************************************
 *
 * Description:
 *    Get the page size of the NAND flash
 *
 * Returns:
 *    page size in bytes
 *
 *****************************************************************************/
uint32_t nand_getPageSize(void)
{
  return pageSize;
}

/******************************************************************************
 *
 * Description:
 *    Get the block size of the NAND flash
 *
 * Returns:
 *    block size in bytes
 *
 *****************************************************************************/
uint32_t nand_getBlockSize(void)
{
  return blockSize;
}

/******************************************************************************
 *
 * Description:
 *    Get the redundant (spare) size per page
 *
 * Returns:
 *    redundant/spare size in bytes
 *
 *****************************************************************************/
uint32_t nand_getRedundantSize(void)
{
  return reduntSize * (pageSize/512);
}

/******************************************************************************
 *
 * Description:
 *    Check if a block is valid
 *
 * Returns:
 *    TRUE if the block is valid; otherwise FALSE
 *
 *****************************************************************************/
uint32_t nand_isBlockValid(uint32_t block)
{
  uint32_t addr = 0;
  uint32_t page = 0;

  volatile uint8_t *pCLE;
  volatile uint8_t *pALE;
  volatile uint8_t *pData;

  
  pCLE  = K9F1G_CLE;
  pALE  = K9F1G_ALE;
  pData = K9F1G_DATA;

  if (block >= NAND_NUM_BLOCKS) {
    return FALSE;
  }

  addr = block * (blockSize/pageSize);

  /* 
   * Check page 0 and page 1 in each block. If the first byte
   * in the spare area (of either page 0 or page 1) is != 0xFF 
   * the block is invalid.
   */

  nandWaitReady();

  for (page = 0; page < 2; page++) {
    addr += page;

    *pCLE = K9FXX_READ_1;
    *pALE = (uint8_t) (pageSize & 0x00FF);
    *pALE = (uint8_t)((pageSize & 0xFF00) >> 8);
    *pALE = (uint8_t)((addr & 0x00FF));
    *pALE = (uint8_t)((addr & 0xFF00) >> 8);
    *pCLE = K9FXX_READ_2;

    WAIT_READY();

    if (*pData != 0xFF) {
      return FALSE;
    }
        
  }

  return TRUE;
}


/******************************************************************************
 *
 * Description:
 *    Read a page from the NAND memory
 *
 * Params:
 *    block - block number to read from
 *    page  - page with�n block to read from
 *    pageBuf - data is copied to this buffer. The size must be at least 
 *              pageSize.
 *
 * Returns:
 *    TRUE if read successful; otherwise FALSE
 *
 *****************************************************************************/
uint32_t nand_readPage(uint32_t block, uint32_t page, uint8_t* pageBuf)
{
  uint32_t i = 0;
  uint32_t addr = 0;

  volatile uint8_t *pCLE;
  volatile uint8_t *pALE;
  volatile uint8_t *pData;

  
  pCLE  = K9F1G_CLE;
  pALE  = K9F1G_ALE;
  pData = K9F1G_DATA;

  if (block >= NAND_NUM_BLOCKS) {
    return FALSE;
  }

  if (page >= blockSize/pageSize) {
    return FALSE;
  }

  addr = block * (blockSize/pageSize) + page;

  /*
   * Always reading from start of a page address.
   * This means that the column address is always 0.
   */

  *pCLE = K9FXX_READ_1;
  *pALE = 0;
  *pALE = 0;
  *pALE = (uint8_t)((addr & 0x00FF));
  *pALE = (uint8_t)((addr & 0xFF00) >> 8);
  *pCLE = K9FXX_READ_2;

  WAIT_READY(); 
  

  for (i = 0; i < pageSize; i++) {
    *pageBuf++ = *pData;  
  }


  return TRUE;
}

/******************************************************************************
 *
 * Description:
 *    Write a page of data to the NAND memory
 *
 * Params:
 *    block - block number to write to
 *    page  - page within block to write to
 *    pageBuf - data is copied from this buffer. The size must be at least 
 *              pageSize.
 *
 * Returns:
 *    TRUE if write successful; otherwise FALSE
 *
 *****************************************************************************/
uint32_t nand_writePage(uint32_t block, uint32_t page, uint8_t* pageBuf)
{
  uint32_t i = 0;
  uint32_t addr = 0;

  volatile uint8_t *pCLE;
  volatile uint8_t *pALE;
  volatile uint8_t *pData;

  
  pCLE  = K9F1G_CLE;
  pALE  = K9F1G_ALE;
  pData = K9F1G_DATA;

  if (block >= NAND_NUM_BLOCKS) {
    return FALSE;
  }

  if (page >= blockSize/pageSize) {
    return FALSE;
  }

  addr = block * (blockSize/pageSize) + page;

  /*
   * Always writing to start of a page address.
   * This means that the column address is always 0.
   */ 

  *pCLE = K9FXX_BLOCK_PROGRAM_1;
  *pALE = 0; 
  *pALE = 0; 
  *pALE = (uint8_t)((addr & 0x00FF));
  *pALE = (uint8_t)((addr & 0xFF00) >> 8);


  for (i = 0; i < pageSize; i++) {
    *pData = *pageBuf++;
  }

  *pCLE = K9FXX_BLOCK_PROGRAM_2;

  TIM_Waitus(700);  
  nandWaitReady();
  
  return ((nandStatus() & 0x01) != 0x01); 
}

/******************************************************************************
 *
 * Description:
 *    Erase a block
 *
 * Params:
 *    block - block number to erase
 *
 * Returns:
 *    TRUE if eras successful; otherwise FALSE
 *
 *****************************************************************************/
uint32_t nand_eraseBlock(uint32_t block)
{
  uint32_t addr = 0;

  volatile uint8_t *pCLE;
  volatile uint8_t *pALE;

  pCLE  = K9F1G_CLE;
  pALE  = K9F1G_ALE;

  if (block >= NAND_NUM_BLOCKS) {
    return FALSE;
  }
    
  addr = block * (blockSize/pageSize);

  *pCLE = K9FXX_BLOCK_ERASE_1;
  *pALE = (uint8_t)(addr & 0x00FF);
  *pALE = (uint8_t)((addr & 0xFF00) >> 8);
  *pCLE = K9FXX_BLOCK_ERASE_2;

  TIM_Waitus(700);
  nandWaitReady();

  return ((nandStatus() & 0x01) != 0x01); 
}

#endif
