硬汉嵌入式论坛

 找回密码
 立即注册
查看: 2066|回复: 8
收起左侧

RL-FlashFS开源出来的NAND Flash Translation Layer非常有参考价值

[复制链接]

1万

主题

7万

回帖

11万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
117512
QQ
发表于 2024-6-4 10:00:25 | 显示全部楼层 |阅读模式



源码如下:

[C] 纯文本查看 复制代码
/*------------------------------------------------------------------------------
 * MDK Middleware - Component ::File System
 * Copyright (c) 2004-2023 Arm Limited (or its affiliates). All rights reserved.
 *------------------------------------------------------------------------------
 * Name:    fs_nftl.c
 * Purpose: NAND FTL Implementation
 *----------------------------------------------------------------------------*/

#include <string.h>
#include "cmsis_compiler.h"

#include "fs_nftl.h"
#include "fs_nand_media.h"
#include "fs_evr.h"

/* Access point to the NAND Media driver */
extern NAND_MEDIA_DRIVER NAND_MediaDriver;


/*-----------------------------------------------------------------------------
 *      Local function prototypes
 *----------------------------------------------------------------------------*/
static uint32_t ScanBlock     (NAND_FTL_DEV *ftl, uint16_t bn, uint32_t lsn, uint32_t *pageNum);
static uint32_t AllocBlock    (NAND_FTL_DEV *ftl, uint16_t *bn, uint32_t restriction, uint32_t area);
static uint32_t UpdateBTT     (NAND_FTL_DEV *ftl, uint16_t lbn, uint16_t *primBN, uint16_t *replBN);
static uint32_t GcRun         (NAND_FTL_DEV *ftl, uint16_t lbn, BTT_ITEM *btt, uint32_t gcArea, uint16_t allocBn);
static uint32_t MoveBlock     (NAND_FTL_DEV *ftl, uint16_t srcBN, uint16_t destBN, uint32_t endPg, uint32_t area);
static uint32_t RelocBlock    (NAND_FTL_DEV *ftl, uint16_t *pbn, uint32_t endPg, uint32_t area, uint32_t eraseBlock);
static uint32_t ForceDataGc   (NAND_FTL_DEV *ftl);
static uint32_t MarkBlockBad  (NAND_FTL_DEV *ftl, uint16_t pbn, uint32_t eraseBlock);

static void     EncodeECC     (NAND_FTL_DEV *ftl, uint8_t *pgBuf);
static uint32_t DecodeECC     (NAND_FTL_DEV *ftl, uint8_t *pgBuf);


/**
  NAND Flash Translation Layer Media event handler

  \param[in]      event     NAND media driver event flags
  \param[in]      ftl       FTL instance object
*/
void ftl_DrvEvent (uint32_t event, NAND_FTL_DEV *ftl) {
  (void)event;
  (void)ftl;
  /* Handle event here */
}


/**
  Callback function used to override the default NAND page layout setup.

  Using this function it is possible to override the default NAND page layout
  used by the NAND Flash Translation Layer.

  Setup offers the following configuration options:
  ofs_lsn: Position in spare area, where logical sector number (LSN) is
           placed. Usually, this is the first byte of spare, therefore
           ofs_lsn has value zero. LSN is a 32-bit value.
  ofs_dcm: Position of data corrupted marker in spare area. Usually, this byte
           is the fifth byte of spare and ofs_dcm has value four.
  ofs_bbm: Position of bad block marker (BBM) in spare area and is usually
           placed as the sixth byte of spare, ofs_bbm has value 5.
  ofs_ecc: Position of the first byte of Error Correction Code (ECC) bytes in
           the spare area. First ECC byte is default seventh byte of spare
           (ofs_ecc == 6). This value is used by flash translation layer only
           if ECC is encoded and decoded in software.

  sector_inc: Provides information about user data sector locations within
              page. If page contains multiple sectors, first sector always
              starts at the beginning of the page (byte zero). Second sector
              starts at sector_inc, third sector at sector_inc + sector_inc
              and so on.
  spare_ofs:  Provides information about the location of the first spare area
              byte within page.
  spare_inc:  Provides information about spare area locations within page.
              If page contains multiple sectors, first byte of the first
              spare area is determined by reading spare_ofs value. Location of
              the first byte of the second spare, can be determined by adding
              spare_inc value to the spare_ofs value.

  \param[in]     drive_num NAND drive number
  \param[in,out] layout    page layout definition structure
*/
__WEAK void fs_nand_setup_layout (uint32_t drive_num, NAND_PAGE_LAYOUT *layout) {
  (void)drive_num;
  (void)layout;
/*
  Example: default spare metadata layout
  layout->spare.ofs_lsn  = 0;
  layout->spare.ofs_dcm  = 4;
  layout->spare.ofs_bbm  = 5;
  layout->spare.ofs_ecc  = 6;

  Example: default |sector|spare| ... |sector|spare| layout
  layout->sector_inc     = 528;
  layout->spare_ofs      = 512;
  layout->spare_inc      = 528;

  Example: default |sector|sector| ... |spare|spare| layout
  layout->sector_inc     = 512;
  layout->spare_ofs      = 2112;
  layout->spare_inc      = 16;
*/
}


/**
  Callback function used to retrieve NAND feature address

  This function is called continuously until it returns -1. Parameter addr
  indicates which feature address was returned on the previous call and is
  equal to -1 when function is called for the first time.

  \param[in]  drive_num NAND drive number
  \param[in]  addr      previous feature address
  \return feature address or -1 to stop the iteration
*/
__WEAK int32_t fs_nand_feature_addr (uint32_t drive_num, int32_t addr) {
  (void)drive_num;

  /* Not implemented by default */
  return (addr);
}


/**
  Callback function used to review and modify NAND feature parameters

  This function provides feature parameters retrieved from the address which was
  previously returned by the GetFeatureAddress function.
  Application can change the parameters and return 0 to modify them. In this case
  feature parameters will be written back to the device. When function returns -1
  feature parameters are not modified and are left intact.

  \param[in]     drive_num NAND drive number
  \param[in]     addr      feature address
  \param[in,out] fp        feature parameters
  \return 0 to set the parameters or -1 to continue without changes
*/
__WEAK int32_t fs_nand_feature_param (uint32_t drive_num, int32_t addr, NAND_FEATURE_PARAMETER *fp) {
  (void)drive_num;
  (void)addr;
  (void)fp;

  /* Not implemented by default */
  return (-1);
}


/**
  Retrieve NAND feature address.
  See \ref fs_nand_feature_addr

  \param[in]  h         NAND media handle
  \param[in]  addr      previous feature address
  \return feature address or -1 to stop the iteration
*/
static int32_t get_feature_addr (NAND_MEDIA_HANDLE *h, int32_t addr) {
  int32_t next;

  /* Break function execution (default) */
  next = -1;

  if (addr == -1) {
    /* First function call */
    if (h->dev->sw_ecc == 2U) {
      /* EZ NAND */
      if (h->jedec_id == JEDEC_ID_MICRON_TECH) {
        /* Special handling for Micron devices which require "Enable ECC" bit */
        next = 0x90; /* Array operation mode */
      }
    }
  }

  return (next); /* Return value is the address in the NAND "Get Features" command */
}


/**
  Review and modify NAND feature parameters.
  See \ref fs_nand_feature_param

  \param[in]     h         NAND media handle
  \param[in]     addr      feature address
  \param[in,out] fp        feature parameters
  \return 0 to set the parameters or -1 to continue without changes
*/
static int32_t set_feature_param (NAND_MEDIA_HANDLE *h, int32_t addr, NAND_FEATURE_PARAMETER *fp) {
  int32_t modify;

  modify = -1;

  if (h->jedec_id == JEDEC_ID_MICRON_TECH) {
    /* Special handling for Micron devices which require "Enable ECC" bit */
    if (addr == 0x90U) {
      /* Received "Array operation mode" feature parameters */
      fp->P1 = 0x08U; /* Enable internal ECC */
      modify = 0;
    }
  }

  /*  0: modify parameters using NAND "Set Features" command*/
  /* -1: continue without changes */
  return (modify);
}


/* ------------------------- Media driver functions ------------------------- */


/**
  NAND media driver ReadPage wrapper

  \param[in]     row       page row address
  \param[out]    buf       page buffer
  \param[in]     ftl       FTL instance object
*/
static uint32_t Drv_ReadPage (uint32_t row, uint8_t *buf, NAND_FTL_DEV *ftl) {
  int32_t  rtv;
  uint32_t status;
  uint8_t  nand_status;

  EvrFsNFTL_PageRead (ftl->Media->instance, row / ftl->Media->dev->page_count, row % ftl->Media->dev->page_count);

  rtv = NAND_MediaDriver.ReadPage (ftl->Media, row, 0, buf, pDev->page_size);

  switch (rtv) {
    case NAND_OK:
      status = FTL_OK;

      if (pDev->sw_ecc == 2) {
        /* EZ NAND is used, check ECC status */
        NAND_MediaDriver.ReadStatus (ftl->Media, &nand_status);

        EvrFsNFTL_StatusRead (ftl->Media->instance, nand_status);

        if (nand_status & NAND_STAT_CSP) {
          status = FTL_ERROR_ECC_COR;
        }
      }
      break;

    case NAND_ERROR_ECC_CORRECTED:
      status = FTL_ERROR_ECC_COR;
      break;

    case NAND_ERROR_ECC_FAILED:
      status = FTL_ERROR_ECC_FAIL;
      break;

    default:
      /* Read page error */
      EvrFsNFTL_PageReadFailed (ftl->Media->instance, row);
      status = FTL_ERROR_DRIVER;
      break;
  }

  return (status);
}


/**
  NAND media driver WritePage wrapper

  \param[in]     row       page row address
  \param[in]     buf       page buffer
  \param[in]     ftl       FTL instance object
*/
static uint32_t Drv_WritePage (uint32_t row, uint8_t *buf, NAND_FTL_DEV *ftl) {
  int32_t  rtv;
  uint32_t tick, tout;
  uint32_t exec_status;
  uint8_t  nand_status;

  EvrFsNFTL_PageWrite (ftl->Media->instance, row / ftl->Media->dev->page_count, row % ftl->Media->dev->page_count);

  rtv = NAND_MediaDriver.WritePage (ftl->Media, row, 0, buf, pDev->page_size);

  if (rtv == NAND_OK) {
    nand_status = 0;

    /* Wait until NAND ready or timeout expires */
    tout = fs_get_sys_tick_us (NAND_WRITE_TIMEOUT);
    tick = fs_get_sys_tick();
    do {
      NAND_MediaDriver.ReadStatus (ftl->Media, &nand_status);

      if (nand_status & NAND_STAT_RDY) {
        break;
      }
    } while ((fs_get_sys_tick() - tick) < tout);

    if ((nand_status & NAND_STAT_RDY) == 0) {
      NAND_MediaDriver.ReadStatus (ftl->Media, &nand_status);
    }

    EvrFsNFTL_StatusRead (ftl->Media->instance, nand_status);

    if (nand_status & NAND_STAT_RDY) {
      if (nand_status & NAND_STAT_FAIL) {
        /* Page program failed */
        EvrFsNFTL_PageProgramStatusErr (ftl->Media->instance, row);
        exec_status = FTL_ERROR_PROGRAM;
      } else {
        /* Page program completed */
        exec_status = FTL_OK;
      }
    }
    else {
      /* Page program timeout expired */
      EvrFsNFTL_PageProgramTimeout (ftl->Media->instance, row);
      exec_status = FTL_ERROR_TIMEOUT;
    }
  }
  else {
    /* Write error */
    EvrFsNFTL_PageProgramFailed (ftl->Media->instance, row);
    /* Driver error */
    exec_status = FTL_ERROR_DRIVER;
  }

  return (exec_status);
}


/**
  NAND media driver EraseBlock wrapper

  \param[in]     row       page row address
  \param[in]     ftl       FTL instance object
*/
static uint32_t Drv_EraseBlock (uint32_t row, NAND_FTL_DEV *ftl) {
  int32_t  rtv;
  uint32_t tick, tout;
  uint32_t exec_status;
  uint8_t  nand_status;

  EvrFsNFTL_BlockErase (ftl->Media->instance, row / ftl->Media->dev->page_count);

  rtv = NAND_MediaDriver.EraseBlock (ftl->Media, row);

  if (rtv == NAND_OK) {
    nand_status = 0;

    /* Wait until NAND ready or timeout expires */
    tout = fs_get_sys_tick_us (NAND_ERASE_TIMEOUT);
    tick = fs_get_sys_tick();
    do {
      NAND_MediaDriver.ReadStatus (ftl->Media, &nand_status);

      if (nand_status & NAND_STAT_RDY) {
        break;
      }
    } while ((fs_get_sys_tick() - tick) < tout);

    if ((nand_status & NAND_STAT_RDY) == 0) {
      NAND_MediaDriver.ReadStatus (ftl->Media, &nand_status);
    }

    EvrFsNFTL_StatusRead (ftl->Media->instance, nand_status);

    if (nand_status & NAND_STAT_RDY) {
      if (nand_status & NAND_STAT_FAIL) {
        /* Block erase failed */
        EvrFsNFTL_BlockEraseStatusErr (ftl->Media->instance, row);
        exec_status = FTL_ERROR_ERASE;
      } else {
        /* Block erased */
        exec_status = FTL_OK;
      }
    }
    else {
      /* Block erase timed out */
      EvrFsNFTL_BlockEraseTimeout (ftl->Media->instance, row);
      exec_status = FTL_ERROR_TIMEOUT;
    }
  }
  else {
    /* Block erase error (driver error) */
    EvrFsNFTL_BlockEraseFailed (ftl->Media->instance, row);
    /* Driver error */
    exec_status = FTL_ERROR_DRIVER;
  }

  return (exec_status);
}


/**
  NAND media driver ResetDevice wrapper

  \param[in]     ftl       FTL instance object
*/
static uint32_t Drv_ResetDevice (NAND_FTL_DEV *ftl) {
  uint32_t tick, tout;
  uint32_t exec_status;
  uint8_t  nand_status;

  EvrFsNFTL_ResetDevice (ftl->Media->instance);

  if (NAND_MediaDriver.ResetDevice (ftl->Media) == NAND_OK) {
    /* Wait until device ready */
    tout = fs_get_sys_tick_us (NAND_RESET_TIMEOUT);
    tick = fs_get_sys_tick();
    do {
      NAND_MediaDriver.ReadStatus (ftl->Media, &nand_status);
      if (nand_status & NAND_STAT_RDY) {
        break;
      }
    } while ((fs_get_sys_tick() - tick) < tout);

    NAND_MediaDriver.ReadStatus (ftl->Media, &nand_status);

    if (nand_status & NAND_STAT_RDY) {
      exec_status = FTL_OK;
    }
    else {
      /* Timeout expired */
      EvrFsNFTL_ResetDeviceTimeout (ftl->Media->instance);
      exec_status = FTL_ERROR_TIMEOUT;
    }
  }
  else {
    /* Media reset failed */
    EvrFsNFTL_ResetDeviceFailed (ftl->Media->instance);
    exec_status = FTL_ERROR_DRIVER;
  }

  return (exec_status);
}


/**
  NAND media driver ReadParamPage wrapper

  \param[in]     ftl       FTL instance object
*/
static uint32_t Drv_ReadParameters (NAND_FTL_DEV *ftl) {
  uint32_t status;
  NAND_PARAM_PAGE *p;

  if (NAND_MediaDriver.ReadParamPage (ftl->Media, 0, ftl->PgBuf, 256) == NAND_OK) {
    /* Examine parameter page values */
    p = (NAND_PARAM_PAGE *)ftl->PgBuf;

    if ((p->signature[0] == 'O') && (p->signature[1] == 'N') &&
        (p->signature[2] == 'F') && (p->signature[3] == 'I')) {
      /* ONFI compliant device, save manufacturer ID */
      ftl->Media->jedec_id =  p->jedec_id;

      /* Remember correctability level */
      ftl->Media->ecc_req = p->ecc_correctability;
    }
    status = FTL_OK;
  }
  else {
    status = FTL_ERROR_DRIVER;
  }

  return (status);
}


