blob: 1f7092a783ef5f77e6aed8fa45d2f4e660436574 [file] [log] [blame]
/*
* Copyright 2022 NXP
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <string.h>
#include <common/debug.h>
#include <drivers/io/io_block.h>
#include "ifc.h"
#include <lib/xlat_tables/xlat_tables_v2.h>
#include <nxp_timer.h>
/* Private structure for NAND driver data */
static struct nand_info nand_drv_data;
static int update_bbt(uint32_t idx, uint32_t blk, uint32_t *updated,
struct nand_info *nand);
static int nand_wait(struct nand_info *nand)
{
int timeout = 1;
uint32_t neesr;
unsigned long start_time;
start_time = get_timer_val(0);
while (get_timer_val(start_time) < NAND_TIMEOUT_MS) {
/* clear the OPC event */
neesr = read_reg(nand, NAND_EVTER_STAT);
if (neesr & NAND_EVTER_STAT_OPC_DN) {
write_reg(nand, NAND_EVTER_STAT, neesr);
timeout = 0;
/* check for other errors */
if (neesr & NAND_EVTER_STAT_FTOER) {
ERROR("%s NAND_EVTER_STAT_FTOER occurs\n",
__func__);
return -1;
} else if (neesr & NAND_EVTER_STAT_ECCER) {
ERROR("%s NAND_EVTER_STAT_ECCER occurs\n",
__func__);
return -1;
} else if (neesr & NAND_EVTER_STAT_DQSER) {
ERROR("%s NAND_EVTER_STAT_DQSER occurs\n",
__func__);
return -1;
}
break;
}
}
if (timeout) {
ERROR("%s ERROR_NAND_TIMEOUT occurs\n", __func__);
return -1;
}
return 0;
}
static uint32_t nand_get_port_size(struct nand_info *nand)
{
uint32_t port_size = U(0);
uint32_t cs_reg;
uint32_t cur_cs;
cur_cs = U(0);
cs_reg = CSPR(cur_cs);
port_size = (read_reg(nand, cs_reg) & CSPR_PS) >> CSPR_PS_SHIFT;
switch (port_size) {
case CSPR_PS_8:
port_size = U(8);
break;
case CSPR_PS_16:
port_size = U(16);
break;
case CSPR_PS_32:
port_size = U(32);
break;
default:
port_size = U(8);
}
return port_size;
}
static uint32_t nand_get_page_size(struct nand_info *nand)
{
uint32_t pg_size;
uint32_t cs_reg;
uint32_t cur_cs;
cur_cs = 0;
cs_reg = CSOR(cur_cs);
pg_size = read_reg(nand, cs_reg) & CSOR_NAND_PGS;
switch (pg_size) {
case CSOR_NAND_PGS_2K:
pg_size = U(2048);
break;
case CSOR_NAND_PGS_4K:
pg_size = U(4096);
break;
case CSOR_NAND_PGS_8K:
pg_size = U(8192);
break;
case CSOR_NAND_PGS_16K:
pg_size = U(16384);
break;
default:
pg_size = U(512);
}
return pg_size;
}
static uint32_t nand_get_pages_per_blk(struct nand_info *nand)
{
uint32_t pages_per_blk;
uint32_t cs_reg;
uint32_t cur_cs;
cur_cs = 0;
cs_reg = CSOR(cur_cs);
pages_per_blk = (read_reg(nand, cs_reg) & CSOR_NAND_PB);
switch (pages_per_blk) {
case CSOR_NAND_PB_32:
pages_per_blk = U(32);
break;
case CSOR_NAND_PB_64:
pages_per_blk = U(64);
break;
case CSOR_NAND_PB_128:
pages_per_blk = U(128);
break;
case CSOR_NAND_PB_256:
pages_per_blk = U(256);
break;
case CSOR_NAND_PB_512:
pages_per_blk = U(512);
break;
case CSOR_NAND_PB_1024:
pages_per_blk = U(1024);
break;
case CSOR_NAND_PB_2048:
pages_per_blk = U(2048);
break;
default:
pages_per_blk = U(0);
}
return pages_per_blk;
}
static uint32_t get_page_index_width(uint32_t ppb)
{
switch (ppb) {
case CSOR_NAND_PPB_32:
return U(5);
case CSOR_NAND_PPB_64:
return U(6);
case CSOR_NAND_PPB_128:
return U(7);
case CSOR_NAND_PPB_256:
return U(8);
case CSOR_NAND_PPB_512:
return U(9);
case CSOR_NAND_PPB_1024:
return U(10);
case CSOR_NAND_PPB_2048:
return U(11);
default:
return U(5);
}
}
static void nand_get_params(struct nand_info *nand)
{
nand->port_size = nand_get_port_size(nand);
nand->page_size = nand_get_page_size(nand);
/*
* Set Bad marker Location for LP / SP
* Small Page : 8 Bit : 0x5
* Small Page : 16 Bit : 0xa
* Large Page : 8 /16 Bit : 0x0
*/
nand->bad_marker_loc = (nand->page_size == 512) ?
((nand->port_size == 8) ? 0x5 : 0xa) : 0;
/* check for the device is ONFI compliant or not */
nand->onfi_dev_flag =
(read_reg(nand, NAND_EVTER_STAT) & NAND_EVTER_STAT_BBI_SRCH_SEL)
? 1 : 0;
/* NAND Blk serached count for incremental Bad block search cnt */
nand->bbs = 0;
/* pages per Block */
nand->ppb = nand_get_pages_per_blk(nand);
/* Blk size */
nand->blk_size = nand->page_size * nand->ppb;
/* get_page_index_width */
nand->pi_width = get_page_index_width(nand->ppb);
/* bad block table init */
nand->lgb = 0;
nand->bbt_max = 0;
nand->bzero_good = 0;
memset(nand->bbt, EMPTY_VAL, BBT_SIZE * sizeof(nand->bbt[0]));
}
static int nand_init(struct nand_info *nand)
{
uint32_t ncfgr = 0;
/* Get nand Parameters from IFC */
nand_get_params(nand);
/* Clear all errors */
write_reg(nand, NAND_EVTER_STAT, U(0xffffffff));
/*
* Disable autoboot in NCFGR. Mapping will change from
* physical to logical for SRAM buffer
*/
ncfgr = read_reg(nand, NCFGR);
write_reg(nand, NCFGR, (ncfgr & ~NCFGR_BOOT));
return 0;
}
static int nand_read_data(
uintptr_t ifc_region_addr,
uint32_t row_add,
uint32_t col_add,
uint32_t byte_cnt,
uint8_t *data,
uint32_t main_spare,
struct nand_info *nand)
{
uint32_t page_size_add_bits = U(0);
uint32_t page_add_in_actual, page_add;
uintptr_t sram_addr_calc;
int ret;
uint32_t col_val;
/* Programming MS bit to read from spare area.*/
col_val = (main_spare << NAND_COL_MS_SHIFT) | col_add;
write_reg(nand, NAND_BC, byte_cnt);
write_reg(nand, ROW0, row_add);
write_reg(nand, COL0, col_val);
/* Program FCR for small Page */
if (nand->page_size == U(512)) {
if (byte_cnt == 0 ||
(byte_cnt != 0 && main_spare == 0 && col_add <= 255)) {
write_reg(nand, NAND_FCR0,
(NAND_CMD_READ0 << FCR_CMD0_SHIFT));
} else if (main_spare == 0) {
write_reg(nand, NAND_FCR0,
(NAND_CMD_READ1 << FCR_CMD0_SHIFT));
} else {
write_reg(nand, NAND_FCR0,
(NAND_CMD_READOOB << FCR_CMD0_SHIFT));
}
} else {
/* Program FCR for Large Page */
write_reg(nand, NAND_FCR0, (NAND_CMD_READ0 << FCR_CMD0_SHIFT) |
(NAND_CMD_READSTART << FCR_CMD1_SHIFT));
}
if (nand->page_size == U(512)) {
write_reg(nand, NAND_FIR0, ((FIR_OP_CW0 << FIR_OP0_SHIFT) |
(FIR_OP_CA0 << FIR_OP1_SHIFT) |
(FIR_OP_RA0 << FIR_OP2_SHIFT) |
(FIR_OP_BTRD << FIR_OP3_SHIFT) |
(FIR_OP_NOP << FIR_OP4_SHIFT)));
write_reg(nand, NAND_FIR1, U(0x00000000));
} else {
write_reg(nand, NAND_FIR0, ((FIR_OP_CW0 << FIR_OP0_SHIFT) |
(FIR_OP_CA0 << FIR_OP1_SHIFT) |
(FIR_OP_RA0 << FIR_OP2_SHIFT) |
(FIR_OP_CMD1 << FIR_OP3_SHIFT) |
(FIR_OP_BTRD << FIR_OP4_SHIFT)));
write_reg(nand, NAND_FIR1, (FIR_OP_NOP << FIR_OP5_SHIFT));
}
write_reg(nand, NANDSEQ_STRT, NAND_SEQ_STRT_FIR_STRT);
ret = nand_wait(nand);
if (ret != 0)
return ret;
/* calculate page_size_add_bits i.e bits
* in sram address corresponding to area
* within a page for sram
*/
if (nand->page_size == U(512))
page_size_add_bits = U(10);
else if (nand->page_size == U(2048))
page_size_add_bits = U(12);
else if (nand->page_size == U(4096))
page_size_add_bits = U(13);
else if (nand->page_size == U(8192))
page_size_add_bits = U(14);
else if (nand->page_size == U(16384))
page_size_add_bits = U(15);
page_add = row_add;
page_add_in_actual = (page_add << page_size_add_bits) & U(0x0000FFFF);
if (byte_cnt == 0)
col_add = U(0);
/* Calculate SRAM address for main and spare area */
if (main_spare == 0)
sram_addr_calc = ifc_region_addr | page_add_in_actual | col_add;
else
sram_addr_calc = ifc_region_addr | page_add_in_actual |
(col_add + nand->page_size);
/* Depending Byte_count copy full page or partial page from SRAM */
if (byte_cnt == 0)
memcpy(data, (void *)sram_addr_calc,
nand->page_size);
else
memcpy(data, (void *)sram_addr_calc, byte_cnt);
return 0;
}
static int nand_read(struct nand_info *nand, int32_t src_addr,
uintptr_t dst, uint32_t size)
{
uint32_t log_blk = U(0);
uint32_t pg_no = U(0);
uint32_t col_off = U(0);
uint32_t row_off = U(0);
uint32_t byte_cnt = U(0);
uint32_t read_cnt = U(0);
uint32_t i = U(0);
uint32_t updated = U(0);
int ret = 0;
uint8_t *out = (uint8_t *)dst;
uint32_t pblk;
/* loop till size */
while (size) {
log_blk = (src_addr / nand->blk_size);
pg_no = ((src_addr - (log_blk * nand->blk_size)) /
nand->page_size);
pblk = log_blk;
// iterate the bbt to find the block
for (i = 0; i <= nand->bbt_max; i++) {
if (nand->bbt[i] == EMPTY_VAL_CHECK) {
ret = update_bbt(i, pblk, &updated, nand);
if (ret != 0)
return ret;
/*
* if table not updated and we reached
* end of table
*/
if (!updated)
break;
}
if (pblk < nand->bbt[i])
break;
else if (pblk >= nand->bbt[i])
pblk++;
}
col_off = (src_addr % nand->page_size);
if (col_off) {
if ((col_off + size) < nand->page_size)
byte_cnt = size;
else
byte_cnt = nand->page_size - col_off;
row_off = (pblk << nand->pi_width) | pg_no;
ret = nand_read_data(
nand->ifc_region_addr,
row_off,
col_off,
byte_cnt, out, MAIN, nand);
if (ret != 0)
return ret;
} else {
/*
* fullpage/Partial Page
* if byte_cnt = 0 full page
* else partial page
*/
if (size < nand->page_size) {
byte_cnt = size;
read_cnt = size;
} else {
byte_cnt = nand->page_size;
read_cnt = 0;
}
row_off = (pblk << nand->pi_width) | pg_no;
ret = nand_read_data(
nand->ifc_region_addr,
row_off,
0,
read_cnt, out, MAIN, nand);
if (ret != 0) {
ERROR("Error from nand-read_data %d\n", ret);
return ret;
}
}
src_addr += byte_cnt;
out += byte_cnt;
size -= byte_cnt;
}
return 0;
}
static int isgoodblock(uint32_t blk, uint32_t *gb, struct nand_info *nand)
{
uint8_t buf[2];
int ret;
uint32_t row_add;
*gb = 0;
/* read Page 0 of blk */
ret = nand_read_data(
nand->ifc_region_addr,
blk << nand->pi_width,
nand->bad_marker_loc,
0x2, buf, 1, nand);
if (ret != 0)
return ret;
/* For ONFI devices check Page 0 and Last page of block for
* Bad Marker and for NON-ONFI Page 0 and 1 for Bad Marker
*/
row_add = (blk << nand->pi_width);
if (nand->port_size == 8) {
/* port size is 8 Bit */
/* check if page 0 has 0xff */
if (buf[0] == 0xff) {
/* check page 1 */
if (nand->onfi_dev_flag)
ret = nand_read_data(
nand->ifc_region_addr,
row_add | (nand->ppb - 1),
nand->bad_marker_loc,
0x2, buf, SPARE, nand);
else
ret = nand_read_data(
nand->ifc_region_addr,
row_add | 1,
nand->bad_marker_loc,
0x2, buf, SPARE, nand);
if (ret != 0)
return ret;
if (buf[0] == 0xff)
*gb = GOOD_BLK;
else
*gb = BAD_BLK;
} else {
/* no, so it is bad blk */
*gb = BAD_BLK;
}
} else {
/* Port size 16-Bit */
/* check if page 0 has 0xffff */
if ((buf[0] == 0xff) &&
(buf[1] == 0xff)) {
/* check page 1 for 0xffff */
if (nand->onfi_dev_flag) {
ret = nand_read_data(
nand->ifc_region_addr,
row_add | (nand->ppb - 1),
nand->bad_marker_loc,
0x2, buf, SPARE, nand);
} else {
ret = nand_read_data(
nand->ifc_region_addr,
row_add | 1,
nand->bad_marker_loc,
0x2, buf, SPARE, nand);
}
if (ret != 0)
return ret;
if ((buf[0] == 0xff) &&
(buf[1] == 0xff)) {
*gb = GOOD_BLK;
} else {
*gb = BAD_BLK;
}
} else {
/* no, so it is bad blk */
*gb = BAD_BLK;
}
}
return 0;
}
static int update_bbt(uint32_t idx, uint32_t blk,
uint32_t *updated, struct nand_info *nand)
{
uint32_t sblk;
uint32_t lgb;
int ret;
if (nand->bzero_good && blk == 0)
return 0;
/* special case for lgb == 0 */
/* if blk <= lgb retrun */
if (nand->lgb != 0 && blk <= nand->lgb)
return 0;
*updated = 0;
/* if blk is more than lgb, iterate from lgb till a good block
* is found for blk
*/
if (nand->lgb < blk)
sblk = nand->lgb;
else
/* this is when lgb = 0 */
sblk = blk;
lgb = nand->lgb;
/* loop from blk to find a good block */
while (1) {
while (lgb <= sblk) {
uint32_t gb = 0;
ret = isgoodblock(lgb, &gb, nand);
if (ret != 0)
return ret;
/* special case block 0 is good then set this flag */
if (lgb == 0 && gb == GOOD_BLK)
nand->bzero_good = 1;
if (gb == BAD_BLK) {
if (idx >= BBT_SIZE) {
ERROR("NAND BBT Table full\n");
return -1;
}
*updated = 1;
nand->bbt[idx] = lgb;
idx++;
blk++;
sblk++;
if (idx > nand->bbt_max)
nand->bbt_max = idx;
}
lgb++;
}
/* the access block found */
if (sblk == blk) {
/* when good block found update lgb */
nand->lgb = blk;
break;
}
sblk++;
}
return 0;
}
static size_t ifc_nand_read(int lba, uintptr_t buf, size_t size)
{
int ret;
uint32_t page_size;
uint32_t src_addr;
struct nand_info *nand = &nand_drv_data;
page_size = nand_get_page_size(nand);
src_addr = lba * page_size;
ret = nand_read(nand, src_addr, buf, size);
return ret ? 0 : size;
}
static struct io_block_dev_spec ifc_nand_spec = {
.buffer = {
.offset = 0,
.length = 0,
},
.ops = {
.read = ifc_nand_read,
},
/*
* Default block size assumed as 2K
* Would be updated based on actual size
*/
.block_size = UL(2048),
};
int ifc_nand_init(uintptr_t *block_dev_spec,
uintptr_t ifc_region_addr,
uintptr_t ifc_register_addr,
size_t ifc_sram_size,
uintptr_t ifc_nand_blk_offset,
size_t ifc_nand_blk_size)
{
struct nand_info *nand = NULL;
int ret;
nand = &nand_drv_data;
memset(nand, 0, sizeof(struct nand_info));
nand->ifc_region_addr = ifc_region_addr;
nand->ifc_register_addr = ifc_register_addr;
VERBOSE("nand_init\n");
ret = nand_init(nand);
if (ret) {
ERROR("nand init failed\n");
return ret;
}
ifc_nand_spec.buffer.offset = ifc_nand_blk_offset;
ifc_nand_spec.buffer.length = ifc_nand_blk_size;
ifc_nand_spec.block_size = nand_get_page_size(nand);
VERBOSE("Page size is %ld\n", ifc_nand_spec.block_size);
*block_dev_spec = (uintptr_t)&ifc_nand_spec;
/* Adding NAND SRAM< Buffer in XLAT Table */
mmap_add_region(ifc_region_addr, ifc_region_addr,
ifc_sram_size, MT_DEVICE | MT_RW);
return 0;
}