mirror of
https://github.com/ARM-software/arm-trusted-firmware.git
synced 2025-04-17 01:54:22 +00:00

Update nand driver to match GHRD design, fix row address calculation method and other misc updates. Signed-off-by: Girisha Dengi <girisha.dengi@intel.com> Signed-off-by: Sieu Mun Tang <sieu.mun.tang@intel.com> Change-Id: I1cb3dda43e767ba243fbe89bfa18818db321c5c2
458 lines
12 KiB
C
458 lines
12 KiB
C
/*
|
|
* Copyright (c) 2022-2023, Intel Corporation. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
|
|
#include <arch_helpers.h>
|
|
#include <common/debug.h>
|
|
#include <drivers/cadence/cdns_nand.h>
|
|
#include <drivers/delay_timer.h>
|
|
#include <lib/mmio.h>
|
|
#include <lib/utils.h>
|
|
#include <platform_def.h>
|
|
|
|
/* NAND flash device information struct */
|
|
static cnf_dev_info_t dev_info;
|
|
|
|
/*
|
|
* Scratch buffers for read and write operations
|
|
* DMA transfer of Cadence NAND expects data 8 bytes aligned
|
|
* to be written to register
|
|
*/
|
|
static uint8_t scratch_buff[PLATFORM_MTD_MAX_PAGE_SIZE] __aligned(8);
|
|
|
|
/* Wait for controller to be in idle state */
|
|
static inline void cdns_nand_wait_idle(void)
|
|
{
|
|
uint32_t reg = 0U;
|
|
|
|
do {
|
|
udelay(CNF_DEF_DELAY_US);
|
|
reg = mmio_read_32(CNF_CMDREG(CTRL_STATUS));
|
|
} while (CNF_GET_CTRL_BUSY(reg) != 0U);
|
|
}
|
|
|
|
/* Wait for given thread to be in ready state */
|
|
static inline void cdns_nand_wait_thread_ready(uint8_t thread_id)
|
|
{
|
|
uint32_t reg = 0U;
|
|
|
|
do {
|
|
udelay(CNF_DEF_DELAY_US);
|
|
reg = mmio_read_32(CNF_CMDREG(TRD_STATUS));
|
|
reg &= (1U << (uint32_t)thread_id);
|
|
} while (reg != 0U);
|
|
}
|
|
|
|
/* Check if the last operation/command in selected thread is completed */
|
|
static int cdns_nand_last_opr_status(uint8_t thread_id)
|
|
{
|
|
uint8_t nthreads = 0U;
|
|
uint32_t reg = 0U;
|
|
|
|
/* Get number of threads */
|
|
reg = mmio_read_32(CNF_CTRLPARAM(FEATURE));
|
|
nthreads = CNF_GET_NTHREADS(reg);
|
|
|
|
if (thread_id > nthreads) {
|
|
ERROR("%s: Invalid thread ID\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Select thread */
|
|
mmio_write_32(CNF_CMDREG(CMD_STAT_PTR), (uint32_t)thread_id);
|
|
|
|
uint32_t err_mask = CNF_ECMD | CNF_EECC | CNF_EDEV | CNF_EDQS | CNF_EFAIL |
|
|
CNF_EBUS | CNF_EDI | CNF_EPAR | CNF_ECTX | CNF_EPRO;
|
|
|
|
do {
|
|
udelay(CNF_DEF_DELAY_US * 2);
|
|
reg = mmio_read_32(CNF_CMDREG(CMD_STAT));
|
|
} while ((reg & CNF_CMPLT) == 0U);
|
|
|
|
/* last operation is completed, make sure no other error bits are set */
|
|
if ((reg & err_mask) == 1U) {
|
|
ERROR("%s, CMD_STATUS:0x%x\n", __func__, reg);
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Set feature command */
|
|
int cdns_nand_set_feature(uint8_t feat_addr, uint8_t feat_val, uint8_t thread_id)
|
|
{
|
|
/* Wait for thread to be ready */
|
|
cdns_nand_wait_thread_ready(thread_id);
|
|
|
|
/* Set feature address */
|
|
mmio_write_32(CNF_CMDREG(CMD_REG1), (uint32_t)feat_addr);
|
|
/* Set feature volume */
|
|
mmio_write_32(CNF_CMDREG(CMD_REG2), (uint32_t)feat_val);
|
|
|
|
/* Set feature command */
|
|
uint32_t reg = (CNF_WORK_MODE_PIO << CNF_CMDREG0_CT);
|
|
|
|
reg |= (thread_id << CNF_CMDREG0_TRD);
|
|
reg |= (CNF_DEF_VOL_ID << CNF_CMDREG0_VOL);
|
|
reg |= (CNF_INT_DIS << CNF_CMDREG0_INTR);
|
|
reg |= (CNF_CT_SET_FEATURE << CNF_CMDREG0_CMD);
|
|
mmio_write_32(CNF_CMDREG(CMD_REG0), reg);
|
|
|
|
return cdns_nand_last_opr_status(thread_id);
|
|
}
|
|
|
|
/* Reset command to the selected device */
|
|
int cdns_nand_reset(uint8_t thread_id)
|
|
{
|
|
/* Operation is executed in selected thread */
|
|
cdns_nand_wait_thread_ready(thread_id);
|
|
|
|
/* Select memory */
|
|
mmio_write_32(CNF_CMDREG(CMD_REG4),
|
|
(CNF_DEF_DEVICE << CNF_CMDREG4_MEM));
|
|
|
|
/* Issue reset command */
|
|
uint32_t reg = (CNF_WORK_MODE_PIO << CNF_CMDREG0_CT);
|
|
|
|
reg |= (thread_id << CNF_CMDREG0_TRD);
|
|
reg |= (CNF_DEF_VOL_ID << CNF_CMDREG0_VOL);
|
|
reg |= (CNF_INT_DIS << CNF_CMDREG0_INTR);
|
|
reg |= (CNF_CT_RESET_ASYNC << CNF_CMDREG0_CMD);
|
|
mmio_write_32(CNF_CMDREG(CMD_REG0), reg);
|
|
|
|
return cdns_nand_last_opr_status(thread_id);
|
|
}
|
|
|
|
/* Set operation work mode */
|
|
static void cdns_nand_set_opr_mode(uint8_t opr_mode)
|
|
{
|
|
/* Wait for controller to be in idle state */
|
|
cdns_nand_wait_idle();
|
|
|
|
/* Reset DLL PHY */
|
|
uint32_t reg = mmio_read_32(CNF_MINICTRL(DLL_PHY_CTRL));
|
|
|
|
reg &= ~(1 << CNF_DLL_PHY_RST_N);
|
|
mmio_write_32(CNF_MINICTRL(DLL_PHY_CTRL), reg);
|
|
|
|
if (opr_mode == CNF_OPR_WORK_MODE_SDR) {
|
|
/* Combo PHY Control Timing Block register settings */
|
|
mmio_write_32(CP_CTB(CTRL_REG), CP_CTRL_REG_SDR);
|
|
mmio_write_32(CP_CTB(TSEL_REG), CP_TSEL_REG_SDR);
|
|
|
|
/* Combo PHY DLL register settings */
|
|
mmio_write_32(CP_DLL(DQ_TIMING_REG), CP_DQ_TIMING_REG_SDR);
|
|
mmio_write_32(CP_DLL(DQS_TIMING_REG), CP_DQS_TIMING_REG_SDR);
|
|
mmio_write_32(CP_DLL(GATE_LPBK_CTRL_REG), CP_GATE_LPBK_CTRL_REG_SDR);
|
|
mmio_write_32(CP_DLL(MASTER_CTRL_REG), CP_DLL_MASTER_CTRL_REG_SDR);
|
|
|
|
/* Async mode timing settings */
|
|
mmio_write_32(CNF_MINICTRL(ASYNC_TOGGLE_TIMINGS),
|
|
(2 << CNF_ASYNC_TIMINGS_TRH) |
|
|
(4 << CNF_ASYNC_TIMINGS_TRP) |
|
|
(2 << CNF_ASYNC_TIMINGS_TWH) |
|
|
(4 << CNF_ASYNC_TIMINGS_TWP));
|
|
|
|
/* Set extended read and write mode */
|
|
reg |= (1 << CNF_DLL_PHY_EXT_RD_MODE);
|
|
reg |= (1 << CNF_DLL_PHY_EXT_WR_MODE);
|
|
|
|
/* Set operation work mode in common settings */
|
|
mmio_clrsetbits_32(CNF_MINICTRL(CMN_SETTINGS),
|
|
CNF_CMN_SETTINGS_OPR_MASK,
|
|
CNF_OPR_WORK_MODE_SDR);
|
|
} else if (opr_mode == CNF_OPR_WORK_MODE_NVDDR) {
|
|
; /* ToDo: add DDR mode settings also once available on SIMICS */
|
|
} else {
|
|
;
|
|
}
|
|
|
|
reg |= (1 << CNF_DLL_PHY_RST_N);
|
|
mmio_write_32(CNF_MINICTRL(DLL_PHY_CTRL), reg);
|
|
}
|
|
|
|
/* Data transfer configuration */
|
|
static void cdns_nand_transfer_config(void)
|
|
{
|
|
/* Wait for controller to be in idle state */
|
|
cdns_nand_wait_idle();
|
|
|
|
/* Configure data transfer parameters */
|
|
mmio_write_32(CNF_CTRLCFG(TRANS_CFG0), 1);
|
|
|
|
/* ECC is disabled */
|
|
mmio_write_32(CNF_CTRLCFG(ECC_CFG0), 0);
|
|
|
|
/* DMA burst select */
|
|
mmio_write_32(CNF_CTRLCFG(DMA_SETTINGS),
|
|
(CNF_DMA_BURST_SIZE_MAX << CNF_DMA_SETTINGS_BURST) |
|
|
(1 << CNF_DMA_SETTINGS_OTE));
|
|
|
|
/* Enable pre-fetching for 1K */
|
|
mmio_write_32(CNF_CTRLCFG(FIFO_TLEVEL),
|
|
(CNF_DMA_PREFETCH_SIZE << CNF_FIFO_TLEVEL_POS) |
|
|
(CNF_DMA_PREFETCH_SIZE << CNF_FIFO_TLEVEL_DMA_SIZE));
|
|
|
|
/* Select access type */
|
|
mmio_write_32(CNF_CTRLCFG(MULTIPLANE_CFG), 0);
|
|
mmio_write_32(CNF_CTRLCFG(CACHE_CFG), 0);
|
|
}
|
|
|
|
/* Update the nand flash device info */
|
|
static int cdns_nand_update_dev_info(void)
|
|
{
|
|
uint32_t reg = 0U;
|
|
|
|
/* Read the device type and number of LUNs */
|
|
reg = mmio_read_32(CNF_CTRLPARAM(DEV_PARAMS0));
|
|
dev_info.type = CNF_GET_DEV_TYPE(reg);
|
|
if (dev_info.type == CNF_DT_UNKNOWN) {
|
|
ERROR("%s: device type unknown\n", __func__);
|
|
return -ENXIO;
|
|
}
|
|
dev_info.nluns = CNF_GET_NLUNS(reg);
|
|
|
|
/* Pages per block */
|
|
reg = mmio_read_32(CNF_CTRLCFG(DEV_LAYOUT));
|
|
dev_info.npages_per_block = CNF_GET_NPAGES_PER_BLOCK(reg);
|
|
|
|
/* Sector size and last sector size */
|
|
reg = mmio_read_32(CNF_CTRLCFG(TRANS_CFG1));
|
|
dev_info.sector_size = CNF_GET_SCTR_SIZE(reg);
|
|
dev_info.last_sector_size = CNF_GET_LAST_SCTR_SIZE(reg);
|
|
|
|
/* Page size and spare size */
|
|
reg = mmio_read_32(CNF_CTRLPARAM(DEV_AREA));
|
|
dev_info.page_size = CNF_GET_PAGE_SIZE(reg);
|
|
dev_info.spare_size = CNF_GET_SPARE_SIZE(reg);
|
|
|
|
/* Device blocks per LUN */
|
|
dev_info.nblocks_per_lun = mmio_read_32(CNF_CTRLPARAM(DEV_BLOCKS_PLUN));
|
|
|
|
/* Calculate block size and total device size */
|
|
dev_info.block_size = (dev_info.npages_per_block * dev_info.page_size);
|
|
dev_info.total_size = ((unsigned long long)dev_info.block_size *
|
|
(unsigned long long)dev_info.nblocks_per_lun *
|
|
dev_info.nluns);
|
|
|
|
VERBOSE("CNF params: page_size %d, spare_size %d, block_size %u, total_size %llu\n",
|
|
dev_info.page_size, dev_info.spare_size,
|
|
dev_info.block_size, dev_info.total_size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* NAND Flash Controller/Host initialization */
|
|
int cdns_nand_host_init(void)
|
|
{
|
|
uint32_t reg = 0U;
|
|
int ret = 0;
|
|
|
|
do {
|
|
/* Read controller status register for init complete */
|
|
reg = mmio_read_32(CNF_CMDREG(CTRL_STATUS));
|
|
} while (CNF_GET_INIT_COMP(reg) == 0);
|
|
|
|
ret = cdns_nand_update_dev_info();
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
INFO("CNF: device discovery process completed and device type %d\n",
|
|
dev_info.type);
|
|
|
|
/* Enable data integrity, enable CRC and parity */
|
|
reg = mmio_read_32(CNF_DI(CONTROL));
|
|
reg |= (1 << CNF_DI_PAR_EN);
|
|
reg |= (1 << CNF_DI_CRC_EN);
|
|
mmio_write_32(CNF_DI(CONTROL), reg);
|
|
|
|
/* Status polling mode, device control and status register */
|
|
cdns_nand_wait_idle();
|
|
reg = mmio_read_32(CNF_CTRLCFG(DEV_STAT));
|
|
reg = reg & ~1;
|
|
mmio_write_32(CNF_CTRLCFG(DEV_STAT), reg);
|
|
|
|
/* Set operation work mode */
|
|
cdns_nand_set_opr_mode(CNF_OPR_WORK_MODE_SDR);
|
|
|
|
/* Set data transfer configuration parameters */
|
|
cdns_nand_transfer_config();
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* erase: Block erase command */
|
|
int cdns_nand_erase(uint32_t offset, uint32_t size)
|
|
{
|
|
/* Determine the starting block offset i.e row address */
|
|
uint32_t row_address = dev_info.npages_per_block * offset;
|
|
|
|
/* Wait for thread to be in ready state */
|
|
cdns_nand_wait_thread_ready(CNF_DEF_TRD);
|
|
|
|
/*Set row address */
|
|
mmio_write_32(CNF_CMDREG(CMD_REG1), row_address);
|
|
|
|
/* Operation bank number */
|
|
mmio_write_32(CNF_CMDREG(CMD_REG4), (CNF_DEF_DEVICE << CNF_CMDREG4_MEM));
|
|
|
|
/* Block erase command */
|
|
uint32_t reg = (CNF_WORK_MODE_PIO << CNF_CMDREG0_CT);
|
|
|
|
reg |= (CNF_DEF_TRD << CNF_CMDREG0_TRD);
|
|
reg |= (CNF_DEF_VOL_ID << CNF_CMDREG0_VOL);
|
|
reg |= (CNF_INT_DIS << CNF_CMDREG0_INTR);
|
|
reg |= (CNF_CT_ERASE << CNF_CMDREG0_CMD);
|
|
reg |= (((size-1) & 0xFF) << CNF_CMDREG0_CMD);
|
|
mmio_write_32(CNF_CMDREG(CMD_REG0), reg);
|
|
|
|
/* Wait for erase operation to complete */
|
|
return cdns_nand_last_opr_status(CNF_DEF_TRD);
|
|
}
|
|
|
|
/* io mtd functions */
|
|
int cdns_nand_init_mtd(unsigned long long *size, unsigned int *erase_size)
|
|
{
|
|
*size = dev_info.total_size;
|
|
*erase_size = dev_info.block_size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint32_t cdns_nand_get_row_address(uint32_t page, uint32_t block)
|
|
{
|
|
uint32_t row_address = 0U;
|
|
uint32_t req_bits = 0U;
|
|
|
|
/* The device info is not populated yet. */
|
|
if (dev_info.npages_per_block == 0U)
|
|
return 0;
|
|
|
|
for (uint32_t i = 0U; i < sizeof(uint32_t) * 8; i++) {
|
|
if ((1U << i) & dev_info.npages_per_block)
|
|
req_bits = i;
|
|
}
|
|
|
|
row_address = ((page & GENMASK_32((req_bits - 1), 0)) |
|
|
(block << req_bits));
|
|
|
|
return row_address;
|
|
}
|
|
|
|
/* NAND Flash page read */
|
|
static int cdns_nand_read_page(uint32_t block, uint32_t page, uintptr_t buffer)
|
|
{
|
|
|
|
/* Wait for thread to be ready */
|
|
cdns_nand_wait_thread_ready(CNF_DEF_TRD);
|
|
|
|
/* Select device */
|
|
mmio_write_32(CNF_CMDREG(CMD_REG4),
|
|
(CNF_DEF_DEVICE << CNF_CMDREG4_MEM));
|
|
|
|
/* Set host memory address for DMA transfers */
|
|
mmio_write_32(CNF_CMDREG(CMD_REG2), (buffer & UINT32_MAX));
|
|
mmio_write_32(CNF_CMDREG(CMD_REG3), ((buffer >> 32) & UINT32_MAX));
|
|
|
|
/* Set row address */
|
|
mmio_write_32(CNF_CMDREG(CMD_REG1),
|
|
cdns_nand_get_row_address(page, block));
|
|
|
|
/* Page read command */
|
|
uint32_t reg = (CNF_WORK_MODE_PIO << CNF_CMDREG0_CT);
|
|
|
|
reg |= (CNF_DEF_TRD << CNF_CMDREG0_TRD);
|
|
reg |= (CNF_DEF_VOL_ID << CNF_CMDREG0_VOL);
|
|
reg |= (CNF_INT_DIS << CNF_CMDREG0_INTR);
|
|
reg |= (CNF_DMA_MASTER_SEL << CNF_CMDREG0_DMA);
|
|
reg |= (CNF_CT_PAGE_READ << CNF_CMDREG0_CMD);
|
|
reg |= (((CNF_READ_SINGLE_PAGE-1) & 0xFF) << CNF_CMDREG0_CMD);
|
|
mmio_write_32(CNF_CMDREG(CMD_REG0), reg);
|
|
|
|
/* Wait for read operation to complete */
|
|
if (cdns_nand_last_opr_status(CNF_DEF_TRD)) {
|
|
ERROR("%s: Page read failed\n", __func__);
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int cdns_nand_read(unsigned int offset, uintptr_t buffer, size_t length,
|
|
size_t *out_length)
|
|
{
|
|
uint32_t block = offset / dev_info.block_size;
|
|
uint32_t end_block = (offset + length - 1U) / dev_info.block_size;
|
|
uint32_t page_start = (offset % dev_info.block_size) / dev_info.page_size;
|
|
uint32_t start_offset = offset % dev_info.page_size;
|
|
uint32_t nb_pages = dev_info.block_size / dev_info.page_size;
|
|
uint32_t bytes_read = 0U;
|
|
uint32_t page = 0U;
|
|
int result = 0;
|
|
|
|
INFO("CNF: %s: block %u-%u, page_start %u, len %zu, offset %u\n",
|
|
__func__, block, end_block, page_start, length, offset);
|
|
|
|
if ((offset >= dev_info.total_size) ||
|
|
(offset + length-1 >= dev_info.total_size) ||
|
|
(length == 0U)) {
|
|
ERROR("CNF: Invalid read parameters\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
*out_length = 0UL;
|
|
|
|
while (block <= end_block) {
|
|
for (page = page_start; page < nb_pages; page++) {
|
|
if ((start_offset != 0U) || (length < dev_info.page_size)) {
|
|
/* Partial page read */
|
|
result = cdns_nand_read_page(block, page,
|
|
(uintptr_t)scratch_buff);
|
|
if (result != 0) {
|
|
return result;
|
|
}
|
|
|
|
bytes_read = MIN((size_t)(dev_info.page_size - start_offset),
|
|
length);
|
|
|
|
memcpy((uint8_t *)buffer, scratch_buff + start_offset,
|
|
bytes_read);
|
|
start_offset = 0U;
|
|
} else {
|
|
/* Full page read */
|
|
result = cdns_nand_read_page(block, page,
|
|
(uintptr_t)scratch_buff);
|
|
if (result != 0) {
|
|
return result;
|
|
}
|
|
|
|
bytes_read = dev_info.page_size;
|
|
memcpy((uint8_t *)buffer, scratch_buff, bytes_read);
|
|
}
|
|
|
|
length -= bytes_read;
|
|
buffer += bytes_read;
|
|
*out_length += bytes_read;
|
|
|
|
/* All the bytes have read */
|
|
if (length == 0U) {
|
|
break;
|
|
}
|
|
|
|
udelay(CNF_READ_INT_DELAY_US);
|
|
} /* for */
|
|
|
|
page_start = 0U;
|
|
block++;
|
|
} /* while */
|
|
|
|
return 0;
|
|
}
|