1. add cvitek folders to u-boot-2021.10 2. add cv183x/cv182x part 3. add cv181x/cv180x part Change-Id: I6dc2e5ff509dbab16bd60bfb3fd61852da5e01f6
1811 lines
50 KiB
C
1811 lines
50 KiB
C
#include <common.h>
|
|
#include <nand.h>
|
|
#include <asm/io.h>
|
|
#include <malloc.h>
|
|
//#include <match_table.h>
|
|
#include <linux/mtd/nand.h>
|
|
#include <cvsnfc_common.h>
|
|
#include <cvsnfc_spi_ids.h>
|
|
#include <cvsnfc.h>
|
|
#include <watchdog.h>
|
|
|
|
#ifdef CONFIG_ARCH_MMU
|
|
#include <asm/cache-cp15.h>
|
|
#endif
|
|
|
|
#if CONFIG_IS_ENABLED(RISCV)
|
|
#include <asm/cache.h>
|
|
#endif
|
|
|
|
//#define CVSNFC_DEBUG
|
|
#ifdef CVSNFC_DEBUG
|
|
#define DEBUG_ERASE 1
|
|
#define DEBUG_WRITE 1
|
|
#define DEBUG_READ 1
|
|
#define DEBUG_DMA 1
|
|
#define DEBUG_REG 1
|
|
#define DEBUG_CMD_FLOW 1
|
|
#define DEBUG_ECC 1
|
|
#else
|
|
#define DEBUG_ERASE 0
|
|
#define DEBUG_WRITE 0
|
|
#define DEBUG_READ 0
|
|
#define DEBUG_DMA 0
|
|
#define DEBUG_REG 0
|
|
#define DEBUG_CMD_FLOW 0
|
|
#define DEBUG_ECC 0
|
|
#endif
|
|
|
|
#ifdef DEBUG_WRITE_TWICE
|
|
uint8_t dbg_pg_buf[MAX_PAGE_SIZE + MAX_SPARE_SIZE];
|
|
#endif
|
|
|
|
static int cvsnfc_dev_ready(struct mtd_info *mtd);
|
|
//static void cvsnfc_ctrl_ecc(struct mtd_info *mtd, bool enable);
|
|
static void cvsnfc_setup_intr(struct cvsnfc_host *host);
|
|
|
|
static struct cvsnfc_host snfc_host = {
|
|
.chip = NULL,
|
|
};
|
|
|
|
/*****************************************************************************/
|
|
void cvsnfc_register_dump(void)
|
|
{
|
|
if (DEBUG_REG) {
|
|
int ix;
|
|
void __iomem *regbase = (void __iomem *)CONFIG_CVSNFC_REG_BASE_ADDRESS;
|
|
|
|
printf("Register dump:");
|
|
for (ix = 0; ix <= 0x7C; ix += 0x04) {
|
|
if (!(ix & 0x0F))
|
|
printf("\n0x%08lX: ", (uintptr_t)(regbase + ix));
|
|
printf("%08X ", readl(regbase + ix));
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Send set/get features command to SPI Nand flash */
|
|
#define DEBUG_SPI_NAND_DRV 0
|
|
|
|
void spi_feature_op(struct cvsnfc_host *host, int op, int fe, unsigned int *val)
|
|
{
|
|
if (op == GET_OP) {
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL2, 1 << TRX_DATA_SIZE_SHIFT | 1 << TRX_CMD_CONT_SIZE_SHIFT);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL3, 0);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CMD0, fe << 8 | SPI_NAND_CMD_GET_FEATURE);
|
|
|
|
cvsnfc_setup_intr(host);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL0, cvsfc_read(host, REG_SPI_NAND_TRX_CTRL0) | BIT_REG_TRX_START);
|
|
|
|
CVSNFC_CMD_WAIT_CPU_FINISH(host);
|
|
CVSNFC_CLEAR_INT(host);
|
|
|
|
*val = cvsfc_read(host, REG_SPI_NAND_RX_DATA) & 0xff;
|
|
if (DEBUG_CMD_FLOW)
|
|
pr_info("cvsnfc: get feature [%#x]<==[%#x]\n", fe, *val);
|
|
} else {
|
|
uint32_t fe_set = fe | (*val << 8);
|
|
|
|
if (DEBUG_CMD_FLOW)
|
|
pr_info("cvsnfc: set feature [%#x] = [%#x]\n", fe, *val);
|
|
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL2, 2 << TRX_CMD_CONT_SIZE_SHIFT);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL3, BIT_REG_TRX_RW);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CMD0, (fe_set << TRX_CMD_CONT0_SHIFT) | SPI_NAND_CMD_SET_FEATURE);
|
|
|
|
cvsnfc_setup_intr(host);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL0, cvsfc_read(host, REG_SPI_NAND_TRX_CTRL0) | BIT_REG_TRX_START);
|
|
|
|
CVSNFC_CMD_WAIT_CPU_FINISH(host);
|
|
CVSNFC_CLEAR_INT(host);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static void cvsnfc_send_cmd_pageprog(struct cvsnfc_host *host)
|
|
{
|
|
unsigned char pages_per_block_shift;
|
|
unsigned int val, block_num, block_num_h, page_num;
|
|
struct cvsnfc_op *spi = host->spi;
|
|
struct nand_chip *chip = host->chip;
|
|
#ifdef CVSNFC_SUPPORT_REG_WRITE
|
|
const char *op_type = "reg";
|
|
#else
|
|
const char *op_type = "dma";
|
|
#endif
|
|
|
|
if (DEBUG_WRITE)
|
|
pr_info("* Enter %s page program!\n", op_type);
|
|
|
|
val = spi->driver->wait_ready(spi);
|
|
if (val) {
|
|
printf("%s: %s page program wait ready fail! status[%#x]\n",
|
|
__func__, op_type, val);
|
|
return;
|
|
}
|
|
|
|
if (spi->driver->write_enable(spi)) {
|
|
printf("%s %s page program write enable failed!\n", __func__,
|
|
op_type);
|
|
return;
|
|
}
|
|
|
|
host->set_system_clock(spi->write, ENABLE);
|
|
|
|
val = CVSNFC_INT_CLR_ALL;
|
|
cvsfc_write(host, CVSNFC_INT_CLR, val);
|
|
if (DEBUG_WRITE)
|
|
pr_info(" Set REG INT_CLR[0x14]%#x\n", val);
|
|
|
|
val = CVSNFC_OP_CFG_MEM_IF_TYPE(spi->write->iftype);
|
|
cvsfc_write(host, CVSNFC_OP_CFG, val);
|
|
|
|
if (DEBUG_WRITE)
|
|
pr_info(" Set REG OP_CFG[0x28]%#x\n", val);
|
|
|
|
pages_per_block_shift = chip->phys_erase_shift - chip->page_shift;
|
|
block_num = host->addr_value[1] >> pages_per_block_shift;
|
|
block_num_h = block_num >> REG_CNT_HIGH_BLOCK_NUM_SHIFT;
|
|
val = CVSNFC_ADDRH_SET(block_num_h);
|
|
cvsfc_write(host, CVSNFC_ADDRH, val);
|
|
if (DEBUG_WRITE)
|
|
pr_info(" Set REG ADDRH[0x2c]%#x\n", val);
|
|
|
|
page_num = host->addr_value[1] - (block_num << pages_per_block_shift);
|
|
val = ((block_num & REG_CNT_BLOCK_NUM_MASK) << REG_CNT_BLOCK_NUM_SHIFT)
|
|
| ((page_num & REG_CNT_PAGE_NUM_MASK) << REG_CNT_PAGE_NUM_SHIFT);
|
|
cvsfc_write(host, CVSNFC_ADDRL, val);
|
|
|
|
if (DEBUG_WRITE)
|
|
pr_info(" Set REG ADDRL[0x30]%#x\n", val);
|
|
|
|
#ifndef CVSNFC_SUPPORT_REG_WRITE
|
|
val = CVSNFC_DMA_CTRL_ALL_ENABLE;
|
|
cvsfc_write(host, CVSNFC_DMA_CTRL, val);
|
|
if (DEBUG_WRITE)
|
|
pr_info(" Set REG DMA_CTRL[0x3c]%#x\n", val);
|
|
|
|
val = host->dma_buffer;
|
|
cvsfc_write(host, CVSNFC_DMA_SADDR_D, val);
|
|
if (DEBUG_WRITE)
|
|
pr_info(" Set REG DMA_SADDR_D[0x40]%#x\n", val);
|
|
|
|
val = host->dma_oob;
|
|
cvsfc_write(host, CVSNFC_DMA_SADDR_OOB, val);
|
|
if (DEBUG_WRITE)
|
|
pr_info(" Set REG DMA_SADDR_OOB[%#x]%#x\n",
|
|
CVSNFC_DMA_SADDR_OOB, val);
|
|
#ifdef CONFIG_ARCH_MMU
|
|
dcache_clean_range((unsigned int)host->dma_buffer,
|
|
(unsigned int)(host->dma_buffer +
|
|
host->pagesize + host->oobsize));
|
|
#endif
|
|
|
|
#endif
|
|
|
|
val = CVSNFC_OP_CTRL_WR_OPCODE(spi->write->cmd)
|
|
| CVSNFC_OP_CTRL_CS_OP(host->cmd_option.chipselect)
|
|
#ifdef CVSNFC_SUPPORT_REG_WRITE
|
|
| CVSNFC_OP_CTRL_OP_TYPE(OP_TYPE_REG)
|
|
#else
|
|
| CVSNFC_OP_CTRL_OP_TYPE(OP_TYPE_DMA)
|
|
#endif
|
|
| CVSNFC_OP_CTRL_RW_OP(RW_OP_WRITE)
|
|
| CVSNFC_OP_CTRL_OP_READY;
|
|
cvsfc_write(host, CVSNFC_OP_CTRL, val);
|
|
if (DEBUG_WRITE)
|
|
pr_info(" Set REG OP_CTRL[0x34]%#x\n", val);
|
|
|
|
CVSNFC_DMA_WAIT_INT_FINISH(host);
|
|
|
|
if (DEBUG_WRITE) {
|
|
val = spi->driver->wait_ready(spi);
|
|
|
|
if (val & STATUS_P_FAIL_MASK)
|
|
printf("cvsnfc: %s page program failed! status[%#x]\n",
|
|
op_type, val);
|
|
}
|
|
|
|
if (DEBUG_WRITE)
|
|
pr_info("* End %s page program!\n", op_type);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static void cvsnfc_send_cmd_readstart(struct cvsnfc_host *host)
|
|
{
|
|
unsigned char pages_per_block_shift, only_oob = 0;
|
|
unsigned short wrap = 0;
|
|
unsigned int val, block_num, block_num_h, page_num, addr_of = 0;
|
|
struct cvsnfc_op *spi = host->spi;
|
|
struct nand_chip *chip = host->chip;
|
|
|
|
#ifdef CVSNFC_SUPPORT_REG_READ
|
|
char *op_type = "reg";
|
|
#else
|
|
char *op_type = "dma";
|
|
#endif
|
|
|
|
if (DEBUG_READ)
|
|
pr_info("* Enter %s page read start!\n", op_type);
|
|
|
|
if ((host->addr_value[0] == host->cache_addr_value[0]) &&
|
|
(host->addr_value[1] == host->cache_addr_value[1])) {
|
|
if (DEBUG_READ)
|
|
pr_info("* %s page read cache hit! addr1[%#x], addr0[%#x]\n",
|
|
op_type, host->addr_value[1], host->addr_value[0]);
|
|
return;
|
|
}
|
|
|
|
val = spi->driver->wait_ready(spi);
|
|
if (val) {
|
|
printf("%s: %s read wait ready fail! status[%#x]\n", __func__,
|
|
op_type, val);
|
|
return;
|
|
}
|
|
|
|
host->set_system_clock(spi->read, ENABLE);
|
|
|
|
val = CVSNFC_INT_CLR_ALL;
|
|
cvsfc_write(host, CVSNFC_INT_CLR, val);
|
|
if (DEBUG_READ)
|
|
pr_info(" Set REG INT_CLR[0x14]%#x\n", val);
|
|
|
|
if (host->cmd_option.last_cmd == NAND_CMD_READOOB) {
|
|
only_oob = 1;
|
|
host->cmd_option.op_config =
|
|
CVSNFC_OP_CFG_RD_OP_SEL(RD_OP_READ_OOB);
|
|
} else
|
|
host->cmd_option.op_config =
|
|
CVSNFC_OP_CFG_RD_OP_SEL(RD_OP_READ_PAGE);
|
|
|
|
val = host->cmd_option.op_config
|
|
| CVSNFC_OP_CFG_MEM_IF_TYPE(spi->read->iftype)
|
|
| CVSNFC_OP_CFG_DUMMY_ADDR_NUM(spi->read->dummy);
|
|
cvsfc_write(host, CVSNFC_OP_CFG, val);
|
|
if (DEBUG_READ)
|
|
pr_info(" Set REG OP_CFG[0x28]%#x\n", val);
|
|
|
|
pages_per_block_shift = chip->phys_erase_shift - chip->page_shift;
|
|
block_num = host->addr_value[1] >> pages_per_block_shift;
|
|
block_num_h = block_num >> REG_CNT_HIGH_BLOCK_NUM_SHIFT;
|
|
|
|
val = CVSNFC_ADDRH_SET(block_num_h);
|
|
cvsfc_write(host, CVSNFC_ADDRH, val);
|
|
if (DEBUG_READ)
|
|
pr_info(" Set REG ADDRH[0x2c]%#x\n", val);
|
|
|
|
page_num = host->addr_value[1] - (block_num << pages_per_block_shift);
|
|
if (only_oob)
|
|
switch (host->ecctype) {
|
|
case NAND_ECC_8BIT:
|
|
addr_of = REG_CNT_ECC_8BIT_OFFSET;
|
|
break;
|
|
case NAND_ECC_16BIT:
|
|
addr_of = REG_CNT_ECC_16BIT_OFFSET;
|
|
break;
|
|
case NAND_ECC_24BIT:
|
|
addr_of = REG_CNT_ECC_24BIT_OFFSET;
|
|
break;
|
|
case NAND_ECC_0BIT:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
val = (((block_num & REG_CNT_BLOCK_NUM_MASK) << REG_CNT_BLOCK_NUM_SHIFT)
|
|
| ((page_num & REG_CNT_PAGE_NUM_MASK) << REG_CNT_PAGE_NUM_SHIFT)
|
|
| ((wrap & REG_CNT_WRAP_MASK) << REG_CNT_WRAP_SHIFT)
|
|
| (addr_of & REG_CNT_ECC_OFFSET_MASK));
|
|
cvsfc_write(host, CVSNFC_ADDRL, val);
|
|
if (DEBUG_READ)
|
|
pr_info(" Set REG ADDRL[0x30]%#x\n", val);
|
|
|
|
#ifndef CVSNFC_SUPPORT_REG_READ
|
|
val = CVSNFC_DMA_CTRL_ALL_ENABLE;
|
|
cvsfc_write(host, CVSNFC_DMA_CTRL, val);
|
|
if (DEBUG_READ)
|
|
pr_info(" Set REG DMA_CTRL[0x3c]%#x\n", val);
|
|
|
|
val = host->dma_buffer;
|
|
cvsfc_write(host, CVSNFC_DMA_SADDR_D, val);
|
|
if (DEBUG_READ)
|
|
pr_info(" Set REG DMA_SADDR_D[0x40]%#x\n", val);
|
|
|
|
val = host->dma_oob;
|
|
cvsfc_write(host, CVSNFC_DMA_SADDR_OOB, val);
|
|
if (DEBUG_READ)
|
|
pr_info(" Set REG DMA_SADDR_OOB[%#x]%#x\n",
|
|
CVSNFC_DMA_SADDR_OOB, val);
|
|
|
|
#ifdef CONFIG_ARCH_MMU
|
|
dcache_inv_range((unsigned int)host->dma_buffer,
|
|
(unsigned int)(host->dma_buffer +
|
|
host->pagesize + host->oobsize));
|
|
#endif
|
|
|
|
#endif
|
|
|
|
val = CVSNFC_OP_CTRL_RD_OPCODE(spi->read->cmd)
|
|
| CVSNFC_OP_CTRL_CS_OP(host->cmd_option.chipselect)
|
|
#ifdef CVSNFC_SUPPORT_REG_READ
|
|
| CVSNFC_OP_CTRL_OP_TYPE(OP_TYPE_REG)
|
|
#else
|
|
| CVSNFC_OP_CTRL_OP_TYPE(OP_TYPE_DMA)
|
|
#endif
|
|
| CVSNFC_OP_CTRL_RW_OP(RW_OP_READ)
|
|
| CVSNFC_OP_CTRL_OP_READY;
|
|
cvsfc_write(host, CVSNFC_OP_CTRL, val);
|
|
if (DEBUG_READ)
|
|
pr_info(" Set REG OP_CTRL[0x34]%#x\n", val);
|
|
|
|
CVSNFC_DMA_WAIT_INT_FINISH(host);
|
|
|
|
host->cache_addr_value[0] = host->addr_value[0];
|
|
host->cache_addr_value[1] = host->addr_value[1];
|
|
|
|
if (DEBUG_READ)
|
|
pr_info("* End %s page read start!\n", op_type);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
void cvsnfc_send_cmd_erase(struct cvsnfc_host *host)
|
|
{
|
|
unsigned int val;
|
|
struct cvsnfc_op *spi = host->spi;
|
|
uint32_t row_addr = host->addr_value[1];
|
|
uint32_t r_row_addr = ((row_addr & 0xff0000) >> 16) | (row_addr & 0xff00)
|
|
| ((row_addr & 0xff) << 16);
|
|
|
|
if (DEBUG_ERASE) {
|
|
printf("* Enter send cmd erase\n");
|
|
printf("%s row_addr 0x%x\n", __func__, host->addr_value[1]);
|
|
}
|
|
if (spi->driver->select_die) {
|
|
unsigned int die_id =
|
|
row_addr / (host->diesize / host->pagesize);
|
|
|
|
spi->driver->select_die(spi, die_id);
|
|
}
|
|
|
|
val = spi->driver->wait_ready(spi);
|
|
if (val) {
|
|
printf("cvsnfc: erase wait ready fail! status[%#x]\n", val);
|
|
return;
|
|
}
|
|
|
|
if (spi->driver->write_enable(spi)) {
|
|
printf("%s erase write enable failed\n", __func__);
|
|
return;
|
|
}
|
|
|
|
if (DEBUG_ERASE) {
|
|
spi_feature_op(host, GET_OP, STATUS_ADDR, &val);
|
|
pr_info(" Get feature addr[0xC0], val[%#x]\n", val);
|
|
}
|
|
|
|
if (DEBUG_ERASE) {
|
|
pr_info("%s row_addr 0x%x\n", __func__, host->addr_value[1]);
|
|
pr_info("r_row_addr 0x%x\n", r_row_addr);
|
|
}
|
|
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL2, 3 << TRX_CMD_CONT_SIZE_SHIFT); // 3 bytes for 24-bit row address
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL3, 0x0);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CMD0, (r_row_addr << TRX_CMD_CONT0_SHIFT) | SPI_NAND_CMD_BLOCK_ERASE);
|
|
|
|
cvsnfc_setup_intr(host);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL0, cvsfc_read(host, REG_SPI_NAND_TRX_CTRL0) | BIT_REG_TRX_START);
|
|
|
|
CVSNFC_CMD_WAIT_CPU_FINISH(host);
|
|
CVSNFC_CLEAR_INT(host);
|
|
|
|
val = spi->driver->wait_ready(spi);
|
|
if (val & STATUS_E_FAIL_MASK)
|
|
printf("cvsnfc: erase failed! status[%#x]\n", val);
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static void cvsnfc_send_cmd_status(struct cvsnfc_host *host)
|
|
{
|
|
unsigned int regval, addr = 0;
|
|
|
|
if ((host->cmd_option.last_cmd == NAND_CMD_ERASE1) ||
|
|
(host->cmd_option.last_cmd == NAND_CMD_PAGEPROG))
|
|
addr = PROTECTION_ADDR;
|
|
else
|
|
addr = STATUS_ADDR;
|
|
|
|
spi_feature_op(host, GET_OP, addr, ®val);
|
|
|
|
if (DEBUG_ERASE || DEBUG_WRITE)
|
|
pr_info("cvsnfc: %s get %#x status[%#x]\n",
|
|
((host->cmd_option.last_cmd == NAND_CMD_ERASE1) ? "erase" : "write"),
|
|
addr, regval);
|
|
}
|
|
|
|
void cvsnfc_setup_intr(struct cvsnfc_host *host)
|
|
{
|
|
cvsfc_write(host, REG_SPI_NAND_INT_EN, 0x1f1);
|
|
cvsfc_write(host, REG_SPI_NAND_INT_CLR, BITS_SPI_NAND_INT_CLR_ALL);
|
|
cvsfc_write(host, REG_SPI_NAND_INT_MASK, 0);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static void cvsnfc_send_cmd_readid(struct cvsnfc_host *host)
|
|
{
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL2, 0x00030001);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL3, 0);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CMD0, SPI_NAND_CMD_READ_ID);
|
|
//cvsfc_write(host, REG_SPI_NAND_BOOT_CTRL, BIT_REG_BOOT_PRD);
|
|
|
|
cvsnfc_setup_intr(host);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL0, cvsfc_read(host, REG_SPI_NAND_TRX_CTRL0) | BIT_REG_TRX_START);
|
|
|
|
CVSNFC_CMD_WAIT_CPU_FINISH(host);
|
|
CVSNFC_CLEAR_INT(host);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static void cvsnfc_send_cmd_reset(struct cvsnfc_host *host)
|
|
{
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL2, 0);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL3, 0);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CMD0, SPI_NAND_CMD_RESET);
|
|
|
|
cvsnfc_setup_intr(host);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL0, cvsfc_read(host, REG_SPI_NAND_TRX_CTRL0) | BIT_REG_TRX_START);
|
|
|
|
CVSNFC_CMD_WAIT_CPU_FINISH(host);
|
|
CVSNFC_CLEAR_INT(host);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static uint8_t cvsnfc_read_byte(struct mtd_info *mtd)
|
|
{
|
|
unsigned char value = 0;
|
|
struct nand_chip *chip = mtd->priv;
|
|
static unsigned int data;
|
|
|
|
struct cvsnfc_host *host = chip->priv;
|
|
|
|
if ((host->offset % 4) == 0) {
|
|
data = cvsfc_read(host, REG_SPI_NAND_RX_DATA);
|
|
pr_info("cvsnfc_read_byte read data 0x%x, offset %d\n", data, host->offset);
|
|
}
|
|
|
|
if (host->cmd_option.last_cmd == NAND_CMD_READID) {
|
|
unsigned char off = host->offset % 4;
|
|
|
|
value = (data & (0xff << (off * 8))) >> (off * 8);
|
|
pr_info("read id off %d, value 0x%x\n", host->offset, value);
|
|
|
|
host->offset++;
|
|
|
|
if (host->cmd_option.date_num == host->offset) {
|
|
host->cmd_option.last_cmd = 0;
|
|
host->offset = 0;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
if ((host->cmd_option.last_cmd == NAND_CMD_ERASE1) ||
|
|
(host->cmd_option.last_cmd == NAND_CMD_PAGEPROG)) {
|
|
return value;
|
|
}
|
|
|
|
if (host->cmd_option.last_cmd == NAND_CMD_ERASE2) {
|
|
return value;
|
|
}
|
|
|
|
if (host->cmd_option.command == NAND_CMD_STATUS) {
|
|
return value;
|
|
}
|
|
|
|
if (host->cmd_option.last_cmd == NAND_CMD_READOOB) {
|
|
return value;
|
|
}
|
|
|
|
host->offset++;
|
|
|
|
pr_info("%s return\n", __func__);
|
|
|
|
return 0;// FIXME : readb(host->buffer + host->column + host->offset - 1);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static u16 cvsnfc_read_word(struct mtd_info *mtd)
|
|
{
|
|
struct nand_chip *chip = mtd->priv;
|
|
struct cvsnfc_host *host = chip->priv;
|
|
|
|
host->offset += 2;
|
|
return readw(host->buffer + host->column + host->offset - 2);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static void cvsnfc_write_buf(struct mtd_info *mtd,
|
|
const uint8_t *buf, int len)
|
|
{
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static void cvsnfc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
|
|
{
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static void cvsnfc_select_chip(struct mtd_info *mtd, int chipselect)
|
|
{
|
|
}
|
|
|
|
static void cvsnfc_cmdfunc(struct mtd_info *mtd, unsigned int cmd, int col,
|
|
int page)
|
|
{
|
|
struct nand_chip *chip = mtd->priv;
|
|
struct cvsnfc_host *host = chip->priv;
|
|
|
|
if (DEBUG_CMD_FLOW)
|
|
pr_info("=> %s, r 0x%p\n", __func__, __builtin_return_address(0));
|
|
|
|
switch (cmd) {
|
|
case NAND_CMD_PAGEPROG:
|
|
break;
|
|
case NAND_CMD_STATUS:
|
|
host->offset = 0;
|
|
|
|
break;
|
|
case NAND_CMD_READID:
|
|
host->offset = 0;
|
|
host->cmd_option.last_cmd = cmd;
|
|
host->cmd_option.date_num = MAX_ID_LEN;
|
|
host->send_cmd_readid(host); // cvsnfc_send_cmd_readid
|
|
break;
|
|
case NAND_CMD_PARAM:
|
|
|
|
break;
|
|
case NAND_CMD_READ0:
|
|
case NAND_CMD_SEQIN:
|
|
break;
|
|
case NAND_CMD_RESET:
|
|
/* resets a specific device connected to the core */
|
|
host->cmd_option.last_cmd = cmd;
|
|
// cvsnfc_send_cmd_reset(host);
|
|
// cvsnfc_dev_ready(mtd);
|
|
|
|
break;
|
|
case NAND_CMD_READOOB:
|
|
break;
|
|
case NAND_CMD_RNDOUT:
|
|
break;
|
|
case NAND_CMD_ERASE1:
|
|
host->addr_value[0] = 0;
|
|
host->addr_value[1] = page;
|
|
host->cmd_option.last_cmd = cmd;
|
|
host->send_cmd_erase(host); //cvsnfc_send_cmd_erase
|
|
break;
|
|
case NAND_CMD_ERASE2:
|
|
break;
|
|
default:
|
|
pr_info("unsupported command received 0x%x\n", cmd);
|
|
break;
|
|
}
|
|
|
|
if (DEBUG_CMD_FLOW)
|
|
pr_info("<= %s, exit\n", __func__);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static void cvsnfc_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl)
|
|
{
|
|
unsigned char cmd;
|
|
int is_cache_invalid = 1;
|
|
struct nand_chip *chip = mtd->priv;
|
|
struct cvsnfc_host *host = chip->priv;
|
|
|
|
if (DEBUG_CMD_FLOW)
|
|
pr_info("%s\n", __func__);
|
|
|
|
if (ctrl & NAND_ALE) {
|
|
unsigned int addr_value = 0;
|
|
unsigned int addr_offset = 0;
|
|
|
|
if (ctrl & NAND_CTRL_CHANGE) {
|
|
host->addr_cycle = 0x0;
|
|
host->addr_value[0] = 0x0;
|
|
host->addr_value[1] = 0x0;
|
|
}
|
|
addr_offset = host->addr_cycle << 3;
|
|
|
|
if (host->addr_cycle >= CVSNFC_ADDR_CYCLE_MASK) {
|
|
addr_offset = (host->addr_cycle -
|
|
CVSNFC_ADDR_CYCLE_MASK) << 3;
|
|
addr_value = 1;
|
|
}
|
|
|
|
host->addr_value[addr_value] |=
|
|
((dat & 0xff) << addr_offset);
|
|
|
|
host->addr_cycle++;
|
|
}
|
|
|
|
if ((ctrl & NAND_CLE) && (ctrl & NAND_CTRL_CHANGE)) {
|
|
cmd = dat & 0xff;
|
|
host->cmd_option.command = cmd;
|
|
switch (cmd) {
|
|
case NAND_CMD_PAGEPROG:
|
|
host->offset = 0;
|
|
host->send_cmd_pageprog(host);
|
|
break;
|
|
|
|
case NAND_CMD_READSTART:
|
|
is_cache_invalid = 0;
|
|
if (host->addr_value[0] == host->pagesize)
|
|
host->cmd_option.last_cmd = NAND_CMD_READOOB;
|
|
host->send_cmd_readstart(host);
|
|
break;
|
|
|
|
case NAND_CMD_ERASE2:
|
|
host->cmd_option.last_cmd = cmd;
|
|
host->send_cmd_erase(host);
|
|
break;
|
|
|
|
case NAND_CMD_READID:
|
|
memset((unsigned char *)(chip->IO_ADDR_R), 0, MAX_ID_LEN);
|
|
host->cmd_option.last_cmd = cmd;
|
|
host->cmd_option.date_num = MAX_ID_LEN;
|
|
host->send_cmd_readid(host);
|
|
break;
|
|
|
|
case NAND_CMD_STATUS:
|
|
host->send_cmd_status(host);
|
|
break;
|
|
|
|
case NAND_CMD_SEQIN:
|
|
break;
|
|
|
|
case NAND_CMD_ERASE1:
|
|
break;
|
|
|
|
case NAND_CMD_READ0:
|
|
host->cmd_option.last_cmd = cmd;
|
|
break;
|
|
|
|
case NAND_CMD_RESET:
|
|
host->send_cmd_reset(host);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((dat == NAND_CMD_NONE) && host->addr_cycle) {
|
|
if (host->cmd_option.command == NAND_CMD_SEQIN ||
|
|
host->cmd_option.command == NAND_CMD_READ0 ||
|
|
host->cmd_option.command == NAND_CMD_READID) {
|
|
host->offset = 0x0;
|
|
host->column = (host->addr_value[0] & 0xffff);
|
|
}
|
|
}
|
|
|
|
if (is_cache_invalid) {
|
|
host->cache_addr_value[0] = ~0;
|
|
host->cache_addr_value[1] = ~0;
|
|
}
|
|
|
|
if (DEBUG_CMD_FLOW)
|
|
pr_info("%s exit\n", __func__);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static int cvsnfc_waitfunc(struct mtd_info *mtd, struct nand_chip *this)
|
|
{
|
|
unsigned int regval;
|
|
unsigned int deadline = 0;
|
|
struct cvsnfc_host *host = this->priv;
|
|
|
|
if (DEBUG_CMD_FLOW)
|
|
pr_info("=>%s\n", __func__);
|
|
|
|
do {
|
|
spi_feature_op(host, GET_OP, STATUS_ADDR, ®val);
|
|
|
|
if (!(regval & STATUS_OIP_MASK))
|
|
return NAND_STATUS_READY;
|
|
|
|
udelay(1);
|
|
} while (deadline++ < (40 << 5));
|
|
|
|
if (DEBUG_CMD_FLOW)
|
|
pr_info("Wait spi nand flash ready timeout.\n");
|
|
|
|
return NAND_STATUS_FAIL;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static int cvsnfc_dev_ready(struct mtd_info *mtd)
|
|
{
|
|
unsigned int regval;
|
|
unsigned int deadline = 0;
|
|
struct nand_chip *chip = mtd->priv;
|
|
struct cvsnfc_host *host = chip->priv;
|
|
|
|
do {
|
|
spi_feature_op(host, GET_OP, STATUS_ADDR, ®val);
|
|
if (!(regval & STATUS_OIP_MASK))
|
|
return 1;
|
|
//udelay(1);
|
|
} while (deadline++ < (40 << 5));
|
|
|
|
if (DEBUG_CMD_FLOW)
|
|
pr_info("Wait spi nand flash ready timeout.\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*
|
|
* 'host->epm' only use the first oobfree[0] field, it looks very simple, But...
|
|
*/
|
|
static struct nand_ecclayout nand_ecc_default = {
|
|
.oobfree = {{2, 30} }
|
|
};
|
|
|
|
/*****************************************************************************/
|
|
static struct nand_config_info cvsnfc_spi_nand_config_table[] = {
|
|
{NAND_PAGE_4K, NAND_ECC_24BIT, 200, &nand_ecc_default},
|
|
{NAND_PAGE_4K, NAND_ECC_16BIT, 144, &nand_ecc_default},
|
|
{NAND_PAGE_4K, NAND_ECC_8BIT, 128/*88*/, &nand_ecc_default},
|
|
{NAND_PAGE_4K, NAND_ECC_0BIT, 32, &nand_ecc_default},
|
|
|
|
{NAND_PAGE_2K, NAND_ECC_24BIT, 128/*116*/, &nand_ecc_default},
|
|
{NAND_PAGE_2K, NAND_ECC_16BIT, 88, &nand_ecc_default},
|
|
{NAND_PAGE_2K, NAND_ECC_8BIT, 64/*60*/, &nand_ecc_default},
|
|
{NAND_PAGE_2K, NAND_ECC_0BIT, 32, &nand_ecc_default},
|
|
|
|
{0, 0, 0, NULL},
|
|
};
|
|
|
|
/*****************************************************************************/
|
|
/* used the best correct arithmetic. */
|
|
struct nand_config_info *cvsnfc_get_best_ecc(struct mtd_info *mtd)
|
|
{
|
|
struct nand_config_info *best = NULL;
|
|
struct nand_config_info *info = cvsnfc_spi_nand_config_table;
|
|
|
|
for (; info->layout; info++) {
|
|
if (nandpage_type2size(info->pagetype) != mtd->writesize)
|
|
continue;
|
|
|
|
if (mtd->oobsize < info->oobsize)
|
|
continue;
|
|
|
|
if (!best || (best->ecctype < info->ecctype))
|
|
best = info;
|
|
}
|
|
|
|
if (!best)
|
|
pr_info("Driver does not support the pagesize (%d) and oobsize(%d).\n",
|
|
mtd->writesize, mtd->oobsize);
|
|
|
|
return best;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* force the pagesize and ecctype */
|
|
struct nand_config_info *cvsnfc_force_ecc(struct mtd_info *mtd, int pagetype,
|
|
int ecctype, char *cfgmsg, int allow_pagediv)
|
|
{
|
|
int pagesize;
|
|
struct nand_config_info *fit = NULL;
|
|
struct nand_config_info *info = cvsnfc_spi_nand_config_table;
|
|
|
|
if (DEBUG_ECC)
|
|
printf("pagetype %d, ecctype %d\n", pagetype, ecctype);
|
|
|
|
// NAND_PAGE_2K, NAND_ECC_8BIT
|
|
for (; info->layout; info++) {
|
|
if (info->pagetype == pagetype && info->ecctype == ecctype) {
|
|
if (DEBUG_ECC)
|
|
printf("pagetype %d, ecctype %d\n", pagetype, ecctype);
|
|
fit = info;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!fit) {
|
|
cvsnfc_register_dump();
|
|
return NULL;
|
|
}
|
|
|
|
pagesize = nandpage_type2size(pagetype);
|
|
|
|
if (DEBUG_ECC)
|
|
printf("pagesize %d, mtd->writesize %d\n", pagesize, mtd->writesize);
|
|
|
|
if ((pagesize != mtd->writesize) &&
|
|
(pagesize > mtd->writesize || !allow_pagediv)) {
|
|
cvsnfc_register_dump();
|
|
return NULL;
|
|
}
|
|
|
|
if (DEBUG_ECC)
|
|
printf("fit->oobsize %d, mtd->oobsize %d\n", fit->oobsize, mtd->oobsize);
|
|
|
|
if (fit->oobsize > mtd->oobsize)
|
|
return NULL;
|
|
|
|
return fit;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
int cvsnfc_ecc_probe(struct mtd_info *mtd, struct nand_chip *chip,
|
|
struct nand_flash_dev_ex *flash_dev_ex)
|
|
{
|
|
char *start_type = "unknown";
|
|
struct nand_config_info *best = NULL;
|
|
struct cvsnfc_host *host = chip->priv;
|
|
unsigned int pagetype, ecctype;
|
|
|
|
|
|
#ifdef CONFIG_CVSNFC_HARDWARE_PAGESIZE_ECC
|
|
|
|
pagetype = nandpage_size2type(mtd->writesize);
|
|
ecctype = NAND_ECC_8BIT; // FIXME!!
|
|
|
|
best = cvsnfc_force_ecc(mtd, pagetype, ecctype, "hardware config", 0);
|
|
start_type = "Hardware";
|
|
|
|
#endif /* CONFIG_CVSNFC_HARDWARE_PAGESIZE_ECC */
|
|
|
|
if (!best)
|
|
DBG_BUG(ERSTR_HARDWARE
|
|
"Please configure SPI Nand Flash pagesize and ecctype!\n");
|
|
|
|
if (DEBUG_ECC)
|
|
printf("best->oobsize %d best->ecctype %d\n", best->oobsize, best->ecctype);
|
|
|
|
if (best->ecctype != NAND_ECC_0BIT)
|
|
mtd->oobsize = best->oobsize;
|
|
|
|
if (flash_dev_ex->oobsize > mtd->oobsize)
|
|
mtd->oobsize = flash_dev_ex->oobsize;
|
|
|
|
if (DEBUG_ECC)
|
|
printf("mtd->oobsize %d\n", mtd->oobsize);
|
|
|
|
chip->ecc.layout = best->layout;
|
|
|
|
host->ecctype = NAND_ECC_8BIT;
|
|
host->pagesize = mtd->writesize;
|
|
host->oobsize = mtd->oobsize;
|
|
host->block_page_cnt = (mtd->erasesize / mtd->writesize);
|
|
host->block_page_mask = ((mtd->erasesize / mtd->writesize) - 1);
|
|
|
|
pr_info("host->block_page_cnt %d\n", host->block_page_cnt);
|
|
pr_info("host->block_page_mask %d\n", host->block_page_mask);
|
|
|
|
host->dma_oob = host->dma_buffer + host->pagesize;
|
|
|
|
host->bbm = (unsigned char *)(host->buffer + host->pagesize
|
|
+ HINFC_BAD_BLOCK_POS);
|
|
|
|
host->epm = (unsigned short *)(host->buffer + host->pagesize
|
|
+ chip->ecc.layout->oobfree[0].offset + 28);
|
|
|
|
if (mtd->writesize > SPI_NAND_MAX_PAGESIZE ||
|
|
mtd->oobsize > SPI_NAND_MAX_OOBSIZE) {
|
|
pr_info("Driver does not support this Nand Flash.\n");
|
|
pr_info("Please increase SPI_NAND_MAX_PAGESIZE and SPI_NAND_MAX_OOBSIZE.\n");
|
|
}
|
|
|
|
flash_dev_ex->start_type = start_type;
|
|
flash_dev_ex->ecctype = host->ecctype;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spi_nand_read_page(struct cvsnfc_host *host, int page)
|
|
{
|
|
int row_addr = page;
|
|
int r_row_addr = ((row_addr & 0xff0000) >> 16) | (row_addr & 0xff00) | ((row_addr & 0xff) << 16);
|
|
|
|
if (DEBUG_READ)
|
|
pr_info("%s row_addr 0x%x\n", __func__, row_addr);
|
|
|
|
if (DEBUG_READ)
|
|
pr_info("%s r_row_addr 0x%x\n", __func__, r_row_addr);
|
|
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL2, 0 << TRX_DATA_SIZE_SHIFT | 3 << TRX_CMD_CONT_SIZE_SHIFT);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL3, 0);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CMD0, r_row_addr << TRX_CMD_CONT0_SHIFT | SPI_NAND_CMD_PAGE_READ_TO_CACHE);
|
|
|
|
cvsnfc_setup_intr(host);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL0, cvsfc_read(host, REG_SPI_NAND_TRX_CTRL0) | BIT_REG_TRX_START);
|
|
|
|
CVSNFC_CMD_WAIT_CPU_FINISH(host);
|
|
CVSNFC_CLEAR_INT(host);
|
|
|
|
cvsnfc_dev_ready(host->mtd);
|
|
|
|
if (DEBUG_READ)
|
|
pr_info("<=%s\n", __func__);
|
|
|
|
//flush_dcache_all();
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* spi_nand user guide v0.3.docx
|
|
* 5.4 : Write Command with DMA Data
|
|
*
|
|
* rw : 0 for read, 1 for write
|
|
*/
|
|
static void spi_nand_rw_dma_setup(struct cvsnfc_host *host, const uint8_t *buf, int len, int rw)
|
|
{
|
|
int ch = 0;
|
|
void __iomem *remap_base = (void __iomem *)CVI_SYSDMA_REMAP_BASE;
|
|
|
|
if (DEBUG_DMA)
|
|
pr_info("%s: buf %p, len %d, %s, dmabase %p\n", __func__, buf, len,
|
|
rw == 1 ? "dma write" : "dma read", host->dmabase);
|
|
|
|
writel(0x00000003, host->dmabase + 0x010);
|
|
writel(0x00000f00, host->dmabase + 0x018);
|
|
|
|
writel((len / 4) - 1, host->dmabase + (0x110 + ch * (0x100)));
|
|
writel(0x000f0792, host->dmabase + (0x11C + ch * (0x100)));
|
|
|
|
if (rw) {
|
|
// // for dma write
|
|
writel(((uintptr_t)buf) & 0xFFFFFFFF, host->dmabase + 0x100 + ch * (0x100));
|
|
writel(((uintptr_t)buf) >> 32, host->dmabase + 0x104 + ch * (0x100));
|
|
|
|
if (DEBUG_DMA)
|
|
pr_info("w0x100: 0x%x\n", readl(host->dmabase + 0x100 + ch * (0x100)));
|
|
if (DEBUG_DMA)
|
|
pr_info("w0x104: 0x%x\n", readl(host->dmabase + 0x104 + ch * (0x100)));
|
|
|
|
writel((uintptr_t)host->regbase + 0x800, host->dmabase + 0x108 + ch * (0x100));
|
|
writel(0, host->dmabase + 0x10C + ch * (0x100));
|
|
|
|
if (DEBUG_DMA)
|
|
pr_info("w0x108: 0x%x\n", readl(host->dmabase + 0x108 + ch * (0x100)));
|
|
if (DEBUG_DMA)
|
|
pr_info("w0x10C: 0x%x\n", readl(host->dmabase + 0x10C + ch * (0x100)));
|
|
|
|
writel(0x00045441, host->dmabase + 0x118 + ch * (0x100));
|
|
writel(0x00000001, host->dmabase + 0x124 + ch * (0x100)); // [0:2] = 1 : MEM_TO_PER_DMAC, PER dst = 0
|
|
|
|
writel(0x26 << (ch * 8), remap_base); //PER dst => remap chx
|
|
uint32_t val = readl(remap_base) | 0x80000000; // update bit
|
|
|
|
writel(val, remap_base); // set sdma remap update bit
|
|
|
|
if (DEBUG_DMA)
|
|
pr_info("w0x03000154: 0x%x\n", readl(remap_base));
|
|
|
|
flush_dcache_range((unsigned long)buf, (unsigned long)(buf + len));
|
|
} else {
|
|
// for dma read
|
|
writel((uintptr_t)host->regbase + 0xC00, host->dmabase + 0x100 + ch * (0x100));
|
|
writel(0, host->dmabase + 0x104 + ch * (0x100));
|
|
|
|
if (DEBUG_DMA)
|
|
pr_info("r0x100: 0x%x\n", readl(host->dmabase + 0x100 + ch * (0x100)));
|
|
if (DEBUG_DMA)
|
|
pr_info("r0x104: 0x%x\n", readl(host->dmabase + 0x104 + ch * (0x100)));
|
|
|
|
writel(((uintptr_t)buf) & 0xFFFFFFFF, host->dmabase + 0x108 + ch * (0x100));
|
|
writel(((uintptr_t)buf) >> 32, host->dmabase + 0x10C + ch * (0x100));
|
|
|
|
if (DEBUG_DMA)
|
|
pr_info("r0x108: 0x%x\n", readl(host->dmabase + 0x108 + ch * (0x100)));
|
|
if (DEBUG_DMA)
|
|
pr_info("r0x10C: 0x%x\n", readl(host->dmabase + 0x10C + ch * (0x100)));
|
|
|
|
writel(0x00046214, host->dmabase + 0x118 + ch * (0x100));
|
|
writel(0x00000002, host->dmabase + 0x124 + ch * (0x100)); // [0:2] = 2 : PER_TO_MEM_DMAC, PER src = 0
|
|
|
|
writel(0x26 << (ch * 8), remap_base); //PER src => remap chx
|
|
uint32_t val = readl(remap_base) | 0x80000000; // update bit
|
|
|
|
writel(val, remap_base); // set sdma remap update bit
|
|
|
|
if (DEBUG_DMA)
|
|
pr_info("r0x03000154: 0x%x\n", readl(remap_base));
|
|
}
|
|
|
|
//flush_dcache_range((unsigned long)buf, (unsigned long)(buf + len));
|
|
|
|
writel(0, host->dmabase + 0x120 + ch * (0x100));
|
|
writel(0x00000101, host->dmabase + 0x018);
|
|
}
|
|
|
|
static void spi_nand_set_read_from_cache_mode(struct cvsnfc_host *host, uint32_t mode, uint32_t r_col_addr)
|
|
{
|
|
switch (mode) {
|
|
case SPI_NAND_READ_FROM_CACHE_MODE_X1:
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL3, BIT_REG_TRX_DMA_EN | BIT_REG_TRX_DUMMY_HIZ);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CMD0, r_col_addr << TRX_CMD_CONT0_SHIFT |
|
|
SPI_NAND_CMD_READ_FROM_CACHE);
|
|
break;
|
|
case SPI_NAND_READ_FROM_CACHE_MODE_X2:
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL3, BIT_REG_TRX_DMA_EN |
|
|
SPI_NAND_CTRL3_IO_TYPE_X2_MODE | BIT_REG_TRX_DUMMY_HIZ);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CMD0, r_col_addr << TRX_CMD_CONT0_SHIFT |
|
|
SPI_NAND_CMD_READ_FROM_CACHEX2);
|
|
|
|
break;
|
|
case SPI_NAND_READ_FROM_CACHE_MODE_X4:
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL3, BIT_REG_TRX_DMA_EN |
|
|
SPI_NAND_CTRL3_IO_TYPE_X4_MODE | BIT_REG_TRX_DUMMY_HIZ);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CMD0, r_col_addr << TRX_CMD_CONT0_SHIFT |
|
|
SPI_NAND_CMD_READ_FROM_CACHEX4);
|
|
break;
|
|
default:
|
|
pr_err("unsupport mode!\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int parse_status_info(struct cvsnfc_host *host)
|
|
{
|
|
uint32_t statusc0 = 0;
|
|
uint8_t ecc_sts = 0;
|
|
int ret;
|
|
|
|
spi_feature_op(host, GET_OP, host->nand_chip_info->ecc_status_offset, &statusc0);
|
|
ecc_sts = (statusc0 & host->nand_chip_info->ecc_status_mask) >> host->nand_chip_info->ecc_status_shift;
|
|
|
|
if (ecc_sts == 0) {
|
|
pr_debug("ECC pass!!\n");
|
|
ret = 0;
|
|
} else if (ecc_sts == host->nand_chip_info->ecc_status_uncorr_val) {
|
|
pr_info("%s statusc0 0x%x\n", __func__, statusc0);
|
|
pr_info("ECC_UNCORR!!\n");
|
|
ret = -EBADMSG;
|
|
} else {
|
|
pr_debug("%s statusc0 0x%x\n", __func__, statusc0);
|
|
pr_debug("ECC Corr!!\n");
|
|
ret = 1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int spi_nand_read_from_cache(struct cvsnfc_host *host, int col_addr, int len, void *buf)
|
|
{
|
|
|
|
int ret = 0;
|
|
int r_col_addr = ((col_addr & 0xff00) >> 8) | ((col_addr & 0xff) << 8);
|
|
|
|
if (DEBUG_READ)
|
|
pr_info("=>%s\n", __func__);
|
|
|
|
if (DEBUG_READ)
|
|
pr_info("%s col_addr 0x%x, len %d\n", __func__, col_addr, len);
|
|
|
|
spi_nand_rw_dma_setup(host, buf, len, 0);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL2, len << TRX_DATA_SIZE_SHIFT | 3 << TRX_CMD_CONT_SIZE_SHIFT);
|
|
|
|
//here might be changed to use 4 bit mode if support QE bit
|
|
if (host->nand_chip_info->flags & FLAGS_ENABLE_X4_BIT)
|
|
spi_nand_set_read_from_cache_mode(host, SPI_NAND_READ_FROM_CACHE_MODE_X4, r_col_addr);
|
|
else if (host->nand_chip_info->flags & FLAGS_ENABLE_X2_BIT)
|
|
spi_nand_set_read_from_cache_mode(host, SPI_NAND_READ_FROM_CACHE_MODE_X2, r_col_addr);
|
|
else
|
|
spi_nand_set_read_from_cache_mode(host, SPI_NAND_READ_FROM_CACHE_MODE_X1, r_col_addr);
|
|
|
|
|
|
cvsnfc_setup_intr(host);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL0,
|
|
cvsfc_read(host, REG_SPI_NAND_TRX_CTRL0) | BIT_REG_TRX_START);
|
|
|
|
flush_dcache_range((unsigned long)buf, (unsigned long)(buf + len));
|
|
|
|
CVSNFC_CMD_WAIT_CPU_FINISH(host);
|
|
CVSNFC_WAIT_DMA_FINISH(host);
|
|
CVSNFC_CLEAR_INT(host);
|
|
|
|
ret = parse_status_info(host);
|
|
|
|
return ret;
|
|
}
|
|
|
|
//extern void bbt_dump_buf(char *s, void *buf, int len);
|
|
#define SPI_NAND_PLANE_BIT_OFFSET BIT(12)
|
|
|
|
int cvsnfc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
|
|
uint8_t *buf, int bytes, int page)
|
|
{
|
|
struct cvsnfc_host *host = chip->priv;
|
|
uint32_t col_addr = 0;
|
|
struct cvsnfc_op *spi = host->spi;
|
|
int ret = 0;
|
|
|
|
if (DEBUG_READ)
|
|
printf("=>%s, page 0x%x\n", __func__, page);
|
|
|
|
if (spi->driver->select_die) {
|
|
unsigned int die_id = page / (host->diesize / host->pagesize);
|
|
|
|
spi->driver->select_die(spi, die_id);
|
|
}
|
|
|
|
//cvsnfc_ctrl_ecc(mtd, 1);
|
|
|
|
spi_nand_read_page(host, page);
|
|
|
|
uint32_t blk_idx = page / host->block_page_cnt;
|
|
|
|
if (DEBUG_READ)
|
|
pr_info("%s, blk_idx %d, page 0x%x, chip->options 0x%x\n",
|
|
__func__, blk_idx, page, chip->options);
|
|
|
|
if (host->flags & FLAGS_SET_PLANE_BIT && (blk_idx & BIT(0))) {
|
|
if (DEBUG_READ)
|
|
pr_info("%s set plane bit for blkidx %d\n", __func__, blk_idx);
|
|
col_addr |= SPI_NAND_PLANE_BIT_OFFSET;
|
|
}
|
|
|
|
ret = spi_nand_read_from_cache(host, col_addr, mtd->writesize, buf);
|
|
|
|
if (ret == -EBADMSG) {
|
|
pr_info("%s : ECC status ECC_UNCORR on page 0x%x, block %d\n", __func__, page, blk_idx);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void cv_spi_nand_adjust_freq(struct cvsnfc_host *host, uint8_t sck_l, uint8_t sck_h, uint16_t max_freq)
|
|
{
|
|
uint32_t val;
|
|
|
|
switch (max_freq) {
|
|
case SPI_NAND_FREQ_XTAL: /* use XTAL, 25/4 = 6.25Mhz */
|
|
cvsfc_write(host, REG_SPI_NAND_BOOT_CTRL,
|
|
(cvsfc_read(host, REG_SPI_NAND_BOOT_CTRL) | BIT_REG_BOOT_PRD));
|
|
|
|
val = cvsfc_read(host, REG_SPI_NAND_TRX_CTRL1) & ~BIT_REG_NAND_SCK_MASK;
|
|
|
|
val |= SPI_NAND_SET_SCK_L(1) | SPI_NAND_SET_SCK_H(0);
|
|
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL1, val);
|
|
break;
|
|
case SPI_NAND_FREQ_23MHz: /* use FPLL, 187.5/8 = 23.4375Mhz */
|
|
cvsfc_write(host, REG_SPI_NAND_BOOT_CTRL,
|
|
(cvsfc_read(host, REG_SPI_NAND_BOOT_CTRL) & ~BIT_REG_BOOT_PRD));
|
|
|
|
val = cvsfc_read(host, REG_SPI_NAND_TRX_CTRL1) & ~BIT_REG_NAND_SCK_MASK;
|
|
|
|
if ((sck_l + sck_h) != 6)
|
|
val |= SPI_NAND_SET_SCK_L(3) | SPI_NAND_SET_SCK_H(3);
|
|
else
|
|
val |= SPI_NAND_SET_SCK_L(sck_l) | SPI_NAND_SET_SCK_H(sck_h);
|
|
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL1, val);
|
|
break;
|
|
case SPI_NAND_FREQ_26MHz: /* use FPLL, 187.5/7 = 26.7857Mhz */
|
|
cvsfc_write(host, REG_SPI_NAND_BOOT_CTRL,
|
|
(cvsfc_read(host, REG_SPI_NAND_BOOT_CTRL) & ~BIT_REG_BOOT_PRD));
|
|
|
|
val = cvsfc_read(host, REG_SPI_NAND_TRX_CTRL1) & ~BIT_REG_NAND_SCK_MASK;
|
|
|
|
if ((sck_l + sck_h) != 5)
|
|
val |= SPI_NAND_SET_SCK_L(3) | SPI_NAND_SET_SCK_H(2);
|
|
else
|
|
val |= SPI_NAND_SET_SCK_L(sck_l) | SPI_NAND_SET_SCK_H(sck_h);
|
|
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL1, val);
|
|
break;
|
|
case SPI_NAND_FREQ_31MHz: /* use FPLL, 187.5/6 = 31.25Mhz */
|
|
cvsfc_write(host, REG_SPI_NAND_BOOT_CTRL,
|
|
(cvsfc_read(host, REG_SPI_NAND_BOOT_CTRL) & ~BIT_REG_BOOT_PRD));
|
|
|
|
val = cvsfc_read(host, REG_SPI_NAND_TRX_CTRL1) & ~BIT_REG_NAND_SCK_MASK;
|
|
|
|
if ((sck_l + sck_h) != 4)
|
|
val |= SPI_NAND_SET_SCK_L(2) | SPI_NAND_SET_SCK_H(2);
|
|
else
|
|
val |= SPI_NAND_SET_SCK_L(sck_l) | SPI_NAND_SET_SCK_H(sck_h);
|
|
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL1, val);
|
|
break;
|
|
case SPI_NAND_FREQ_37MHz: /* use FPLL, 187.5/5 = 37.5Mhz */
|
|
cvsfc_write(host, REG_SPI_NAND_BOOT_CTRL,
|
|
(cvsfc_read(host, REG_SPI_NAND_BOOT_CTRL) & ~BIT_REG_BOOT_PRD));
|
|
|
|
val = cvsfc_read(host, REG_SPI_NAND_TRX_CTRL1) & ~BIT_REG_NAND_SCK_MASK;
|
|
|
|
if ((sck_l + sck_h) != 3)
|
|
val |= SPI_NAND_SET_SCK_L(2) | SPI_NAND_SET_SCK_H(1);
|
|
else
|
|
val |= SPI_NAND_SET_SCK_L(sck_l) | SPI_NAND_SET_SCK_H(sck_h);
|
|
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL1, val);
|
|
break;
|
|
case SPI_NAND_FREQ_46MHz: /* use FPLL, 187.5/4 = 46.875Mhz */
|
|
cvsfc_write(host, REG_SPI_NAND_BOOT_CTRL,
|
|
(cvsfc_read(host, REG_SPI_NAND_BOOT_CTRL) & ~BIT_REG_BOOT_PRD));
|
|
|
|
val = cvsfc_read(host, REG_SPI_NAND_TRX_CTRL1) & ~BIT_REG_NAND_SCK_MASK;
|
|
|
|
if ((sck_l + sck_h) != 2)
|
|
val |= SPI_NAND_SET_SCK_L(1) | SPI_NAND_SET_SCK_H(1);
|
|
else
|
|
val |= SPI_NAND_SET_SCK_L(sck_l) | SPI_NAND_SET_SCK_H(sck_h);
|
|
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL1, val);
|
|
break;
|
|
case SPI_NAND_FREQ_62MHz: /* use FPLL, 187.5/3 = 62.5Mhz */
|
|
cvsfc_write(host, REG_SPI_NAND_BOOT_CTRL,
|
|
(cvsfc_read(host, REG_SPI_NAND_BOOT_CTRL) & ~BIT_REG_BOOT_PRD));
|
|
|
|
val = cvsfc_read(host, REG_SPI_NAND_TRX_CTRL1) & ~BIT_REG_NAND_SCK_MASK;
|
|
|
|
if ((sck_l + sck_h) != 1)
|
|
val |= SPI_NAND_SET_SCK_L(1) | SPI_NAND_SET_SCK_H(0);
|
|
else
|
|
val |= SPI_NAND_SET_SCK_L(sck_l) | SPI_NAND_SET_SCK_H(sck_h);
|
|
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL1, val);
|
|
break;
|
|
case SPI_NAND_FREQ_93MHz: /* use FPLL, 187.5/2 = 93.75Mhz */
|
|
cvsfc_write(host, REG_SPI_NAND_BOOT_CTRL,
|
|
(cvsfc_read(host, REG_SPI_NAND_BOOT_CTRL) & ~BIT_REG_BOOT_PRD));
|
|
|
|
val = cvsfc_read(host, REG_SPI_NAND_TRX_CTRL1) & ~BIT_REG_NAND_SCK_MASK;
|
|
|
|
val |= SPI_NAND_SET_SCK_L(0) | SPI_NAND_SET_SCK_H(0);
|
|
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL1, val);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
#define PATTERN1_OFFSET 0
|
|
#define PATTERN2_OFFSET 8
|
|
|
|
int spi_nand_adjust_max_freq(struct cvsnfc_host *host, uint32_t *pattern1, uint32_t *pattern2)
|
|
{
|
|
uint dly_ow_val, rsp_pos_sel, dly_line_sel;
|
|
int ori_boot_ctl;
|
|
int ori_trx_ctl;
|
|
int val1, val2;
|
|
unsigned char header_buf[32];
|
|
struct cvsnfc_dly_param_grp dly_grp[10];
|
|
unsigned int dly_grp_idx;
|
|
unsigned int dly_param[128]; /* the range of dly_line_sel is 0 ~ 127 */
|
|
unsigned int dly_param_idx;
|
|
unsigned int old_dly_param;
|
|
unsigned int max_count;
|
|
unsigned int select_grp;
|
|
|
|
dly_ow_val = 1; /* fix with 1 clk*/
|
|
rsp_pos_sel = 0; /* fix to select nagetive edge */
|
|
max_count = 0;
|
|
select_grp = 0;
|
|
|
|
ori_boot_ctl = cvsfc_read(host, REG_SPI_NAND_BOOT_CTRL);
|
|
ori_trx_ctl = cvsfc_read(host, REG_SPI_NAND_TRX_CTRL1);
|
|
|
|
val1 = ori_boot_ctl & ~BIT_REG_RSP_DLY_MASK;
|
|
host->nand_chip_info->max_freq = SPI_NAND_FREQ_93MHz;
|
|
|
|
printf("Tuning SPINAND run on maximum speed...\n");
|
|
|
|
freq_retry:
|
|
dly_grp_idx = 0;
|
|
dly_param_idx = 0;
|
|
old_dly_param = 0;
|
|
|
|
memset(&dly_grp, 0x0, sizeof(struct cvsnfc_dly_param_grp) * 10);
|
|
|
|
cv_spi_nand_adjust_freq(host, host->nand_chip_info->sck_l, host->nand_chip_info->sck_h,
|
|
host->nand_chip_info->max_freq);
|
|
|
|
for (dly_line_sel = 0; dly_line_sel < 128; dly_line_sel++) {
|
|
val2 = val1;
|
|
unsigned int nand_id;
|
|
|
|
val2 = (BIT_REG_RSP_DLY_OW_EN | (dly_ow_val << 8) | (rsp_pos_sel << 12) | (dly_line_sel << 16));
|
|
cvsfc_write(host, REG_SPI_NAND_BOOT_CTRL, val2);
|
|
|
|
cvsnfc_send_cmd_readid(host);
|
|
|
|
nand_id = cvsfc_read(host, REG_SPI_NAND_RX_DATA);
|
|
|
|
if (!memcmp(&nand_id, host->nand_chip_info->id, host->nand_chip_info->id_len)) {
|
|
int ret;
|
|
|
|
memset(header_buf, 0x0, 0x20);
|
|
ret = spi_nand_read_from_cache(host, 0, 0x20, header_buf); /* read first 32 bytes */
|
|
if (ret == -EBADMSG) {
|
|
printf("%s : ECC status ECC_UNCORR\n", __func__);
|
|
}
|
|
if (!memcmp(header_buf + PATTERN1_OFFSET, pattern1, 4) &&
|
|
!memcmp(header_buf + PATTERN2_OFFSET, pattern2, 4)) {
|
|
if (dly_param_idx == 0 && dly_grp_idx == 0) {
|
|
dly_grp[dly_grp_idx].start = dly_param_idx;
|
|
dly_grp[dly_grp_idx].count = 1;
|
|
} else if ((val2 - old_dly_param) == 0x10000) {
|
|
dly_grp[dly_grp_idx].count++;
|
|
} else {
|
|
dly_grp_idx++;
|
|
dly_grp[dly_grp_idx].start = dly_param_idx;
|
|
dly_grp[dly_grp_idx].count = 1;
|
|
}
|
|
|
|
old_dly_param = val2;
|
|
dly_param[dly_param_idx] = cvsfc_read(host, REG_SPI_NAND_BOOT_CTRL);
|
|
dly_param_idx++;
|
|
}
|
|
}
|
|
}
|
|
|
|
pr_info("dly_param_idx=%d, dly_grp_idx=%d\n", dly_param_idx, dly_grp_idx);
|
|
if (dly_param_idx != 0) { /* at least find 1 parameter */
|
|
for (int i = 0; i <= dly_grp_idx; i++) {
|
|
|
|
if (dly_grp[i].count > max_count) {
|
|
select_grp = i;
|
|
max_count = dly_grp[i].count;
|
|
}
|
|
pr_info("dly_param[%d].start=%d, count=%d\n", i, dly_grp[i].start, dly_grp[i].count);
|
|
}
|
|
|
|
cvsfc_write(host, REG_SPI_NAND_BOOT_CTRL, dly_param[dly_grp[select_grp].start + (max_count / 2)]);
|
|
return 0;
|
|
}
|
|
|
|
if (dly_param_idx == 0 && host->nand_chip_info->max_freq != 0) {
|
|
if (host->nand_chip_info->max_freq > 0) {
|
|
host->nand_chip_info->max_freq--;
|
|
goto freq_retry;
|
|
}
|
|
}
|
|
|
|
/* Can't find an suitable parameter for this frequency, use original one */
|
|
cvsfc_write(host, REG_SPI_NAND_BOOT_CTRL, ori_boot_ctl);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL1, ori_trx_ctl);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
void cvsnfc_ctrl_ecc(struct mtd_info *mtd, bool enable)
|
|
{
|
|
unsigned int status = 0;
|
|
struct nand_chip *chip = mtd->priv;
|
|
struct cvsnfc_host *host = chip->priv;
|
|
|
|
if (DEBUG_CMD_FLOW)
|
|
pr_info("%s %s ecc\n", __func__, (enable) ? "enable" : "disable");
|
|
|
|
spi_feature_op(host, GET_OP, FEATURE_ADDR, &status);
|
|
|
|
if (enable) {
|
|
status = status | (SPI_NAND_FEATURE0_ECC_EN);
|
|
spi_feature_op(host, SET_OP, FEATURE_ADDR, &status);
|
|
} else { //disable ecc
|
|
status = status & ~(SPI_NAND_FEATURE0_ECC_EN);
|
|
spi_feature_op(host, SET_OP, FEATURE_ADDR, &status);
|
|
}
|
|
}
|
|
|
|
static int read_oob_data(struct mtd_info *mtd, uint8_t *buf, int page)
|
|
{
|
|
struct nand_chip *chip = mtd->priv;
|
|
struct cvsnfc_host *host = chip->priv;
|
|
uint32_t col_addr = mtd->writesize;
|
|
uint32_t blk_idx = page / host->block_page_cnt;
|
|
struct cvsnfc_op *spi = host->spi;
|
|
int ret = 0;
|
|
|
|
if (DEBUG_READ)
|
|
pr_info("%s, page 0x%x, buf %p, mtd->oobsize %d\n", __func__,
|
|
page, buf, mtd->oobsize);
|
|
|
|
if (spi->driver->select_die) {
|
|
unsigned int die_id = page / (host->diesize / host->pagesize);
|
|
|
|
spi->driver->select_die(spi, die_id);
|
|
}
|
|
|
|
cvsnfc_ctrl_ecc(mtd, 0);
|
|
|
|
spi_nand_read_page(host, page);
|
|
|
|
if (host->flags & FLAGS_SET_PLANE_BIT && (blk_idx & BIT(0))) {
|
|
pr_info("%s set plane bit for blkidx %d\n", __func__, blk_idx);
|
|
col_addr |= SPI_NAND_PLANE_BIT_OFFSET;
|
|
}
|
|
|
|
ret = spi_nand_read_from_cache(host, col_addr, mtd->oobsize, buf);
|
|
|
|
if (DEBUG_READ) {
|
|
bbt_dump_buf("1oob data:", buf, 16);
|
|
}
|
|
|
|
cvsnfc_ctrl_ecc(mtd, 1);
|
|
|
|
if (ret != 0) {
|
|
printf("%s : ECC status ret %d page 0x%x\n", __func__, ret, page);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int cvsnfc_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
|
|
int page)
|
|
{
|
|
if (DEBUG_READ)
|
|
printf("%s\n", __func__);
|
|
|
|
return read_oob_data(mtd, chip->oob_poi, page);
|
|
}
|
|
|
|
int cvsnfc_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
|
|
uint8_t *buf, int oob_required, int page)
|
|
{
|
|
struct cvsnfc_host *host = chip->priv;
|
|
uint32_t col_addr = 0;
|
|
uint32_t blk_idx = page / host->block_page_cnt;
|
|
struct cvsnfc_op *spi = host->spi;
|
|
|
|
if (DEBUG_READ)
|
|
pr_info("%s, page 0x%x\n", __func__, page);
|
|
|
|
if (spi->driver->select_die) {
|
|
unsigned int die_id = page / (host->diesize / host->pagesize);
|
|
|
|
spi->driver->select_die(spi, die_id);
|
|
}
|
|
|
|
cvsnfc_ctrl_ecc(mtd, 0);
|
|
|
|
spi_nand_read_page(host, page);
|
|
|
|
if (host->flags & FLAGS_SET_PLANE_BIT && (blk_idx & BIT(0))) {
|
|
if (DEBUG_READ)
|
|
pr_info("%s set plane bit for blkidx %d\n", __func__, blk_idx);
|
|
col_addr |= SPI_NAND_PLANE_BIT_OFFSET;
|
|
}
|
|
|
|
spi_nand_read_from_cache(host, col_addr, mtd->writesize + mtd->oobsize, buf);
|
|
|
|
if (DEBUG_READ)
|
|
bbt_dump_buf("read raw page:", buf, mtd->writesize + mtd->oobsize);
|
|
|
|
cvsnfc_ctrl_ecc(mtd, 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cvsnfc_read_subpage(struct mtd_info *mtd, struct nand_chip *chip,
|
|
u32 data_offs, u32 readlen, u8 *buf, int page)
|
|
{
|
|
struct cvsnfc_host *host = chip->priv;
|
|
uint32_t col_addr = 0;
|
|
struct cvsnfc_op *spi = host->spi;
|
|
int ret = 0;
|
|
|
|
if (DEBUG_READ)
|
|
pr_info("=>%s, page 0x%x, data_offs %d, readlen %d\n",
|
|
__func__, page, data_offs, readlen);
|
|
|
|
if (spi->driver->select_die) {
|
|
unsigned int die_id = page / (host->diesize / host->pagesize);
|
|
|
|
spi->driver->select_die(spi, die_id);
|
|
}
|
|
|
|
//cvsnfc_ctrl_ecc(mtd, 1);
|
|
|
|
spi_nand_read_page(host, page);
|
|
|
|
uint32_t blk_idx = page / host->block_page_cnt;
|
|
|
|
if (host->flags & FLAGS_SET_PLANE_BIT && (blk_idx & BIT(0))) {
|
|
pr_info("%s set plane bit for blkidx %d\n", __func__, blk_idx);
|
|
col_addr |= SPI_NAND_PLANE_BIT_OFFSET;
|
|
}
|
|
|
|
ret = spi_nand_read_from_cache(host, col_addr, mtd->writesize, host->buffer);
|
|
|
|
if (ret != 0) {
|
|
printf("%s : ECC status ret %d page 0x%x\n", __func__, ret, page);
|
|
}
|
|
|
|
if (DEBUG_READ)
|
|
bbt_dump_buf("data:", buf, 16);
|
|
|
|
memcpy(buf, host->buffer, host->pagesize);
|
|
|
|
if (DEBUG_READ)
|
|
pr_info("<=%s\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint8_t spi_nand_prog_load(struct cvsnfc_host *host, const uint8_t *buf,
|
|
size_t size, uint32_t col_addr, uint32_t qe)
|
|
{
|
|
uint8_t cmd = qe ? SPI_NAND_CMD_PROGRAM_LOADX4 : SPI_NAND_CMD_PROGRAM_LOAD;
|
|
uint32_t r_col_addr = ((col_addr & 0xff00) >> 8) | ((col_addr & 0xff) << 8);
|
|
uint32_t ctrl3 = 0;
|
|
|
|
if (DEBUG_WRITE)
|
|
pr_info("=>%s size %ld, col_addr 0x%x, r_col_addr 0x%x, qe %d\n",
|
|
__func__, size, col_addr, r_col_addr, qe);
|
|
|
|
spi_nand_rw_dma_setup(host, buf, size, 1);
|
|
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL2, (size << TRX_DATA_SIZE_SHIFT) | 2 << TRX_CMD_CONT_SIZE_SHIFT);
|
|
|
|
ctrl3 = qe ? (BIT_REG_TRX_RW | BIT_REG_TRX_DMA_EN | SPI_NAND_CTRL3_IO_TYPE_X4_MODE) :
|
|
(BIT_REG_TRX_RW | BIT_REG_TRX_DMA_EN);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL3, ctrl3);
|
|
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CMD0, cmd | (r_col_addr << TRX_CMD_CONT0_SHIFT));
|
|
|
|
cvsnfc_setup_intr(host);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL0, cvsfc_read(host, REG_SPI_NAND_TRX_CTRL0) | BIT_REG_TRX_START);
|
|
|
|
CVSNFC_CMD_WAIT_CPU_FINISH(host);
|
|
CVSNFC_WAIT_DMA_FINISH(host);
|
|
CVSNFC_CLEAR_INT(host);
|
|
|
|
if (DEBUG_WRITE)
|
|
pr_info("<=%s\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spi_nand_prog_exec(struct cvsnfc_host *host, uint32_t row_addr)
|
|
{
|
|
uint32_t r_row_addr = ((row_addr & 0xff0000) >> 16) | (row_addr & 0xff00) | ((row_addr & 0xff) << 16);
|
|
|
|
if (DEBUG_WRITE)
|
|
pr_info("row_addr 0x%x\n", row_addr);
|
|
|
|
if (DEBUG_WRITE)
|
|
pr_info("r_row_addr 0x%x\n", r_row_addr);
|
|
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL2, 3 << TRX_CMD_CONT_SIZE_SHIFT);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL3, 0);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CMD0, SPI_NAND_CMD_PROGRAM_EXECUTE | (r_row_addr << TRX_CMD_CONT0_SHIFT));
|
|
cvsfc_write(host, REG_SPI_NAND_RSP_POLLING, 0xff00ff);
|
|
|
|
cvsnfc_setup_intr(host);
|
|
cvsfc_write(host, REG_SPI_NAND_TRX_CTRL0, cvsfc_read(host, REG_SPI_NAND_TRX_CTRL0) | BIT_REG_TRX_START);
|
|
|
|
CVSNFC_CMD_WAIT_CPU_FINISH(host);
|
|
CVSNFC_CLEAR_INT(host);
|
|
|
|
cvsnfc_dev_ready(host->mtd);
|
|
|
|
if (DEBUG_WRITE)
|
|
pr_info("<=%s\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
|
const uint8_t *buf, int oob_required, int page)
|
|
{
|
|
struct cvsnfc_host *host = chip->priv;
|
|
struct cvsnfc_op *spi = host->spi;
|
|
uint32_t col_addr = 0;
|
|
|
|
if (DEBUG_WRITE)
|
|
printf("=>%s, buf %p, page 0x%x\n", __func__, buf, page);
|
|
|
|
if (spi->driver->select_die) {
|
|
unsigned int die_id = page / (host->diesize / host->pagesize);
|
|
|
|
spi->driver->select_die(spi, die_id);
|
|
}
|
|
|
|
if (spi->driver->write_enable(spi)) {
|
|
printf("%s write enable failed!\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
uint32_t row_addr = page;
|
|
uint32_t blk_idx = page / host->block_page_cnt;
|
|
|
|
if (DEBUG_WRITE)
|
|
pr_info("blk idx %d\n", blk_idx);
|
|
|
|
cvsnfc_ctrl_ecc(mtd, 1);
|
|
|
|
if (host->flags & FLAGS_SET_PLANE_BIT && (blk_idx & BIT(0))) {
|
|
if (DEBUG_WRITE)
|
|
pr_info("%s set plane bit for blkidx %d\n", __func__, blk_idx);
|
|
col_addr |= SPI_NAND_PLANE_BIT_OFFSET;
|
|
}
|
|
|
|
spi_nand_prog_load(host, buf, host->pagesize, col_addr, 0);
|
|
|
|
WATCHDOG_RESET();
|
|
|
|
spi_nand_prog_exec(host, row_addr);
|
|
|
|
if (DEBUG_WRITE)
|
|
pr_info("<=%s\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int cvsnfc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
|
const uint8_t *buf, int oob_required, int page)
|
|
{
|
|
/*
|
|
* for regular page writes, we let HW handle all the ECC
|
|
* data written to the device.
|
|
*/
|
|
|
|
#ifdef DEBUG_WRITE_TWICE
|
|
struct cvsnfc_host *host = chip->priv;
|
|
|
|
if (host->flags & FLAGS_SUPPORT_W_TWICE_DEBUG) {
|
|
int status = cvsnfc_read_page_raw(mtd, chip, dbg_pg_buf, 0, page);
|
|
u32 page_size = mtd->writesize;
|
|
u32 spare_size = mtd->oobsize;
|
|
u32 offset = page_size + (spare_size / 2);
|
|
u32 i = 0;
|
|
u32 write_twice = 0;
|
|
|
|
if (status) {
|
|
printf("read status %d\n", status);
|
|
assert(0);
|
|
}
|
|
|
|
u8 *mark = (u8 *)&dbg_pg_buf[offset];
|
|
|
|
for (i = 0; i < spare_size / 2; i++) {
|
|
if (mark[i] != 0xff) {
|
|
write_twice = 1;
|
|
printf("offset %d, mark 0x%x\n", i, mark[i]);
|
|
}
|
|
}
|
|
|
|
if (write_twice) {
|
|
printf("%s pg 0x%x already programmed\n", __func__, page);
|
|
return 0;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return write_page(mtd, chip, buf, oob_required, page);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
void cvsnfc_nand_init(struct nand_chip *chip)
|
|
{
|
|
struct cvsnfc_host *host = chip->priv;
|
|
|
|
cvsnfc_setup_intr(host);
|
|
|
|
chip->read_byte = cvsnfc_read_byte;
|
|
chip->read_word = cvsnfc_read_word;
|
|
chip->write_buf = cvsnfc_write_buf;
|
|
chip->read_buf = cvsnfc_read_buf;
|
|
|
|
chip->select_chip = cvsnfc_select_chip;
|
|
chip->cmdfunc = cvsnfc_cmdfunc;
|
|
chip->waitfunc = cvsnfc_waitfunc;
|
|
chip->cmd_ctrl = cvsnfc_cmd_ctrl;
|
|
chip->dev_ready = cvsnfc_dev_ready;
|
|
|
|
chip->chip_delay = CVSNFC_CHIP_DELAY;
|
|
|
|
chip->options = NAND_BROKEN_XD;// | NAND_BBT_SCANNED;
|
|
|
|
chip->bbt_options = NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB;
|
|
|
|
chip->ecc.layout = NULL;
|
|
// chip->ecc.mode = NAND_ECC_NONE;
|
|
|
|
/* override the default read operations */
|
|
chip->ecc.size = 512; // FIXME!!
|
|
chip->ecc.strength = 8; // FIXME!!
|
|
chip->ecc.read_page = cvsnfc_read_page;
|
|
chip->ecc.read_oob = cvsnfc_read_oob;
|
|
chip->ecc.read_page_raw = cvsnfc_read_page_raw;
|
|
chip->ecc.read_subpage = cvsnfc_read_subpage;
|
|
chip->ecc.write_page = cvsnfc_write_page;
|
|
|
|
chip->options |= NAND_SUBPAGE_READ;
|
|
|
|
chip->ecc.mode = NAND_ECC_HW;
|
|
chip->ecc.bytes = 0;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
int cvsnfc_host_init(struct cvsnfc_host *host)
|
|
{
|
|
host->tx_iobase = (void __iomem *)SPI_NAND_TX_DATA_BASE;
|
|
host->rx_iobase = (void __iomem *)SPI_NAND_RX_DATA_BASE;
|
|
host->regbase = (void __iomem *)CONFIG_CVSNFC_REG_BASE_ADDRESS;
|
|
host->dmabase = (void __iomem *)0x04330000;
|
|
|
|
host->set_system_clock = 0;// FIXME : cvsnfc_set_system_clock;
|
|
|
|
if (host->set_system_clock)
|
|
host->set_system_clock(NULL, ENABLE);
|
|
|
|
host->buforg = kmalloc((CVSNFC_BUFFER_LEN + CVSNFC_DMA_ALIGN),
|
|
GFP_KERNEL);
|
|
|
|
// if (!host->buforg) {
|
|
// pr_err("cvsnfc: Can't malloc memory for NAND driver.\n");
|
|
// return 1;
|
|
// }
|
|
|
|
/* 32 bytes alignment */
|
|
host->buffer = (char *)((uintptr_t)(host->buforg
|
|
+ CVSNFC_DMA_ALIGN - 1) & ~(CVSNFC_DMA_ALIGN - 1));
|
|
memset(host->buffer, 0xff, CVSNFC_BUFFER_LEN);
|
|
|
|
host->dma_buffer = (uintptr_t)host->buffer;
|
|
|
|
host->send_cmd_pageprog = cvsnfc_send_cmd_pageprog;
|
|
host->send_cmd_status = cvsnfc_send_cmd_status;
|
|
host->send_cmd_readstart = cvsnfc_send_cmd_readstart;
|
|
host->send_cmd_erase = cvsnfc_send_cmd_erase;
|
|
host->send_cmd_readid = cvsnfc_send_cmd_readid;
|
|
host->send_cmd_reset = cvsnfc_send_cmd_reset;
|
|
|
|
spi_nand_ids_register();
|
|
|
|
cvsnfc_register_dump();
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct cvsnfc_host *cvsnfc_get_host(void)
|
|
{
|
|
return &snfc_host;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
int board_nand_init(struct nand_chip *nand)
|
|
{
|
|
struct cvsnfc_host *host = &snfc_host;
|
|
struct mtd_info *mtd = nand_to_mtd(nand);
|
|
|
|
if (host->chip)
|
|
return 0;
|
|
|
|
pr_info("Init SPI Nand Flash Controller ... ");
|
|
|
|
mtd->priv = nand;
|
|
|
|
memset((char *)host, 0, sizeof(struct cvsnfc_host));
|
|
|
|
host->chip = nand;
|
|
if (cvsnfc_host_init(host)) {
|
|
printf("cvsnfc: failed to allocate device buffer.\n");
|
|
host->chip = 0;
|
|
return -ENOMEM;
|
|
}
|
|
|
|
nand->priv = host;
|
|
cvsnfc_nand_init(nand);
|
|
|
|
nand->ecc.mode = NAND_ECC_HW;
|
|
nand->ecc.bytes = 0;
|
|
|
|
spi_nand_ids_register();
|
|
nand_oob_resize = cvsnfc_ecc_probe;
|
|
|
|
return 0;
|
|
}
|