1016 lines
24 KiB
C
1016 lines
24 KiB
C
/*
|
|
* spinand.c for SUNXI NAND .
|
|
*
|
|
* Copyright (C) 2016 Allwinner.
|
|
*
|
|
*
|
|
* This file is licensed under the terms of the GNU General Public
|
|
* License version 2. This program is licensed "as is" without any
|
|
* warranty of any kind, whether express or implied.
|
|
*/
|
|
|
|
/**
|
|
* spinand2 feature:
|
|
*
|
|
* 1. Complete and continuous ECC protected area
|
|
* 2. suitable for 2 or 3 bit ecc status. (see function spi_nand_read_status)
|
|
* 3. driver register:
|
|
* 0: 50% (default)
|
|
* 1: 25%
|
|
* 2: 75%
|
|
* 3: 100%
|
|
* 4. if use quad mode, must enable QE
|
|
*/
|
|
#include <mach/sunxi_spinand.h>
|
|
#include "spinand_ecc_op.h"
|
|
|
|
#define STAT_MODE_READ 0
|
|
/* Just for compatibility of function PHY_Wait_Status() */
|
|
#define STAT_MODE_ERASE_WRITE 1
|
|
#define STAT_MODE_WRITE 3
|
|
#define STAT_MODE_ERASE 4
|
|
|
|
extern struct __NandStorageInfo_t NandStorageInfo;
|
|
extern struct __NandPageCachePool_t PageCachePool;
|
|
extern int NAND_Print(const char *fmt, ...);
|
|
extern void NAND_Memcpy(void *pAddr_dst, void *pAddr_src, unsigned int len);
|
|
extern void NAND_Memset(void *pAddr, unsigned char value, unsigned int len);
|
|
|
|
|
|
#define PHY_ERR(...) NAND_Print(__VA_ARGS__)
|
|
|
|
///////////////
|
|
|
|
unsigned int cal_addr_in_chip(unsigned int block, unsigned int page)
|
|
{
|
|
return block * NandStorageInfo.PageCntPerPhyBlk + page;
|
|
}
|
|
|
|
unsigned int cal_first_valid_bit(unsigned int secbitmap)
|
|
{
|
|
unsigned int firstbit = 0;
|
|
u8 i = 0;
|
|
|
|
while (1) {
|
|
if (secbitmap & (0x1 << i)) {
|
|
firstbit = i;
|
|
break;
|
|
}
|
|
i++;
|
|
if (i == 32)
|
|
break;
|
|
}
|
|
|
|
return firstbit;
|
|
}
|
|
|
|
unsigned int cal_valid_bits(unsigned int secbitmap)
|
|
{
|
|
unsigned int validbit = 0;
|
|
|
|
while (secbitmap) {
|
|
if (secbitmap & 0x1)
|
|
validbit++;
|
|
secbitmap >>= 1;
|
|
}
|
|
|
|
return validbit;
|
|
}
|
|
void spic_select_ss(unsigned int spi_no, unsigned int ssx)
|
|
{
|
|
|
|
unsigned int rval = readw(SPI_TCR) & (~(3 << 4));
|
|
rval |= ssx << 4;
|
|
writew(rval, SPI_TCR);
|
|
}
|
|
|
|
void spic_config_dual_mode(unsigned int spi_no, unsigned int rxdual, unsigned int dbc, unsigned int stc)
|
|
{
|
|
writew((rxdual<<28)|(dbc<<24)|(stc), SPI_BCC);
|
|
}
|
|
|
|
|
|
__s32 wait_sfer_complete(void)
|
|
{
|
|
unsigned int timeout = 0xffffff;
|
|
|
|
while (!(readw(SPI_ISR)&(0x1<<12))) {//wait transfer complete
|
|
timeout--;
|
|
if (!timeout)
|
|
break;
|
|
}
|
|
if (timeout == 0) {
|
|
NAND_Print("TC Complete wait status timeout!\n");
|
|
return -ERR_TIMEOUT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
__s32 spic0_rw(__u32 spi_no, __u32 tcnt, __u8 *txbuf, __u32 rcnt, __u8 *rxbuf,
|
|
__u32 dummy_cnt)
|
|
{
|
|
__u32 xcnt = 0, fcr;
|
|
__u32 tx_dma_flag = 0;
|
|
__u32 rx_dma_flag = 0;
|
|
__s32 timeout = 0xffff;
|
|
__s32 ret_dma_config = 0;
|
|
__s32 ret = NAND_OP_TRUE;
|
|
|
|
writew(0, SPI_IER);
|
|
writew(0xffffffff, SPI_ISR);//clear status register
|
|
|
|
writew(tcnt, SPI_MTC);
|
|
writew(tcnt+rcnt+dummy_cnt, SPI_MBC);
|
|
|
|
/* start transmit */
|
|
writew(readw(SPI_TCR) | SPI_EXCHANGE, SPI_TCR);
|
|
if (tcnt) {
|
|
if (tcnt <= 64)
|
|
tx_dma_flag = 0;
|
|
else
|
|
tx_dma_flag = 1;
|
|
|
|
|
|
if (tx_dma_flag == 1) {
|
|
writew((readw(SPI_FCR)|SPI_TXDMAREQ_EN), SPI_FCR);
|
|
ret_dma_config = nand_dma_config_start(1, (__u32) txbuf, tcnt);
|
|
if (ret_dma_config != 0) {
|
|
PHY_ERR("Spic_rw: tx nand_dma_config_start fail!\n");
|
|
writew((readw(SPI_FCR)&~SPI_TXDMAREQ_EN), SPI_FCR);
|
|
tx_dma_flag = 0; /* clear dma flag, to try use cpu */
|
|
}
|
|
}
|
|
|
|
if (tx_dma_flag == 0) {
|
|
xcnt = 0;
|
|
timeout = 0xfffff;
|
|
while (xcnt < tcnt) {
|
|
while (((readw(SPI_FSR) >> 16) & 0x7f) >= SPI_FIFO_SIZE) {
|
|
if (--timeout < 0) {
|
|
PHY_ERR("Spic_rw: cpu send data timeout!\n");
|
|
ret = ERR_TIMEOUT;
|
|
goto out;
|
|
}
|
|
}
|
|
writeb(*(txbuf + xcnt), SPI_TXD);
|
|
xcnt++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (rcnt) {
|
|
if (rcnt <= 64)
|
|
rx_dma_flag = 0;
|
|
else
|
|
rx_dma_flag = 1;
|
|
|
|
if (rx_dma_flag == 1) {
|
|
writew((readw(SPI_FCR)|SPI_RXDMAREQ_EN), SPI_FCR);
|
|
ret_dma_config = nand_dma_config_start(0, (__u32) rxbuf, rcnt);
|
|
if (ret_dma_config != 0) {
|
|
PHY_ERR("Spic_rw: rx nand_dma_config_start fail!\n");
|
|
writew((readw(SPI_FCR)&~SPI_RXDMAREQ_EN), SPI_FCR);
|
|
rx_dma_flag = 0; /* clear dma flag, to try use cpu */
|
|
}
|
|
}
|
|
|
|
if (rx_dma_flag == 0) {
|
|
xcnt = 0;
|
|
timeout = 0xfffff;
|
|
while (xcnt < rcnt) {
|
|
if (((readw(SPI_FSR)) & 0x7f) && (--timeout > 0)) {
|
|
*(rxbuf + xcnt) = readb(SPI_RXD);
|
|
xcnt++;
|
|
}
|
|
}
|
|
if (timeout <= 0) {
|
|
PHY_ERR("Spic_rw: cpu receive data timeout!\n");
|
|
ret = ERR_TIMEOUT;
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (NAND_WaitDmaFinish(tx_dma_flag, rx_dma_flag)) {
|
|
PHY_ERR("Spic_rw: DMA wait status timeout!\n");
|
|
ret = ERR_TIMEOUT;
|
|
goto out;
|
|
}
|
|
|
|
if (wait_sfer_complete()) {
|
|
PHY_ERR("Spic_rw: wait tc complete timeout!\n");
|
|
ret = ERR_TIMEOUT;
|
|
goto out;
|
|
}
|
|
|
|
if (readw(SPI_ISR) & (0xf << 8)) {
|
|
PHY_ERR("Spic_rw: FIFO status error: 0x%x!\n", readw(SPI_ISR));
|
|
ret = NAND_OP_FALSE;
|
|
goto out;
|
|
}
|
|
|
|
if (readw(SPI_TCR) & SPI_EXCHANGE)
|
|
PHY_ERR("Spic_rw: XCH Control Error!!\n");
|
|
|
|
out:
|
|
/* clear spi reg */
|
|
fcr = readw(SPI_FCR);
|
|
fcr &= ~(SPI_TXDMAREQ_EN|SPI_RXDMAREQ_EN);
|
|
writew(fcr, SPI_FCR);
|
|
|
|
writew(0xffffffff, SPI_ISR); /* clear flag */
|
|
|
|
/* release dma resource */
|
|
if (tx_dma_flag)
|
|
Nand_Dma_End(1, (__u32) txbuf, tcnt);
|
|
|
|
if (rx_dma_flag)
|
|
Nand_Dma_End(0, (__u32) rxbuf, rcnt);
|
|
|
|
return ret;
|
|
}
|
|
|
|
__s32 spinand_get(unsigned int spi_no, unsigned int chip, __u8 addr, __u8 *reg)
|
|
{
|
|
__u8 sdata[2];
|
|
unsigned int txnum;
|
|
unsigned int rxnum;
|
|
|
|
txnum = 2;
|
|
rxnum = 1;
|
|
|
|
sdata[0] = SPI_NAND_GETSR;
|
|
sdata[1] = addr;
|
|
|
|
spic_select_ss(spi_no, chip);
|
|
|
|
spic_config_dual_mode(spi_no, 0, 0, txnum);
|
|
return spic0_rw(spi_no, txnum, (void *)sdata, rxnum, (void *)reg, 0);
|
|
}
|
|
|
|
__s32 spinand_set(unsigned int spi_no, unsigned int chip, __u8 addr, __u8 reg)
|
|
{
|
|
__u8 sdata[3];
|
|
unsigned int txnum;
|
|
unsigned int rxnum;
|
|
|
|
txnum = 3;
|
|
rxnum = 0;
|
|
|
|
sdata[0] = SPI_NAND_SETSR;
|
|
sdata[1] = addr;
|
|
sdata[2] = reg;
|
|
|
|
spic_select_ss(spi_no, chip);
|
|
|
|
spic_config_dual_mode(spi_no, 0, 0, txnum);
|
|
return spic0_rw(spi_no, txnum, (void *)sdata, rxnum, NULL, 0);
|
|
}
|
|
|
|
/* Write Enable */
|
|
__s32 spinand_wren(unsigned int spi_no)
|
|
{
|
|
__u8 sdata = SPI_NAND_WREN;
|
|
unsigned int txnum;
|
|
unsigned int rxnum;
|
|
|
|
txnum = 1;
|
|
rxnum = 0;
|
|
|
|
spic_config_dual_mode(spi_no, 0, 0, txnum);
|
|
return spic0_rw(spi_no, txnum, (void *)&sdata, rxnum, NULL, 0);
|
|
}
|
|
|
|
/* Write Disable */
|
|
__s32 spinand_wrdi(unsigned int spi_no)
|
|
{
|
|
__u8 sdata = SPI_NAND_WRDI;
|
|
unsigned int txnum;
|
|
unsigned int rxnum;
|
|
|
|
txnum = 1;
|
|
rxnum = 0;
|
|
|
|
spic_config_dual_mode(spi_no, 0, 0, txnum);
|
|
return spic0_rw(spi_no, txnum, (void *)&sdata, rxnum, NULL, 0);
|
|
}
|
|
|
|
/* get status register */
|
|
__s32 spinand_getsr(unsigned int spi_no, __u8 *reg)
|
|
{
|
|
__u8 sdata[2];
|
|
unsigned int txnum;
|
|
unsigned int rxnum;
|
|
|
|
txnum = 2;
|
|
rxnum = 1;
|
|
|
|
sdata[0] = SPI_NAND_GETSR;
|
|
/*
|
|
* status adr:0xc0
|
|
* feature adr:0xb0
|
|
* protection adr:0xa0
|
|
*/
|
|
sdata[1] = 0xc0;
|
|
|
|
spic_config_dual_mode(spi_no, 0, 0, txnum);
|
|
return spic0_rw(spi_no, txnum, (void *)sdata, rxnum, (void *)reg, 0);
|
|
}
|
|
|
|
/**
|
|
* wait nand finish operation
|
|
*
|
|
* operation: Program Execute, Page Read, Block Erase, Reset
|
|
*/
|
|
__s32 spinand_wait_status(unsigned int spi_no, __u8 *status)
|
|
{
|
|
__s32 timeout = 0xfffff;
|
|
__s32 ret;
|
|
|
|
while (timeout--) {
|
|
ret = spinand_getsr(spi_no, status);
|
|
if (ret != NAND_OP_TRUE) {
|
|
NAND_Print("%s getsr fail!\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
if (!(*(__u8 *)status & SPI_NAND_READY))
|
|
break;
|
|
|
|
if (timeout < 0) {
|
|
NAND_Print("%s timeout!\n", __func__);
|
|
return ERR_TIMEOUT;
|
|
}
|
|
}
|
|
return NAND_OP_TRUE;
|
|
}
|
|
|
|
__s32 spinand_setstatus(unsigned int spi_no, unsigned int chip, __u8 reg)
|
|
{
|
|
__u8 sdata[3] = {0};
|
|
__s32 ret = NAND_OP_TRUE;
|
|
unsigned int txnum;
|
|
unsigned int rxnum;
|
|
|
|
ret = spinand_wren(spi_no);
|
|
if (ret != NAND_OP_TRUE)
|
|
return ret;
|
|
|
|
txnum = 3;
|
|
rxnum = 0;
|
|
|
|
sdata[0] = SPI_NAND_SETSR;
|
|
sdata[1] = 0xc0;
|
|
sdata[2] = reg;
|
|
|
|
spic_select_ss(spi_no, chip);
|
|
|
|
spic_config_dual_mode(spi_no, 0, 0, txnum);
|
|
return spic0_rw(spi_no, txnum, (void *)sdata, rxnum, NULL, 0);
|
|
}
|
|
|
|
__s32 spinand_getblocklock(unsigned int spi_no, unsigned int chip, __u8 *reg)
|
|
{
|
|
__u8 sdata[2];
|
|
unsigned int txnum;
|
|
unsigned int rxnum;
|
|
|
|
txnum = 2;
|
|
rxnum = 1;
|
|
|
|
sdata[0] = SPI_NAND_GETSR;
|
|
/*
|
|
* status addr: 0xC0
|
|
* feature addr: 0xB0(control others) and 0xD0 (control driver)
|
|
* protection addr: 0xA0
|
|
*/
|
|
sdata[1] = 0xA0;
|
|
|
|
spic_select_ss(spi_no, chip);
|
|
|
|
spic_config_dual_mode(spi_no, 0, 0, txnum);
|
|
return spic0_rw(spi_no, txnum, (void *)sdata, rxnum, (void *)reg, 0);
|
|
}
|
|
|
|
__s32 spinand_setblocklock(unsigned int spi_no, unsigned int chip, __u8 reg)
|
|
{
|
|
__u8 sdata[3];
|
|
unsigned int txnum;
|
|
unsigned int rxnum;
|
|
|
|
txnum = 3;
|
|
rxnum = 0;
|
|
|
|
sdata[0] = SPI_NAND_SETSR;
|
|
/*
|
|
* status addr: 0xC0
|
|
* feature addr: 0xB0(control others) and 0xD0 (control driver)
|
|
* protection addr: 0xA0
|
|
*/
|
|
sdata[1] = 0xA0;
|
|
sdata[2] = reg;
|
|
|
|
spic_select_ss(spi_no, chip);
|
|
|
|
spic_config_dual_mode(spi_no, 0, 0, txnum);
|
|
return spic0_rw(spi_no, txnum, (void *)sdata, rxnum, NULL, 0);
|
|
}
|
|
|
|
__s32 spinand_getotp(unsigned int spi_no, unsigned int chip, __u8 *reg)
|
|
{
|
|
__u8 sdata[2];
|
|
unsigned int txnum;
|
|
unsigned int rxnum;
|
|
|
|
txnum = 2;
|
|
rxnum = 1;
|
|
|
|
sdata[0] = SPI_NAND_GETSR;
|
|
/*
|
|
* status addr: 0xC0
|
|
* feature addr: 0xB0(control others) and 0xD0 (control driver)
|
|
* protection addr: 0xA0
|
|
*/
|
|
sdata[1] = 0xB0;
|
|
|
|
spic_select_ss(spi_no, chip);
|
|
|
|
spic_config_dual_mode(spi_no, 0, 0, txnum);
|
|
return spic0_rw(spi_no, txnum, (void *)sdata, rxnum, (void *)reg, 0);
|
|
}
|
|
|
|
__s32 spinand_setotp(unsigned int spi_no, unsigned int chip, __u8 reg)
|
|
{
|
|
__u8 sdata[3];
|
|
unsigned int txnum;
|
|
unsigned int rxnum;
|
|
|
|
txnum = 3;
|
|
rxnum = 0;
|
|
|
|
sdata[0] = SPI_NAND_SETSR;
|
|
/*
|
|
* status addr: 0xC0
|
|
* feature addr: 0xB0(control others) and 0xD0 (control driver)
|
|
* protection addr: 0xA0
|
|
*/
|
|
sdata[1] = 0xB0;
|
|
sdata[2] = reg;
|
|
|
|
spic_select_ss(spi_no, chip);
|
|
|
|
spic_config_dual_mode(spi_no, 0, 0, txnum);
|
|
return spic0_rw(spi_no, txnum, (void *)sdata, rxnum, NULL, 0);
|
|
}
|
|
|
|
__s32 spinand_getoutdriver(unsigned int spi_no, unsigned int chip, __u8 *reg)
|
|
{
|
|
__u8 sdata[2] ;
|
|
unsigned int txnum;
|
|
unsigned int rxnum;
|
|
|
|
txnum = 2;
|
|
rxnum = 1;
|
|
|
|
sdata[0] = SPI_NAND_GETSR;
|
|
/*
|
|
* status addr: 0xC0
|
|
* feature addr: 0xB0(control others) and 0xD0 (control driver)
|
|
* protection addr: 0xA0
|
|
*/
|
|
sdata[1] = 0xD0;
|
|
|
|
spic_select_ss(spi_no, chip);
|
|
|
|
spic_config_dual_mode(spi_no, 0, 0, txnum);
|
|
return spic0_rw(spi_no, txnum, (void *)sdata, rxnum, (void *)reg, 0);
|
|
}
|
|
|
|
__s32 spinand_setoutdriver(unsigned int spi_no, unsigned int chip, __u8 reg)
|
|
{
|
|
__u8 sdata[3] ;
|
|
unsigned int txnum;
|
|
unsigned int rxnum;
|
|
|
|
txnum = 3;
|
|
rxnum = 0;
|
|
|
|
sdata[0] = SPI_NAND_SETSR;
|
|
/*
|
|
* status addr: 0xC0
|
|
* feature addr: 0xB0 and 0xD0
|
|
* protection addr: 0xA0
|
|
*/
|
|
sdata[1] = 0xD0;
|
|
sdata[2] = reg;
|
|
|
|
spic_select_ss(spi_no, chip);
|
|
|
|
spic_config_dual_mode(spi_no, 0, 0, txnum);
|
|
return spic0_rw(spi_no, txnum, (void *)sdata, rxnum, NULL, 0);
|
|
}
|
|
|
|
__s32 spinand_read_int_eccsr(unsigned int spi_no, __u8 *reg)
|
|
{
|
|
__s32 ret = NAND_OP_TRUE;
|
|
__u8 sdata[2] ;
|
|
unsigned int txnum;
|
|
unsigned int rxnum;
|
|
|
|
txnum = 2;
|
|
rxnum = 1;
|
|
|
|
sdata[0] = SPI_NAND_READ_INT_ECCSTATUS;
|
|
sdata[1] = 0x0;
|
|
|
|
spic_config_dual_mode(spi_no, 0, 0, txnum);
|
|
ret = spic0_rw(spi_no, txnum, (void *)sdata, rxnum, (void *)reg, 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline __s32 spinand_check_fail_status(__u8 status, __u8 mask)
|
|
{
|
|
if (status & mask) {
|
|
if (mask == SPI_NAND_ERASE_FAIL)
|
|
NAND_Print("%s: erase fail, status = 0x%x\n",
|
|
__func__, status);
|
|
else
|
|
NAND_Print("%s: write fail, status = 0x%x\n",
|
|
__func__, status);
|
|
return NAND_OP_FALSE;
|
|
}
|
|
return NAND_OP_TRUE;
|
|
}
|
|
|
|
/**
|
|
* check status
|
|
*
|
|
* @mode: 0-check ecc status 1-check operation status
|
|
*
|
|
* All of modes will wait status, it means waiting operation end.
|
|
*/
|
|
__s32 spinand_read_status(unsigned int spi_no, unsigned int chip, __u8 status, unsigned int mode)
|
|
{
|
|
__s32 ret;
|
|
__u8 ext_ecc = 0;
|
|
|
|
struct __NandStorageInfo_t *info = &NandStorageInfo;
|
|
|
|
spic_select_ss(spi_no, chip);
|
|
|
|
/* write and erase */
|
|
if (mode) {
|
|
ret = spinand_wait_status(spi_no, &status);
|
|
if (ret != NAND_OP_TRUE)
|
|
return ret;
|
|
|
|
if (mode == STAT_MODE_ERASE) {
|
|
return spinand_check_fail_status(status,
|
|
(__u8)SPI_NAND_ERASE_FAIL);
|
|
} else if (mode == STAT_MODE_WRITE) {
|
|
return spinand_check_fail_status(status,
|
|
(__u8)SPI_NAND_WRITE_FAIL);
|
|
} else {
|
|
ret |= spinand_check_fail_status(status,
|
|
(__u8)SPI_NAND_ERASE_FAIL);
|
|
ret |= spinand_check_fail_status(status,
|
|
(__u8)SPI_NAND_WRITE_FAIL);
|
|
return ret;
|
|
}
|
|
/* read (ecc status) */
|
|
} else {
|
|
unsigned int ecc_type;
|
|
|
|
if (info->EccType == ECC_TYPE_ERR) {
|
|
NAND_Print("invalid EccType on nand info\n");
|
|
return ERR_ECC;
|
|
}
|
|
|
|
ret = spinand_wait_status(spi_no, &status);
|
|
if (ret != NAND_OP_TRUE)
|
|
return ret;
|
|
|
|
if (info->EccType & HAS_EXT_ECC_STATUS) {
|
|
/* extern ecc status should not shift */
|
|
ret = spinand_read_int_eccsr(spi_no, &status);
|
|
if (ret != NAND_OP_TRUE)
|
|
return ret;
|
|
} else {
|
|
if (info->ecc_status_shift)
|
|
status = status >> info->ecc_status_shift;
|
|
else
|
|
status = status >> SPI_NAND_ECC_FIRST_BIT;
|
|
}
|
|
|
|
if (status && (info->EccType & HAS_EXT_ECC_SE01)) {
|
|
ret = spinand_get(spi_no, 0, 0xF0, &ext_ecc);
|
|
if (ret != NAND_OP_TRUE)
|
|
return ret;
|
|
ext_ecc = ((ext_ecc >> 4) & 0x3);
|
|
status = ((status << 2) | ext_ecc);
|
|
}
|
|
|
|
ecc_type = info->EccType & ECC_TYPE_BITMAP;
|
|
return spinand_check_ecc(ecc_type, status);
|
|
}
|
|
}
|
|
|
|
__s32 spinand_block_erase(unsigned int spi_no, unsigned int row_addr)
|
|
{
|
|
__u8 sdata[4] = {0};
|
|
__s32 ret = NAND_OP_TRUE;
|
|
__u8 status = 0;
|
|
unsigned int txnum;
|
|
unsigned int rxnum;
|
|
|
|
//N_mdelay(1);
|
|
ret = spinand_wren(spi_no);
|
|
if (ret != NAND_OP_TRUE)
|
|
return ret;
|
|
|
|
txnum = 4;
|
|
rxnum = 0;
|
|
|
|
sdata[0] = SPI_NAND_BE;
|
|
sdata[1] = (row_addr >> 16) & 0xff;
|
|
sdata[2] = (row_addr >> 8) & 0xff;
|
|
sdata[3] = row_addr & 0xff;
|
|
|
|
spic_config_dual_mode(spi_no, 0, 0, txnum);
|
|
ret = spic0_rw(spi_no, txnum, (void *)sdata, rxnum, NULL, 0);
|
|
if (ret != NAND_OP_TRUE)
|
|
return ret;
|
|
|
|
return spinand_read_status(spi_no, 0, status, STAT_MODE_ERASE);
|
|
}
|
|
|
|
__s32 spinand_reset(unsigned int spi_no, unsigned int chip)
|
|
{
|
|
__u8 sdata = SPI_NAND_RESET;
|
|
__s32 ret = NAND_OP_TRUE;
|
|
unsigned int txnum;
|
|
unsigned int rxnum;
|
|
__u8 status = 0;
|
|
|
|
txnum = 1;
|
|
rxnum = 0;
|
|
|
|
NAND_Print("%s-%d\n", __func__, __LINE__);
|
|
spic_select_ss(spi_no, chip);
|
|
|
|
NAND_Print("%s-%d\n", __func__, __LINE__);
|
|
spic_config_dual_mode(spi_no, 0, 0, txnum);
|
|
NAND_Print("%s-%d\n", __func__, __LINE__);
|
|
ret = spic0_rw(spi_no, txnum, (void *)&sdata, rxnum, NULL, 0);
|
|
if (ret != NAND_OP_TRUE)
|
|
return ret;
|
|
|
|
NAND_Print("%s-%d\n", __func__, __LINE__);
|
|
return spinand_wait_status(spi_no, &status);
|
|
}
|
|
|
|
/**
|
|
* read data from nand
|
|
*
|
|
* return ecc status.
|
|
*/
|
|
__s32 spinand_read(unsigned int spi_no, unsigned int page_num, unsigned int mbyte_cnt,
|
|
unsigned int sbyte_cnt, void *mbuf, void *sbuf, unsigned int column)
|
|
{
|
|
unsigned int txnum, rxnum, page_addr = page_num;
|
|
__u8 sdata[8] = {0}, status = 0;
|
|
__s32 ret, ecc_status = 0;
|
|
__u8 *spare_buf = PageCachePool.SpareCache;
|
|
struct __NandStorageInfo_t *info = &NandStorageInfo;
|
|
unsigned int op_opt = info->OperationOpt;
|
|
unsigned int data_area_size = SECTOR_SIZE * info->SectorCntPerPage;
|
|
unsigned int spare_area_size;
|
|
__u8 op_width, op_dummy = 0;
|
|
|
|
if (op_opt & NAND_ONEDUMMY_AFTER_RANDOMREAD)
|
|
op_dummy = 1;
|
|
if (op_opt & NAND_QUAD_READ)
|
|
op_width = 4;
|
|
else if (op_opt & NAND_DUAL_READ)
|
|
op_width = 2;
|
|
else
|
|
op_width = 1;
|
|
if (info->EccProtectedType == ECC_PROTECTED_TYPE) {
|
|
NAND_Print("invalid EccProtectedType on nand info\n");
|
|
return NAND_OP_FALSE;
|
|
}
|
|
|
|
spare_area_size = spinand_get_spare_size(info->EccProtectedType);
|
|
|
|
/* read page to nand cache */
|
|
txnum = 4;
|
|
rxnum = 0;
|
|
sdata[0] = SPI_NAND_PAGE_READ;
|
|
sdata[1] = (page_addr >> 16) & 0xff;
|
|
sdata[2] = (page_addr >> 8) & 0xff;
|
|
sdata[3] = page_addr & 0xff;
|
|
spic_config_dual_mode(spi_no, 0, 0, txnum);
|
|
ret = spic0_rw(spi_no, txnum, (void *)sdata, rxnum, NULL, 0);
|
|
if (ret != NAND_OP_TRUE)
|
|
return ret;
|
|
|
|
/* wait operation and get ecc status */
|
|
ecc_status = spinand_read_status(spi_no, 0, status, STAT_MODE_READ);
|
|
|
|
if (mbuf) {
|
|
/* read main data and spare data from nand cache */
|
|
if (op_dummy) {
|
|
txnum = 5;
|
|
/* 1byte dummy */
|
|
sdata[1] = 0x0;
|
|
sdata[2] = ((column >> 8) & 0xff);
|
|
sdata[3] = column & 0xff;
|
|
sdata[4] = 0x0;
|
|
} else {
|
|
txnum = 4;
|
|
sdata[1] = ((column >> 8) & 0xff);
|
|
if (op_opt & NAND_TWO_PLANE_SELECT) {
|
|
if (page_num & info->PageCntPerPhyBlk)
|
|
sdata[1] = ((column >> 8) & 0x0f) | 0x10;
|
|
else
|
|
sdata[1] = ((column >> 8) & 0x0f);
|
|
}
|
|
sdata[2] = column & 0xff;
|
|
sdata[3] = 0x0;
|
|
}
|
|
rxnum = sbuf ? mbyte_cnt + spare_area_size : mbyte_cnt;
|
|
if (op_width == 4) {
|
|
sdata[0] = SPI_NAND_READ_X4;
|
|
spic_config_dual_mode(spi_no, 2, 0, txnum);
|
|
} else if (op_width == 2) {
|
|
sdata[0] = SPI_NAND_READ_X2;
|
|
spic_config_dual_mode(spi_no, 1, 0, txnum);
|
|
} else {
|
|
sdata[0] = SPI_NAND_FAST_READ_X1;
|
|
spic_config_dual_mode(spi_no, 0, 0, txnum);
|
|
}
|
|
|
|
ret = spic0_rw(spi_no, txnum, (void *)sdata, rxnum, mbuf, 0);
|
|
if (ret != NAND_OP_TRUE)
|
|
goto err;
|
|
|
|
if (sbuf) {
|
|
ret = spinand_copy_from_spare(info->EccProtectedType,
|
|
sbuf, mbuf + data_area_size, sbyte_cnt);
|
|
if (ret != NAND_OP_TRUE)
|
|
goto err;
|
|
/* invalid data */
|
|
if (((__u8 *)mbuf)[data_area_size] != 0xff ||
|
|
(((__u8 *)sbuf)[0] != 0xff))
|
|
((__u8 *)sbuf)[0] = 0x0;
|
|
}
|
|
} else if (sbuf) {
|
|
/* read spare data only */
|
|
if (op_dummy) {
|
|
txnum = 5;
|
|
/* 1byte dummy */
|
|
sdata[1] = 0x0;
|
|
sdata[2] = ((data_area_size >> 8) & 0xff);
|
|
sdata[3] = data_area_size & 0xff;
|
|
sdata[4] = 0x0;
|
|
} else {
|
|
txnum = 4;
|
|
sdata[1] = ((data_area_size >> 8) & 0xff);
|
|
sdata[2] = data_area_size & 0xff;
|
|
sdata[3] = 0x0;
|
|
}
|
|
rxnum = spare_area_size;
|
|
if (op_width == 4) {
|
|
sdata[0] = SPI_NAND_READ_X4;
|
|
spic_config_dual_mode(spi_no, 2, 0, txnum);
|
|
} else if (op_width == 2) {
|
|
sdata[0] = SPI_NAND_READ_X2;
|
|
spic_config_dual_mode(spi_no, 1, 0, txnum);
|
|
} else {
|
|
sdata[0] = SPI_NAND_FAST_READ_X1;
|
|
spic_config_dual_mode(spi_no, 0, 0, txnum);
|
|
}
|
|
|
|
ret = spic0_rw(spi_no, txnum, (void *)sdata, rxnum, spare_buf, 0);
|
|
if (ret != NAND_OP_TRUE)
|
|
goto err;
|
|
|
|
ret = spinand_copy_from_spare(info->EccProtectedType, sbuf,
|
|
spare_buf, sbyte_cnt);
|
|
if (ret != NAND_OP_TRUE)
|
|
goto err;
|
|
/* invalid data */
|
|
if ((spare_buf[0] != 0xff) || (((__u8 *)sbuf)[0] != 0xff))
|
|
((__u8 *)sbuf)[0] = 0x0;
|
|
}
|
|
|
|
ret = ecc_status;
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
__s32 spinand_write(unsigned int spi_no, unsigned int page_addr, unsigned int mbyte_cnt,
|
|
unsigned int sbyte_cnt, void *mbuf, void *sbuf, unsigned int column)
|
|
{
|
|
unsigned int txnum, rxnum;
|
|
__u8 *sdata, status = 0;
|
|
__s32 ret;
|
|
struct __NandStorageInfo_t *info = &NandStorageInfo;
|
|
unsigned int op_opt = info->OperationOpt;
|
|
unsigned int data_area_size = SECTOR_SIZE * info->SectorCntPerPage;
|
|
unsigned int spare_area_size;
|
|
__u8 op_width;
|
|
|
|
sdata = PageCachePool.SpiPageCache;
|
|
spare_area_size = spinand_get_spare_size(info->EccProtectedType);
|
|
NAND_Memset((void *)sdata, 0xFF, data_area_size + spare_area_size + 64);
|
|
|
|
if (op_opt & NAND_QUAD_PROGRAM)
|
|
op_width = 4;
|
|
else
|
|
op_width = 1;
|
|
if (info->EccProtectedType == ECC_PROTECTED_TYPE) {
|
|
NAND_Print("invalid EccProtectedType on nand info\n");
|
|
return NAND_OP_FALSE;
|
|
}
|
|
|
|
/* write enable */
|
|
ret = spinand_wren(spi_no);
|
|
if (ret != NAND_OP_TRUE)
|
|
return ret;
|
|
|
|
if (0xef != info->NandChipId[0]) {
|
|
/* cmd(1B) + addr(2B) + data(2048B) + spare */
|
|
txnum = 3 + data_area_size + spare_area_size;
|
|
rxnum = 0;
|
|
sdata[0] = op_width == 4 ? SPI_NAND_PP_X4 : SPI_NAND_PP;
|
|
sdata[1] = (column >> 8) & 0xff;
|
|
if (op_opt & NAND_TWO_PLANE_SELECT) {
|
|
if (page_addr & info->PageCntPerPhyBlk)
|
|
sdata[1] = ((column >> 8) & 0x0f) | 0x10;
|
|
else
|
|
sdata[1] = ((column >> 8) & 0x0f);
|
|
}
|
|
sdata[2] = column & 0xff;
|
|
/* copy data */
|
|
NAND_Memcpy((void *)(sdata + 3), mbuf, mbyte_cnt);
|
|
/* copy spare data */
|
|
ret = spinand_copy_to_spare(info->EccProtectedType,
|
|
sdata + 3 + data_area_size, sbuf, sbyte_cnt);
|
|
if (ret != NAND_OP_TRUE)
|
|
return ret;
|
|
/* Program Load */
|
|
if (op_width == 4)
|
|
spic_config_dual_mode(spi_no, 2, 0, 3);
|
|
else
|
|
spic_config_dual_mode(spi_no, 0, 0, txnum);
|
|
ret = spic0_rw(spi_no, txnum, (void *)sdata, rxnum, NULL, 0);
|
|
if (ret != NAND_OP_TRUE)
|
|
return ret;
|
|
} else {
|
|
/* Special case for Winbond 4x100 bug: there may be an
|
|
* unexpected 0xFF @ 0x400 or 0x401 offset address of
|
|
* physical page*/
|
|
/* step 1, program half page */
|
|
/* cmd(1B) + addr(2B) + data((data_area_size>>1)) */
|
|
txnum = 3 + (data_area_size >> 1);
|
|
rxnum = 0;
|
|
sdata[0] = op_width == 4 ? SPI_NAND_PP_X4 : SPI_NAND_PP;
|
|
sdata[1] = (column >> 8) & 0xff;
|
|
if (op_opt & NAND_TWO_PLANE_SELECT) {
|
|
if (page_addr & info->PageCntPerPhyBlk)
|
|
sdata[1] = ((column >> 8) & 0x0f) | 0x10;
|
|
else
|
|
sdata[1] = ((column >> 8) & 0x0f);
|
|
}
|
|
sdata[2] = column & 0xff;
|
|
/* copy data */
|
|
NAND_Memcpy((void *)(sdata + 3), mbuf, (data_area_size >> 1));
|
|
/* Program Load */
|
|
if (op_width == 4)
|
|
spic_config_dual_mode(spi_no, 2, 0, 3);
|
|
else
|
|
spic_config_dual_mode(spi_no, 0, 0, txnum);
|
|
ret = spic0_rw(spi_no, txnum, (void *)sdata, rxnum, NULL, 0);
|
|
if (ret != NAND_OP_TRUE)
|
|
return ret;
|
|
|
|
/* step 2, random_program another half page */
|
|
/* cmd(1B) + addr(2B) + data((data_area_size>>1)) + spare(64B) */
|
|
txnum = 3 + (data_area_size >> 1) + spare_area_size;
|
|
rxnum = 0;
|
|
|
|
NAND_Memset((void *)sdata, 0xff, txnum);
|
|
|
|
sdata[0] = op_width == 4 ? SPI_NAND_RANDOM_PP_X4 : SPI_NAND_RANDOM_PP;
|
|
// column address
|
|
sdata[1] = ((data_area_size >> 1) >> 8) & 0xff; // 4bit dummy,12bit column adr
|
|
sdata[2] = (__u8)((data_area_size >> 1) & 0xff); // A7:A0
|
|
|
|
NAND_Memcpy((void *)(sdata + 3), mbuf + (data_area_size >> 1), (data_area_size >> 1));
|
|
/* copy spare data */
|
|
ret = spinand_copy_to_spare(info->EccProtectedType,
|
|
sdata + 3 + (data_area_size >> 1), sbuf, sbyte_cnt);
|
|
if (ret != NAND_OP_TRUE)
|
|
return ret;
|
|
|
|
spic_config_dual_mode(spi_no, 2, 0, 3);
|
|
ret = spic0_rw(spi_no, txnum, (void *)sdata, rxnum, NULL, 0);
|
|
if (ret != NAND_OP_TRUE)
|
|
return ret;
|
|
}
|
|
|
|
txnum = 4;
|
|
rxnum = 0;
|
|
sdata[0] = SPI_NAND_PE;
|
|
sdata[1] = (page_addr >> 16) & 0xff;
|
|
sdata[2] = (page_addr >> 8) & 0xff;
|
|
sdata[3] = page_addr & 0xff;
|
|
/* Program Execute */
|
|
spic_config_dual_mode(spi_no, 0, 0, txnum);
|
|
ret = spic0_rw(spi_no, txnum, (void *)sdata, rxnum, NULL, 0);
|
|
if (ret != NAND_OP_TRUE)
|
|
return ret;
|
|
|
|
/* Wait End and return result */
|
|
return spinand_read_status(spi_no, 0, status, STAT_MODE_WRITE);
|
|
}
|
|
|
|
__s32 spinand_read_single_page(struct boot_physical_param *readop,
|
|
unsigned int spare_only_flag)
|
|
{
|
|
__s32 ret = NAND_OP_TRUE;
|
|
unsigned int addr;
|
|
unsigned int first_sector;
|
|
unsigned int sector_num;
|
|
__u8 *data_buf;
|
|
|
|
data_buf = PageCachePool.SpiPageCache;
|
|
|
|
addr = cal_addr_in_chip(readop->block, readop->page);
|
|
|
|
spic_select_ss(0, readop->chip);
|
|
|
|
first_sector = cal_first_valid_bit(readop->sectorbitmap);
|
|
sector_num = cal_valid_bits(readop->sectorbitmap);
|
|
/*NAND_Print("read page@%d, sector_num@%d\n", addr, sector_num);*/
|
|
|
|
if (spare_only_flag)
|
|
ret = spinand_read(0, addr, 0, 16, NULL, readop->oobbuf, 0);
|
|
else
|
|
ret = spinand_read(0, addr, 512 * sector_num, 16, data_buf,
|
|
readop->oobbuf, 512 * first_sector);
|
|
|
|
if (spare_only_flag == 0)
|
|
NAND_Memcpy((__u8 *)readop->mainbuf + 512 * first_sector, data_buf,
|
|
512 * sector_num);
|
|
|
|
return ret;
|
|
}
|
|
|
|
__s32 spinand_write_single_page(struct boot_physical_param *writeop)
|
|
{
|
|
__u8 sparebuf[32];
|
|
unsigned int addr;
|
|
|
|
/* select chip */
|
|
spic_select_ss(0, writeop->chip);
|
|
|
|
NAND_Memset(sparebuf, 0xff, 32);
|
|
/* we just use 16 bytes spare area */
|
|
if (writeop->oobbuf)
|
|
NAND_Memcpy(sparebuf, writeop->oobbuf, 16);
|
|
|
|
addr = cal_addr_in_chip(writeop->block, writeop->page);
|
|
|
|
return spinand_write(0, addr,
|
|
512 * NandStorageInfo.SectorCntPerPage, 16,
|
|
writeop->mainbuf, sparebuf, 0);
|
|
}
|
|
|
|
__s32 spinand_erase_single_block(struct boot_physical_param *eraseop)
|
|
{
|
|
unsigned int addr;
|
|
|
|
addr = cal_addr_in_chip(eraseop->block, 0);
|
|
spic_select_ss(0, eraseop->chip);
|
|
return spinand_block_erase(0, addr);
|
|
}
|
|
|
|
struct spi_nand_function spinand_function = {
|
|
spinand_reset,
|
|
spinand_read_status,
|
|
spinand_setstatus,
|
|
spinand_getblocklock,
|
|
spinand_setblocklock,
|
|
spinand_getotp,
|
|
spinand_setotp,
|
|
spinand_getoutdriver,
|
|
spinand_setoutdriver,
|
|
spinand_erase_single_block,
|
|
spinand_write_single_page,
|
|
spinand_read_single_page,
|
|
};
|