/**
  NAND media driver GetFeatures/SetFeatures wrapper

  \param[in]     ftl       FTL instance object
*/
static uint32_t Drv_SetupFeatures (NAND_FTL_DEV *ftl) {
  int32_t  rtv;
  uint32_t status, mode;
  int32_t  addr, val;
  NAND_FEATURE_PARAMETER fparam;

  mode   = 0U;
  addr   = -1;
  status = FTL_OK;

  while (status == FTL_OK) {
    if (mode == 0U) {
      /* Retrieve feature address */
      addr = get_feature_addr (ftl->Media, addr);
    } else {
      /* Retrieve vendor feature address from the application */
      addr = fs_nand_feature_addr (ftl->Media->instance, addr);
    }

    if (addr == -1) {
      if (mode == 0U) {
        /* Set to application mode */
        mode = 1U;
      }
      else {
        break;
      }
    }
    else {
      /* Retrieve features from specified address */
      rtv = NAND_MediaDriver.GetFeatures (ftl->Media, (uint8_t)addr, (uint8_t *)&fparam, 4);

      if (rtv != NAND_OK) {
        status = FTL_ERROR;
      }
      else {
        /* Deliver feature parameters */
        if (mode == 0U) {
          /* To NFTL */
          val = set_feature_param (ftl->Media, addr, &fparam);
        } else {
          /* To application */
          val = fs_nand_feature_param (ftl->Media->instance, addr, &fparam);
        }

        if (val == 0) {
          /* Update feature parameters */
          rtv = NAND_MediaDriver.SetFeatures (ftl->Media, (uint8_t)addr, (uint8_t *)&fparam, 4);

          if (rtv != NAND_OK) {
            status = FTL_ERROR;
          }
        }
      }
    }
  }

  return (status);
}


/**
  Determine ECC configuration and manage page layout

  \param[in]     ftl       FTL instance object
*/
static uint32_t Drv_ManageECC (NAND_FTL_DEV *ftl) {
  const NAND_DEVICE *dev = ftl->Media->dev;
  uint32_t status;

  status = FTL_OK;

  if (dev->sw_ecc == 2) {
    /* EZ NAND on-chip ECC */
    if (dev->ecc != NULL) {
      /* ECC info structure is provided */
      if (NAND_EccToLayout (dev->page_layout, dev->ecc) != NAND_OK) {
        status = FTL_ERROR;
      }
    }
    else {
      status = FTL_ERROR;
    }
  }
  else {
    if (dev->sw_ecc == 3) {
      /* Hardware ECC, take driver provided ECC info structure */
      if (NAND_SetupLayout (ftl->Media) != NAND_OK) {
        status = FTL_ERROR;
      }
    }
  }

  /* Provide layout override support */
  fs_nand_setup_layout (ftl->Media->instance, &ftl->PgLay);

  EvrFsNFTL_SetupPageLayout  (ftl->Media->instance, ftl->PgLay.sector_inc,    ftl->PgLay.spare_ofs,     ftl->PgLay.spare_inc);
  EvrFsNFTL_SetupSpareLayout (ftl->Media->instance, ftl->PgLay.spare.ofs_lsn, ftl->PgLay.spare.ofs_dcm, ftl->PgLay.spare.ofs_bbm, ftl->PgLay.spare.ofs_ecc);

  return (status);
}


/* ------------------------- NFTL functions --------------------------------- */


/**
  Get logical sector number and block type from buffer

  \param[in]     *buf       data buffer
  \param[out]    *blType    block type
  \return logical sector number
*/
static uint32_t GetLSN(uint8_t *buf, uint32_t *blType) {
  uint32_t lsn;

  lsn  = (uint32_t)buf[0];
  lsn |= (uint32_t)buf[1] << 8;
  lsn |= (uint32_t)buf[2] << 16;
  lsn |= (uint32_t)buf[3] << 24;

  if(blType != NULL) {
    *blType = (lsn & BIT_PRIM) ? TYP_PRIM : TYP_REPL;
  }
  return (lsn & 0x7FFFFFFF);
}

/**
  Set logical sector number and block type to output buffer

  \param[in]      lsn       logical sector number
  \param[in]      blType    block type
  \param[out]     buf       output buffer
*/
static void SetLSN(uint32_t lsn, uint32_t blType, uint8_t *buf) {

  (blType == TYP_PRIM) ? (lsn |= BIT_PRIM) : (lsn &= ~BIT_PRIM);

  buf[0] = (uint8_t)(lsn);
  buf[1] = (uint8_t)(lsn >> 8);
  buf[2] = (uint8_t)(lsn >> 16);
  buf[3] = (uint8_t)(lsn >> 24);
}

/**
  Calc n = Log2(x) if x == 2^n

  \param[in]      x         number to the power of two
  \return n or INVALID for x == 0
*/
static uint32_t Log2(uint32_t x) {
  uint32_t n;

  if (x == 0) return INVALID;

  for (n = 31; n != 0; n--) {
    if (x & (1UL << n)) break;
  }
  return (n);
}

/**
  Select new slot from page cache array

  \param[in,out]  Ca        indexing cache control block
  \return slot number
*/
__STATIC_FORCEINLINE uint32_t GetPageCacheSlot (NAND_FTL_CACHE *Ca) {
  uint32_t slot;

  /* Select next slot */
  slot = Ca->CachePS++;
  if (Ca->CachePS == Ca->CachedPages) { Ca->CachePS = 0; }
  return slot;
}

/**
  Cached page read

  \param[in,out]  ftl       FTL instance object
  \param[in]      row       row address of a page
  \param[in]      col       column address of a page
  \param[in]      sz        size of data to read
  \return execution status FTL_STATUS
*/
static uint32_t CachePgRead (NAND_FTL_DEV *ftl, uint32_t row, uint32_t col, uint32_t sz) {
  uint32_t slot, rtv;
  uint32_t usecache = 0;

  EvrFsNFTL_CacheRead (ftl->Media->instance, row / ftl->Media->dev->page_count, row % ftl->Media->dev->page_count, col);

  rtv = FTL_OK;

  for (slot = 0; slot < ftl->Ca.CachedPages; slot++) {
    if ((ftl->Ca.Page[slot].row & ~BIT_UNCOR) == row) {
      usecache = 1;

      if (ftl->Ca.Page[slot].row & BIT_UNCOR) {
        /* Return ECC Error */
        rtv = FTL_ERROR_ECC;
      }
      break;
    }
  }

  /* Read from flash to cache if row isn't cached */
  if (!usecache) {
    slot = GetPageCacheSlot (&ftl->Ca);
    ftl->Ca.Page[slot].row = row;

    rtv = Drv_ReadPage (row, ftl->Ca.Page[slot].buf, ftl);

    if (rtv == FTL_OK) {
      if (pDev->sw_ecc == 1) {
        /* Hamming ECC is enabled, decode ECC */
        rtv = DecodeECC(ftl, ftl->Ca.Page[slot].buf);
      }
    }

    if (rtv == FTL_ERROR_ECC_FAIL || rtv == FTL_ERROR_ECC_COR) {
      if (rtv == FTL_ERROR_ECC_FAIL) {
        /* ECC could't correct the data - set the flag */
        ftl->Ca.Page[slot].row |= BIT_UNCOR;
      }
      #ifdef FS_DEBUG
      if (rtv == FTL_ERROR_ECC_COR) {
        /* ECC error corrected */
        EvrFsNFTL_BitErrorCorrected (ftl->Media->instance, row);
      }
      #endif
      /* Return ECC Error */
      rtv = FTL_ERROR_ECC;
    }
  }
  memcpy (ftl->PgBuf, &ftl->Ca.Page[slot].buf[col], sz);

  /* Set ECC Status */
  ftl->LastECC = ECC_NOERR;
  if (ftl->Ca.Page[slot].row & BIT_UNCOR) {
    ftl->LastECC = ECC_UNCORRECTED;
  }

  return rtv;
}

/**
  Cached page write

  \param[in,out]  ftl       FTL instance object
  \param[in]      row       row address of a page
  \return execution status FTL_STATUS
*/
static uint32_t CachePgWrite (NAND_FTL_DEV *ftl, uint32_t row) {
  uint32_t slot;
  uint32_t newslot = 1;

  EvrFsNFTL_CacheWrite (ftl->Media->instance, row / ftl->Media->dev->page_count, row % ftl->Media->dev->page_count);

  for (slot = 0; slot < ftl->Ca.CachedPages; slot++) {
    if (ftl->Ca.Page[slot].row == row) {
      newslot = 0;
      break;
    }
  }
  if (newslot) {
    slot = GetPageCacheSlot (&ftl->Ca);
    ftl->Ca.Page[slot].row = row;
  }

  if (pDev->sw_ecc == 1) {
    /* Hamming ECC is enabled, encode ECC */
    EncodeECC (ftl, ftl->PgBuf);
  }
  memcpy (ftl->Ca.Page[slot].buf, ftl->PgBuf, pDev->page_size);

  return (Drv_WritePage (row, ftl->Ca.Page[slot].buf, ftl));
}


/**
  Flush cached page from cache

  \param[in,out]  Ca        indexing cache control block
  \param[in]      row       page row address
*/
static void FlushPgCache (NAND_FTL_CACHE *Ca, uint32_t row) {
  uint32_t slot;

  for (slot = 0; slot < Ca->CachedPages; slot++) {
    if (Ca->Page[slot].row == row) {
      Ca->Page[slot].row = INVALID;
      break;
    }
  }
}

/**
  Remove cached block from cache

  \param[in,out]  Ca        indexing cache control block
  \param[in]      pbn       physical block number
*/
static void FlushIdxCache(NAND_FTL_CACHE *Ca, uint16_t pbn) {
  uint32_t slot;

  /* Check all slots */
  for (slot = 0; slot < Ca->CachedBlocks; slot++) {
    /* If physical block number match, invalidate entry */
    if (Ca->Block[slot].pbn == pbn) {
      Ca->Block[slot].pbn    = INVALID_BLOCK;
      Ca->Block[slot].nextPg = 0;
      break;
    }
  }
}

/**
  Select new slot from index cache array

  \param[in]      Ca        indexing cache control block
  \return slot number
*/
static uint32_t GetIdxCacheSlot(NAND_FTL_CACHE *Ca) {
  uint32_t slot;

  /* Select next slot */
  slot = Ca->CacheBS++;

  if (Ca->CacheBS == Ca->CachedBlocks) {
    Ca->CacheBS = 0;
  }

  return slot;
}

/**
  Counts number of blocks in queue

  \param[in]     *PbnQ      block queue
  \return number of blocks in queue
*/
static uint32_t NumBlocksInQueue(uint16_t *PbnQ) {
  uint32_t i, cnt;

  cnt = 0;

  for (i = 0; i < 3; i++) {
    if (PbnQ[i] != 0) {
      cnt++;
    }
  }

  return cnt;
}


/**
  Writes physical block number into block allocation queue

  \param[in,out] *PbnQ      block queue
  \param[in]      pbn       physical block number
  \return number of block in queue or INVALID if queue full
*/
static uint32_t PutBlockInQueue (uint16_t *PbnQ, uint32_t pbn) {
  uint16_t bn = (uint16_t)pbn;
  uint32_t num = 0;

  if (PbnQ[0] == 0) {
    PbnQ[0] = bn;
    num = 1;
  }
  else if (PbnQ[1] == 0  && PbnQ[0] != bn) {
    PbnQ[1] = bn;
    num = 2;
  }
  else if (PbnQ[2] == 0 && PbnQ[1] != bn && PbnQ[0] != bn) {
    PbnQ[2] = bn;
    num = 3;
  }
  else num = INVALID;
  return num;
}

/**
  Selects physical block number of an empty block from allocation queue

  \param[in,out] *PbnQ      block queue
  \param[out]    *pbn       pointer where physical block number will be stored
  \param[in]      restriction allocation restriction
  \return execution status FTL_STATUS
*/
static uint32_t GetBlockFromQueue (uint16_t *PbnQ, uint16_t *pbn, uint32_t restriction) {
  uint32_t cnt;

  /* Restriction: ALLOC_REST: 1, ALLOC_PENUL: 2, ALLOC_LAST: 3 */
  cnt = NumBlocksInQueue(PbnQ);

  if (cnt < restriction) {
    return FTL_ERROR_NOT_FOUND;
  }

  *pbn = PbnQ[0];

  PbnQ[0] = PbnQ[1];
  PbnQ[1] = PbnQ[2];
  PbnQ[2] = 0;

  return FTL_OK;
}


/**
  Erase block and remove it from cache. When block erase fails,
  block is marked as bad.

  \param[in,out]  ftl       FTL instance object
  \param[in,out] *bn        pointer to physical block to be erased
  \return execution status FTL_STATUS
*/
static uint32_t EraseBlock (NAND_FTL_DEV *ftl, uint16_t *pbn) {
  uint32_t rtv, row;

  /* Flush cache */
  FlushIdxCache (&ftl->Ca, *pbn);

  row = ROW(*pbn, 0);

  /* Erase block */
  rtv = Drv_EraseBlock (row, ftl);

  if (rtv != FTL_OK) {
    if (rtv == FTL_ERROR_ERASE) {
      rtv = MarkBlockBad (ftl, *pbn, false);
      *pbn = INVALID_BLOCK;
    }
  }
  return rtv;
}


/**
  Scan translation table and find a block which contains translation info
  for given logical block number (lbn) and block type (blTyp). Physical
  location of containing block is set to *pbn if block is found, otherwise
  *pbn will be invalidated.
  Blocks marked as bad are not scanned.

  \param[in,out]  ftl       FTL instance object
  \param[in]      lbn       logical block number
  \param[in]      blTyp     block type
  \param[out]    *pbn       pointer where physical block number is stored
  \returns execution status FTL_STATUS
*/
static uint32_t ScanTable (NAND_FTL_DEV *ftl, uint32_t lbn, uint32_t blTyp, uint16_t *pbn) {
  uint16_t block;
  uint32_t lsn, type, err, rtv;

  rtv = FTL_OK;
  err = 0;

  for (block = ftl->Cfg->BttStartBn; block <= ftl->Cfg->BttEndBn; block++) {
    /* Read first page */
    rtv = CachePgRead (ftl, ROW(block, 0), ftl->PgLay.spare_ofs, __SZ_SP_USED);

    if (rtv == FTL_OK) {
      err = 0;
      /* Skip bad block */
      if (ftl->PgBuf[ftl->PgLay.spare.ofs_bbm] == BB_MASK) {

        lsn = GetLSN (&ftl->PgBuf[ftl->PgLay.spare.ofs_lsn], &type);

        /* Continue if free block or LBN searched != LBN found */
        if (lsn != EMPTY && blTyp == type && lbn == LBN(lsn)) {
          /* Block found */
          *pbn = block;

          return FTL_OK;
        }
      }
      #ifdef FS_DEBUG
      else {
        EvrFsNFTL_SkipBadBlock (ftl->Media->instance, block);
      }
      #endif
    }
    else {
      err = 1;
      if (rtv == FTL_ERROR_ECC) {
        if (ftl->PgBuf[ftl->PgLay.spare.ofs_bbm] == BB_MASK) {
          /* Relocate block on ECC error */
          rtv = RelocBlock (ftl, &block, pDev->page_count, AREA_TBL, true);
          if (rtv == FTL_OK) {
            block = ftl->Cfg->BttStartBn;
          }
        }
      }
      else break;
    }
  }
  *pbn = INVALID_BLOCK;
  if (err) {
    return rtv;
  }
  return FTL_ERROR_NOT_FOUND;
}


/**
  Mark block specified by pbn as bad. If erase argument is set to true, block is first
  erased or not erased if set to false.
  All spare area bytes of the first sector in the first page are invalidated
  (set to 0). In case if write to the first page is unsuccessful, then spare area
  of the last page is invalidated (set to 0).

  \param[in,out]  ftl       FTL instance object
  \param[in]      pbn       physical block number
  \param[in]      erase     erase flag
  \returns execution status FTL_STATUS
*/
static uint32_t MarkBlockBad (NAND_FTL_DEV *ftl, uint16_t pbn, uint32_t erase) {
  uint32_t row, rtv;

  /* Marking block as bad */
  EvrFsNFTL_SetBadBlockMarker (ftl->Media->instance, pbn);

  row = ROW(pbn, 0);

  if (erase) {
    rtv = Drv_EraseBlock (row, ftl);
    if (rtv != FTL_OK && rtv != FTL_ERROR_ERASE) {
      /* Driver error */
      return rtv;
    }
  }

  /* Invalidate first spare area in page */
  memset (ftl->PgBuf, 0xFF, pDev->page_size);
  memset (&ftl->PgBuf[ftl->PgLay.spare_ofs], 0x00, __SZ_SPARE);

  ftl->BadBlockCnt++;

  /* Try to mark first page */
  rtv = Drv_WritePage (row, ftl->PgBuf, ftl);

  if (rtv == FTL_ERROR_PROGRAM) {
    /* If write to first page unsuccessful, try last page in block */
    row = ROW(pbn, pDev->page_count - 1);
    rtv = Drv_WritePage (row, ftl->PgBuf, ftl);
    if (rtv == FTL_ERROR_PROGRAM) { rtv = FTL_OK; }
  }

  return (rtv);
}

