rt-thread-official/bsp/hc32/libraries/hc32_drivers/drv_nand.c

616 lines
18 KiB
C

/*
* Copyright (C) 2022-2024, Xiaohua Semiconductor Co., Ltd.
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2023-03-01 CDT first version
*/
/*******************************************************************************
* Include files
******************************************************************************/
#include <rtthread.h>
#if defined (BSP_USING_EXMC)
#if defined (BSP_USING_NAND)
#include "drv_nand.h"
#include "board_config.h"
#include "nand_port.h"
/*******************************************************************************
* Local type definitions ('typedef')
******************************************************************************/
/* rthw nand */
struct rthw_nand
{
struct rt_mtd_nand_device nand_dev;
rt_uint32_t nfc_bank;
rt_uint32_t id;
struct rt_mutex lock;
};
/*******************************************************************************
* Local pre-processor symbols/macros ('#define')
******************************************************************************/
//#define DRV_DEBUG
#define LOG_TAG "drv.nand"
#include <drv_log.h>
/* Nand status */
#define NAND_BUSY 0x00000000U
#define NAND_FAIL 0x00000001U
#define NAND_READY 0x00000040U
#define NAND_VALID_ADDRESS 0x00000100U
#define NAND_INVALID_ADDRESS 0x00000200U
#define NAND_TIMEOUT_ERROR 0x00000400U
#define NAND_ERASE_TIMEOUT 2000000UL
#define NAND_READ_TIMEOUT 2000000UL
#define NAND_WRITE_TIMEOUT 2000000UL
#define NAND_RESET_TIMEOUT 2000000UL
#define NAND_ECC_SECTOR_SIZE 512UL
#define NAND_ECC_CODE_SIZE ((NAND_EXMC_NFC_ECC_MD == EXMC_NFC_1BIT_ECC) ? 3UL : 8UL)
#define NAND_SPARE_FREE_SIZE (NAND_SPARE_AREA_SIZE - (NAND_BYTES_PER_PAGE / NAND_ECC_SECTOR_SIZE) * NAND_ECC_CODE_SIZE)
/*******************************************************************************
* Global variable definitions (declared in header file with 'extern')
******************************************************************************/
#if defined (BSP_USING_NAND)
extern rt_err_t rt_hw_board_nand_init(void);
#endif
/*******************************************************************************
* Local function prototypes ('static')
******************************************************************************/
/*******************************************************************************
* Local variable definitions ('static')
******************************************************************************/
struct rthw_nand _hw_nand;
/*******************************************************************************
* Function implementation - global ('extern') and local ('static')
******************************************************************************/
static rt_err_t _nand_verify_clock_frequency(void)
{
rt_err_t ret = RT_EOK;
#if defined (HC32F4A0)
/* EXCLK max frequency for Nand: 60MHz */
if (CLK_GetBusClockFreq(CLK_BUS_EXCLK) > (60 * 1000000))
{
ret = -RT_ERROR;
}
#endif
return ret;
}
static rt_err_t _nand_init(struct rt_mtd_nand_device *device)
{
rt_uint8_t au8DevId[4];
rt_err_t ret = -RT_ERROR;
stc_exmc_nfc_init_t nfc_init_params;
struct rthw_nand *hw_nand = (struct rthw_nand *)device;
rt_uint16_t oob_free = (rt_uint16_t)(NAND_SPARE_AREA_SIZE - \
(NAND_BYTES_PER_PAGE / NAND_ECC_SECTOR_SIZE) * NAND_ECC_CODE_SIZE);
RT_ASSERT(device != RT_NULL);
hw_nand->nfc_bank = NAND_EXMC_NFC_BANK;
/* verify nand clock frequency */
if (_nand_verify_clock_frequency() != RT_EOK)
{
LOG_E("EXMC clock frequency is over limit for NAND!");
return -RT_ERROR;
}
/* Initialize nand port.*/
rt_hw_board_nand_init();
/* Enable NFC module clock */
FCG_Fcg3PeriphClockCmd(FCG3_PERIPH_NFC, ENABLE);
/* Enable NFC. */
EXMC_NFC_Cmd(ENABLE);
/* Configure NFC base parameters. */
nfc_init_params.u32OpenPage = EXMC_NFC_OPEN_PAGE_DISABLE;
nfc_init_params.stcBaseConfig.u32CapacitySize = NAND_EXMC_NFC_BANK_CAPACITY;
nfc_init_params.stcBaseConfig.u32MemoryWidth = NAND_EXMC_NFC_MEMORY_WIDTH;
nfc_init_params.stcBaseConfig.u32BankNum = EXMC_NFC_1BANK;
nfc_init_params.stcBaseConfig.u32PageSize = NAND_EXMC_NFC_PAGE_SIZE;
nfc_init_params.stcBaseConfig.u32WriteProtect = EXMC_NFC_WR_PROTECT_DISABLE;
nfc_init_params.stcBaseConfig.u32EccMode = NAND_EXMC_NFC_ECC_MD;
nfc_init_params.stcBaseConfig.u32RowAddrCycle = NAND_EXMC_NFC_ROW_ADDR_CYCLE;
nfc_init_params.stcBaseConfig.u8SpareSizeForUserData = (rt_uint8_t)(oob_free >> 2);
/* Configure NFC timing */
nfc_init_params.stcTimingReg0.u32TS = NAND_TS;
nfc_init_params.stcTimingReg0.u32TWP = NAND_TWP;
nfc_init_params.stcTimingReg0.u32TRP = NAND_TRP;
nfc_init_params.stcTimingReg0.u32TH = NAND_TH;
nfc_init_params.stcTimingReg1.u32TWH = NAND_TWH;
nfc_init_params.stcTimingReg1.u32TRH = NAND_TRH;
nfc_init_params.stcTimingReg1.u32TRR = NAND_TRR;
nfc_init_params.stcTimingReg1.u32TWB = NAND_TWB;
nfc_init_params.stcTimingReg2.u32TCCS = NAND_TCCS;
nfc_init_params.stcTimingReg2.u32TWTR = NAND_TWTR;
nfc_init_params.stcTimingReg2.u32TRTW = NAND_TRTW;
nfc_init_params.stcTimingReg2.u32TADL = NAND_TADL;
if (LL_OK == EXMC_NFC_Init(&nfc_init_params))
{
/* Reset NFC device. */
if (LL_OK == EXMC_NFC_Reset(hw_nand->nfc_bank, NAND_RESET_TIMEOUT))
{
EXMC_NFC_ReadId(hw_nand->nfc_bank, 0UL, au8DevId, sizeof(au8DevId), NAND_READ_TIMEOUT);
hw_nand->id = (((rt_uint32_t)au8DevId[3]) << 24 | ((rt_uint32_t)au8DevId[2]) << 16 | \
((rt_uint32_t)au8DevId[1]) << 8 | (rt_uint32_t)au8DevId[0]);
LOG_D("Nand Flash ID = 0x%02X,0x%02X,0x%02X,0x%02X",
au8DevId[0], au8DevId[1], au8DevId[2], au8DevId[3]);
ret = RT_EOK;
}
}
return ret;
}
static rt_err_t _nand_wait_ready(rt_uint32_t nfc_bank, rt_uint32_t timeout)
{
rt_err_t ret = RT_EOK;
rt_uint32_t to = 0UL;
rt_uint32_t status = 0UL;
do
{
/* Block checking flag if timeout value is NAND_WRITE_TIMEOUT */
if (to++ > timeout)
{
ret = -RT_ETIMEOUT;
LOG_E("get nand status timeout!");
break;
}
status = EXMC_NFC_ReadStatus(nfc_bank);
}
while (0UL == (status & NAND_READY));
if (RT_ETIMEOUT != ret)
{
if (0UL != (status & NAND_FAIL))
{
ret = -RT_ERROR;
LOG_E("nand status error!");
}
}
return ret;
}
rt_err_t _nand_erase_block(struct rt_mtd_nand_device *device, rt_uint32_t block)
{
rt_err_t ret = -RT_ERROR;
rt_uint32_t block_num;
struct rthw_nand *hw_nand = (struct rthw_nand *)device;
RT_ASSERT(device != RT_NULL);
block = block + device->block_start;
block_num = block << 6;
rt_mutex_take(&hw_nand->lock, RT_WAITING_FOREVER);
if (LL_OK == EXMC_NFC_EraseBlock(hw_nand->nfc_bank, block_num, NAND_ERASE_TIMEOUT))
{
if (_nand_wait_ready(hw_nand->nfc_bank, NAND_ERASE_TIMEOUT) == RT_EOK)
{
ret = RT_MTD_EOK;
}
}
rt_mutex_release(&hw_nand->lock);
return ret;
}
static rt_err_t _nand_check_block(struct rt_mtd_nand_device *device, rt_uint32_t block)
{
RT_ASSERT(device != RT_NULL);
return (RT_MTD_EOK);
}
static rt_err_t _nand_mark_badblock(struct rt_mtd_nand_device *device, rt_uint32_t block)
{
RT_ASSERT(device != RT_NULL);
return (RT_MTD_EOK);
}
/* read nand flash id */
static rt_err_t _nand_read_id(struct rt_mtd_nand_device *device)
{
rt_uint8_t device_id[4];
struct rthw_nand *hw_nand = (struct rthw_nand *)device;
RT_ASSERT(device != RT_NULL);
EXMC_NFC_ReadId(hw_nand->nfc_bank, 0UL, device_id, sizeof(device_id), NAND_READ_TIMEOUT);
hw_nand->id = (((rt_uint32_t)device_id[3]) << 24 | ((rt_uint32_t)device_id[2]) << 16 | \
((rt_uint32_t)device_id[1]) << 8 | (rt_uint32_t)device_id[0]);
LOG_D("Nand Flash ID: Manufacturer ID = 0x%02X, Device ID=[0x%02X,0x%02X,0x%02X]",
device_id[0], device_id[1], device_id[2], device_id[3]);
return RT_EOK;
}
static rt_ssize_t _nand_read_page(struct rt_mtd_nand_device *device,
rt_off_t page,
rt_uint8_t *data,
rt_uint32_t data_len,
rt_uint8_t *spare,
rt_uint32_t spare_len)
{
rt_err_t result = RT_EOK;
struct rthw_nand *hw_nand = (struct rthw_nand *)device;
RT_ASSERT(device != RT_NULL);
page = page + device->block_start * device->pages_per_block;
if (page / device->pages_per_block > device->block_end)
{
return -RT_EIO;
}
rt_mutex_take(&hw_nand->lock, RT_WAITING_FOREVER);
if ((data != RT_NULL) && (data_len != 0UL))
{
/* not an integer multiple of NAND ECC SECTOR SIZE, no ECC checks */
if ((data_len % NAND_ECC_SECTOR_SIZE) != 0UL)
{
if (LL_OK != EXMC_NFC_ReadPageMeta(hw_nand->nfc_bank, page, data, data_len, NAND_READ_TIMEOUT))
{
result = -RT_EIO;
goto _exit;
}
}
else
{
if (LL_OK != EXMC_NFC_ReadPageHwEcc(hw_nand->nfc_bank, page, data, data_len, NAND_READ_TIMEOUT))
{
result = -RT_EIO;
goto _exit;
}
else
{
if (SET == EXMC_NFC_GetStatus(EXMC_NFC_FLAG_ECC_UNCORRECTABLE_ERR))
{
EXMC_NFC_ClearStatus(EXMC_NFC_FLAG_ECC_UNCORRECTABLE_ERR);
result = RT_MTD_EECC;
goto _exit;
}
}
}
}
if ((spare != RT_NULL) && (spare_len != 0UL))
{
RT_ASSERT(spare_len <= device->oob_free);
if (LL_OK != EXMC_NFC_Read(hw_nand->nfc_bank, page, (rt_uint32_t)device->page_size,
(rt_uint32_t *)spare, (spare_len >> 2), DISABLE, NAND_READ_TIMEOUT))
{
result = -RT_EIO;
goto _exit;
}
}
_exit:
rt_mutex_release(&hw_nand->lock);
return result;
}
static rt_ssize_t _nand_write_page(struct rt_mtd_nand_device *device,
rt_off_t page,
const rt_uint8_t *data,
rt_uint32_t data_len,
const rt_uint8_t *spare,
rt_uint32_t spare_len)
{
rt_err_t result = RT_EOK;
struct rthw_nand *hw_nand = (struct rthw_nand *)device;
RT_ASSERT(device != RT_NULL);
page = page + device->block_start * device->pages_per_block;
if (page / device->pages_per_block > device->block_end)
{
return -RT_EIO;
}
rt_mutex_take(&hw_nand->lock, RT_WAITING_FOREVER);
if ((data != RT_NULL) && (data_len != 0UL))
{
if ((data_len % NAND_ECC_SECTOR_SIZE) != 0UL)
{
if (LL_OK != EXMC_NFC_WritePageMeta(hw_nand->nfc_bank, page, data, data_len, NAND_WRITE_TIMEOUT))
{
result = -RT_EIO;
goto _exit;
}
}
else
{
if (LL_OK != EXMC_NFC_WritePageHwEcc(hw_nand->nfc_bank, page, data, data_len, NAND_WRITE_TIMEOUT))
{
result = -RT_EIO;
goto _exit;
}
}
if (RT_EOK != _nand_wait_ready(hw_nand->nfc_bank, NAND_WRITE_TIMEOUT))
{
result = -RT_EIO;
goto _exit;
}
}
if ((spare != RT_NULL) && (spare_len != 0UL))
{
RT_ASSERT(spare_len <= device->oob_free);
if (LL_OK != EXMC_NFC_Write(hw_nand->nfc_bank, page, (rt_uint32_t)device->page_size,
(rt_uint32_t *)spare, (spare_len >> 2), DISABLE, NAND_WRITE_TIMEOUT))
{
result = -RT_EIO;
goto _exit;
}
if (RT_EOK != _nand_wait_ready(hw_nand->nfc_bank, NAND_WRITE_TIMEOUT))
{
result = -RT_EIO;
goto _exit;
}
}
_exit:
rt_mutex_release(&hw_nand->lock);
return result;
}
rt_err_t _nand_move_page(struct rt_mtd_nand_device *device, rt_off_t src_page, rt_off_t dst_page)
{
RT_ASSERT(device != RT_NULL);
return (RT_MTD_EOK);
}
static const struct rt_mtd_nand_driver_ops _ops =
{
_nand_read_id,
_nand_read_page,
_nand_write_page,
_nand_move_page,
_nand_erase_block,
_nand_check_block,
_nand_mark_badblock,
};
int rt_hw_nand_init(void)
{
rt_err_t result = RT_EOK;
struct rt_mtd_nand_device *nand_dev = &_hw_nand.nand_dev;
result = _nand_init(nand_dev);
if (result != RT_EOK)
{
LOG_D("nand flash init error!");
return -RT_ERROR;
}
rt_mutex_init(&_hw_nand.lock, "nand", RT_IPC_FLAG_PRIO);
nand_dev->page_size = NAND_BYTES_PER_PAGE;
nand_dev->pages_per_block = NAND_PAGES_PER_BLOCK;
nand_dev->plane_num = NAND_PLANE_PER_DEVICE;
nand_dev->oob_size = NAND_SPARE_AREA_SIZE;
nand_dev->oob_free = (rt_uint16_t)(NAND_SPARE_AREA_SIZE - (NAND_BYTES_PER_PAGE / NAND_ECC_SECTOR_SIZE) * NAND_ECC_CODE_SIZE);
nand_dev->block_total = NAND_DEVICE_BLOCKS;
nand_dev->block_start = 0;
nand_dev->block_end = nand_dev->block_total - 1UL;
nand_dev->ops = &_ops;
result = rt_mtd_nand_register_device("nand", nand_dev);
if (result != RT_EOK)
{
rt_device_unregister(&nand_dev->parent);
return -RT_ERROR;
}
return RT_EOK;
}
INIT_BOARD_EXPORT(rt_hw_nand_init);
#ifdef DRV_DEBUG
#ifdef FINSH_USING_MSH
static int _nand_test(void)
{
rt_err_t ret;
rt_uint32_t i = 0;
rt_uint32_t err_count = 0;
rt_uint32_t page;
rt_uint32_t block;
rt_uint8_t *page_rbuf;
rt_uint8_t *page_wbuf;
rt_uint8_t *page_oob_free_wbuf;
rt_uint8_t *page_oob_free_rbuf;
static rt_device_t nand;
static struct rt_mtd_nand_device *mtd_nand;
nand = rt_device_find("nand");
if (RT_NULL == nand)
{
LOG_E("nand device not found");
return -RT_ERROR;
}
ret = rt_device_open(nand, RT_DEVICE_FLAG_RDWR);
if (ret != RT_EOK)
{
LOG_E("nand device failed to open");
return -RT_ERROR;
}
mtd_nand = (struct rt_mtd_nand_device *)nand;
page_rbuf = rt_malloc(mtd_nand->page_size);
if (page_rbuf == RT_NULL)
{
LOG_E("out of memory!");
return -RT_ERROR;
}
page_wbuf = rt_malloc(mtd_nand->page_size);
if (page_wbuf == RT_NULL)
{
rt_free(page_rbuf);
LOG_E("out of memory!");
return -RT_ERROR;
}
page_oob_free_rbuf = rt_malloc(mtd_nand->oob_free);
if (page_oob_free_rbuf == RT_NULL)
{
rt_free(page_rbuf);
rt_free(page_wbuf);
LOG_E("out of memory!");
return -RT_ERROR;
}
page_oob_free_wbuf = rt_malloc(mtd_nand->oob_free);
if (page_oob_free_wbuf == RT_NULL)
{
rt_free(page_rbuf);
rt_free(page_wbuf);
rt_free(page_oob_free_rbuf);
LOG_E("out of memory!");
return -RT_ERROR;
}
/* Fill the buffer to send */
for (i = 0; i < mtd_nand->page_size; i++)
{
page_wbuf[i] = i;
}
for (i = 0; i < mtd_nand->oob_free; i++)
{
page_oob_free_wbuf[i] = i;
}
/* read ID */
_nand_read_id(mtd_nand);
/* test page */
page = 0UL;
/* erase the NAND Block */
block = page >> 6;
ret = _nand_erase_block(mtd_nand, block);
if (ret == RT_EOK)
{
LOG_D("erase block%d: ok", block);
}
else
{
LOG_E("erase block%d: error", block);
err_count++;
}
/* Write data to NAND memory page 0 */
ret = _nand_write_page(mtd_nand, page, page_wbuf, mtd_nand->page_size, page_oob_free_wbuf, mtd_nand->oob_free);
if (ret == RT_EOK)
{
LOG_D("_nand_write_page page%d(include oob free area): ok", page);
}
else
{
LOG_E("_nand_write_page page%d(include oob free area): error", page);
err_count++;
}
/* Read data from NAND memory page 0 */
ret = _nand_read_page(mtd_nand, page, page_rbuf, mtd_nand->page_size, page_oob_free_rbuf, mtd_nand->oob_free);
if (ret == RT_EOK)
{
LOG_D("_nand_read_page page%d(include oob free area): ok", page);
}
else if (ret == -RT_MTD_EECC)
{
LOG_E("_nand_read_page page%d(include oob free area): ECC error", page);
err_count++;
}
else
{
LOG_E("_nand_read_page page%d(include oob free area): error", page);
err_count++;
}
if (rt_memcmp(page_rbuf, page_wbuf, mtd_nand->page_size) == 0)
{
LOG_D("rt_memcmp page%d data consistency: ok", page);
}
else
{
LOG_E("rt_memcmp page%d data consistency: error", page);
err_count++;
}
if (rt_memcmp(page_oob_free_rbuf, page_oob_free_wbuf, mtd_nand->oob_free) == 0)
{
LOG_D("rt_memcmp page%d oob_free data consistency: ok", page);
}
else
{
LOG_E("rt_memcmp page%d oob_free data consistency: error", page);
err_count++;
}
ret = rt_device_close(nand);
if (ret != RT_EOK)
{
LOG_E("nand device failed to close");
err_count++;
}
rt_free(page_rbuf);
rt_free(page_wbuf);
rt_free(page_oob_free_rbuf);
rt_free(page_oob_free_wbuf);
return (err_count == 0UL) ? RT_EOK : -RT_ERROR;
}
MSH_CMD_EXPORT(_nand_test, nand test)
#endif /* FINSH_USING_MSH */
#endif /* DRV_DEBUG */
#endif /* BSP_USING_NAND */
#endif /* BSP_USING_EXMC */