mirror of
https://github.com/brain-hackers/u-boot-brain
synced 2024-08-31 14:28:52 +09:00
![Tom Rini](/assets/img/avatar_default.png)
When U-Boot started using SPDX tags we were among the early adopters and there weren't a lot of other examples to borrow from. So we picked the area of the file that usually had a full license text and replaced it with an appropriate SPDX-License-Identifier: entry. Since then, the Linux Kernel has adopted SPDX tags and they place it as the very first line in a file (except where shebangs are used, then it's second line) and with slightly different comment styles than us. In part due to community overlap, in part due to better tag visibility and in part for other minor reasons, switch over to that style. This commit changes all instances where we have a single declared license in the tag as both the before and after are identical in tag contents. There's also a few places where I found we did not have a tag and have introduced one. Signed-off-by: Tom Rini <trini@konsulko.com>
519 lines
14 KiB
C
519 lines
14 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* (C) Copyright 2010
|
|
* Vipin Kumar, ST Microelectronics, vipin.kumar@st.com.
|
|
*
|
|
* (C) Copyright 2012
|
|
* Amit Virdi, ST Microelectronics, amit.virdi@st.com.
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <nand.h>
|
|
#include <asm/io.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/err.h>
|
|
#include <linux/mtd/nand_ecc.h>
|
|
#include <linux/mtd/fsmc_nand.h>
|
|
#include <asm/arch/hardware.h>
|
|
|
|
static u32 fsmc_version;
|
|
static struct fsmc_regs *const fsmc_regs_p = (struct fsmc_regs *)
|
|
CONFIG_SYS_FSMC_BASE;
|
|
|
|
/*
|
|
* ECC4 and ECC1 have 13 bytes and 3 bytes of ecc respectively for 512 bytes of
|
|
* data. ECC4 can correct up to 8 bits in 512 bytes of data while ECC1 can
|
|
* correct 1 bit in 512 bytes
|
|
*/
|
|
|
|
static struct nand_ecclayout fsmc_ecc4_lp_layout = {
|
|
.eccbytes = 104,
|
|
.eccpos = { 2, 3, 4, 5, 6, 7, 8,
|
|
9, 10, 11, 12, 13, 14,
|
|
18, 19, 20, 21, 22, 23, 24,
|
|
25, 26, 27, 28, 29, 30,
|
|
34, 35, 36, 37, 38, 39, 40,
|
|
41, 42, 43, 44, 45, 46,
|
|
50, 51, 52, 53, 54, 55, 56,
|
|
57, 58, 59, 60, 61, 62,
|
|
66, 67, 68, 69, 70, 71, 72,
|
|
73, 74, 75, 76, 77, 78,
|
|
82, 83, 84, 85, 86, 87, 88,
|
|
89, 90, 91, 92, 93, 94,
|
|
98, 99, 100, 101, 102, 103, 104,
|
|
105, 106, 107, 108, 109, 110,
|
|
114, 115, 116, 117, 118, 119, 120,
|
|
121, 122, 123, 124, 125, 126
|
|
},
|
|
.oobfree = {
|
|
{.offset = 15, .length = 3},
|
|
{.offset = 31, .length = 3},
|
|
{.offset = 47, .length = 3},
|
|
{.offset = 63, .length = 3},
|
|
{.offset = 79, .length = 3},
|
|
{.offset = 95, .length = 3},
|
|
{.offset = 111, .length = 3},
|
|
{.offset = 127, .length = 1}
|
|
}
|
|
};
|
|
|
|
/*
|
|
* ECC4 layout for NAND of pagesize 4096 bytes & OOBsize 224 bytes. 13*8 bytes
|
|
* of OOB size is reserved for ECC, Byte no. 0 & 1 reserved for bad block & 118
|
|
* bytes are free for use.
|
|
*/
|
|
static struct nand_ecclayout fsmc_ecc4_224_layout = {
|
|
.eccbytes = 104,
|
|
.eccpos = { 2, 3, 4, 5, 6, 7, 8,
|
|
9, 10, 11, 12, 13, 14,
|
|
18, 19, 20, 21, 22, 23, 24,
|
|
25, 26, 27, 28, 29, 30,
|
|
34, 35, 36, 37, 38, 39, 40,
|
|
41, 42, 43, 44, 45, 46,
|
|
50, 51, 52, 53, 54, 55, 56,
|
|
57, 58, 59, 60, 61, 62,
|
|
66, 67, 68, 69, 70, 71, 72,
|
|
73, 74, 75, 76, 77, 78,
|
|
82, 83, 84, 85, 86, 87, 88,
|
|
89, 90, 91, 92, 93, 94,
|
|
98, 99, 100, 101, 102, 103, 104,
|
|
105, 106, 107, 108, 109, 110,
|
|
114, 115, 116, 117, 118, 119, 120,
|
|
121, 122, 123, 124, 125, 126
|
|
},
|
|
.oobfree = {
|
|
{.offset = 15, .length = 3},
|
|
{.offset = 31, .length = 3},
|
|
{.offset = 47, .length = 3},
|
|
{.offset = 63, .length = 3},
|
|
{.offset = 79, .length = 3},
|
|
{.offset = 95, .length = 3},
|
|
{.offset = 111, .length = 3},
|
|
{.offset = 127, .length = 97}
|
|
}
|
|
};
|
|
|
|
/*
|
|
* ECC placement definitions in oobfree type format
|
|
* There are 13 bytes of ecc for every 512 byte block and it has to be read
|
|
* consecutively and immediately after the 512 byte data block for hardware to
|
|
* generate the error bit offsets in 512 byte data
|
|
* Managing the ecc bytes in the following way makes it easier for software to
|
|
* read ecc bytes consecutive to data bytes. This way is similar to
|
|
* oobfree structure maintained already in u-boot nand driver
|
|
*/
|
|
static struct fsmc_eccplace fsmc_eccpl_lp = {
|
|
.eccplace = {
|
|
{.offset = 2, .length = 13},
|
|
{.offset = 18, .length = 13},
|
|
{.offset = 34, .length = 13},
|
|
{.offset = 50, .length = 13},
|
|
{.offset = 66, .length = 13},
|
|
{.offset = 82, .length = 13},
|
|
{.offset = 98, .length = 13},
|
|
{.offset = 114, .length = 13}
|
|
}
|
|
};
|
|
|
|
static struct nand_ecclayout fsmc_ecc4_sp_layout = {
|
|
.eccbytes = 13,
|
|
.eccpos = { 0, 1, 2, 3, 6, 7, 8,
|
|
9, 10, 11, 12, 13, 14
|
|
},
|
|
.oobfree = {
|
|
{.offset = 15, .length = 1},
|
|
}
|
|
};
|
|
|
|
static struct fsmc_eccplace fsmc_eccpl_sp = {
|
|
.eccplace = {
|
|
{.offset = 0, .length = 4},
|
|
{.offset = 6, .length = 9}
|
|
}
|
|
};
|
|
|
|
static struct nand_ecclayout fsmc_ecc1_layout = {
|
|
.eccbytes = 24,
|
|
.eccpos = {2, 3, 4, 18, 19, 20, 34, 35, 36, 50, 51, 52,
|
|
66, 67, 68, 82, 83, 84, 98, 99, 100, 114, 115, 116},
|
|
.oobfree = {
|
|
{.offset = 8, .length = 8},
|
|
{.offset = 24, .length = 8},
|
|
{.offset = 40, .length = 8},
|
|
{.offset = 56, .length = 8},
|
|
{.offset = 72, .length = 8},
|
|
{.offset = 88, .length = 8},
|
|
{.offset = 104, .length = 8},
|
|
{.offset = 120, .length = 8}
|
|
}
|
|
};
|
|
|
|
/* Count the number of 0's in buff upto a max of max_bits */
|
|
static int count_written_bits(uint8_t *buff, int size, int max_bits)
|
|
{
|
|
int k, written_bits = 0;
|
|
|
|
for (k = 0; k < size; k++) {
|
|
written_bits += hweight8(~buff[k]);
|
|
if (written_bits > max_bits)
|
|
break;
|
|
}
|
|
|
|
return written_bits;
|
|
}
|
|
|
|
static void fsmc_nand_hwcontrol(struct mtd_info *mtd, int cmd, uint ctrl)
|
|
{
|
|
struct nand_chip *this = mtd_to_nand(mtd);
|
|
ulong IO_ADDR_W;
|
|
|
|
if (ctrl & NAND_CTRL_CHANGE) {
|
|
IO_ADDR_W = (ulong)this->IO_ADDR_W;
|
|
|
|
IO_ADDR_W &= ~(CONFIG_SYS_NAND_CLE | CONFIG_SYS_NAND_ALE);
|
|
if (ctrl & NAND_CLE)
|
|
IO_ADDR_W |= CONFIG_SYS_NAND_CLE;
|
|
if (ctrl & NAND_ALE)
|
|
IO_ADDR_W |= CONFIG_SYS_NAND_ALE;
|
|
|
|
if (ctrl & NAND_NCE) {
|
|
writel(readl(&fsmc_regs_p->pc) |
|
|
FSMC_ENABLE, &fsmc_regs_p->pc);
|
|
} else {
|
|
writel(readl(&fsmc_regs_p->pc) &
|
|
~FSMC_ENABLE, &fsmc_regs_p->pc);
|
|
}
|
|
this->IO_ADDR_W = (void *)IO_ADDR_W;
|
|
}
|
|
|
|
if (cmd != NAND_CMD_NONE)
|
|
writeb(cmd, this->IO_ADDR_W);
|
|
}
|
|
|
|
static int fsmc_bch8_correct_data(struct mtd_info *mtd, u_char *dat,
|
|
u_char *read_ecc, u_char *calc_ecc)
|
|
{
|
|
/* The calculated ecc is actually the correction index in data */
|
|
u32 err_idx[8];
|
|
u32 num_err, i;
|
|
u32 ecc1, ecc2, ecc3, ecc4;
|
|
|
|
num_err = (readl(&fsmc_regs_p->sts) >> 10) & 0xF;
|
|
|
|
if (likely(num_err == 0))
|
|
return 0;
|
|
|
|
if (unlikely(num_err > 8)) {
|
|
/*
|
|
* This is a temporary erase check. A newly erased page read
|
|
* would result in an ecc error because the oob data is also
|
|
* erased to FF and the calculated ecc for an FF data is not
|
|
* FF..FF.
|
|
* This is a workaround to skip performing correction in case
|
|
* data is FF..FF
|
|
*
|
|
* Logic:
|
|
* For every page, each bit written as 0 is counted until these
|
|
* number of bits are greater than 8 (the maximum correction
|
|
* capability of FSMC for each 512 + 13 bytes)
|
|
*/
|
|
|
|
int bits_ecc = count_written_bits(read_ecc, 13, 8);
|
|
int bits_data = count_written_bits(dat, 512, 8);
|
|
|
|
if ((bits_ecc + bits_data) <= 8) {
|
|
if (bits_data)
|
|
memset(dat, 0xff, 512);
|
|
return bits_data + bits_ecc;
|
|
}
|
|
|
|
return -EBADMSG;
|
|
}
|
|
|
|
ecc1 = readl(&fsmc_regs_p->ecc1);
|
|
ecc2 = readl(&fsmc_regs_p->ecc2);
|
|
ecc3 = readl(&fsmc_regs_p->ecc3);
|
|
ecc4 = readl(&fsmc_regs_p->sts);
|
|
|
|
err_idx[0] = (ecc1 >> 0) & 0x1FFF;
|
|
err_idx[1] = (ecc1 >> 13) & 0x1FFF;
|
|
err_idx[2] = (((ecc2 >> 0) & 0x7F) << 6) | ((ecc1 >> 26) & 0x3F);
|
|
err_idx[3] = (ecc2 >> 7) & 0x1FFF;
|
|
err_idx[4] = (((ecc3 >> 0) & 0x1) << 12) | ((ecc2 >> 20) & 0xFFF);
|
|
err_idx[5] = (ecc3 >> 1) & 0x1FFF;
|
|
err_idx[6] = (ecc3 >> 14) & 0x1FFF;
|
|
err_idx[7] = (((ecc4 >> 16) & 0xFF) << 5) | ((ecc3 >> 27) & 0x1F);
|
|
|
|
i = 0;
|
|
while (i < num_err) {
|
|
err_idx[i] ^= 3;
|
|
|
|
if (err_idx[i] < 512 * 8)
|
|
__change_bit(err_idx[i], dat);
|
|
|
|
i++;
|
|
}
|
|
|
|
return num_err;
|
|
}
|
|
|
|
static int fsmc_read_hwecc(struct mtd_info *mtd,
|
|
const u_char *data, u_char *ecc)
|
|
{
|
|
u_int ecc_tmp;
|
|
int timeout = CONFIG_SYS_HZ;
|
|
ulong start;
|
|
|
|
switch (fsmc_version) {
|
|
case FSMC_VER8:
|
|
start = get_timer(0);
|
|
while (get_timer(start) < timeout) {
|
|
/*
|
|
* Busy waiting for ecc computation
|
|
* to finish for 512 bytes
|
|
*/
|
|
if (readl(&fsmc_regs_p->sts) & FSMC_CODE_RDY)
|
|
break;
|
|
}
|
|
|
|
ecc_tmp = readl(&fsmc_regs_p->ecc1);
|
|
ecc[0] = (u_char) (ecc_tmp >> 0);
|
|
ecc[1] = (u_char) (ecc_tmp >> 8);
|
|
ecc[2] = (u_char) (ecc_tmp >> 16);
|
|
ecc[3] = (u_char) (ecc_tmp >> 24);
|
|
|
|
ecc_tmp = readl(&fsmc_regs_p->ecc2);
|
|
ecc[4] = (u_char) (ecc_tmp >> 0);
|
|
ecc[5] = (u_char) (ecc_tmp >> 8);
|
|
ecc[6] = (u_char) (ecc_tmp >> 16);
|
|
ecc[7] = (u_char) (ecc_tmp >> 24);
|
|
|
|
ecc_tmp = readl(&fsmc_regs_p->ecc3);
|
|
ecc[8] = (u_char) (ecc_tmp >> 0);
|
|
ecc[9] = (u_char) (ecc_tmp >> 8);
|
|
ecc[10] = (u_char) (ecc_tmp >> 16);
|
|
ecc[11] = (u_char) (ecc_tmp >> 24);
|
|
|
|
ecc_tmp = readl(&fsmc_regs_p->sts);
|
|
ecc[12] = (u_char) (ecc_tmp >> 16);
|
|
break;
|
|
|
|
default:
|
|
ecc_tmp = readl(&fsmc_regs_p->ecc1);
|
|
ecc[0] = (u_char) (ecc_tmp >> 0);
|
|
ecc[1] = (u_char) (ecc_tmp >> 8);
|
|
ecc[2] = (u_char) (ecc_tmp >> 16);
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void fsmc_enable_hwecc(struct mtd_info *mtd, int mode)
|
|
{
|
|
writel(readl(&fsmc_regs_p->pc) & ~FSMC_ECCPLEN_256,
|
|
&fsmc_regs_p->pc);
|
|
writel(readl(&fsmc_regs_p->pc) & ~FSMC_ECCEN,
|
|
&fsmc_regs_p->pc);
|
|
writel(readl(&fsmc_regs_p->pc) | FSMC_ECCEN,
|
|
&fsmc_regs_p->pc);
|
|
}
|
|
|
|
/*
|
|
* fsmc_read_page_hwecc
|
|
* @mtd: mtd info structure
|
|
* @chip: nand chip info structure
|
|
* @buf: buffer to store read data
|
|
* @oob_required: caller expects OOB data read to chip->oob_poi
|
|
* @page: page number to read
|
|
*
|
|
* This routine is needed for fsmc verison 8 as reading from NAND chip has to be
|
|
* performed in a strict sequence as follows:
|
|
* data(512 byte) -> ecc(13 byte)
|
|
* After this read, fsmc hardware generates and reports error data bits(upto a
|
|
* max of 8 bits)
|
|
*/
|
|
static int fsmc_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
|
|
uint8_t *buf, int oob_required, int page)
|
|
{
|
|
struct fsmc_eccplace *fsmc_eccpl;
|
|
int i, j, s, stat, eccsize = chip->ecc.size;
|
|
int eccbytes = chip->ecc.bytes;
|
|
int eccsteps = chip->ecc.steps;
|
|
uint8_t *p = buf;
|
|
uint8_t *ecc_calc = chip->buffers->ecccalc;
|
|
uint8_t *ecc_code = chip->buffers->ecccode;
|
|
int off, len, group = 0;
|
|
uint8_t oob[13] __attribute__ ((aligned (2)));
|
|
|
|
/* Differentiate between small and large page ecc place definitions */
|
|
if (mtd->writesize == 512)
|
|
fsmc_eccpl = &fsmc_eccpl_sp;
|
|
else
|
|
fsmc_eccpl = &fsmc_eccpl_lp;
|
|
|
|
for (i = 0, s = 0; s < eccsteps; s++, i += eccbytes, p += eccsize) {
|
|
|
|
chip->cmdfunc(mtd, NAND_CMD_READ0, s * eccsize, page);
|
|
chip->ecc.hwctl(mtd, NAND_ECC_READ);
|
|
chip->read_buf(mtd, p, eccsize);
|
|
|
|
for (j = 0; j < eccbytes;) {
|
|
off = fsmc_eccpl->eccplace[group].offset;
|
|
len = fsmc_eccpl->eccplace[group].length;
|
|
group++;
|
|
|
|
/*
|
|
* length is intentionally kept a higher multiple of 2
|
|
* to read at least 13 bytes even in case of 16 bit NAND
|
|
* devices
|
|
*/
|
|
if (chip->options & NAND_BUSWIDTH_16)
|
|
len = roundup(len, 2);
|
|
chip->cmdfunc(mtd, NAND_CMD_READOOB, off, page);
|
|
chip->read_buf(mtd, oob + j, len);
|
|
j += len;
|
|
}
|
|
|
|
memcpy(&ecc_code[i], oob, 13);
|
|
chip->ecc.calculate(mtd, p, &ecc_calc[i]);
|
|
|
|
stat = chip->ecc.correct(mtd, p, &ecc_code[i],
|
|
&ecc_calc[i]);
|
|
if (stat < 0)
|
|
mtd->ecc_stats.failed++;
|
|
else
|
|
mtd->ecc_stats.corrected += stat;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifndef CONFIG_SPL_BUILD
|
|
/*
|
|
* fsmc_nand_switch_ecc - switch the ECC operation between different engines
|
|
*
|
|
* @eccstrength - the number of bits that could be corrected
|
|
* (1 - HW, 4 - SW BCH4)
|
|
*/
|
|
int fsmc_nand_switch_ecc(uint32_t eccstrength)
|
|
{
|
|
struct nand_chip *nand;
|
|
struct mtd_info *mtd;
|
|
int err;
|
|
|
|
/*
|
|
* This functions is only called on SPEAr600 platforms, supporting
|
|
* 1 bit HW ECC. The BCH8 HW ECC (FSMC_VER8) from the ST-Ericsson
|
|
* Nomadik SoC is currently supporting this fsmc_nand_switch_ecc()
|
|
* function, as it doesn't need to switch to a different ECC layout.
|
|
*/
|
|
mtd = get_nand_dev_by_index(nand_curr_device);
|
|
nand = mtd_to_nand(mtd);
|
|
|
|
/* Setup the ecc configurations again */
|
|
if (eccstrength == 1) {
|
|
nand->ecc.mode = NAND_ECC_HW;
|
|
nand->ecc.bytes = 3;
|
|
nand->ecc.strength = 1;
|
|
nand->ecc.layout = &fsmc_ecc1_layout;
|
|
nand->ecc.calculate = fsmc_read_hwecc;
|
|
nand->ecc.correct = nand_correct_data;
|
|
} else if (eccstrength == 4) {
|
|
/*
|
|
* .calculate .correct and .bytes will be set in
|
|
* nand_scan_tail()
|
|
*/
|
|
nand->ecc.mode = NAND_ECC_SOFT_BCH;
|
|
nand->ecc.strength = 4;
|
|
nand->ecc.layout = NULL;
|
|
} else {
|
|
printf("Error: ECC strength %d not supported!\n", eccstrength);
|
|
}
|
|
|
|
/* Update NAND handling after ECC mode switch */
|
|
err = nand_scan_tail(mtd);
|
|
|
|
return err;
|
|
}
|
|
#endif /* CONFIG_SPL_BUILD */
|
|
|
|
int fsmc_nand_init(struct nand_chip *nand)
|
|
{
|
|
static int chip_nr;
|
|
struct mtd_info *mtd;
|
|
u32 peripid2 = readl(&fsmc_regs_p->peripid2);
|
|
|
|
fsmc_version = (peripid2 >> FSMC_REVISION_SHFT) &
|
|
FSMC_REVISION_MSK;
|
|
|
|
writel(readl(&fsmc_regs_p->ctrl) | FSMC_WP, &fsmc_regs_p->ctrl);
|
|
|
|
#if defined(CONFIG_SYS_FSMC_NAND_16BIT)
|
|
writel(FSMC_DEVWID_16 | FSMC_DEVTYPE_NAND | FSMC_ENABLE | FSMC_WAITON,
|
|
&fsmc_regs_p->pc);
|
|
#elif defined(CONFIG_SYS_FSMC_NAND_8BIT)
|
|
writel(FSMC_DEVWID_8 | FSMC_DEVTYPE_NAND | FSMC_ENABLE | FSMC_WAITON,
|
|
&fsmc_regs_p->pc);
|
|
#else
|
|
#error Please define CONFIG_SYS_FSMC_NAND_16BIT or CONFIG_SYS_FSMC_NAND_8BIT
|
|
#endif
|
|
writel(readl(&fsmc_regs_p->pc) | FSMC_TCLR_1 | FSMC_TAR_1,
|
|
&fsmc_regs_p->pc);
|
|
writel(FSMC_THIZ_1 | FSMC_THOLD_4 | FSMC_TWAIT_6 | FSMC_TSET_0,
|
|
&fsmc_regs_p->comm);
|
|
writel(FSMC_THIZ_1 | FSMC_THOLD_4 | FSMC_TWAIT_6 | FSMC_TSET_0,
|
|
&fsmc_regs_p->attrib);
|
|
|
|
nand->options = 0;
|
|
#if defined(CONFIG_SYS_FSMC_NAND_16BIT)
|
|
nand->options |= NAND_BUSWIDTH_16;
|
|
#endif
|
|
nand->ecc.mode = NAND_ECC_HW;
|
|
nand->ecc.size = 512;
|
|
nand->ecc.calculate = fsmc_read_hwecc;
|
|
nand->ecc.hwctl = fsmc_enable_hwecc;
|
|
nand->cmd_ctrl = fsmc_nand_hwcontrol;
|
|
nand->IO_ADDR_R = nand->IO_ADDR_W =
|
|
(void __iomem *)CONFIG_SYS_NAND_BASE;
|
|
nand->badblockbits = 7;
|
|
|
|
mtd = nand_to_mtd(nand);
|
|
|
|
switch (fsmc_version) {
|
|
case FSMC_VER8:
|
|
nand->ecc.bytes = 13;
|
|
nand->ecc.strength = 8;
|
|
nand->ecc.correct = fsmc_bch8_correct_data;
|
|
nand->ecc.read_page = fsmc_read_page_hwecc;
|
|
if (mtd->writesize == 512)
|
|
nand->ecc.layout = &fsmc_ecc4_sp_layout;
|
|
else {
|
|
if (mtd->oobsize == 224)
|
|
nand->ecc.layout = &fsmc_ecc4_224_layout;
|
|
else
|
|
nand->ecc.layout = &fsmc_ecc4_lp_layout;
|
|
}
|
|
|
|
break;
|
|
default:
|
|
nand->ecc.bytes = 3;
|
|
nand->ecc.strength = 1;
|
|
nand->ecc.layout = &fsmc_ecc1_layout;
|
|
nand->ecc.correct = nand_correct_data;
|
|
break;
|
|
}
|
|
|
|
/* Detect NAND chips */
|
|
if (nand_scan_ident(mtd, CONFIG_SYS_MAX_NAND_DEVICE, NULL))
|
|
return -ENXIO;
|
|
|
|
if (nand_scan_tail(mtd))
|
|
return -ENXIO;
|
|
|
|
if (nand_register(chip_nr++, mtd))
|
|
return -ENXIO;
|
|
|
|
return 0;
|
|
}
|