/**
  Moves block content from src block to the dest block.

  Number of pages that will be moved from src to dest block is specified by
  argument cnt. Move will always begin at page 0. If src blocks resides in
  table area, then translation table will be also updated

  \param[in,out]  ftl       FTL instance object
  \param[in]      src       physical block number of a source block
  \param[in]      dest      physical block number of a destination block
  \param[in]      cnt       number of pages to move
  \param[in]      area      source and destination block area
  \return execution status FTL_STATUS
*/
static uint32_t MoveBlock(NAND_FTL_DEV *ftl, uint16_t src, uint16_t dest, uint32_t cnt, uint32_t area) {
  uint32_t page, empty, eccWarn;
  uint32_t tsn, tsnIdx;
  uint32_t oldRow, newRow;
  uint32_t i, j, si, rtv;

  /* Moving block content from src to dest */
  EvrFsNFTL_MoveData (ftl->Media->instance, src, dest);

  tsnIdx = 0;
  rtv = FTL_OK;

  /* Move data from src (old) to dest (new) block */
  for (page = 0; page < cnt; page++) {
    eccWarn = false;

    oldRow = ROW(src, page);
    rtv = CachePgRead (ftl, oldRow, 0, pDev->page_size);
    switch (rtv) {
      case FTL_OK:
        break;

      case FTL_ERROR_ECC:
        /* Check ECC Status */
        if (ftl->LastECC & ECC_UNCORRECTED) {
          eccWarn = true;
        }
        break;

      default:
        return rtv;
    }

    /* Check first spare in page, to see if empty page reached */
    empty = false;

    si = ftl->PgLay.spare_ofs;

    if (ftl->PgBuf[si + ftl->PgLay.spare.ofs_bbm] == 0xFF) {
      si += ftl->PgLay.spare.ofs_lsn;

      if ((ftl->PgBuf[si]   == 0xFF) && (ftl->PgBuf[si+1] == 0xFF) &&
          (ftl->PgBuf[si+2] == 0xFF) && (ftl->PgBuf[si+3] == 0xFF)) {
        empty = true;
      }
    }

    /* Stop copying if current page empty */
    if (empty) {
      rtv = FTL_OK;
      break;
    }

    /* Set corruption marker in page spare areas */
    if (eccWarn) {
      for (i = ftl->PageSectors, si = ftl->PgLay.spare_ofs;
           i;
           i--,                  si += ftl->PgLay.spare_inc) {
        ftl->PgBuf[si + ftl->PgLay.spare.ofs_dcm] = 0x00;
      }
    }

    if(page == 0 && area == AREA_TBL) {
      tsn = GetLSN (&ftl->PgBuf[ftl->PgLay.spare_ofs + ftl->PgLay.spare.ofs_lsn], NULL);
      /* Align tsnIdx to the start of block */
      tsnIdx = (uint32_t)(LBN(tsn) << ftl->PPB);
    }

    newRow = ROW(dest, page);

    rtv = CachePgWrite (ftl, newRow);
    if (rtv != FTL_OK) { return rtv; }
  }

  if (area == AREA_TBL) {
    j = ftl->TsnTableSize;
    if (ftl->TsnTableSize > pDev->page_count) {
      j = tsnIdx + pDev->page_count;
    }

    for (page = 0; page < cnt; page++) {
      oldRow = ROW(src, page);

      for (i = tsnIdx; i < j; i++) {
        if (oldRow == (ftl->TsnTable[i] & 0x00FFFFFF)) {
          newRow = ROW(dest, page);
          ftl->TsnTable[i] = (ftl->TsnTable[i] & 0xFF000000) | newRow;
        }
      }
    }
  }

  return rtv;
}

/**
  Relocate block

  Function allocates new block and moves pages from given *pbn to
  allocated block. All pages from page 0 until page number given with
  untilPg variable are moved. Proper area must be specified so that
  translation table is updated when needed.
  If error appear when pages are moved, function retries until number of
  retries is exceeded.
  If pages are successfully relocated, physical block number of a new
  block is written to the *pbn variable.

  \param[in,out]  ftl       FTL instance object
  \param[in,out] *pbn       physical block number
  \param[in]      endPg     page number where copying stops
  \param[in]      area      block area
  \return execution status FTL_STATUS
*/
static uint32_t RelocBlock(NAND_FTL_DEV *ftl, uint16_t *pbn, uint32_t endPg, uint32_t area, uint32_t eraseBlock) {
  uint16_t allocBN;
  uint32_t rtv, retry;

  EvrFsNFTL_RelocateBlock (ftl->Media->instance, *pbn);

  retry = 0;
  do {

    /* Allocate new block */
    rtv = AllocBlock (ftl, &allocBN, ALLOC_REST, area);
    if (rtv == FTL_OK) {
      /* Move data */
      rtv = MoveBlock (ftl, *pbn, allocBN, endPg, area);

      if (rtv == FTL_OK) {
        /* Erase Block */
        if (eraseBlock) {
          rtv = EraseBlock(ftl, pbn);
          if (rtv != FTL_OK) {
            return rtv;
          }
        }

        /* Relocation successful */
        *pbn = allocBN;
        return FTL_OK;
      }
      else if (rtv == FTL_ERROR_PROGRAM) {
        rtv = MarkBlockBad (ftl, allocBN, true);
        if (rtv == FTL_OK) {
          /* PREVENT function returning with NO ERROR code */
          retry--;
        }
      }
      else break;
    }
    else {
      return rtv;
    }

    retry++;
  }
  while (retry < __MAX_RETRY);

  /* Failed to relocate block */
  EvrFsNFTL_RelocateBlockFailed (ftl->Media->instance, *pbn);

  return rtv;
}

/**
  Refresh data block by moving block data to new block

  \param[in,out]  ftl       FTL instance object
  \param[in]      lbn       logical block number
  \param[in]      blTyp     block type
  \param[in]      btti      translation table entry
  \param[in]      numPages  number of pages to move
  \return execution status FTL_STATUS
*/
static uint32_t RefreshDataBlock(NAND_FTL_DEV *ftl, uint16_t lbn, uint32_t blTyp, BTT_ITEM *btti, uint32_t numPages) {
  uint16_t bn, tmp;
  uint32_t rtv;

  bn = (blTyp == TYP_PRIM) ? btti->primBN : btti->replBN;

  tmp = bn;

  rtv = RelocBlock (ftl, &bn, numPages, AREA_DAT, false);
  if (rtv != FTL_OK) { return rtv; }

  (blTyp == TYP_PRIM) ? (btti->primBN = bn) : (btti->replBN = bn);

  rtv = UpdateBTT (ftl, lbn, &btti->primBN, &btti->replBN);
  if (rtv != FTL_OK) { return rtv; }

  rtv = EraseBlock(ftl, &tmp);
  if (rtv != FTL_OK) { return rtv; }

  return FTL_OK;
}

/**
  Function finds a page with specified lsn (logical sector number) within
  block with given bn (physical block number). If specified lsn equals
  to EMPTY, function searches for first free page within block.
  If page or free page found, then it's (page) number and block type are
  written to *pageNum variable.
  If sector isn't found then function returns appropriate error code
  and *pageNum variable is set to the first free page if found. If free
  page isn't found, *pageNum is set to number of pages in block.

  Search begins from first page in block and continues till the last page
  or until given lsn is found. Search function behaves differently,
  depending of the block type we are searching in. If block is primary
  and given lsn was found in it, then search immediately breaks as
  successful. In case of replacement block, search always continues till
  the end of block and reports search as unsuccessful if given lsn was
  not found or successful when at least one appearance of lsn was found
  in block. Because multiple sectors with same lsn can exist in
  replacement block, only the last valid location is returned.

  First spare area of the first page in block is also always checked for
  bad block marker and if marker set, function returns immediately.

  \param[in,out]  ftl       FTL instance object
  \param[in]      pbn       physical block number
  \param[in]      lsn       logical sector number
  \param[out]     pgNum     pointer where page number with specified lsn is stored
  \return execution status FTL_STATUS
*/
static uint32_t ScanBlock (NAND_FTL_DEV *ftl, uint16_t pbn, uint32_t lsn, uint32_t *pgNum) {
  uint32_t cLsn, cPg, rePg, type, empty, lbn, slot, rtv;
  uint32_t caRead, caWrite;

  /* Searching for 'lsn' within block 'pbn' */
  EvrFsNFTL_LsnFind (ftl->Media->instance, lsn, pbn);

  *pgNum = INVALID;

  if (pbn > pDev->block_count) {
    /* PBN is out of range (max=pDev->block_count) */
    EvrFsNFTL_PbnOutOfRange (ftl->Media->instance, pbn, pDev->block_count);
    return FTL_ERROR_RANGE;
  }

  /* Align LSN to first sector in page */
  if (lsn != EMPTY) {
    lsn &= ~(ftl->PageSectors - 1);       /* 0, SPP,..., n + SPP,..., SPB */
  }

  /* Scan cached blocks */
  caWrite = 0;
  caRead  = 0;
  slot    = 0;
  lbn     = 0;
  type    = TYP_PRIM;

  if (pbn > ftl->Cfg->BttEndBn) {
    /* Check if block already in cache */
    for (; slot < ftl->Ca.CachedBlocks; slot++) {
      if (ftl->Ca.Block[slot].pbn == pbn) {
        lbn  = ftl->Ca.Block[slot].lbn;
        type = ftl->Ca.Block[slot].typ;
        caRead = 1;
        break;
      }
    }
    /* If we are not reading from cache, we will write */
    if (caRead == 0) {
      /* Select slot to write into */
      slot = GetIdxCacheSlot(&ftl->Ca);

      ftl->Ca.Block[slot].pbn = pbn;
      ftl->Ca.Block[slot].nextPg = 0;
      caWrite = 1;
    }
  }

  /* Scan through pages */
  rePg   = INVALID;
  empty  = 0;

  for (cPg = 0; cPg < pDev->page_count; cPg++) {

    if (caRead && cPg == ftl->Ca.Block[slot].nextPg) {
      caRead  = 0;
      caWrite = 1;
    }

    if (caRead) {
      cLsn = IDX2LSN(lbn, ftl->Ca.Block[slot].pgIdx[cPg]);
    }
    else {
      rtv = CachePgRead (ftl, ROW(pbn, cPg), ftl->PgLay.spare_ofs, __SZ_SP_USED);
      if (rtv != FTL_OK) {
        return rtv;
      }

      /* Get lsn */
      cLsn = GetLSN (&ftl->PgBuf[ftl->PgLay.spare.ofs_lsn], &type);

      /* Cache write */
      if (caWrite) {
        if (cLsn != EMPTY) {
          ftl->Ca.Block[slot].lbn = LBN(cLsn);
          ftl->Ca.Block[slot].typ = (uint8_t)type;
          ftl->Ca.Block[slot].pgIdx[cPg] = LSN2IDX(cLsn);
          ftl->Ca.Block[slot].nextPg++;
        }
      }
    }

    if (cPg == 0 && !caRead) {
      /* Check for bad block */
      if (ftl->PgBuf[ftl->PgLay.spare.ofs_bbm] != BB_MASK) {
        /* This is bad block */
        return FTL_ERROR_BB;
      }

      /* Check if logical sector member of our block */
      if (lsn != EMPTY && cLsn != EMPTY) {
        if (LBN(lsn) != LBN(cLsn) && ftl->PgBuf[ftl->PgLay.spare.ofs_dcm] == 0xFF) {
          /* Misplaced sector, got cLsn instead of lsn in block bn */
          return FTL_ERROR_LBN;
        }
      }
    }

    *pgNum = cPg;

    /* Check if lsn found */
    if (lsn == cLsn) {
      if ((type & TYP_PRIM) || (lsn == EMPTY)) {
        /* LSN found in PRIM, cPG */
        EvrFsNFTL_LsnFound (ftl->Media->instance, lsn, pbn, cPg);
        return FTL_OK;
      }
      else {
        rePg  = cPg;
      }
    }
    else if (cLsn == EMPTY) {
      /* It's free page */
      empty = 1;
      break;
    }
  }

  if (rePg != INVALID) {
    *pgNum = rePg;
    /* LFN found in REPL, rePg */
    EvrFsNFTL_LsnFound (ftl->Media->instance, lsn, pbn, rePg);
    return FTL_OK;
  }

  /* Last page reached and no free page */
  if ((empty == 0) && (cPg == pDev->page_count)) {
    /* End of block */
    EvrFsNFTL_LsnNotFound (ftl->Media->instance, lsn, pbn);
    return FTL_ERROR_NOT_FOUND_EOB;
  }

  /* Free page reached */
  EvrFsNFTL_LsnNotFound (ftl->Media->instance, lsn, pbn);
  return FTL_ERROR_NOT_FOUND;
}

/**
  Find empty block within specified area

  \param[in,out]  ftl       FTL instance object
  \param[in]      area      block area
  \param[out]    *bn        block number of the empty block
  \return execution status FTL_STATUS
*/
static uint32_t FindEmptyBlock (NAND_FTL_DEV *ftl, uint32_t area, uint16_t *bn) {
  uint16_t startBN, endBN, cBN, lastBN;
  uint32_t empty;
  uint32_t i, rtv;
  uint32_t row, col;

  if (area == AREA_DAT) {
    /* Search in data area */
    startBN = ftl->Cfg->DataStartBn;
    endBN   = ftl->Cfg->DataEndBn;
    lastBN  = ftl->LastDBN;
  } else {
    /* Search in table area */
    startBN = ftl->Cfg->BttStartBn;
    endBN   = ftl->Cfg->BttEndBn;
    lastBN  = ftl->LastTBN;
  }

  /* Search for empty block within area */
  empty = false;
  cBN   = lastBN;
  do {
    (cBN == endBN) ? (cBN = startBN) : (cBN++);

    row = ROW (cBN, 0);
    col = ftl->PgLay.spare_ofs;

    rtv = CachePgRead (ftl, row, col, __SZ_SP_USED);
    if (rtv != FTL_OK) {
      if (rtv != FTL_ERROR_ECC) {
        return rtv;
      }
      /* Skip block if ECC error */
    }
    else {
      /* If block not bad, check if it is empty */
      if (ftl->PgBuf[ftl->PgLay.spare.ofs_bbm] == BB_MASK) {

        empty = true;

        /* Only check LSN bytes */
        for  (i = 0; i < 4; i++) {
          if (ftl->PgBuf[ftl->PgLay.spare.ofs_lsn + i] != 0xFF) {
              empty = false;
              break;
          }
        }
      }
      #ifdef FS_DEBUG
      else {
        EvrFsNFTL_SkipBadBlock (ftl->Media->instance, cBN);
      }
      #endif
    }
  }
  while (!empty && (cBN != lastBN));

  if (empty) {
    *bn = cBN;
    (area == AREA_DAT) ? (ftl->LastDBN = (uint16_t)cBN) : (ftl->LastTBN = (uint16_t)cBN);
    return FTL_OK;
  }
  return FTL_ERROR_NOT_FOUND;
}

