/**
******************************************************************************
* @file nand_if.c
* @author MCD Application Team
* @version V4.0.0
* @date 21-January-2013
* @brief manage NAND operations state machine
******************************************************************************
* @attention
*
*
© COPYRIGHT 2013 STMicroelectronics
*
* Licensed under MCD-ST Liberty SW License Agreement V2, (the "License");
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.st.com/software_license_agreement_liberty_v2
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************
*/
#include "platform_config.h"
#ifdef USE_STM3210E_EVAL
/* Includes ------------------------------------------------------------------*/
#include "nand_if.h"
#include "mass_mal.h"
#include "fsmc_nand.h"
#include "memory.h"
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* extern variables-----------------------------------------------------------*/
extern uint32_t SCSI_LBA;
extern uint32_t SCSI_BlkLen;
/* Private variables ---------------------------------------------------------*/
uint16_t LUT[1024]; //Look Up Table Buffer
WRITE_STATE Write_State;
BLOCK_STATE Block_State;
NAND_ADDRESS wAddress, fAddress;
uint16_t phBlock, LogAddress, Initial_Page, CurrentZone = 0;
uint16_t Written_Pages = 0;
uint16_t LUT[1024]; //Look Up Table Buffer
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
static uint16_t NAND_CleanLUT(uint8_t ZoneNbr);
static NAND_ADDRESS NAND_GetAddress(uint32_t Address);
static uint16_t NAND_GetFreeBlock(void);
static uint16_t NAND_Write_Cleanup(void);
SPARE_AREA ReadSpareArea(uint32_t address);
static uint16_t NAND_Copy(NAND_ADDRESS Address_Src, NAND_ADDRESS Address_Dest, uint16_t PageToCopy);
static NAND_ADDRESS NAND_ConvertPhyAddress(uint32_t Address);
static uint16_t NAND_BuildLUT(uint8_t ZoneNbr);
/*******************************************************************************
* Function Name : NAND_Init
* Description : Init NAND Interface
* Input : None
* Output : None
* Return : Status
*******************************************************************************/
uint16_t NAND_Init(void)
{
uint16_t Status = NAND_OK;
FSMC_NAND_Init();
Status = NAND_BuildLUT(0);
Write_State = WRITE_IDLE;
return Status;
}
/*******************************************************************************
* Function Name : NAND_Write
* Description : write one sector by once
* Input : None
* Output : None
* Return : Status
*******************************************************************************/
uint16_t NAND_Write(uint32_t Memory_Offset, uint32_t *Writebuff, uint16_t Transfer_Length)
{
/* check block status and calculate start and end addresses */
wAddress = NAND_GetAddress(Memory_Offset / 512);
/*check Zone: if second zone is requested build second LUT*/
if (wAddress.Zone != CurrentZone)
{
CurrentZone = wAddress.Zone;
NAND_BuildLUT(CurrentZone);
}
phBlock = LUT[wAddress.Block]; /* Block Index + flags */
LogAddress = wAddress.Block ; /* save logical block */
/* IDLE state */
/****************/
if ( Write_State == WRITE_IDLE)
{/* Idle state */
if (phBlock & USED_BLOCK)
{ /* USED BLOCK */
Block_State = OLD_BLOCK;
/* Get a free Block for swap */
fAddress.Block = NAND_GetFreeBlock();
fAddress.Zone = wAddress.Zone;
Initial_Page = fAddress.Page = wAddress.Page;
/* write the new page */
FSMC_NAND_WriteSmallPage((uint8_t *)Writebuff, fAddress, PAGE_TO_WRITE);
Written_Pages++;
/* get physical block */
wAddress.Block = phBlock & 0x3FF;
if (Written_Pages == SCSI_BlkLen)
{
NAND_Write_Cleanup();
Written_Pages = 0;
return NAND_OK;
}
else
{
if (wAddress.Page == (NAND_BLOCK_SIZE - 1))
{
NAND_Write_Cleanup();
return NAND_OK;
}
Write_State = WRITE_ONGOING;
return NAND_OK;
}
}
else
{/* UNUSED BLOCK */
Block_State = UNUSED_BLOCK;
/* write the new page */
wAddress.Block = phBlock & 0x3FF;
FSMC_NAND_WriteSmallPage( (uint8_t *)Writebuff , wAddress, PAGE_TO_WRITE);
Written_Pages++;
if (Written_Pages == SCSI_BlkLen)
{
Written_Pages = 0;
NAND_Write_Cleanup();
return NAND_OK;
}
else
{
Write_State = WRITE_ONGOING;
return NAND_OK;
}
}
}
/* WRITE state */
/***************/
if ( Write_State == WRITE_ONGOING)
{/* Idle state */
if (phBlock & USED_BLOCK)
{ /* USED BLOCK */
wAddress.Block = phBlock & 0x3FF;
Block_State = OLD_BLOCK;
fAddress.Page = wAddress.Page;
/* check if next pages are in next block */
if (wAddress.Page == (NAND_BLOCK_SIZE - 1))
{
/* write Last page */
FSMC_NAND_WriteSmallPage( (uint8_t *)Writebuff , fAddress, PAGE_TO_WRITE);
Written_Pages++;
if (Written_Pages == SCSI_BlkLen)
{
Written_Pages = 0;
}
/* Clean up and Update the LUT */
NAND_Write_Cleanup();
Write_State = WRITE_IDLE;
return NAND_OK;
}
/* write next page */
FSMC_NAND_WriteSmallPage( (uint8_t *)Writebuff , fAddress, PAGE_TO_WRITE);
Written_Pages++;
if (Written_Pages == SCSI_BlkLen)
{
Write_State = WRITE_IDLE;
NAND_Write_Cleanup();
Written_Pages = 0;
}
}
else
{/* UNUSED BLOCK */
wAddress.Block = phBlock & 0x3FF;
/* check if it is the last page in prev block */
if (wAddress.Page == (NAND_BLOCK_SIZE - 1))
{
/* write Last page */
FSMC_NAND_WriteSmallPage( (uint8_t *)Writebuff , wAddress, PAGE_TO_WRITE);
Written_Pages++;
if (Written_Pages == SCSI_BlkLen)
{
Written_Pages = 0;
}
/* Clean up and Update the LUT */
NAND_Write_Cleanup();
Write_State = WRITE_IDLE;
return NAND_OK;
}
/* write next page in same block */
FSMC_NAND_WriteSmallPage( (uint8_t *)Writebuff , wAddress, PAGE_TO_WRITE);
Written_Pages++;
if (Written_Pages == SCSI_BlkLen)
{
Write_State = WRITE_IDLE;
NAND_Write_Cleanup();
Written_Pages = 0;
}
}
}
return NAND_OK;
}
/*******************************************************************************
* Function Name : NAND_Read
* Description : Read sectors
* Input : None
* Output : None
* Return : Status
*******************************************************************************/
uint16_t NAND_Read(uint32_t Memory_Offset, uint32_t *Readbuff, uint16_t Transfer_Length)
{
NAND_ADDRESS phAddress;
phAddress = NAND_GetAddress(Memory_Offset / 512);
if (phAddress.Zone != CurrentZone)
{
CurrentZone = phAddress.Zone;
NAND_BuildLUT(CurrentZone);
}
if (LUT [phAddress.Block] & BAD_BLOCK)
{
return NAND_FAIL;
}
else
{
phAddress.Block = LUT [phAddress.Block] & ~ (USED_BLOCK | VALID_BLOCK);
FSMC_NAND_ReadSmallPage ( (uint8_t *)Readbuff , phAddress, Transfer_Length / 512);
}
return NAND_OK;
}
/*******************************************************************************
* Function Name : NAND_CleanLUT
* Description : Erase old blocks & rebuild the look up table
* Input : None
* Output : None
* Return : Status
*******************************************************************************/
static uint16_t NAND_CleanLUT (uint8_t ZoneNbr)
{
#ifdef WEAR_LEVELLING_SUPPORT
uint16_t BlockIdx, LUT_Item;
#endif
/* Rebuild the LUT for the current zone */
NAND_BuildLUT (ZoneNbr);
#ifdef WEAR_LEVELLING_SUPPORT
/* Wear Leveling : circular use of free blocks */
LUT_Item = LUT [BlockIdx]
for (BlockIdx == MAX_LOG_BLOCKS_PER_ZONE ; BlockIdx < MAX_LOG_BLOCKS_PER_ZONE + WEAR_DEPTH ; BlockIdx++)
{
LUT [BlockIdx] = LUT [BlockIdx + 1];
}
LUT [ MAX_LOG_BLOCKS_PER_ZONE + WEAR_DEPTH - 1] = LUT_Item ;
#endif
return NAND_OK;
}
/*******************************************************************************
* Function Name : NAND_GetAddress
* Description : Translate logical address into a phy one
* Input : None
* Output : None
* Return : Status
*******************************************************************************/
static NAND_ADDRESS NAND_GetAddress (uint32_t Address)
{
NAND_ADDRESS Address_t;
Address_t.Page = Address & (NAND_BLOCK_SIZE - 1);
Address_t.Block = Address / NAND_BLOCK_SIZE;
Address_t.Zone = 0;
while (Address_t.Block >= MAX_LOG_BLOCKS_PER_ZONE)
{
Address_t.Block -= MAX_LOG_BLOCKS_PER_ZONE;
Address_t.Zone++;
}
return Address_t;
}
/*******************************************************************************
* Function Name : NAND_GetFreeBlock
* Description : Look for a free block for data exchange
* Input : None
* Output : None
* Return : Status
*******************************************************************************/
static uint16_t NAND_GetFreeBlock (void)
{
return LUT[MAX_LOG_BLOCKS_PER_ZONE]& ~(USED_BLOCK | VALID_BLOCK);
}
/*******************************************************************************
* Function Name : ReadSpareArea
* Description : Check used block
* Input : None
* Output : None
* Return : Status
*******************************************************************************/
SPARE_AREA ReadSpareArea (uint32_t address)
{
SPARE_AREA t;
uint8_t Buffer[16];
NAND_ADDRESS address_s;
address_s = NAND_ConvertPhyAddress(address);
FSMC_NAND_ReadSpareArea(Buffer , address_s, 1) ;
t = *(SPARE_AREA *)Buffer;
return t;
}
/*******************************************************************************
* Function Name : NAND_Copy
* Description : Copy page
* Input : None
* Output : None
* Return : Status
*******************************************************************************/
static uint16_t NAND_Copy (NAND_ADDRESS Address_Src, NAND_ADDRESS Address_Dest, uint16_t PageToCopy)
{
uint8_t Copybuff[512];
for ( ; PageToCopy > 0 ; PageToCopy-- )
{
FSMC_NAND_ReadSmallPage ((uint8_t *)Copybuff, Address_Src , 1 );
FSMC_NAND_WriteSmallPage ((uint8_t *)Copybuff, Address_Dest, 1);
FSMC_NAND_AddressIncrement(&Address_Src);
FSMC_NAND_AddressIncrement(&Address_Dest);
}
return NAND_OK;
}
/*******************************************************************************
* Function Name : NAND_Format
* Description : Format the entire NAND flash
* Input : None
* Output : None
* Return : Status
*******************************************************************************/
uint16_t NAND_Format (void)
{
NAND_ADDRESS phAddress;
SPARE_AREA SpareArea;
uint32_t BlockIndex;
for (BlockIndex = 0 ; BlockIndex < NAND_ZONE_SIZE * NAND_MAX_ZONE; BlockIndex++)
{
phAddress = NAND_ConvertPhyAddress(BlockIndex * NAND_BLOCK_SIZE );
SpareArea = ReadSpareArea(BlockIndex * NAND_BLOCK_SIZE);
if((SpareArea.DataStatus != 0)||(SpareArea.BlockStatus != 0)){
FSMC_NAND_EraseBlock (phAddress);
}
}
NAND_BuildLUT(0);
return NAND_OK;
}
/*******************************************************************************
* Function Name : NAND_Write_Cleanup
* Description : None
* Input : None
* Output : None
* Return : Status
*******************************************************************************/
static uint16_t NAND_Write_Cleanup (void)
{
uint16_t tempSpareArea [8];
uint16_t Page_Back;
if ( Block_State == OLD_BLOCK )
{
/* precopy old first pages */
if (Initial_Page != 0)
{
Page_Back = wAddress.Page;
fAddress.Page = wAddress.Page = 0;
NAND_Copy (wAddress, fAddress, Initial_Page);
wAddress.Page = Page_Back ;
}
/* postcopy remaining pages */
if ((NAND_BLOCK_SIZE - (wAddress.Page + 1)) != 0)
{
FSMC_NAND_AddressIncrement(&wAddress);
fAddress.Page = wAddress.Page;
NAND_Copy (wAddress, fAddress, NAND_BLOCK_SIZE - wAddress.Page);
}
/* assign logical address to new block */
tempSpareArea [0] = LogAddress | USED_BLOCK ;
tempSpareArea [1] = 0xFFFF;
tempSpareArea [2] = 0xFFFF;
fAddress.Page = 0x00;
FSMC_NAND_WriteSpareArea( (uint8_t *)tempSpareArea , fAddress , 1);
/* erase old block */
FSMC_NAND_EraseBlock(wAddress);
NAND_CleanLUT(wAddress.Zone);
}
else
{/* unused block case */
/* assign logical address to the new used block */
tempSpareArea [0] = LogAddress | USED_BLOCK ;
tempSpareArea [1] = 0xFFFF;
tempSpareArea [2] = 0xFFFF;
wAddress.Page = 0x00;
FSMC_NAND_WriteSpareArea((uint8_t *)tempSpareArea , wAddress, 1);
NAND_CleanLUT(wAddress.Zone);
}
return NAND_OK;
}
/*******************************************************************************
* Function Name : NAND_ConvertPhyAddress
* Description : None
* Input : physical Address
* Output : None
* Return : Status
*******************************************************************************/
static NAND_ADDRESS NAND_ConvertPhyAddress (uint32_t Address)
{
NAND_ADDRESS Address_t;
Address_t.Page = Address & (NAND_BLOCK_SIZE - 1);
Address_t.Block = Address / NAND_BLOCK_SIZE;
Address_t.Zone = 0;
while (Address_t.Block >= MAX_PHY_BLOCKS_PER_ZONE)
{
Address_t.Block -= MAX_PHY_BLOCKS_PER_ZONE;
Address_t.Zone++;
}
return Address_t;
}
/*******************************************************************************
* Function Name : NAND_BuildLUT
* Description : Build the look up table
* Input : None
* Output : None
* Return : Status
* !!!! NOTE : THIS ALGORITHM IS A SUBJECT OF PATENT FOR STMICROELECTRONICS !!!!!
*******************************************************************************/
static uint16_t NAND_BuildLUT (uint8_t ZoneNbr)
{
uint16_t pBadBlock, pCurrentBlock, pFreeBlock;
SPARE_AREA SpareArea;
/*****************************************************************************
1st step : Init.
*****************************************************************************/
/*Init the LUT (assume all blocks free) */
for (pCurrentBlock = 0 ; pCurrentBlock < MAX_PHY_BLOCKS_PER_ZONE ; pCurrentBlock++)
{
LUT[pCurrentBlock] = FREE_BLOCK; /* 12th bit is set to 1 */
}
/* Init Pointers */
pBadBlock = MAX_PHY_BLOCKS_PER_ZONE - 1;
pCurrentBlock = 0;
/*****************************************************************************
2nd step : locate used and bad blocks
*****************************************************************************/
while (pCurrentBlock < MAX_PHY_BLOCKS_PER_ZONE)
{
SpareArea = ReadSpareArea(pCurrentBlock * NAND_BLOCK_SIZE + (ZoneNbr * NAND_BLOCK_SIZE * MAX_PHY_BLOCKS_PER_ZONE));
if ((SpareArea.DataStatus == 0) || (SpareArea.BlockStatus == 0))
{
LUT[pBadBlock--] |= pCurrentBlock | (uint16_t)BAD_BLOCK ;
LUT[pCurrentBlock] &= (uint16_t)~FREE_BLOCK;
if (pBadBlock == MAX_LOG_BLOCKS_PER_ZONE)
{
return NAND_FAIL;
}
}
else if (SpareArea.LogicalIndex != 0xFFFF)
{
LUT[SpareArea.LogicalIndex & 0x3FF] |= pCurrentBlock | VALID_BLOCK | USED_BLOCK;
LUT[pCurrentBlock] &= (uint16_t)( ~FREE_BLOCK);
}
pCurrentBlock++ ;
}
/*****************************************************************************
3rd step : locate Free Blocks by scanning the LUT already built partially
*****************************************************************************/
pFreeBlock = 0;
for (pCurrentBlock = 0 ; pCurrentBlock < MAX_PHY_BLOCKS_PER_ZONE ; pCurrentBlock++ )
{
if ( !(LUT[pCurrentBlock]& USED_BLOCK))
{
do
{
if (LUT[pFreeBlock] & FREE_BLOCK)
{
LUT [pCurrentBlock] |= pFreeBlock;
LUT [pFreeBlock] &= ~FREE_BLOCK;
break;
}
pFreeBlock++;
}
while ( pFreeBlock < MAX_PHY_BLOCKS_PER_ZONE );
}
}
return NAND_OK;
}
#endif
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/