/**
  Function searches for logical block in data area with allocated
  PRIM/REPL pair. When pair of blocks is found, garbage collection is
  performed on them and data are copied to specified allocBN. Block
  translation table is then updated and two free blocks are gained.
  Their address is then written to the given *firstBn and *secondBn
  variable addresses.

  \param[in,out]  ftl       FTL instance object
  \return execution status FTL_STATUS
*/
static uint32_t ForceDataGc(NAND_FTL_DEV *ftl) {
  BTT_ITEM btti;
  uint16_t lbn, pbn, emptyBN;
  uint32_t row, prev_row, col, tsn, tsnIdx, rtv;

  /* Find empty block */
  rtv = GetBlockFromQueue (ftl->PbnQ, &emptyBN, ALLOC_LAST);
  if (rtv == FTL_ERROR_NOT_FOUND) {
    rtv = FindEmptyBlock (ftl, AREA_DAT, &emptyBN);
    if (rtv != FTL_OK) { return rtv; }
  }

  prev_row = 0;

  lbn = ftl->GcLBN;
  /* Look for PRIM/REPL pair on all logical blocks */
  do {
    tsn = lbn >> BTT_EPS;
    tsnIdx = tsn >> ftl->SPP;

    row = ftl->TsnTable[tsnIdx] & 0x00FFFFFF;

    if (row != 0x00FFFFFF) {

      if (row != prev_row) {

        rtv = CachePgRead (ftl, row, 0, pDev->page_size);

        switch (rtv) {
          case FTL_OK:
            /* Lock page read until new row (page) */
            prev_row = row;
            break;

          case FTL_ERROR_ECC:
            pbn = PBN(row);
            rtv = RelocBlock (ftl, &pbn, pDev->page_count, AREA_TBL, true);

            if (rtv != FTL_OK) {
              return rtv;
            }
            continue;

          default:
            return rtv;
        }
      }

      /* Align column to block entry */
      col = ((tsn & (ftl->PageSectors - 1)) * ftl->PgLay.sector_inc) +
             (lbn & ((1 << BTT_EPS) - 1)) * BTT_ENTRY_SZ;

      btti.primBN  = ftl->PgBuf[col];
      btti.primBN |= ftl->PgBuf[col + 1] << 8;

      btti.replBN  = ftl->PgBuf[col + 2];
      btti.replBN |= ftl->PgBuf[col + 3] << 8;

      if (btti.primBN != INVALID_BLOCK && btti.replBN != INVALID_BLOCK) {
        if (lbn != ftl->CurrLBN) {
          /* Start garbage collection */
          rtv = GcRun (ftl, lbn, &btti, AREA_DAT, emptyBN);

          if (rtv == FTL_OK) {

            /* GC ok, now update translation table */
            pbn = INVALID_BLOCK;
            rtv = UpdateBTT(ftl, lbn, &emptyBN, &pbn);

            if (rtv != FTL_OK) {
              return rtv;
            }

            emptyBN = INVALID_BLOCK;

            /* Erase old blocks */
            rtv = EraseBlock (ftl, &btti.primBN);
            if (rtv != FTL_OK) { return rtv; }
            rtv = EraseBlock (ftl, &btti.replBN);
            if (rtv != FTL_OK) { return rtv; }

            /* Put blocks in queue if erase was ok */
            if (btti.primBN != INVALID_BLOCK) PutBlockInQueue(ftl->PbnQ, btti.primBN);
            if (btti.replBN != INVALID_BLOCK) PutBlockInQueue(ftl->PbnQ, btti.replBN);
          }
          else {
            return rtv;
          }

          if (emptyBN == INVALID_BLOCK) {
            /* Empty block was used, set LBN for next GC and return */
            ftl->GcLBN = lbn + 1;
            if (ftl->GcLBN == ftl->NumDataBlocks) { ftl->GcLBN = 0; }
            return FTL_OK;
          }
        }
      }
    }
    lbn++;
    if (lbn == ftl->NumDataBlocks) { lbn = 0; }
  }
  while (lbn != ftl->GcLBN);
  /* Nothing was done or found */

  /* Put empty block back in queue */
  PutBlockInQueue (ftl->PbnQ, emptyBN);

  return FTL_ERROR_NOT_FOUND;
}


/**
  Allocate a free block

  \param[in,out]  ftl       FTL instance object
  \param[in,out] *bn        pointer to block number variable
  \param[in]      restriction allocation restriction
  \param[in]      area      area specifier
  \return execution status FTL_STATUS
*/
static uint32_t AllocBlock(NAND_FTL_DEV *ftl, uint16_t *bn, uint32_t restriction, uint32_t area) {
  uint16_t pbn;
  uint32_t num, rtv = FTL_OK;

  if (area == AREA_TBL) {
    /* Allocating table block */
    EvrFsNFTL_AllocateBlock (ftl->Media->instance, 0);

    rtv = FindEmptyBlock (ftl, AREA_TBL, bn);
    if (rtv == FTL_ERROR_NOT_FOUND) {
      rtv = FTL_ERROR_ALLOC;
    }
  }
  else {
    /* Allocating data block */
    EvrFsNFTL_AllocateBlock (ftl->Media->instance, 1);

    num = 0;

    while (num < __MIN_IN_QUEUE && num != INVALID) {
      rtv = FindEmptyBlock (ftl, AREA_DAT, &pbn);
      if (rtv != FTL_OK) {
        if (rtv == FTL_ERROR_NOT_FOUND) break;
        else return rtv;
      }
      else {
        num = PutBlockInQueue (ftl->PbnQ, pbn);
      }
    }

  do {
    num = NumBlocksInQueue(ftl->PbnQ);
    if (num == 0) return FTL_ERROR_ALLOC;

    if (num < __MIN_IN_QUEUE) {
      rtv = ForceDataGc(ftl);
      switch (rtv) {
        case FTL_OK:
        case FTL_ERROR_NOT_FOUND:
        case FTL_ERROR_PROGRAM:
          break;

        default:
          return rtv;
      }
    }
  }
  while (rtv != FTL_ERROR_NOT_FOUND && num < __MIN_IN_QUEUE);

    rtv = GetBlockFromQueue(ftl->PbnQ, &pbn, restriction);
    if (rtv == FTL_OK) {
      *bn = pbn;
    }
  }
  #ifdef FS_DEBUG
  if (rtv == FTL_OK) {
    EvrFsNFTL_AllocatedBlock (ftl->Media->instance, *bn);
  }
  #endif

  return rtv;
}

/**
  Searches for physical block addresses of a given logical block number.
  Physical adresses are written to the *btt structure. If block does
  not exist then values in *btt structure are set to INVALID_BLOCK.

  Row addresses of all valid pages in block translation table area are
  gathered in nftl_TsnTable array. Array index is first determined from
  given logical block number and row address is selected from
  nftl_TsnTable. Page at that row address can then be read and physical
  block addresses can be extracted from column defined by logical block
  number.
  In case of ECC error, block addresses are written in *btt then table
  block is relocated.

  \param[in,out]  ftl       FTL instance object
  \param[in]      lbn       logical block number
  \parma[in]     *btt       pointer to entry structure
  \return execution status FTL_STATUS
*/
static uint32_t SearchBTT(NAND_FTL_DEV *ftl, uint32_t lbn, BTT_ITEM *btt) {
  uint32_t eccErr;
  uint32_t rtv, tsn, tsnIdx, row, col;
  uint16_t pbn;

  /* Retrieving translation info */
  EvrFsNFTL_TableLookup (ftl->Media->instance, lbn);

  /* Check if given LSN in range for our flash device */
  if (lbn < ftl->NumDataBlocks) {

    tsn = lbn >> BTT_EPS;

    /* Find entry in ram table */
    tsnIdx = tsn >> ftl->SPP;
    row    = ftl->TsnTable[tsnIdx] & 0x00FFFFFF;

    if (row == 0x00FFFFFF) {
      /* No entry */
      btt->primBN = INVALID_BLOCK;
      btt->replBN = INVALID_BLOCK;
      rtv = FTL_OK;

      /* No translation info for given LBN */
      EvrFsNFTL_TableEntryNotFound (ftl->Media->instance, lbn);
    }
    else {
      /* Align column to sector within page and add entry offset */
      col = ((tsn & (ftl->PageSectors - 1)) * ftl->PgLay.sector_inc) +
             (lbn & ((1 << BTT_EPS) - 1)) * BTT_ENTRY_SZ;

      eccErr = false;

      /* Read entry */
      rtv = CachePgRead (ftl, row, col, BTT_ENTRY_SZ);
      
      if ((rtv == FTL_OK) || (rtv == FTL_ERROR_ECC)) {
        if (rtv == FTL_ERROR_ECC) {
          eccErr = true;
        }

        /* Read entry */
        btt->primBN = (uint16_t)((ftl->PgBuf[1] << 8) | ftl->PgBuf[0]);
        btt->replBN = (uint16_t)((ftl->PgBuf[3] << 8) | ftl->PgBuf[2]);

        /* Found [%d, %d] translation entry for LBN %d */
        EvrFsNFTL_LbnToPbn (ftl->Media->instance, lbn, btt->primBN, btt->replBN);
      }

      if (eccErr) {
        pbn = PBN(row);
        rtv = RelocBlock (ftl, &pbn, pDev->page_count, AREA_TBL, true);
      }
    }
    return rtv;
  }
  /* LBN is out of range (max: NumDataBlocks-1) */
  EvrFsNFTL_LbnOutOfRange (ftl->Media->instance, lbn, ftl->NumDataBlocks-1);

  return FTL_ERROR_RANGE;
}


/**
  Write or update entry in block translation table.

  A logical block number and pointers to the variables containing its physical
  block numbers must be provided.

  Block translation table translates logical block numbers into physical
  block numbers. For each allocated primary/replacement block pair, one
  four bytes long entry exists, represented by the btt structure. This
  means, that one table sector (512 bytes) can hold translations for 128
  blocks.

  Translation table is updated each time new data block is allocated
  or when allocated block is moved (relocation or garbage collection).
  From logical block number, table sector is determined and also table
  block number which is needed when one block in table has to be found.
  For large page devices sector offset within page is calculated
  and table sector number is aligned to multiple of sectors per page.

  Whenever an entry in block translation table is updated, existing
  primary or replacement table block must be found and page with
  previously written table sector must be read and updated with new
  entry. Physical address of the last page written for each logical
  table sector is held in RAM array. If logical table sector was not
  yet written it's coressponding address in RAM array is invalid.
  Primary block must then be found (if exist) or allocated, else
  replacement block is used.

  In case when data block is relocated one of the *primBN and
  *replBN parameters can be NULL. This way table can be updated even if
  location of only one block is known.

  Table update is retried for __MAX_RETRY times if some NAND flash
  read/write error appear.

  \param[in,out]  ftl       FTL instance object
  \param[in]      lbn       logical block number
  \param[in]     *primBN    pointer to primary physical block number for lbn
  \param[in]     *replBN    pointer to replacement physical block number for lbn
  \return execution status FTL_STATUS
*/
static uint32_t UpdateBTT(NAND_FTL_DEV *ftl, uint16_t lbn, uint16_t *primBN, uint16_t *replBN) {
  BTT_ITEM entBl;
  uint16_t cBN, allocBN, badBN;
  uint32_t i, rtv, freePg;
  uint32_t blTyp, wrTyp, retry, err;
  uint32_t tbnS, tsnS, tsnB, tsnE, tsnIdx;
  uint32_t sOffs, row, col, primRow, replRow;

  EvrFsNFTL_TableUpdate (ftl->Media->instance, lbn, *primBN, *replBN);

  tsnS  = lbn >> BTT_EPS;                    /* TSN we are searching for */
  tbnS  = LBN(tsnS);                         /* is in block with TBN     */
  sOffs = tsnS &  (ftl->PageSectors - 1);    /* Sector number (offset)   */
  tsnS  = tsnS & ~(ftl->PageSectors - 1);    /* Sector per page multiple */

  retry = 0;
  do {
    /* Try to find PRIM and REPL block for tsnS */
    primRow = 0x00FFFFFF;
    replRow = 0x00FFFFFF;
    tsnB = tbnS << ftl->PPB;                /* First table index in block */
    tsnE = tsnB + ftl->TsnTableSize;        /* Last table index in block  */

    if (ftl->TsnTableSize > pDev->page_count) {
      tsnE = tsnB + pDev->page_count;
    }

    for (tsnIdx = tsnB; tsnIdx < tsnE; tsnIdx++) {
      blTyp = ftl->TsnTable[tsnIdx] >> 24;
      row   = ftl->TsnTable[tsnIdx] & 0x00FFFFFF;
      if (blTyp == TYP_PRIM) {
        if ((primRow == 0x00FFFFFF) || (primRow < row)) {
          primRow = row;
        }
      }
      else if (blTyp == TYP_REPL) {
        if ((replRow == 0x00FFFFFF) || (replRow < row)) {
          replRow = row;
        }
      }
    }
    /* Get tsn table index */
    tsnIdx = tsnS >> ftl->SPP;

    /* Select block to write updated table sector into */
    blTyp = ftl->TsnTable[tsnIdx] >> 24;

    wrTyp = TYP_REPL;
    if (blTyp == 0xFF) {
      wrTyp = TYP_PRIM;
    }

    freePg = INVALID;

    /* Check if we will write to PRIM */
    if (wrTyp == TYP_PRIM) {

      if (primRow == 0x00FFFFFF) {
        /* PRIM block is not known */
        rtv = ScanTable (ftl, LBN(tsnS), TYP_PRIM, &entBl.primBN);

        switch (rtv) {
          case FTL_OK:
            /* We have PRIM, find a free page in it! */
            break;

          case FTL_ERROR_NOT_FOUND:
            rtv = AllocBlock (ftl, &entBl.primBN, ALLOC_LAST, AREA_TBL);
            if (rtv != FTL_OK) {
              return rtv;
            }
            freePg = 0;
            break;

          default:
            return rtv;
        }
      }
      else {
        /* Set physical block number and free page */
        entBl.primBN = PBN(primRow);
      }
      /* We have PRIM block - find a free page in it */
      if (freePg != 0) {
        rtv = ScanBlock (ftl, entBl.primBN, EMPTY, &freePg);
        if (rtv != FTL_OK) {
          return rtv;
        }
      }
    }
    else {
      /* If no REPL found, allocate it */
      if (replRow == 0x00FFFFFF) {
        rtv = AllocBlock (ftl, &entBl.replBN, ALLOC_LAST, AREA_TBL);

        if (rtv != FTL_OK) {
          return rtv;
        }
        freePg = 0;
      }
      else {
        /* Set physical block number and free page */
        entBl.replBN = PBN(replRow);
        freePg = replRow - (uint32_t)(entBl.replBN << ftl->PPB) + 1;
      }
    }

    /* Physical block number and last written page for table block
       should be known by now. Just find a free page in it. Last valid
       sector, if exists, is written in nftl_TsnTable. All other parameters are
       also known by here.
    */

    err = 0;

    /* Find free page */
    cBN = (wrTyp == TYP_PRIM) ? entBl.primBN : entBl.replBN;

    /* Read last valid sector */
    row = ftl->TsnTable[tsnIdx] & 0x00FFFFFF;
    if (row == 0x00FFFFFF) {
      memset(ftl->PgBuf, 0xFF, pDev->page_size);
    }
    else {
      rtv = CachePgRead (ftl, row, 0, pDev->page_size);

      switch (rtv) {
        case FTL_OK:
          break;

        case FTL_ERROR_ECC:
          cBN = PBN(row);

          rtv = RelocBlock (ftl, &cBN, pDev->page_count, AREA_TBL, true);
          if (rtv != FTL_OK) {
            return rtv;
          }

          err = 1;
          retry++;
          continue;

        default:
          return rtv;
      }
    }
    /* Set entry column */
    col  = ftl->PgLay.sector_inc * sOffs +  (lbn & ((1 << BTT_EPS) - 1)) * BTT_ENTRY_SZ;

    /* Update entry in tsn page */
    if (primBN != NULL) {
      ftl->PgBuf[col]     = (uint8_t)(*primBN);
      ftl->PgBuf[col + 1] = (uint8_t)(*primBN >> 8);
    }
    if (replBN != NULL) {
      ftl->PgBuf[col + 2] = (uint8_t)(*replBN);
      ftl->PgBuf[col + 3] = (uint8_t)(*replBN >> 8);
    }

    /* Set spare */
    for (i = 0,   col = ftl->PgLay.spare_ofs;
         i < ftl->PageSectors;
         i++,     col += ftl->PgLay.spare_inc) {
      SetLSN (tsnS + i, wrTyp, &ftl->PgBuf[col + ftl->PgLay.spare.ofs_lsn]);
    }

    row = ROW(cBN, freePg);

    /* Write page */
    rtv = CachePgWrite (ftl, row);
    switch (rtv) {
      case FTL_OK:
        break;

      case FTL_ERROR_PROGRAM:
        /* Retire block? */
        badBN = cBN;
        rtv = RelocBlock (ftl, &cBN, freePg, AREA_TBL, true);
        if (rtv != FTL_OK) {
          return rtv;
        }
        rtv = MarkBlockBad (ftl, badBN, false);
        if (rtv != FTL_OK) {
          return rtv;
        }

        err = 1;
        retry++;
        continue;

      default:
        FlushPgCache (&ftl->Ca, row);
        return rtv;
    }

    /* Update entry in RAM table */
    ftl->TsnTable[tsnIdx] = row | (wrTyp << 24);

    if ((wrTyp == TYP_REPL) && (freePg == (uint32_t)(pDev->page_count-1))) {
      /* This is last page in replacement block, start GC */

      if (primRow == 0x00FFFFFF) {
        rtv = ScanTable (ftl, LBN(tsnS), TYP_PRIM, &entBl.primBN);
        switch (rtv) {
          case FTL_OK:
            break;

          case FTL_ERROR_NOT_FOUND:
            /* If PRIM can't be found, something must be seriously wrong */
            /* ECC may be the cause and consistency check should repair this */
            return FTL_ERROR;

          default:
            return rtv;
        }
      }
      else {
        entBl.primBN = PBN(primRow);
      }
      retry = 0;
gc:
      /* Allocate block */
      rtv = AllocBlock (ftl, &allocBN, ALLOC_LAST, AREA_TBL);
      if (rtv != FTL_OK) {
        return rtv;
      }

      /* Do garbage collection */
      rtv = GcRun (ftl, LBN(tsnS), &entBl, AREA_TBL, allocBN);

      switch (rtv) {
        case FTL_OK:
          break;

        case FTL_ERROR_PROGRAM:
          goto gc;

        default:
          return rtv;
      }

      /* Erase blocks */
      rtv = EraseBlock (ftl, &entBl.primBN);
      if (rtv != FTL_OK) { return rtv; }
      rtv = EraseBlock (ftl, &entBl.replBN);
      if (rtv != FTL_OK) { return rtv; }
    }
  }
  while(err && (retry < __MAX_RETRY));
  if (err) {
    return rtv;
  }

  return FTL_OK;
}

/**
  Perform garabage collection.

  Garbage collection tables are first build and flags are set according to data
  in primary and replacement blocks. Pages from both blocks are then copied to
  empty block whose number is specified in allocBn variable.

  \param[in,out]  ftl       FTL instance object
  \param[in]      lbn       logical block number
  \param[in,out] *btt       pointer to the block translation table entry
  \param[in]      gcArea    area of a logical block number
  \param[in]      allocBn   physical block number of a new primary block
  \return execution status FTL_STATUS
*/
static uint32_t GcRun(NAND_FTL_DEV *ftl, uint16_t lbn, BTT_ITEM *btt, uint32_t gcArea, uint16_t allocBn) {
  uint32_t i, cPg, oldPg, cLsn, lsn, row, col, type, eccWarn, rtv;
  uint32_t slot, primSlot, replSlot;

  /* Consolidating blocks */
  EvrFsNFTL_GarbageCollection (ftl->Media->instance, btt->primBN, btt->replBN, allocBn);

  primSlot = INVALID;
  replSlot = INVALID;

  /* Check if block in cache */
  for (slot = 0; slot < ftl->Ca.CachedBlocks; slot++) {
    if (ftl->Ca.Block[slot].pbn == btt->primBN) { primSlot = slot; }
    if (ftl->Ca.Block[slot].pbn == btt->replBN) { replSlot = slot; }
  }

  /* Assign cache slots to noncached blocks */
  if (primSlot == INVALID) {
    /* Primary block is not in cache */
    slot = GetIdxCacheSlot(&ftl->Ca);
    if (slot == replSlot) { slot = GetIdxCacheSlot(&ftl->Ca); }
    primSlot = slot;

    ftl->Ca.Block[primSlot].pbn = btt->primBN;
    ftl->Ca.Block[primSlot].lbn = lbn;
    ftl->Ca.Block[primSlot].typ = TYP_PRIM;
    ftl->Ca.Block[primSlot].nextPg = 0;
  }

  if (replSlot == INVALID) {
    /* Replacement block is not in cache */
    slot = GetIdxCacheSlot(&ftl->Ca);
    if (slot == primSlot) { slot = GetIdxCacheSlot(&ftl->Ca); }
    replSlot = slot;

    ftl->Ca.Block[replSlot].pbn = btt->replBN;
    ftl->Ca.Block[replSlot].lbn = lbn;
    ftl->Ca.Block[replSlot].typ = TYP_REPL;
    ftl->Ca.Block[replSlot].nextPg = 0;
  }

  /* Read REPL and get page indexes */
  for (cPg = 0; cPg < pDev->page_count; cPg++) {
    cLsn = INVALID;

    /* Check if we need to index the page */
    if (cPg == ftl->Ca.Block[replSlot].nextPg) {

      row = ROW (ftl->Ca.Block[replSlot].pbn, cPg);
      rtv = CachePgRead (ftl, row, ftl->PgLay.spare_ofs, __SZ_SP_USED);

      /* Ignore ECC error */
      if (rtv != FTL_OK && rtv != FTL_ERROR_ECC) { return rtv; }

      /* Get LSN */
      cLsn = GetLSN (&ftl->PgBuf[ftl->PgLay.spare.ofs_lsn], &type);

      /* Write index cache */
      if (cLsn != EMPTY) {
        ftl->Ca.Block[replSlot].pgIdx[cPg] = LSN2IDX(cLsn);
        ftl->Ca.Block[replSlot].nextPg++;
      }
    }

    /* Check if current page empty or last in block */
    if (cLsn == EMPTY || (cPg == (uint32_t)(pDev->page_count - 1))) {
      break;
    }
  }

  /* REPL block is indexed, start copying old valid pages */
  for (cPg = 0; cPg < pDev->page_count; cPg++) {
    eccWarn = false;
    oldPg   = INVALID;

    /* Check REPL block first */
    for (i = 0; i < ftl->Ca.Block[replSlot].nextPg; i++) {

      if (ftl->Ca.Block[replSlot].pgIdx[i] == cPg) {
        oldPg = i;
      }
    }
    cLsn = IDX2LSN (lbn, cPg);

    if (oldPg == INVALID) {
      /* Old page is not in REPL, go to PRIM */

      /* Try direct offset */
      row = ROW (ftl->Ca.Block[primSlot].pbn, cPg);
      rtv = CachePgRead (ftl, row, 0, pDev->page_size);

      if (rtv != FTL_OK) {
        if (rtv != FTL_ERROR_ECC) {
          return rtv;
        }
        if (ftl->LastECC & ECC_UNCORRECTED) {
          eccWarn = true;
        }
      }

      if (cLsn != GetLSN (&ftl->PgBuf[ftl->PgLay.spare_ofs + ftl->PgLay.spare.ofs_lsn], NULL)) {
        /* LSN not found at direct offset */
        eccWarn = false;

        /* Check other pages */
        for (i = 0; i < pDev->page_count; i++) {

          if (i < ftl->Ca.Block[primSlot].nextPg) {
            /* Read from cache */
            if (cLsn == IDX2LSN (lbn, ftl->Ca.Block[primSlot].pgIdx[i])) {

              row = ROW(ftl->Ca.Block[primSlot].pbn, i);
              rtv = CachePgRead (ftl, row, 0, pDev->page_size);

              if (rtv != FTL_OK) {
                if (rtv != FTL_ERROR_ECC) { return rtv; }
                if (ftl->LastECC & ECC_UNCORRECTED) {
                  eccWarn = true;
                }
              }
              /* LSN found in primary block */
              break;
            }
          }
          else {
            /* Do the indexing for PRIM block */
            row = ROW (ftl->Ca.Block[primSlot].pbn, i);
            rtv = CachePgRead (ftl, row, 0, pDev->page_size);

            if (rtv != FTL_OK) {
              if (rtv != FTL_ERROR_ECC) { return rtv; }
            }

            lsn = GetLSN (&ftl->PgBuf[ftl->PgLay.spare_ofs + ftl->PgLay.spare.ofs_lsn], NULL);

            /* Write in cache */
            if (lsn != EMPTY) {
              ftl->Ca.Block[primSlot].pgIdx[i] = LSN2IDX(lsn);
              ftl->Ca.Block[primSlot].nextPg++;
            }
            else {
              /* lsn == EMPTY */
              break;
            }

            if (lsn == cLsn) {
              if (ftl->LastECC & ECC_UNCORRECTED) {
                eccWarn = true;
              }
              /* LSN found in primary block */
              break;
            }
          }
        }
      }
    }
    else {
      /* Read old page */
      row = ROW (ftl->Ca.Block[replSlot].pbn, oldPg);
      rtv = CachePgRead (ftl, row, 0, pDev->page_size);

      if (rtv != FTL_OK) {
        if (rtv != FTL_ERROR_ECC) { return rtv; }

        if (ftl->LastECC & ECC_UNCORRECTED) {
          eccWarn = true;
        }
      }
    }
    /* Old page should be read by here */

    /* Set LSNs */
    for (i = 0,                 col = ftl->PgLay.spare_ofs;
         i < ftl->PageSectors;
         i++,                   col += ftl->PgLay.spare_inc) {
        SetLSN (cLsn + i, TYP_PRIM, &ftl->PgBuf[col + ftl->PgLay.spare.ofs_lsn]);
        if (eccWarn) {
          ftl->PgBuf[col + ftl->PgLay.spare.ofs_dcm] = 0x00;
        }
    }

    /* Write page */
    row = ROW (allocBn, cPg);
    rtv = CachePgWrite (ftl, row);

    if (rtv != FTL_OK) {
      if (rtv == FTL_ERROR_PROGRAM) {
        /* Mark old block as bad */
        MarkBlockBad (ftl, allocBn, true);
      }
      return rtv;
    }

    /* Update table if blocks in table area */
    if (gcArea == AREA_TBL) {
      /* Get TSN table index */
      i = cLsn >> ftl->SPP;
      if (i < ftl->TsnTableSize) {
        ftl->TsnTable[i] = row | (TYP_PRIM << 24);
      }
    }
  }

  ftl->Ca.Block[primSlot].pbn    = INVALID_BLOCK;
  ftl->Ca.Block[primSlot].nextPg = 0;
  ftl->Ca.Block[replSlot].pbn    = INVALID_BLOCK;
  ftl->Ca.Block[replSlot].nextPg = 0;

  return FTL_OK;
}

/**
  Reads cnt sectors from NAND flash into *buf, starting from given
  logical sector number.

  Primary and replacement blocks are first determined for given logical
  sector number by searching block translation table.
  When there was no primary block previously allocated,
  invalid or unwritten data can be reported and for the whole block
  0xFF values are returned. If either primary or replacement block
  exist, search for logical sector must be performed within. Replacement
  block is scanned first if exist. If logical sector was found in
  replacement block, data are returned to the caller. If only primary
  block exist for given logical sector, then spare area at direct sector
  offset is examined first, looking for logical sector number we are
  searching for. If sector number isn't found at direct offset, then
  whole primary block is scanned and if sector found, it's data are
  returned else empty sector is reported, by sending 0xFF values.

  \param[in]      lsn       logical sector number
  \param[in]     *buf       pointer to data buffer
  \param[in]      cnt       number of sectors to read
  \param[in,out]  ftl       FTL instance object
  \return execution status
            - true if cnt sectors successfully read
            - false otherwise
*/
uint32_t ftl_ReadSect(uint32_t lsn, uint8_t *buf, uint32_t cnt, NAND_FTL_DEV *ftl) {
  BTT_ITEM btti;
  uint32_t empty, lookup, eccWarn;
  uint32_t row, col;
  uint32_t cBN, sOffs, blTyp, sectPg, rtv;
  uint32_t di, si;

  /* Read sector */
  EvrFsNFTL_ReadSector (ftl->Media->instance, lsn, buf, cnt);

  if ((ftl->Status & FTL_STATUS_MOUNT) == 0) {
    return FTL_ERROR_UNMOUNTED;
  }

  rtv     = FTL_OK;
  blTyp   = TYP_PRIM;
  row     = INVALID;
  empty   = false;
  eccWarn = false;
  lookup  = true;
  sectPg  = 0;

  ftl->CurrLBN = LBN(lsn);

  for (; cnt; ) {
    eccWarn = false;

    /* Check table */
    if (lookup) {
      lookup = false;

      EvrFsNFTL_LsnToLbn (ftl->Media->instance, lsn, LBN(lsn));

      rtv = SearchBTT (ftl, LBN(lsn), &btti);

      if (rtv != FTL_OK) {
        return rtv;
      }
    }

    cBN = btti.primBN;

    /* Check PRIM existence */
    if (btti.primBN == INVALID_BLOCK) {
      /* PRIM does not exist, send empty sectors */
      empty = true;
    }
    else {
      /* Check REPL existence */
      if (btti.replBN != INVALID_BLOCK) {
        blTyp = TYP_REPL;
        cBN = btti.replBN;

        /* Search LSN in REPL */
        rtv = ScanBlock (ftl, btti.replBN, lsn, §Pg);

        switch (rtv) {
          case FTL_OK:
            /* Page with specified LSN found, REPL is current block */
            break;
          case FTL_ERROR_NOT_FOUND_EOB:
            /* break; */

          case FTL_ERROR_NOT_FOUND:
            /* Set block back to PRIM */
            blTyp = TYP_PRIM;
            cBN   = btti.primBN;
            break;

          case FTL_ERROR_ECC:
            rtv = RefreshDataBlock (ftl, ftl->CurrLBN, TYP_REPL, &btti, pDev->page_count);
            if (rtv != FTL_OK) {
              return rtv;
            }
            continue;

          default:
            return rtv;
        }
      }
    }

    /* Set sector offset within page */
    sOffs = lsn & (ftl->PageSectors - 1);

    if (!empty) {
      /* Check PRIM if needed */
      if (cBN == btti.primBN) {
        blTyp = TYP_PRIM;

        /* Calculate page at direct offset */
        sectPg = LSN2IDX(lsn);

        row = ROW(cBN, sectPg);
        col = ftl->PgLay.spare_ofs + ftl->PgLay.spare_inc * sOffs;

        /* First check spare at direct offset */
        rtv = CachePgRead (ftl, row, col, __SZ_SP_USED);

        if (rtv != FTL_OK) {
          if (rtv == FTL_ERROR_ECC) {
            /* Relocate block */
            rtv = RefreshDataBlock(ftl, ftl->CurrLBN, TYP_PRIM, &btti, pDev->page_count);
            if (rtv != FTL_OK) {
              return rtv;
            }
            continue;
          }
          else return rtv;
        }

        /* Check if LSN found at direct offset */
        if (GetLSN (&ftl->PgBuf[ftl->PgLay.spare.ofs_lsn], NULL) == lsn) {
          /* Sector found, check for data corruption flag */

          if (ftl->PgBuf[ftl->PgLay.spare.ofs_dcm] != 0xFF) {
            eccWarn = true;
          }
        }
        else {
          /* Not found, so scan PRIM block */
          rtv = ScanBlock (ftl, btti.primBN, lsn, §Pg);

          switch (rtv) {
            case FTL_OK:
              break;

            case FTL_ERROR_NOT_FOUND:
              /* Sector not found, send empty */
              empty = true;
              break;

            case FTL_ERROR_ECC:
              rtv = RefreshDataBlock(ftl, ftl->CurrLBN, TYP_PRIM, &btti, pDev->page_count);
              if (rtv != FTL_OK) {
                return rtv;
              }
              continue;

            default:
              return rtv;
          }
        }
      }

      /* Block with logical sector must be in cBN and its page (unless empty) is in sectPg */

      /* Set row if sectors not empty */
      row = ROW (cBN, sectPg);

      rtv = CachePgRead (ftl, row, 0, pDev->page_size);
      if (rtv != FTL_OK) {
        if (rtv == FTL_ERROR_ECC) {
          rtv = RefreshDataBlock(ftl, ftl->CurrLBN, blTyp, &btti, pDev->page_count);
          if (rtv != FTL_OK) {
            return rtv;
          }
          continue;
        }
        else return rtv;
      }
    }

    /* Read sectors */
    si = ftl->PgLay.spare_ofs;
    for (di = sOffs * ftl->PgLay.sector_inc,      si += sOffs * ftl->PgLay.spare_inc;
         cnt && sOffs < ftl->PageSectors;
         di += ftl->PgLay.sector_inc,             si += ftl->PgLay.spare_inc) {

      EvrFsNFTL_LoadSector (ftl->Media->instance, lsn, di, si);

      if (empty) {
        /* Send empty sector */
        memset (buf, 0xFF, __SZ_SECT);
      }
      else {
        memcpy (buf, &ftl->PgBuf[di], __SZ_SECT);

        if (ftl->PgBuf[si + ftl->PgLay.spare.ofs_dcm] != 0xFF) {
          eccWarn = true;
        }
      }
      buf += __SZ_SECT;
      lsn++;
      cnt--;
      sOffs++;
    }

    /* Check if current LSN goes to other block */
    if (LBN(lsn - 1) < LBN(lsn)) {
      lookup = true;
      ftl->CurrLBN = LBN(lsn);
    }
  }

  EvrFsNFTL_ReadSectorSuccess (ftl->Media->instance);

  if (eccWarn) {
    return FTL_WARNING;
  }
  return FTL_OK;
}

/**
  Writes cnt sectors from *buf to NAND flash starting from given logical
  sector number.

  Logical sector being written to the NAND flash device must be written
  to the corresponding primary or replacement block. So, search in the
  block translation table is first performed, looking for previously
  allocated primary and replacement block. On devices with multiple
  sectors per page, old sector must be located first, updated and copied,
  if needed, together with other valid sectors in that page, to the
  new location in memory.
  After search in the block translation table, replacement block, if
  exist, is scanned first for given logical sector number. If sector is
  found, it's position is remembered as old, and free page in replacement
  block must be found.
  If old sector location was determined by scanning primary block, then
  another scan has to be performed in replacement block (if exists) and
  if given logical sector number was also found in replacement block,
  old sector location is updated with his location in replacement block.
  A free page is located in every case. If there is no free page found
  garbage collection is started and whole search is repeated.
  If either primary or replacement block doesn't exist, new block is
  allocated when needed, sector is then written to the first page in that
  block and block entry in translation table is updated.
  If last valid (old) sector position was discovered, then it's
  corresponding page is read to buffer if needed and new or updated
  sectors are written to it together with updated spare area(s) and
  whole page is written to block in which free page was located.
  When old sector could not be located, meaning it does not exist yet,
  only current sectors are written to the located free page.

  In devices with multiple sectors per page, sector numbers within each
  page are alligned to multiple of sectors per page, meaning, for example,
  in device with four sectors per page, first sector in page has
  number which is multiple of four: 0, 4, 8, ... , 132, 136 and so on.
  Sector numbers for next sectors are then incremented sequentialy till
  the last sector in page, ti. one page with four sectors could comprise
  of sectors 8, 9, 10 and 11.

  \param[in]      lsn       logical sector number
  \param[in]     *buf       pointer to data buffer
  \param[in]      cnt       number of sectors to write
  \param[in,out]  ftl       FTL instance object
  \return execution status
            - true if cnt sectors successfully written
            - false otherwise
*/
uint32_t ftl_WriteSect(uint32_t lsn, const uint8_t *buf, uint32_t cnt, NAND_FTL_DEV *ftl) {
  BTT_ITEM btti, oldBtti;
  uint16_t cBN, allocBN;
  uint32_t lookup, alloc;
  const uint8_t *p;
  uint32_t oldBn, oldTyp;
  uint32_t sectPg, freePg, oldPg;
  uint32_t i, di, si, blTyp, rtv;
  uint32_t k, cLsn, secNum;

  /* Write sector */
  EvrFsNFTL_WriteSector (ftl->Media->instance, lsn, buf, cnt);

  if ((ftl->Status & FTL_STATUS_MOUNT) == 0) {
    return FTL_ERROR_UNMOUNTED;
  }

  lookup  = true;

  ftl->CurrLBN = LBN(lsn);

  for (; cnt; ) {
    /* Check table */
    if (lookup) {
      lookup = false;

      EvrFsNFTL_LsnToLbn (ftl->Media->instance, lsn, LBN(lsn));

      rtv = SearchBTT (ftl, LBN(lsn), &btti);
      if (rtv != FTL_OK) {
        return rtv;
      }
    }

    k      = 0;
    sectPg = 0;
    p      = NULL;
    oldBn  = INVALID_BLOCK;
    oldPg  = INVALID;
    oldTyp = INVALID;
    freePg = INVALID;
    alloc  = false;
    cLsn   = lsn & ~(ftl->PageSectors - 1);

    blTyp  = TYP_PRIM;
    cBN    = btti.primBN;

    if (btti.primBN == INVALID_BLOCK) {
      /* Allocate PRIM block */
      rtv = AllocBlock (ftl, &btti.primBN, ALLOC_REST, AREA_DAT);
      if (rtv != FTL_OK) {
        return rtv;
      }
      alloc = true;
      cBN   = btti.primBN;
      freePg = 0;
    }
    else {
      /* Check REPL block first */
      if (btti.replBN != INVALID_BLOCK) {
        cBN = btti.replBN;
        rtv = ScanBlock (ftl, btti.replBN, lsn, §Pg);

        switch (rtv) {
          case FTL_OK:
            oldBn = btti.replBN;
            oldPg = sectPg;
            break;

          case FTL_ERROR_NOT_FOUND:
            freePg = sectPg;
            break;

          case FTL_ERROR_NOT_FOUND_EOB:
            goto gc;

          case FTL_ERROR_ECC:
            rtv = RefreshDataBlock(ftl, ftl->CurrLBN, TYP_REPL, &btti, pDev->page_count);
            if (rtv != FTL_OK) {
              return rtv;
            }
            continue;

          default:
            return rtv;
        }

        if (oldPg != INVALID) {
          /* Find empty page in REPL */
          rtv = ScanBlock (ftl, btti.replBN, EMPTY, §Pg);

          switch (rtv) {
            case FTL_OK:
              blTyp  = TYP_REPL;
              freePg = sectPg;
              break;

            case FTL_ERROR_ECC:
              rtv = RefreshDataBlock(ftl, ftl->CurrLBN, TYP_REPL, &btti, pDev->page_count);
              if (rtv != FTL_OK) {
                return rtv;
              }
              continue;

            case FTL_ERROR_NOT_FOUND_EOB:
              /* SET BN */
              goto gc;

            default:
              return rtv;
          }
        }
        else {
          /* Find old in PRIM */
          rtv = ScanBlock (ftl, btti.primBN, lsn, §Pg);

          switch (rtv) {
            case FTL_OK:
              oldBn = btti.primBN;
              oldPg = sectPg;
              cBN = btti.replBN;
              blTyp = TYP_REPL;
              break;

            case FTL_ERROR_NOT_FOUND:
              cBN = btti.primBN;
              freePg = sectPg;
              break;

             case FTL_ERROR_ECC:
              rtv = RefreshDataBlock(ftl, ftl->CurrLBN, TYP_PRIM, &btti, pDev->page_count);
              if (rtv != FTL_OK) {
                return rtv;
              }
              continue;

            default:
              return rtv;
          }
        }
      }
      else {
        /* REPL block does not exist, find old in PRIM */
        rtv = ScanBlock (ftl, btti.primBN, lsn, §Pg);

        switch (rtv) {
          case FTL_OK:
            oldBn = btti.primBN;
            oldPg = sectPg;
            cBN = btti.replBN;
            blTyp = TYP_REPL;
            break;

          case FTL_ERROR_NOT_FOUND:
            freePg = sectPg;
            break;

          case FTL_ERROR_ECC:
            rtv = RefreshDataBlock(ftl, ftl->CurrLBN, TYP_PRIM, &btti, pDev->page_count);
            if (rtv != FTL_OK) {
              return rtv;
            }
            continue;

          default:
            return rtv;
        }
        if (cBN == INVALID_BLOCK) {
          rtv = AllocBlock (ftl, &btti.replBN, ALLOC_PENUL, AREA_DAT);
          if (rtv != FTL_OK) {
            return rtv;
          }
          cBN = btti.replBN;
          alloc = true;
          freePg = 0;
        }
      }
    }

    /* Read old LSN if exists or is needed */
    if (oldPg != INVALID) {
      if (!(lsn == cLsn && cnt >= ftl->PageSectors)) {
        /* Read last valid page */
        rtv = CachePgRead (ftl, ROW(oldBn, oldPg), 0, pDev->page_size);
        if (rtv != FTL_OK) {
          if (rtv != FTL_ERROR_ECC) { return rtv; }
          /* Relocate block */
          rtv = RefreshDataBlock(ftl, ftl->CurrLBN, oldTyp, &btti, pDev->page_count);
          if (rtv != FTL_OK) {
            return rtv;
          }
          continue;
        }
      }
    }
    else {
      memset (ftl->PgBuf, 0xFF, pDev->page_size);
    }

    /* Prepare buffer for writing */
    secNum = lsn & (ftl->PageSectors - 1);
    p = buf;

    for (i = 0,       di = 0,                      si = ftl->PgLay.spare_ofs;
         i < ftl->PageSectors;
         i++, cLsn++, di += ftl->PgLay.sector_inc, si += ftl->PgLay.spare_inc) {

      if (i == secNum) {
        EvrFsNFTL_LoadSector (ftl->Media->instance, cLsn, di, si);

        memcpy (&ftl->PgBuf[di], p, __SZ_SECT);
        ftl->PgBuf[si + ftl->PgLay.spare.ofs_dcm] = 0xFF;
        p += __SZ_SECT;
        k++;
        if (cnt - k) secNum++;
      }

      SetLSN (cLsn, blTyp, &ftl->PgBuf[si + ftl->PgLay.spare.ofs_lsn]);
    }

    /* Write page */
    rtv = CachePgWrite (ftl, ROW(cBN, freePg));
    if (rtv != FTL_OK) {
      if(rtv != FTL_ERROR_PROGRAM) {
        return rtv;
      }
      /* Program failed, relocate current block and repeat write */
      rtv = RefreshDataBlock (ftl, ftl->CurrLBN, blTyp, &btti, freePg);
      if (rtv != FTL_OK) {
        return rtv;
      }
      rtv = MarkBlockBad(ftl, cBN, false);
      if (rtv != FTL_OK) {
        return rtv;
      }
      continue;
    }

    /* Update table if we allocated new block */
    if (alloc) {
      rtv = UpdateBTT (ftl, LBN(lsn), &btti.primBN, &btti.replBN);
      if (rtv != FTL_OK) {
        return rtv;
      }
    }

    if ((blTyp == TYP_REPL) && (freePg == (uint32_t)(pDev->page_count - 1))) {
      /* Last page in replacement block written, start GC */
gc:
      /* Allocate block */
      rtv = AllocBlock (ftl, &allocBN, ALLOC_LAST, AREA_DAT);

      if (rtv != FTL_OK) {
        return rtv;
      }

      /* Run GC */
      rtv = GcRun (ftl, LBN(lsn), &btti, AREA_DAT, allocBN);

      switch (rtv) {
        case FTL_OK:
          break;

        case FTL_ERROR_PROGRAM:
          goto gc;

        default:
          return rtv;
      }

      oldBtti = btti;
      btti.primBN = allocBN;
      btti.replBN = INVALID_BLOCK;

      /* Update table */
      rtv = UpdateBTT (ftl, LBN(lsn), &btti.primBN, &btti.replBN);
      if (rtv != FTL_OK) {
        return rtv;
      }

      /* Erase old blocks */
      rtv = EraseBlock (ftl, &oldBtti.primBN);
      if (rtv != FTL_OK) { return rtv; }
      rtv = EraseBlock (ftl, &oldBtti.replBN);
      if (rtv != FTL_OK) { return rtv; }
    }

    /* Update counter and buffer pointer */
    if (k) {
      cnt -= k;
      lsn += k;
      buf  = p;
    }

    /* Check if current lsn goes to other block */
    if (LBN(lsn - 1) < LBN(lsn)) {
      lookup = true;
      ftl->CurrLBN = LBN(lsn);
    }
  }

  EvrFsNFTL_WriteSectorSuccess (ftl->Media->instance);

  return FTL_OK;
}

/**
  Determine number of blocks available in data area

  \param[in,out]  ftl       FTL instance object
  \param[in]      DataBBCnt number of bad blocks in data area
  \return number of blocks in data area
*/
static uint16_t CalcDataBlocks (NAND_FTL_DEV *ftl, uint16_t DataBBCnt) {
  uint16_t num;

  /* Block 0 + EndOfArea - StartOfArea - BadBlockInArea */
  num  = 1 + ftl->Cfg->DataEndBn - ftl->Cfg->DataStartBn - DataBBCnt;
  /* Reduce number of blocks for 2% overhead */
  num -= (pDev->block_count * 2) / 100;

  return num;
}

/**
  Translation table cache initialization

  Fills RAM translation table cache with default values.

  \param[in,out]  ftl       FTL instance object
*/
static void InitBtt (NAND_FTL_DEV *ftl) {
  uint32_t tsnIdx;

  /* Init table array */
  for (tsnIdx = 0; tsnIdx < ftl->TsnTableSize; tsnIdx++) {
    ftl->TsnTable[tsnIdx] = INVALID;
  }
}

/**
  Translation table initialization

  Valid state must be determined during table initialisation. Each page
  in table area is therefore read and table sector number is extracted
  from spare. Table sector number is then divided with number of sectors
  per page so that index in nftl_TsnTable array is obtained. Row
  address together with the block type of the current page is then written
  in nftl_TsnTable array at that index.
  Last page of each replacement block is also checked for valid sector
  and if is found, garbage collection is (must be) performed.

  \param[in,out]  ftl       FTL instance object
  \return execution status FTL_STATUS
*/
static uint32_t LoadBtt (NAND_FTL_DEV *ftl) {
  BTT_ITEM btti;
  uint16_t cBN, allocBN;
  uint32_t cPg, row, rdTyp, tblTyp, tsn, tsnIdx, rtv;

  /* Loading translation table */
  EvrFsNFTL_ReadTable (ftl->Media->instance);

  /* Init translation table cache */
  InitBtt (ftl);

  /* Check all table blocks */
  for (cBN = ftl->Cfg->BttStartBn; cBN < ftl->Cfg->DataStartBn; cBN++) {
    for (cPg = 0; cPg < pDev->page_count; cPg++) {
      row = ROW(cBN, cPg);

      rtv = CachePgRead (ftl, row, ftl->PgLay.spare_ofs, __SZ_SP_USED);
      if (rtv != FTL_OK) {
        /* Ignore ECC error */
        if (rtv != FTL_ERROR_ECC) {
          return rtv;
        }
      }

      /* Skip bad block */
      if (cPg == 0 && ftl->PgBuf[ftl->PgLay.spare.ofs_bbm] != BB_MASK) {
        /* Skipping bad block */
        EvrFsNFTL_SkipBadBlock (ftl->Media->instance, cBN);
        break;
      }

      /* Get TSN */
      tsn = GetLSN (&ftl->PgBuf[ftl->PgLay.spare.ofs_lsn], &rdTyp);

      /* Skip to next block if empty */
      if (tsn == EMPTY) {
        break;
      }
      else {
        /* Get table index and update row if needed */
        tsnIdx = tsn >> ftl->SPP;

        if (tsnIdx < ftl->TsnTableSize) {
          tblTyp = ftl->TsnTable[tsnIdx] >> 24;

          if (tblTyp == 0xFF || rdTyp == TYP_REPL) {
            ftl->TsnTable[tsnIdx] = row | rdTyp << 24;
          }
        }
      }

      /* Check if last page in REPL isn't empty */
      if ((rdTyp == TYP_REPL) && (cPg == (uint32_t)(pDev->page_count - 1))) {
        /* Start garbage collection */
        btti.primBN = INVALID_BLOCK;

        rtv = ScanTable (ftl, LBN(tsn), TYP_PRIM, &btti.primBN);
        if (rtv != FTL_OK) { return rtv; }

        btti.replBN = cBN;
gc:
        /* Allocate new block */
        rtv = AllocBlock (ftl, &allocBN, ALLOC_LAST, AREA_TBL);
        if (rtv != FTL_OK) { return rtv; }

        /* Start garbage collection */
        rtv = GcRun (ftl, LBN(tsn), &btti, AREA_TBL, allocBN);
        if (rtv != FTL_OK) {
          if (rtv == FTL_ERROR_PROGRAM) {
            goto gc;
          }
          return rtv;
        }

        /* Now erase old blocks */
        rtv = EraseBlock (ftl, &btti.primBN);
        if (rtv != FTL_OK) { return rtv; }
        rtv = EraseBlock (ftl, &btti.replBN);
        if (rtv != FTL_OK) { return rtv; }
      }
    }
  }
  return FTL_OK;
}

/**
  Read Boot Block

  Block 0 is first examined for FTL signature. If FTL signature is
  present then number of available blocks in data area is calculated and
  unformatted flag is cleared else number of available blocks is set to
  zero and FTL unformatted flag is set in FTL status variable.

  \param[in,out]  ftl       FTL instance object
  \return execution status FTL_STATUS
*/
static uint32_t RdBootBlock (NAND_FTL_DEV *ftl) {
  uint32_t stat, *p;

  /* Reading boot block */
  EvrFsNFTL_ReadBootBlock (ftl->Media->instance);

  stat = Drv_ReadPage (0, ftl->PgBuf, ftl);

  if (stat == FTL_OK || stat == FTL_ERROR_ECC_COR || stat == FTL_ERROR_ECC_FAIL) {
    if (pDev->sw_ecc == 1) {
      /* Hamming ECC is enabled, decode ECC */
      DecodeECC(ftl, ftl->PgBuf);
    }

    /* PgBuf is aligned, we can access it as u32 */
    p = (uint32_t *)(uint32_t)ftl->PgBuf;

    /* Check FTL format signature */
    if ((p[0] == BOOT_SIGN) && (p[9] == BOOT_SIGN)) {
      /* Boot signature is valid */
      EvrFsNFTL_BootSignatureValid (ftl->Media->instance);

      /* Calculate number of data blocks reported at format */
      ftl->NumDataBlocks = CalcDataBlocks (ftl, (uint16_t)p[8]);
      ftl->Status |= FTL_STATUS_FORMAT;

      EvrFsNFTL_DataBlockCapacity (ftl->Media->instance, ftl->NumDataBlocks);
    }
    else {
      /* Boot signature is invalid */
      EvrFsNFTL_BootSignatureInvalid (ftl->Media->instance);

      /* Default values for unformatted NAND */
      ftl->NumDataBlocks = 0;
      ftl->Status &= ~FTL_STATUS_FORMAT;
    }
    stat = FTL_OK;
  }

  return (stat);
}


/**
  Format FTL

  First page in each block of the device is checked for manufacturer bad block
  marker. If marker is found, whole page is programmed to 0x00.
  Good blocks are all erased. If erase fails, then block is marked as bad.
  At the end, signature is written to the first page of block 0, together with
  formatting informations.
  Block 0 is therefore reserved and should not be used at any other time.

  \param[in,out]  ftl       FTL instance object
  \param[par]     par       formatting parameters
  \return execution_status
            - true if format successful
            - false on read/write error
*/
static uint32_t ftl_Format (NAND_FTL_DEV *ftl, void *par) {
  char *fp = (char *)par;
  bool bad, ebb;
  uint32_t row, rtv, bbmPos;
  uint32_t block, page;
  uint16_t TableBBCnt, DataBBCnt;
  uint32_t *p;

  /* Formatting NAND flash */
  EvrFsNFTL_Format (ftl->Media->instance);

  ftl->Status &= ~(FTL_STATUS_FORMAT | FTL_STATUS_MOUNT);

  ebb = false;

  /* Check which parameter is provided */
  if (fp && fp[0] == '/') {
    if (fp[1] == 'L' && fp[2] == 'L' && fp[3] == 'E' && fp[4] == 'B') {
      /* Bad block erase enabled */
      EvrFsNFTL_FormatLLEB (ftl->Media->instance);
      ebb = true;
    }
  }

  /* Calculate spare area byte */
  bbmPos = (uint32_t)(__SZ_SECT << Log2(ftl->PageSectors));

  if (ftl->PageSectors == 1) {
    /* Small page: bad block marker is 6th byte in spare */
    bbmPos += 5;
  }

  /* Read Boot Block */
  if (RdBootBlock (ftl) != FTL_OK) {
    EvrFsNFTL_ReadBootBlockFailed (ftl->Media->instance);
    return (false);
  }

  if (ftl->Status & FTL_STATUS_FORMAT) {
    /* Override factory bad block marker position if we already have ours */
    bbmPos = ftl->PgLay.spare_ofs + ftl->PgLay.spare.ofs_bbm;
  }

  EvrFsNFTL_BBMPositionSet (ftl->Media->instance, bbmPos);

  /* Init bad block counters */
  TableBBCnt = 0;
  DataBBCnt  = 0;

  /* Clear number of user data blocks */
  ftl->NumDataBlocks = 0;

  /* Loop through all blocks, erase good and mark bad ones */
  for (block = 0; block < pDev->block_count; block++) {
    bad  = false;
    page = 0;

    do {
      /* Check page for bad block marker */
      row = ROW (block, page);

      rtv = Drv_ReadPage (row, ftl->PgBuf, ftl);
      switch (rtv) {
        case FTL_OK:
        case FTL_ERROR_ECC_COR:
        case FTL_ERROR_ECC_FAIL:
          break;

        default:
          return rtv;
      }

      if (ftl->PgBuf[bbmPos] != 0xFF) {
        /* Bad block marker is set */
        EvrFsNFTL_BadBlockDetected (ftl->Media->instance, block);
        bad = true;
        break;
      }

      if (page == 1) {
        /* Go to last page in block */
        page = pDev->page_count - 1;
      }
      else {
        page++;
      }
    }
    while (page != pDev->page_count);

    row = ROW (block, 0);

    if (bad == false || ebb == true) {
      /* Erase block if not bad or if erase of bad blocks requested */
      rtv = Drv_EraseBlock (row, ftl);
      switch (rtv) {
        case FTL_OK:
          bad = false;
          break;

        case FTL_ERROR_ERASE:
          /* Erase failed, this is bad block */
          bad = true;
          ftl->BadBlockCnt++;
          break;

        default:
          return false;
      }
    }

    if (bad) {
      /* Block is bad, mark it as defective */
      EvrFsNFTL_BadBlockMark (ftl->Media->instance, block);

      memset (ftl->PgBuf, 0x00, pDev->page_size);
      Drv_WritePage (row, ftl->PgBuf, ftl);

      /* Increment bad block counter */
      if (block >= ftl->Cfg->BttStartBn && block <= ftl->Cfg->BttEndBn) {
        TableBBCnt++;
      }
      else {
        if (block >= ftl->Cfg->DataStartBn && block <= ftl->Cfg->DataEndBn) {
          DataBBCnt++;
        }
      }
    }
  }
  memset(ftl->PgBuf, 0xFF, pDev->page_size);
  /* Write formating info in boot block */
  p = (uint32_t *)(uint32_t)&ftl->PgBuf[0];

  p[0] = BOOT_SIGN;                 /* 0. signature                       */
  p[1] = ftl->Cfg->BttStartBn;      /* 1. start of table                  */
  p[2] = ftl->Cfg->BttEndBn;        /* 2. end of table                    */
  p[3] = ftl->Cfg->DataStartBn;     /* 3. start of data                   */
  p[4] = ftl->Cfg->DataEndBn;       /* 4. end of data                     */
  p[5] = ftl->PgLay.spare.ofs_bbm;  /* 5. bad block marker position       */
  p[6] = ftl->PgLay.spare.ofs_lsn;  /* 6. logical sector number position  */
  p[7] = TableBBCnt;                /* 7. table bad block count           */
  p[8] = DataBBCnt;                 /* 8. data bad block count            */
  p[9] = BOOT_SIGN;                 /* 9. signature                       */

  if (pDev->sw_ecc == 1) {
    /* Hamming ECC is enabled, encode ECC */
    EncodeECC (ftl, ftl->PgBuf);
  }

  rtv = Drv_WritePage (0, ftl->PgBuf, ftl);
  if (rtv != FTL_OK) {
    return false;
  }

  InitBtt (ftl);

  ftl->NumDataBlocks = CalcDataBlocks (ftl, DataBBCnt);

  /* Low level formatting completed */
  EvrFsNFTL_FormatSuccess (ftl->Media->instance);

  ftl->Status |= FTL_STATUS_FORMAT | FTL_STATUS_MOUNT;
  return true;
}


/**
  Initialize storage media

  \param[in,out]  ftl       FTL instance object
  \return execution status
            - true if operation ok
            - false otherwise
*/
static uint32_t ftl_init_media (NAND_FTL_DEV *ftl) {
  uint32_t status;

  /* Mounting NFTL */
  EvrFsNFTL_Mount (ftl->Media->instance);

  status = true;

  ftl->Status &= ~(FTL_STATUS_FORMAT | FTL_STATUS_MOUNT);

  if ((ftl->Status & FTL_STATUS_INIT) == 0) {
    /* Driver not initialized */
    EvrFsNFTL_DeviceNotInitialized (ftl->Media->instance);
    status = false;
  }
  else if (Drv_ResetDevice (ftl) != FTL_OK) {
    /* Failed to reset the device */
    status = false;
  }
  else if (Drv_ReadParameters (ftl) != FTL_OK) {
    /* Failed to read parameter page */
    status = false;
  }
  else if (Drv_ManageECC (ftl) != FTL_OK) {
    /* Failed to determine ECC configuration */
    status = false;
  }
  else if (Drv_SetupFeatures (ftl) != FTL_OK) {
    /* Failed to retrieve or modify feature parameters */
    status = false;
  }
  else if (RdBootBlock (ftl) != FTL_OK) {
    /* Failed to read boot block */
    EvrFsNFTL_ReadBootBlockFailed (ftl->Media->instance);
    status = false;
  }
  else {
    if (ftl->Status & FTL_STATUS_FORMAT) {
      /* Load translation table */
      if (LoadBtt (ftl) != FTL_OK) {
        /* Failed to load translation table */
        EvrFsNFTL_ReadTableFailed (ftl->Media->instance);
        status = false;
      }
    }

    ftl->Status |= FTL_STATUS_MOUNT;
  }

#ifdef FS_DEBUG
  if (status == true) {
    EvrFsNFTL_MountSuccess (ftl->Media->instance);
  }
#endif

  return (status);
}


/**
  Initialize FTL resources

  \param[in,out]  ftl       FTL instance object
  \return execution status
            - true if operation ok
            - false otherwise
*/
static uint32_t ftl_init_resources (NAND_FTL_DEV *ftl) {
  uint32_t i, status;
  uint8_t *bp;
  NAND_FTL_CFG *cfg = ftl->Cfg;

  /* Initializing NFTL */
  EvrFsNFTL_Init            (ftl->Media->instance);
  EvrFsNFTL_LoadTableLayout (ftl->Media->instance, ftl->Cfg->BttStartBn,  ftl->Cfg->BttEndBn);
  EvrFsNFTL_LoadDataLayout  (ftl->Media->instance, ftl->Cfg->DataStartBn, ftl->Cfg->DataEndBn);

  /* Load buffer pointers */
  ftl->PgBuf    = cfg->PgBuf;
  ftl->Ca.Block = cfg->BlockCache;
  ftl->Ca.Page  = cfg->PageCache;
  ftl->TsnTable = cfg->TsnTable;

  /* Load buffer sizes */
  ftl->Ca.CachedBlocks = cfg->NumCacheBlocks;
  ftl->Ca.CachedPages  = cfg->NumCachePages;
  ftl->TsnTableSize    = cfg->TsnTableSize;

  /* Load default page layout */
  ftl->PgLay.spare.ofs_lsn  = DL_POS_LSN;
  ftl->PgLay.spare.ofs_dcm  = DL_POS_COR;
  ftl->PgLay.spare.ofs_bbm  = DL_POS_BBM;
  ftl->PgLay.spare.ofs_ecc  = DL_POS_ECC;

  ftl->PgLay.sector_inc     = DL_SECT_INC;
  ftl->PgLay.spare_ofs      = DL_SPARE_OFS;
  ftl->PgLay.spare_inc      = DL_SPARE_INC;

  /* Set FTL parameters */
  ftl->PageSectors = pDev->page_sectors;
  ftl->SPP         = (uint8_t)Log2 (pDev->page_sectors);
  ftl->PPB         = (uint8_t)Log2 (pDev->page_count);
  ftl->SPB         = (uint8_t)Log2 (pDev->block_sectors);

  ftl->LastTBN     = cfg->BttEndBn;
  ftl->LastDBN     = cfg->DataEndBn;

  ftl->GcLBN         = 0;
  ftl->BadBlockCnt   = 0;
  ftl->NumDataBlocks = 0;
  ftl->Status        = 0;

  /* Clear block queue */
  ftl->PbnQ[0]     = 0;
  ftl->PbnQ[1]     = 0;
  ftl->PbnQ[2]     = 0;

  /* Init block indexing */
  for (bp = cfg->BlockCacheBuf, i = 0; i < cfg->NumCacheBlocks; i++, bp += pDev->page_count) {
    ftl->Ca.Block[i].pbn   = INVALID_BLOCK;
    ftl->Ca.Block[i].pgIdx = bp;
  }

  /* Init page caching */
  for (bp = cfg->PageCacheBuf, i = 0; i < cfg->NumCachePages; i++, bp += pDev->page_size) {
    ftl->Ca.Page[i].row = INVALID;
    ftl->Ca.Page[i].buf = bp;
  }

  /* Init Block Translation Table Cache */
  InitBtt (ftl);

  if (NAND_MediaDriver.Initialize (ftl->Media) != NAND_OK) {
    /* Media driver initialization failed */
    EvrFsNFTL_InitMediaFailed (ftl->Media->instance);
    status = false;
  }
  else {
    ftl->Status = FTL_STATUS_INIT;
    status = true;
  }

  return (status);
}

/**
  Initialize FTL

  \param[in]      mode      initialization mode
  \param[in,out]  ftl       FTL instance object
  \return execution status
            - true if initialization passed
            - false otherwise
*/
uint32_t ftl_Init (uint32_t mode, NAND_FTL_DEV *ftl) {
  uint32_t status;

  switch (mode) {
    case DM_IO:
      status = ftl_init_resources (ftl);
      break;

    case DM_MEDIA:
      status = ftl_init_media (ftl);
      break;

    default:
      status = false;
      break;
  }

  return (status);
}


/**
  Uninitialize storage media

  \param[in]      mode      uninitialization mode (DM_IO / DN_MEDIA)
  \param[in,out]  ftl       FTL instance object
  \return execution status
            - true on success
            - false otherwise
*/
uint32_t ftl_UnInit (uint32_t mode, NAND_FTL_DEV *ftl) {
  uint32_t status;

  status = true;

  if (mode == DM_IO) {
    /* Uninit media driver */
    EvrFsNFTL_UninitDriver (ftl->Media->instance);

    if (NAND_MediaDriver.Uninitialize (ftl->Media) != NAND_OK) {
      /* Failed to uninit media driver */
      EvrFsNFTL_UninitDriverFailed (ftl->Media->instance);
      status = false;
    }
  }
  else {
    /* Uninit NFTL */
    EvrFsNFTL_Uninit (ftl->Media->instance);

    if (mode != DM_MEDIA) {
      status = false;
    }
  }

  return (status);
}


/**
  Read storage media geometry properties

  \param[out]     info      pointer to \ref FS_MEDIA_INFO object
  \param[in]      ftl       FTL instance object
  \return execution status
            - true on success
            - false otherwise
*/
uint32_t ftl_ReadInfo (fsMediaInfo *info, NAND_FTL_DEV *ftl) {
  uint32_t status;

  /* Retreiving media info */
  EvrFsNFTL_ReadInfo (ftl->Media->instance, (uint32_t)info);

  if (ftl == NULL || info == NULL) {
    /* Invalid parameters */
    EvrFsNFTL_ParameterInvalid (ftl->Media->instance);
    status = false;
  }
  else {
    info->block_cnt  = (uint32_t)(ftl->NumDataBlocks << ftl->SPB);
    info->read_blen  = __SZ_SECT;
    info->write_blen = __SZ_SECT;

    EvrFsNFTL_ReadInfoSuccess (ftl->Media->instance, info->block_cnt);
    status = true;
  }

  return (status);
}


/**
  Process given device control command

  \param[in]      code      device control command code
  \param[in,out]  p         command argument
  \param[in]      ftl       FTL instance object
  \return \ref fsStatus
*/
fsStatus ftl_DevCtrl (fsDevCtrlCode code, void *p, NAND_FTL_DEV *ftl) {
  fsStatus status;

  EvrFsNFTL_DevCtrl (ftl->Media->instance, code, p);

  status = fsError;

  if (code == fsDevCtrlCodeCheckMedia) {
    /* Check for media presence */
    if (p) {
      *(uint32_t *)p = FS_MEDIA_NOCHKMEDIA;
      status = fsOK;
    }
  }
  else if (code == fsDevCtrlCodeControlMedia) {
    /* Control media */
    if (p != NULL) {
      switch (*(uint32_t *)p) {
        case FS_CONTROL_MEDIA_INIT:
          if (ftl_Init (DM_MEDIA, ftl) == true) {
            status = fsOK;
          }
          break;

        default:
          status = fsUnsupported;
          break;
      }
    }
  }
  else if (code == fsDevCtrlCodeFormat) {
    if (ftl_Format (ftl, p) == true) {
      status = fsOK;
    }
  }
  else {
    /* Unsupported */
    EvrFsNFTL_DevCtrlUnsupported (ftl->Media->instance, code);
    status = fsUnsupported;
  }

  return (status);
}


/*-----------------------------------------------------------------------------
 *      32-bit number parity calculation
 *----------------------------------------------------------------------------*/
__STATIC_FORCEINLINE uint32_t parity32(uint32_t num) {
  num ^= num >> 16;
  num ^= num >> 8;
  num ^= num >> 4;
  num ^= num >> 2;
  num ^= num >> 1;
  return num & 0x01;
}

/*-----------------------------------------------------------------------------
 *      Odd and even Hamming parity calculation of a 32-bit number
 *----------------------------------------------------------------------------*/
__STATIC_FORCEINLINE uint32_t ham32bit(uint32_t sum) {
  uint32_t v1, v2, v3, h;

  v1  = sum ^ (sum >> 16);
  v1 ^= v1 >> 8;
  v2  = v1 ^ (v1 >> 4);

  v3 = v2 ^ (v2 >> 2);
  h  = (v3 << 15) & 0x010000;    // P1o == bit(16)
  h |= (v3 & 0x01);              // P1e == bit(0)

  v3 = v2 ^ (v2 >> 1);
  h |= (v3 << 15) & 0x020000;    // P2o == bit(17)
  h |= (v3 << 1) & 0x02;         // P2e == bit(1)

  v1 ^= v1 >> 2;
  v1 ^= v1 >> 1;
  h |= (v1 << 14) & 0x040000;    // P4o == bit(18)
  h |= (v1 << 2) & 0x04;         // P4e == bit(2)

  v1  = sum ^ (sum >> 4);
  v1 ^= v1 >> 2;
  v1 ^= v1 >> 1;
  v2 = v1 ^ (v1 >> 16);
  h |= (v2 << 11) & 0x080000;    // P8o == bit(19)
  h |= (v2 << 3) & 0x0008;       // P8e == bit(3)

  v2 = v1 ^ (v1 >> 8);
  h |= (v2 << 4) & 0x100000;     // P16o == bit(20)
  h |= (v2 << 4) & 0x0010;       // P16e == bit(4)

  return h;
}

/*-----------------------------------------------------------------------------
 *      12 bit Hamming for a 8 bytes of data
 *
 * Input data organization:
 *  ----------------------
 *  bit: 31 30 ... 2 1 0
 *  ----------------------------------------------------------------
 *                                       Row parity bits:
 *  ...      Word   0         |               P1e
 *  ...      Word   1         |               P1o
 *  ----------------------------------------------------------------
 *    Column parity bits:     |
 *    P16e P8e P4e P2e P1e    |         e == even
 *                            |
 *    P16o P8o P4o P2o P1o    |         o == odd
 *  ----------------------------------------------------------------
 *
 * Output 16 bit word organization:
 * --------------------------------------------------------------
 *     |Zero bits| Odd parity bits  |Zero bits| Even parity bits |
 * bit:| 15  14  |  13 |12   ...   8|  7  6   |  5  | 4  ...  0  |
 * ---------------------------------------------------------------
 * par:| 0   0   | P1o |P16o ... P1o|  0  0   | P1e |P16e ... P1e|
 *     | 0   0   | Row |   Column   |  0  0   | Row |   Column   |
 *----------------------------------------------------------------------------*/
static uint32_t ecc_Hamming8 (uint8_t *dataBuf, uint8_t *eccBuf) {
  uint32_t i, colSum, rowSum;
  uint32_t h, val;

  colSum = 0;
  rowSum = 0;

  for (i = 0; i < 2; i++, dataBuf += 4) {
    val = __UNALIGNED_UINT32 (dataBuf);

    colSum ^= val;
    rowSum |= parity32(val) << i;
  }
  /* Column parities */
  h = ham32bit (colSum);

  eccBuf[0] = (uint8_t)((h         | (rowSum << 5)) & 0x3F);    //even byte
  eccBuf[1] = (uint8_t)(((h >> 16) | ((rowSum & 0x02) << 4)));  //odd byte

  return 2;
}

/*-----------------------------------------------------------------------------
 *      Verify 12 bit Hamming for a 8 bytes of data
 *----------------------------------------------------------------------------*/
static uint32_t ecc_Hamming8Verify (uint8_t *dataBuf, uint8_t *eccBuf) {
  uint8_t ecc[2];
  uint32_t err, xor, addr, row;

  ecc_Hamming8(dataBuf, ecc);
  xor = ecc[0] ^ ecc[1] ^ eccBuf[0] ^ eccBuf[1];
  err = 0;
  if (xor == 0x3F) {
    /* Correctable single bit error */
    addr = ecc[1] ^ eccBuf[1];
    row = addr >> 5;

    __UNALIGNED_UINT32 (&dataBuf[row << 2]) ^= (1 << (addr & 0x1F));

    err = ECC_SINGLEBITERR;
  }
  else if (xor != 0x00) {
    /* Uncorrectable error */
    err = ECC_MULTIBITERR;
  }
  else {
    err = ECC_NOERR;
  }

  return err;
}

/*-----------------------------------------------------------------------------
 *      24 bit Hamming for a 512 byte chunk of data
 *
 * Input data organization:
 *  ----------------------
 *  bit: 31 30 ... 2 1 0
 *  ----------------------------------------------------------------
 *  ...      Word   0         |
 *  ...      Word   1         |          Row parity bits:
 *  ...        ...            | P64e  P32e  P16e  P8e  P4e  P2e  P1e
 *  ...        ...            |
 *  ...        ...            | P64o  P32o  P16o  P8o  P4o  P2o  P1o
 *  ...      Word 127         |
 *  ----------------------------------------------------------------
 *    Column parity bits:     |
 *    P16e P8e P4e P2e P1e    |         e == even
 *                            |
 *    P16o P8o P4o P2o P1o    |         o == odd
 *  ----------------------------------------------------------------
 *
 * Output 32 bit word organization:
 * ------------------------------------------------------------------------
 *     |  Zero bits  |       Odd parity bits     |    Even parity bits     |
 * bit:| 31  ...  24 | 23  ...  17 | 16  ...  12 | 11  ...  5 | 4  ...  0  |
 * -------------------------------------------------------------------------
 * par:|  0  ...   0 |P64o ... P1o |P16o ... P1o |P64e ... P1e|P16e ... P1e|
 *                   |     Row     |   Column    |    Row     |   Column   |
 *----------------------------------------------------------------------------*/
static uint32_t ecc_Hamming512 (uint8_t *dataBuf, uint8_t *eccBuf) {
  uint32_t i, colSum, rowSum0, rowSum1, rowSum2, rowSum3;
  uint32_t h, val;

  colSum  = 0;
  rowSum0 = 0;
  rowSum1 = 0;
  rowSum2 = 0;
  rowSum3 = 0;

  for (i = 0; i < 32; i++, dataBuf += 4) {
    val = __UNALIGNED_UINT32 (dataBuf);

    /* Column sum */
    colSum ^= val;

    /* Row sum */
    rowSum0 |= parity32(val) << (i & 0x1F);
  }
  for (; i < 64; i++, dataBuf += 4) {
    val = __UNALIGNED_UINT32 (dataBuf);

    /* Column sum */
    colSum ^= val;

    /* Row sum */
    rowSum1 |= parity32(val) << (i & 0x1F);
  }
  for (; i < 96; i++, dataBuf += 4) {
    val = __UNALIGNED_UINT32 (dataBuf);

    /* Column sum */
    colSum ^= val;

    /* Row sum */
    rowSum2 |= parity32(val) << (i & 0x1F);
  }
  for (; i < 128; i++, dataBuf += 4) {
    val = __UNALIGNED_UINT32 (dataBuf);

    /* Column sum */
    colSum ^= val;

    /* Row sum */
    rowSum3 |= parity32(val) << (i & 0x1F);
  }

  /* Column parity bits: P16, P8, P4, P2, P1 */
  h = ham32bit (colSum);

  /* Row parity bits: P64, P32, P16, P8, P4, P2, P1 */

  /* Row: P64e */
  val = rowSum1 ^ rowSum0;
  h |= parity32 (val) << 11; // even bit(11)
  /* Row: P64o */
  val = rowSum3 ^ rowSum2;
  h |= parity32 (val) << 27; // odd  bit(27)

  /* Row: P32e */
  val = rowSum2 ^ rowSum0;
  h |= parity32 (val) << 10; // even bit(10)

  /* Row: P32o */
  val = rowSum3 ^ rowSum1;
  h |= parity32 (val) << 26; // odd  bit(26)

  /* Row: P16, P8, P4, P2, P1 */
  val = rowSum3 ^ rowSum2 ^ rowSum1 ^ rowSum0;
  h |= ham32bit (val) << 5;

  eccBuf[0] = (uint8_t)(h);
  eccBuf[1] = (uint8_t)(((h & 0x000F0000) >> 12) | (h >> 8));
  eccBuf[2] = (uint8_t)(h >> 20);

  return 3;
}

/*-----------------------------------------------------------------------------
 *      Verify 24 bit Hamming ECC for a 512 bytes chunk of data
 *----------------------------------------------------------------------------*/
static uint32_t ecc_Hamming512Verify (uint8_t *dataBuf, uint8_t *eccBuf) {
  uint8_t ecc[3];
  int32_t newEcc, oldEcc, xorEcc;
  uint32_t err, addr, row;

  ecc_Hamming512(dataBuf, ecc);
  newEcc = ecc[0]    | (ecc[1]    << 8) | (ecc[2]    << 16);
  oldEcc = eccBuf[0] | (eccBuf[1] << 8) | (eccBuf[2] << 16);

  xorEcc  = newEcc ^ oldEcc;
  xorEcc ^= xorEcc >> 12;
  xorEcc &= 0x0FFF;

  err = 0;

  if (xorEcc == 0x0FFF) {
    /* Correctable single bit error */
    addr = (uint32_t)((newEcc >> 12) ^ (oldEcc >> 12));
    row  = addr >> 5;

    /* Flip bit in calculated row */
    __UNALIGNED_UINT32 (&dataBuf[row << 2]) ^= (1UL << (addr & 0x1F));

    err = ECC_SINGLEBITERR;
  }
  else if (xorEcc != 0x00) {
    /* Uncorrectable error */
    err = ECC_MULTIBITERR;
  }
  else {
    err = ECC_NOERR;
  }
  return err;
}

/*-----------------------------------------------------------------------------
 *     Encode Error Correction Code
 *
 * *ftl   = FTL instance pointer
 * *pgBuf = page buffer
 *
 *  Returns: none
 *----------------------------------------------------------------------------*/
static void EncodeECC (NAND_FTL_DEV *ftl, uint8_t *pgBuf) {
  uint32_t sec, ecc_len;
  uint8_t *dp, *sp;

  dp = pgBuf;
  sp = dp + ftl->PgLay.spare_ofs;

  for (sec = 0; sec < ftl->PageSectors; sec++) {
    ecc_len = ecc_Hamming512 (dp, sp + ftl->PgLay.spare.ofs_ecc);
    ecc_Hamming8 (sp, sp + ftl->PgLay.spare.ofs_ecc + ecc_len);

    /* Point to data and spare of the next sector */
    dp += ftl->PgLay.sector_inc;
    sp += ftl->PgLay.spare_inc;
  }
}

/*-----------------------------------------------------------------------------
 *     Decode Error Correction Code
 *
 * *ftl   = FTL instance pointer
 * *pgBuf = page buffer
 *
 *  Returns:  FTL_OK             - No ECC error
 *            FTL_ERROR_ECC_COR  - ECC corrected the data
 *            FTL_ERROR_ECC_FAIL - ECC was not able to correct the data
 *----------------------------------------------------------------------------*/
static uint32_t DecodeECC (NAND_FTL_DEV *ftl, uint8_t *pgBuf) {
  uint32_t sec, ecc, err_s, err_d;
  uint8_t *dp, *sp, *ep;

  dp = pgBuf;
  sp = dp + ftl->PgLay.spare_ofs;

  ecc = ECC_NOERR;
  for (sec = 0; sec < ftl->PageSectors; sec++) {
    /* Set ECC position pointer */
    ep = sp + ftl->PgLay.spare.ofs_ecc;
    /* Detect empty page */
    if ((ep[3] & 0xC0) == 0xC0) {
      /* This is an empty page */
      ep[0] = 0; ep[1] = 0; ep[2] = 0; ep[3] = 0; ep[4] = 0;
    }
    /* Verify data using ECC */
    err_s = ecc_Hamming8Verify   (sp, ep + 3);
    err_d = ecc_Hamming512Verify (dp, ep);

    /* Point to data and spare of the next sector */
    dp += ftl->PgLay.sector_inc;
    sp += ftl->PgLay.spare_inc;

    if ((err_s == ECC_SINGLEBITERR) || (err_d == ECC_SINGLEBITERR)) {
      ecc |= ECC_CORRECTED;
    }
    else if ((err_s == ECC_MULTIBITERR) || (err_d == ECC_MULTIBITERR)) {
      ecc |= ECC_UNCORRECTED;
    }
  }
  if (ecc & ECC_UNCORRECTED) {
    return FTL_ERROR_ECC_FAIL;
  }
  if (ecc & ECC_CORRECTED) {
    return FTL_ERROR_ECC_COR;
  }
  return FTL_OK;
}

评分

参与人数 1金币 +10 收起 理由
zj_chill + 10 很给力!

查看全部评分

回复

使用道具 举报

4

主题

1459

回帖

1471

积分

至尊会员

积分
1471
发表于 2024-6-4 13:04:51 | 显示全部楼层
回复

使用道具 举报

0

主题

2

回帖

2

积分

新手上路

积分
2
发表于 2024-6-5 16:33:07 | 显示全部楼层
   最近刚好再找这些资料,多谢硬汉哥
回复

使用道具 举报

6

主题

683

回帖

701

积分

金牌会员

积分
701
QQ
发表于 2024-6-5 22:13:26 | 显示全部楼层
硬汉源代码研究的怎么样了,USB的RNDIS模拟网卡显示安装驱动失败,无法生成网卡设备
回复

使用道具 举报

1万

主题

7万

回帖

11万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
117512
QQ
 楼主| 发表于 2024-6-6 01:39:35 | 显示全部楼层
yklstudent 发表于 2024-6-5 22:13
硬汉源代码研究的怎么样了,USB的RNDIS模拟网卡显示安装驱动失败,无法生成网卡设备

我还没弄这个的源码工程。
回复

使用道具 举报

1

主题

4

回帖

7

积分

新手上路

积分
7
发表于 2024-6-6 17:45:27 | 显示全部楼层
RL-FlashFS免费的吗?每太看明白它的开源许可,可不可以闭源商用。
回复

使用道具 举报

1万

主题

7万

回帖

11万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
117512
QQ
 楼主| 发表于 2024-6-6 17:50:22 | 显示全部楼层
Jimmy_Huang 发表于 2024-6-6 17:45
RL-FlashFS免费的吗?每太看明白它的开源许可,可不可以闭源商用。

RTX5/RTX4全家桶授权说明:ARM内核芯片免费使用

https://github.com/ARM-software/MDK-Middleware

正式公布License说明:ARM内核芯片免费使用,如果是MDK-Professional 或 MDK-Essential 的用户有权联系 Arm 获取支持和维护。


回复

使用道具 举报

0

主题

4

回帖

4

积分

新手上路

积分
4
QQ
发表于 2024-11-18 09:58:41 | 显示全部楼层
现在有没有什么好的,方便移植的NAND Flash擦写平衡的库啊,原来用的一个随便从网上找的好像不太行,老是有问题,麻烦硬汉哥推荐一个,到时候我自己去找找文档
回复

使用道具 举报

1万

主题

7万

回帖

11万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
117512
QQ
 楼主| 发表于 2024-11-18 10:48:08 | 显示全部楼层
szazone 发表于 2024-11-18 09:58
现在有没有什么好的,方便移植的NAND Flash擦写平衡的库啊,原来用的一个随便从网上找的好像不太行,老是有 ...

就用FlashFS全套方案就行,安全稳定好用。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2025-8-12 03:46 , Processed in 0.074481 second(s), 25 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表