1100 lines
33 KiB
C
1100 lines
33 KiB
C
/*
|
|
Copyright (c) 2008- Broadcom Corporation
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License version 2 as
|
|
published by the Free Software Foundation.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
File: brcmnand_cet.c
|
|
|
|
Broadcom NAND Correctable Error Table Support
|
|
---------------------------------------------
|
|
In case of a single bit correctable error, the block in which correctable error
|
|
occured is refreshed (i.e., read->erase->write the entire block). Following a
|
|
refresh a success value is returned by brcmnand_read() i.e., the error is
|
|
hidden from the file system. The Correctable Error Table (CET) keeps a history
|
|
(bit-vector) of per page correctable errors. If a correctable error happens
|
|
on the same page twice, an error is returned to the file system.
|
|
|
|
The CET starts from the opposite end of BBT with 1-bit per page. The CET is
|
|
initialized to all 1's. On the first correctable error the bit corresponding
|
|
to a page is reset. On an erase, all the bits of the corresponding block are
|
|
set. The CET can span across multiple blocks therefore a signature 'CET#'
|
|
where # is the block number is kept in the OOB area of the first page of a
|
|
CET block. Also, the total correctable error count is kept in the second
|
|
page OOB of the first CET block.
|
|
|
|
There is an in-memory correctable error table during runtime which is flushed
|
|
to the flash every 10 mins (CET_SYNC_FREQ).
|
|
|
|
Description:
|
|
when who what
|
|
----- --- ----
|
|
080519 sidc initial code
|
|
080910 sidc MLC support
|
|
*/
|
|
|
|
#include <linux/types.h>
|
|
#include <linux/mtd/mtd.h>
|
|
#include <linux/mtd/nand.h>
|
|
#include <linux/mtd/nand_ecc.h>
|
|
#include <linux/mtd/compatmac.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/workqueue.h>
|
|
#include "brcmnand_priv.h"
|
|
|
|
#ifdef CONFIG_MTD_BRCMNAND_CORRECTABLE_ERR_HANDLING
|
|
|
|
#define PRINTK(...)
|
|
|
|
#define BBT_SLC_PARTITION (1<<20)
|
|
#define BBT_MAX_BLKS_SLC 4
|
|
#define CET_START_BLK_SLC(x, y) (uint32_t) (((x) >> ((y)->bbt_erase_shift)) - (BBT_SLC_PARTITION/(y)->blockSize))
|
|
|
|
#define BBT_MLC_PARTITION (4<<20)
|
|
#define BBT_MAX_BLKS_MLC(x) (BBT_MLC_PARTITION >> ((x)->bbt_erase_shift))
|
|
#define CET_START_BLK_MLC(x, y, z) (uint32_t) (((x) >> ((y)->bbt_erase_shift)) - ((z)/(y)->blockSize))
|
|
|
|
#define CET_GOOD_BLK 0x00
|
|
#define CET_BAD_WEAR 0x01
|
|
#define CET_BBT_USE 0x02
|
|
#define CET_BAD_FACTORY 0x03
|
|
|
|
#define CET_SYNC_FREQ (10*60*HZ)
|
|
|
|
|
|
static char cet_pattern[] = {'C', 'E', 'T', 0};
|
|
static struct brcmnand_cet_descr cet_descr = {
|
|
.offs = 9,
|
|
.len = 4,
|
|
.pattern = cet_pattern
|
|
};
|
|
|
|
/*
|
|
* This also applies to Large Page SLC flashes with BCH-4 ECC.
|
|
* We don't support BCH-4 on Small Page SLCs because there are not
|
|
* enough free bytes for the OOB, but we don't enforce it,
|
|
* in order to allow page aggregation like in YAFFS2 on small page SLCs.
|
|
*/
|
|
static struct brcmnand_cet_descr cet_descr_mlc = {
|
|
.offs = 1,
|
|
.len = 4,
|
|
.pattern = cet_pattern
|
|
};
|
|
static void sync_cet(struct work_struct *work);
|
|
static int search_cet_blks(struct mtd_info *, struct brcmnand_cet_descr *, char);
|
|
extern char gClearCET;
|
|
|
|
/*
|
|
* Private: Read OOB area in RAW mode
|
|
*/
|
|
static inline int brcmnand_cet_read_oob(struct mtd_info *mtd, uint8_t *buf, loff_t offs)
|
|
{
|
|
struct mtd_oob_ops ops;
|
|
|
|
ops.mode = MTD_OOB_RAW;
|
|
ops.len = mtd->oobsize;
|
|
ops.ooblen = mtd->oobsize;
|
|
ops.datbuf = NULL;
|
|
ops.oobbuf = buf;
|
|
ops.ooboffs = 0;
|
|
|
|
return mtd->read_oob(mtd, offs, &ops);
|
|
}
|
|
|
|
/*
|
|
* Private: Write to the OOB area only
|
|
*/
|
|
static inline int brcmnand_cet_write_oob(struct mtd_info *mtd, uint8_t *buf, loff_t offs)
|
|
{
|
|
#if defined(CONFIG_MIPS_BRCM)
|
|
struct mtd_oob_ops ops;
|
|
|
|
ops.mode = MTD_OOB_PLACE;
|
|
ops.len = mtd->writesize;
|
|
ops.ooblen = mtd->oobsize;
|
|
ops.datbuf = NULL;
|
|
ops.oobbuf = buf;
|
|
ops.ooboffs = 0;
|
|
|
|
return mtd->write_oob(mtd, offs, &ops);
|
|
#else
|
|
struct mtd_oob_ops ops;
|
|
uint8_t databuf[mtd->writesize];
|
|
|
|
memset(databuf, 0xff, mtd->writesize);
|
|
ops.mode = MTD_OOB_PLACE;
|
|
ops.len = mtd->writesize;
|
|
ops.ooblen = mtd->oobsize;
|
|
ops.datbuf = databuf;
|
|
ops.oobbuf = buf;
|
|
ops.ooboffs = 0;
|
|
|
|
return mtd->write_oob(mtd, offs, &ops);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Private: write one page of data and OOB to flash
|
|
*/
|
|
static int brcmnand_cet_write(struct mtd_info *mtd, loff_t offs, size_t len,
|
|
uint8_t *buf, uint8_t *oob)
|
|
{
|
|
struct mtd_oob_ops ops;
|
|
int ret;
|
|
|
|
ops.mode = MTD_OOB_PLACE;
|
|
ops.ooboffs = 0;
|
|
ops.ooblen = mtd->oobsize;
|
|
ops.datbuf = buf;
|
|
ops.oobbuf = oob;
|
|
ops.len = len;
|
|
ret = mtd->write_oob(mtd, offs, &ops);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* bitcount - MIT Hackmem count implementation which is O(1)
|
|
* http://infolab.stanford.edu/~manku/bitcount/bitcount.html
|
|
* Counts the number of 1s in a given unsigned int n
|
|
*/
|
|
static inline int bitcount(uint32_t n)
|
|
{
|
|
uint32_t tmp;
|
|
tmp = n - ((n >> 1) & 033333333333)
|
|
- ((n >> 2) & 011111111111);
|
|
return ((tmp + (tmp >> 3)) & 030707070707) % 63;
|
|
}
|
|
|
|
/*
|
|
* Private debug function: Print OOBs
|
|
*/
|
|
static void cet_printpg_oob(struct mtd_info *mtd, struct brcmnand_cet_descr *cet, int count)
|
|
{
|
|
uint8_t oobbuf[mtd->oobsize];
|
|
loff_t offs;
|
|
int i, gdebug = 0;
|
|
struct brcmnand_chip *this = (struct brcmnand_chip *) mtd->priv;
|
|
|
|
offs = ((loff_t) cet->startblk) << this->bbt_erase_shift;
|
|
if (gdebug) {
|
|
printk(KERN_INFO "%s: %x\n", __FUNCTION__, (unsigned int) offs);
|
|
}
|
|
for (i = 0; i < count; i++) {
|
|
memset(oobbuf, 0, mtd->oobsize);
|
|
if (brcmnand_cet_read_oob(mtd, oobbuf, offs)) {
|
|
return;
|
|
}
|
|
print_oobbuf((const char *) oobbuf, mtd->oobsize);
|
|
offs = offs + cet->sign*this->pageSize;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Private debug function: Prints first OOB area of all blocks <block#, page0>
|
|
*/
|
|
static void cet_printblk_oob(struct mtd_info *mtd, struct brcmnand_cet_descr *cet)
|
|
{
|
|
uint8_t *oobbuf;
|
|
loff_t offs;
|
|
int i;
|
|
struct brcmnand_chip *this = (struct brcmnand_chip *) mtd->priv;
|
|
|
|
if((oobbuf = (uint8_t *) vmalloc(sizeof(uint8_t)*mtd->oobsize)) == NULL) {
|
|
printk(KERN_ERR "brcmnandCET: %s vmalloc failed\n", __FUNCTION__);
|
|
return;
|
|
}
|
|
for (i = 0; i < this->bbt_td->maxblocks; i++) {
|
|
memset(oobbuf, 0, mtd->oobsize);
|
|
offs = ((loff_t) cet->startblk+((cet->sign)*i)) << this->bbt_erase_shift;
|
|
if (brcmnand_cet_read_oob(mtd, oobbuf, offs)) {
|
|
vfree(oobbuf);
|
|
return;
|
|
}
|
|
print_oobbuf((const char *) oobbuf, mtd->oobsize);
|
|
}
|
|
vfree(oobbuf);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Private debug function: erase all blocks belonging to CET
|
|
* Use for testing purposes only
|
|
*/
|
|
static void cet_eraseall(struct mtd_info *mtd, struct brcmnand_cet_descr *cet)
|
|
{
|
|
int i, ret;
|
|
loff_t from;
|
|
struct erase_info einfo;
|
|
struct brcmnand_chip *this = (struct brcmnand_chip *) mtd->priv;
|
|
int gdebug = 0;
|
|
|
|
for (i = 0; i < cet->numblks; i++) {
|
|
if (cet->memtbl[i].blk != -1) {
|
|
from = (uint64_t) cet->memtbl[i].blk << this->bbt_erase_shift;
|
|
if (unlikely(gdebug)) {
|
|
printk(KERN_INFO "DEBUG -> Erasing blk %x\n", cet->memtbl[i].blk);
|
|
}
|
|
memset(&einfo, 0, sizeof(einfo));
|
|
einfo.mtd = mtd;
|
|
einfo.addr = from;
|
|
einfo.len = mtd->erasesize;
|
|
ret = this->erase_bbt(mtd, &einfo, 1, 1);
|
|
if (unlikely(ret < 0)) {
|
|
printk(KERN_ERR "brcmnandCET: %s Error erasing block %llx\n", __FUNCTION__, einfo.addr);
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Private: Check if a block is factory marked bad block
|
|
* Derived from brcmnand_isbad_bbt()
|
|
* Return values:
|
|
* 0x00 Good block
|
|
* 0x01 Marked bad due to wear
|
|
* 0x02 Reserved for BBT
|
|
* 0x03 Factory marked bad
|
|
*/
|
|
static inline int check_badblk(struct mtd_info *mtd, loff_t offs)
|
|
{
|
|
struct brcmnand_chip *this = (struct brcmnand_chip *) mtd->priv;
|
|
uint32_t blk;
|
|
int res;
|
|
|
|
blk = (uint32_t) (offs >> (this->bbt_erase_shift-1));
|
|
res = (this->bbt[blk >> 3] >> blk & 0x06) & 0x03;
|
|
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* Check for CET pattern in the OOB buffer
|
|
* return the blk number present in the CET
|
|
*/
|
|
static inline int found_cet_pattern(struct brcmnand_chip *this, uint8_t *buf)
|
|
{
|
|
struct brcmnand_cet_descr *cet = this->cet;
|
|
int i;
|
|
|
|
for (i = 0; i < cet->len-1; i++) {
|
|
if (buf[cet->offs + i] != cet_pattern[i]) {
|
|
return -1;
|
|
}
|
|
}
|
|
return (int) buf[cet->offs + cet->len-1];
|
|
}
|
|
|
|
/*
|
|
* Check for BBT/Mirror BBT pattern
|
|
* Similar to the implementation in brcmnand_bbt.c
|
|
*/
|
|
static inline int found_bbt_pattern(uint8_t *buf, struct nand_bbt_descr *bd)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < bd->len; i++) {
|
|
if (buf[bd->offs+i] != bd->pattern[i]) {
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Check OOB area to test if the block is erased
|
|
*/
|
|
static inline int cet_iserased(struct mtd_info *mtd, uint8_t *oobbuf)
|
|
{
|
|
struct brcmnand_chip *this = (struct brcmnand_chip *) mtd->priv;
|
|
struct nand_ecclayout *oobinfo = this->ecclayout;
|
|
int i;
|
|
|
|
for (i = 0; i < oobinfo->eccbytes; i++) {
|
|
if (oobbuf[oobinfo->eccpos[i]] != 0xff) {
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Process kernel command line showcet
|
|
* If the CET is loaded, display which blocks of flash the CET is in
|
|
*/
|
|
static inline void cmdline_showcet(struct mtd_info *mtd, struct brcmnand_cet_descr *cet)
|
|
{
|
|
int i;
|
|
loff_t offs;
|
|
uint8_t oobbuf[mtd->oobsize];
|
|
struct brcmnand_chip *this = (struct brcmnand_chip *) mtd->priv;
|
|
|
|
if (cet->flags == BRCMNAND_CET_DISABLED) {
|
|
printk(KERN_INFO "brcmnandCET: Disabled\n");
|
|
return;
|
|
}
|
|
printk(KERN_INFO "brcmnandCET: Correctable error count is 0x%x\n", cet->cerr_count);
|
|
if (cet->flags == BRCMNAND_CET_LAZY) {
|
|
printk(KERN_INFO "brcmnandCET: Deferred until next correctable error\n");
|
|
return;
|
|
}
|
|
printk(KERN_INFO "brcmnandCET: Displaying first OOB area of all CET blocks ...\n");
|
|
for (i = 0; i < cet->numblks; i++) {
|
|
if (cet->memtbl[i].blk == -1)
|
|
continue;
|
|
offs = ((loff_t) cet->memtbl[i].blk) << this->bbt_erase_shift;
|
|
printk(KERN_INFO "brcmnandCET: Block[%d] @ %x\n", i, (unsigned int) offs);
|
|
if (brcmnand_cet_read_oob(mtd, oobbuf, offs)) {
|
|
return;
|
|
}
|
|
print_oobbuf((const char *) oobbuf, mtd->oobsize);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Reset CET to all 0xffs
|
|
*/
|
|
static inline int cmdline_resetcet(struct mtd_info *mtd, struct brcmnand_cet_descr *cet)
|
|
{
|
|
int i;
|
|
|
|
cet_eraseall(mtd, cet);
|
|
for (i = 0; i < cet->numblks; i++) {
|
|
cet->memtbl[i].isdirty = 0;
|
|
cet->memtbl[i].blk = -1;
|
|
cet->memtbl[i].bitvec = NULL;
|
|
}
|
|
printk(KERN_INFO "brcmnandCET: Recreating ... \n");
|
|
|
|
return search_cet_blks(mtd, cet, 0);
|
|
}
|
|
|
|
/*
|
|
* Create a CET pattern in the OOB area.
|
|
*/
|
|
static int create_cet_blks(struct mtd_info *mtd, struct brcmnand_cet_descr *cet)
|
|
{
|
|
int i, j, ret, gdebug = 0;
|
|
loff_t from;
|
|
struct nand_bbt_descr *td, *md;
|
|
struct erase_info einfo;
|
|
struct brcmnand_chip *this = (struct brcmnand_chip *) mtd->priv;
|
|
uint8_t oobbuf[mtd->oobsize];
|
|
char *oobptr, count = 0;
|
|
|
|
td = this->bbt_td;
|
|
md = this->bbt_md;
|
|
if (unlikely(gdebug)) {
|
|
printk(KERN_INFO "brcmnandCET: Inside %s\n", __FUNCTION__);
|
|
}
|
|
for (i = 0; i < td->maxblocks; i++) {
|
|
from = ((loff_t) cet->startblk+i*cet->sign) << this->bbt_erase_shift;
|
|
/* Skip if bad block */
|
|
ret = check_badblk(mtd, from);
|
|
if (ret == CET_BAD_FACTORY || ret == CET_BAD_WEAR) {
|
|
continue;
|
|
}
|
|
memset(oobbuf, 0, mtd->oobsize);
|
|
if (brcmnand_cet_read_oob(mtd, oobbuf, from)) {
|
|
printk(KERN_INFO "brcmnandCET: %s %d Error reading OOB\n", __FUNCTION__, __LINE__);
|
|
return -1;
|
|
}
|
|
/* If BBT/MBT block found we have no space left */
|
|
if (found_bbt_pattern(oobbuf, td) || found_bbt_pattern(oobbuf, md)) {
|
|
printk(KERN_INFO "brcmnandCET: %s blk %x is BBT\n", __FUNCTION__, cet->startblk + i*cet->sign);
|
|
return -1;
|
|
}
|
|
//if (!cet_iserased(mtd, oobbuf)) {
|
|
if (unlikely(gdebug)) {
|
|
printk(KERN_INFO "brcmnandCET: block %x is erased\n", cet->startblk+i*cet->sign);
|
|
}
|
|
/* Erase */
|
|
memset(&einfo, 0, sizeof(einfo));
|
|
einfo.mtd = mtd;
|
|
einfo.addr = from;
|
|
einfo.len = mtd->erasesize;
|
|
ret = this->erase_bbt(mtd, &einfo, 1, 1);
|
|
if (unlikely(ret < 0)) {
|
|
printk(KERN_ERR "brcmnandCET: %s Error erasing block %x\n", __FUNCTION__, cet->startblk+i*cet->sign);
|
|
return -1;
|
|
}
|
|
//}
|
|
/* Write 'CET#' pattern to the OOB area */
|
|
memset(oobbuf, 0xff, mtd->oobsize);
|
|
if (unlikely(gdebug)) {
|
|
printk(KERN_INFO "brcmnandCET: writing CET %d to OOB area\n", (int)count);
|
|
}
|
|
oobptr = (char *) oobbuf;
|
|
for (j = 0; j < cet->len-1; j++) {
|
|
oobptr[cet->offs + j] = cet->pattern[j];
|
|
}
|
|
oobptr[cet->offs + j] = count;
|
|
if (brcmnand_cet_write_oob(mtd, oobbuf, from)) {
|
|
printk(KERN_ERR "brcmnandCET: %s Error writing to OOB# %x\n", __FUNCTION__, (unsigned int)from);
|
|
return -1;
|
|
}
|
|
/* If this is the first CET block, init the correctable erase count to 0 */
|
|
if (count == 0) {
|
|
memset(oobbuf, 0xff, mtd->oobsize);
|
|
oobptr = (char *) oobbuf;
|
|
*((uint32_t *) (oobptr + cet->offs)) = 0x00000000;
|
|
from += this->pageSize;
|
|
if (unlikely(gdebug)) {
|
|
printk(KERN_INFO "DEBUG -> 0: from = %x\n", (unsigned int) from);
|
|
printk(KERN_INFO "brcmnandCET: Writing cer_count to page %x\n", (unsigned int) from);
|
|
}
|
|
if (brcmnand_cet_write_oob(mtd, oobbuf, from)) {
|
|
printk(KERN_INFO "brcmnandCET: %s Error writing to OOB# %x\n", __FUNCTION__, (unsigned int)from);
|
|
return -1;
|
|
}
|
|
}
|
|
count++;
|
|
if (((int)count) == cet->numblks) {
|
|
return 0;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Search for CET blocks
|
|
* force => 1 Force creation of tables, do not defer for later
|
|
*/
|
|
static int search_cet_blks(struct mtd_info *mtd, struct brcmnand_cet_descr *cet, char force)
|
|
{
|
|
int i, count = 0, ret;
|
|
loff_t from;
|
|
struct nand_bbt_descr *td, *md;
|
|
uint8_t oobbuf[mtd->oobsize];
|
|
struct brcmnand_chip *this = (struct brcmnand_chip *) mtd->priv;
|
|
int gdebug = 0;
|
|
|
|
td = this->bbt_td;
|
|
md = this->bbt_md;
|
|
if (unlikely(gdebug)) {
|
|
printk(KERN_INFO "DEBUG -> Inside search_cet_blks\n");
|
|
}
|
|
for (i = 0; i < td->maxblocks; i++) {
|
|
from = ((loff_t) cet->startblk+i*cet->sign) << this->bbt_erase_shift;
|
|
/* Skip if bad block */
|
|
ret = check_badblk(mtd, from);
|
|
if (ret == CET_BAD_FACTORY || ret == CET_BAD_WEAR) {
|
|
continue;
|
|
}
|
|
/* Read the OOB area of the first page of the block */
|
|
memset(oobbuf, 0, mtd->oobsize);
|
|
if (brcmnand_cet_read_oob(mtd, oobbuf, from)) {
|
|
printk(KERN_INFO "brcmnandCET: %s %d Error reading OOB\n", __FUNCTION__, __LINE__);
|
|
cet->flags = BRCMNAND_CET_DISABLED;
|
|
return -1;
|
|
}
|
|
if (unlikely(gdebug)) {
|
|
print_oobbuf(oobbuf, mtd->oobsize);
|
|
}
|
|
/* Return -1 if BBT/MBT block => no space left for CET */
|
|
if (found_bbt_pattern(oobbuf, td) || found_bbt_pattern(oobbuf, md)) {
|
|
printk(KERN_INFO "brcmnandCET: %s blk %x is BBT\n", __FUNCTION__, cet->startblk + i*cet->sign);
|
|
cet->flags = BRCMNAND_CET_DISABLED;
|
|
return -1;
|
|
}
|
|
/* Check for CET pattern */
|
|
ret = found_cet_pattern(this, oobbuf);
|
|
if (unlikely(gdebug)) {
|
|
print_oobbuf((const char *) oobbuf, mtd->oobsize);
|
|
}
|
|
if (ret < 0 || ret >= cet->numblks) {
|
|
/* No CET pattern found due to
|
|
1. first time being booted => normal so create
|
|
2. Did not find CET pattern when we're supposed to
|
|
error => recreate, in either case we call create_cet_blks();
|
|
3. Found an incorrect > cet->numblks count => error => recreate
|
|
*/
|
|
printk(KERN_INFO "brcmnandCET: Did not find CET, recreating\n");
|
|
if (create_cet_blks(mtd, cet) < 0) {
|
|
cet->flags = BRCMNAND_CET_DISABLED;
|
|
return ret;
|
|
}
|
|
cet->flags = BRCMNAND_CET_LAZY;
|
|
return 0;
|
|
}
|
|
/* Found CET pattern */
|
|
if (unlikely(gdebug)) {
|
|
printk(KERN_INFO "brcmnandCET: Found CET block#%d\n", count);
|
|
}
|
|
/* If this is the first block do some extra stuff ... */
|
|
if (count == 0) {
|
|
/* The global cerr_count is in the 2nd page's OOB area */
|
|
from += this->pageSize;
|
|
if (brcmnand_cet_read_oob(mtd, oobbuf, from)) {
|
|
printk(KERN_ERR "brcmnandCET: %s %d Error reading OOB\n", __FUNCTION__, __LINE__);
|
|
cet->flags = BRCMNAND_CET_DISABLED;
|
|
return -1;
|
|
}
|
|
cet->cerr_count = *((uint32_t *) (oobbuf + cet->offs));
|
|
/* TODO - Fix this -> recreate */
|
|
if (cet->cerr_count == 0xffffffff) {
|
|
/* Reset it to 0 */
|
|
cet->cerr_count = 0;
|
|
cet->memtbl[0].isdirty = 1;
|
|
}
|
|
if (unlikely(gdebug)) {
|
|
printk(KERN_INFO "brcmnandCET: correctable error count = %x\n", cet->cerr_count);
|
|
}
|
|
/* If force then go thru all CET blks even if cerr_count is 0 */
|
|
if (!force) {
|
|
if (cet->cerr_count == 0) {
|
|
cet->flags = BRCMNAND_CET_LAZY;
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
cet->memtbl[ret].blk = cet->startblk + i*cet->sign;
|
|
count++;
|
|
#if 0
|
|
printk(KERN_INFO "DEBUG -> count = %d, nblks = %d blk = %d\n", count, cet->numblks, cet->memtbl[ret].blk);
|
|
#endif
|
|
if (count == cet->numblks) {
|
|
cet->flags = BRCMNAND_CET_LOADED;
|
|
return 0;
|
|
}
|
|
}
|
|
/* This should never happen */
|
|
cet->flags = BRCMNAND_CET_DISABLED;
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* flush pending in-memory CET data to the flash. Called as part of a
|
|
* callback function from workqueue that is invoked every SYNC_FREQ seconds
|
|
*/
|
|
static int flush_memcet(struct mtd_info *mtd)
|
|
{
|
|
struct brcmnand_chip *this = (struct brcmnand_chip *) mtd->priv;
|
|
struct brcmnand_cet_descr *cet = this->cet;
|
|
struct erase_info einfo;
|
|
int i, j, k = 0, ret, pg_idx = 0, gdebug = 0;
|
|
uint8_t oobbuf[mtd->oobsize];
|
|
loff_t from, to;
|
|
char *oobptr, count = 0;
|
|
|
|
/* If chip is locked reset timer for a later time */
|
|
if (spin_is_locked(&this->ctrl->chip_lock)) {
|
|
printk(KERN_INFO "brcmnandCET: flash locked reseting timer\n");
|
|
return -1;
|
|
}
|
|
if (unlikely(gdebug)) {
|
|
printk(KERN_INFO "brcmnandCET: Inside %s\n", __FUNCTION__);
|
|
}
|
|
/* For each in-mem dirty block, sync with flash
|
|
sync => erase -> write */
|
|
for (i = 0; i < cet->numblks; i++) {
|
|
if (cet->memtbl[i].isdirty && cet->memtbl[i].blk != -1) {
|
|
/* Erase */
|
|
from = ((loff_t) cet->memtbl[i].blk) << this->bbt_erase_shift;
|
|
to = from;
|
|
memset(&einfo, 0, sizeof(einfo));
|
|
einfo.mtd = mtd;
|
|
einfo.addr = from;
|
|
einfo.len = mtd->erasesize;
|
|
ret = this->erase_bbt(mtd, &einfo, 1, 1);
|
|
if (unlikely(ret < 0)) {
|
|
printk(KERN_ERR "brcmnandCET: %s Error erasing block %x\n", __FUNCTION__, cet->memtbl[i].blk);
|
|
return -1;
|
|
}
|
|
if (unlikely(gdebug)) {
|
|
printk(KERN_INFO "DEBUG -> brcmnandCET: After erasing ...\n");
|
|
cet_printpg_oob(mtd, cet, 3);
|
|
}
|
|
pg_idx = 0;
|
|
/* Write pages i.e., flush */
|
|
for (j = 0; j < mtd->erasesize/this->pageSize; j++) {
|
|
memset(oobbuf, 0xff, mtd->oobsize);
|
|
oobptr = (char *) oobbuf;
|
|
if (j == 0) { /* Write CET# */
|
|
for (k = 0; k < cet->len-1; k++) {
|
|
oobptr[cet->offs + k] = cet->pattern[k];
|
|
}
|
|
oobptr[cet->offs + k] = count;
|
|
if (unlikely(gdebug)) {
|
|
print_oobbuf((const char *) oobbuf, mtd->oobsize);
|
|
}
|
|
}
|
|
if (j == 1 && count == 0) { /* Write cerr_count */
|
|
*((uint32_t *) (oobptr + cet->offs)) = cet->cerr_count;
|
|
}
|
|
ret = brcmnand_cet_write(mtd, to, (size_t) this->pageSize, cet->memtbl[i].bitvec+pg_idx, oobbuf);
|
|
if (ret < 0) {
|
|
printk(KERN_ERR "brcmnandCET: %s Error writing to page %x\n", __FUNCTION__, (unsigned int) to);
|
|
return ret;
|
|
}
|
|
to += mtd->writesize;
|
|
pg_idx += mtd->writesize;
|
|
}
|
|
cet->memtbl[i].isdirty = 0;
|
|
if (unlikely(gdebug)) {
|
|
printk(KERN_INFO "brcmnandCET: flushing CET block %d\n", i);
|
|
}
|
|
}
|
|
count++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The callback function for kernel workq task
|
|
* Checks if there is any work to be done, if so calls flush_memcet
|
|
* Resets timer before returning in any case
|
|
*/
|
|
static void sync_cet(struct work_struct *work)
|
|
{
|
|
int i;
|
|
struct delayed_work *d = container_of(work, struct delayed_work, work);
|
|
struct brcmnand_cet_descr *cet = container_of(d, struct brcmnand_cet_descr, cet_flush);
|
|
struct mtd_info *mtd = cet->mtd;
|
|
|
|
/* Check if all blocks are clean */
|
|
for (i = 0; i < cet->numblks; i++) {
|
|
if (cet->memtbl[i].isdirty) break;
|
|
}
|
|
/* Avoid function call cost if there are no dirty blocks */
|
|
if (i != cet->numblks)
|
|
flush_memcet(mtd);
|
|
schedule_delayed_work(&cet->cet_flush, CET_SYNC_FREQ);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* brcmnand_create_cet - Create a CET (Correctable Error Table)
|
|
* @param mtd MTD device structure
|
|
*
|
|
* Called during mtd init. Checks if a CET already exists or needs
|
|
* to be created. Initializes in-memory CET.
|
|
*/
|
|
int brcmnand_create_cet(struct mtd_info *mtd)
|
|
{
|
|
struct brcmnand_chip *this = (struct brcmnand_chip *) mtd->priv;
|
|
struct brcmnand_cet_descr *cet;
|
|
int gdebug = 0, i, ret, rem;
|
|
uint64_t tmpdiv;
|
|
|
|
if (unlikely(gdebug)) {
|
|
printk(KERN_INFO "brcmnandCET: Creating correctable error table ...\n");
|
|
}
|
|
|
|
if (NAND_IS_MLC(this) || /* MLC flashes */
|
|
/* SLC w/ BCH-n; We don't check for pageSize, and let it be */
|
|
(this->ecclevel >= BRCMNAND_ECC_BCH_1 && this->ecclevel <= BRCMNAND_ECC_BCH_12))
|
|
{
|
|
this->cet = cet = &cet_descr_mlc;
|
|
if (gdebug) printk("%s: CET = cet_desc_mlc\n", __FUNCTION__);
|
|
}
|
|
|
|
else {
|
|
this->cet = cet = &cet_descr;
|
|
if (gdebug) printk("%s: CET = cet_descr\n", __FUNCTION__);
|
|
}
|
|
cet->flags = 0x00;
|
|
/* Check that BBT table and mirror exist */
|
|
if (unlikely(!this->bbt_td && !this->bbt_md)) {
|
|
printk(KERN_INFO "brcmnandCET: BBT tables not found, disabling\n");
|
|
cet->flags = BRCMNAND_CET_DISABLED;
|
|
return -1;
|
|
}
|
|
/* Per chip not supported. We do not use per chip BBT, but this
|
|
is just a safety net */
|
|
if (unlikely(this->bbt_td->options & NAND_BBT_PERCHIP)) {
|
|
printk(KERN_INFO "brcmnandCET: per chip CET not supported, disabling\n");
|
|
cet->flags = BRCMNAND_CET_DISABLED;
|
|
return -1;
|
|
}
|
|
/* Calculate max blocks based on 1-bit per page */
|
|
tmpdiv = this->mtdSize;
|
|
do_div(tmpdiv, this->pageSize);
|
|
do_div(tmpdiv, (8*this->blockSize));
|
|
cet->numblks = (uint32_t) tmpdiv;
|
|
//cet->numblks = (this->mtdSize/this->pageSize)/(8*this->blockSize);
|
|
tmpdiv = this->mtdSize;
|
|
do_div(tmpdiv, this->pageSize);
|
|
do_div(tmpdiv, 8);
|
|
rem = do_div(tmpdiv, this->blockSize);
|
|
//if (((this->mtdSize/this->pageSize)/8)%this->blockSize) {
|
|
if (rem) {
|
|
cet->numblks++;
|
|
}
|
|
/* Allocate twice the size in case we have bad blocks */
|
|
cet->maxblks = cet->numblks*2;
|
|
/* Determine the direction of CET based on reverse direction of BBT */
|
|
cet->sign = (this->bbt_td->options & NAND_BBT_LASTBLOCK) ? 1 : -1;
|
|
/* For flash size <= 512MB BBT and CET share the last 1MB
|
|
for flash size > 512MB CET is at the 512th MB of flash */
|
|
#if 0
|
|
if (NAND_IS_MLC(this)) {
|
|
} else {
|
|
if (this->mtdSize < (1<<29)) {
|
|
if (cet->maxblks + BBT_MAX_BLKS > get_bbt_partition(this)/this->blockSize) {
|
|
printk(KERN_INFO "brcmnandCET: Not enough space to store CET, disabling CET\n");
|
|
cet->flags = BRCMNAND_CET_DISABLED;
|
|
return -1;
|
|
}
|
|
if (cet->sign) {
|
|
cet->startblk = CET_START_BLK(this->mtdSize, this);
|
|
} else {
|
|
cet->startblk = (uint32_t) (this->mtdSize >> this->bbt_erase_shift)-1;
|
|
}
|
|
|
|
} else {
|
|
if (cet->maxblks > (get_bbt_partition(this))/this->blockSize) {
|
|
printk(KERN_INFO "brcmnandCET: Not enough space to store CET, disabling CET\n");
|
|
cet->flags = BRCMNAND_CET_DISABLED;
|
|
return -1;
|
|
}
|
|
cet->startblk = CET_START_BLK((1<<29), this);
|
|
}
|
|
}
|
|
#endif
|
|
if (NAND_IS_MLC(this)) {
|
|
if (this->mtdSize < (1<<29)) {
|
|
if (cet->maxblks + BBT_MAX_BLKS_MLC(this) > BBT_MLC_PARTITION/this->blockSize) {
|
|
printk(KERN_INFO "brcmnandCET: Not enough space to store CET, disabling CET\n");
|
|
cet->flags = BRCMNAND_CET_DISABLED;
|
|
return -1;
|
|
}
|
|
/* Reverse direction of BBT */
|
|
if (cet->sign) {
|
|
cet->startblk = CET_START_BLK_MLC(this->mtdSize, this, BBT_MLC_PARTITION);
|
|
} else {
|
|
cet->startblk = (uint32_t) (this->mtdSize >> this->bbt_erase_shift)-1;
|
|
}
|
|
} else {
|
|
/* 512th MB used by CET */
|
|
if (cet->maxblks > (1<<29)/this->blockSize) {
|
|
printk(KERN_INFO "brcmnandCET: Not enough space to store CET, disabling CET\n");
|
|
cet->flags = BRCMNAND_CET_DISABLED;
|
|
return -1;
|
|
}
|
|
#if defined(CONFIG_MIPS_BRCM)
|
|
cet->startblk = CET_START_BLK_SLC(this->mtdSize, this);
|
|
#else
|
|
cet->startblk = CET_START_BLK_MLC((1<<29), this, (1<<20));
|
|
#endif
|
|
}
|
|
} else {
|
|
if (this->mtdSize < (1<<29)) {
|
|
if (cet->maxblks + BBT_MAX_BLKS_SLC > BBT_SLC_PARTITION/this->blockSize) {
|
|
printk(KERN_INFO "brcmnandCET: Not enough space to store CET, disabling CET\n");
|
|
cet->flags = BRCMNAND_CET_DISABLED;
|
|
return -1;
|
|
}
|
|
/* Reverse direction of BBT */
|
|
if (cet->sign) {
|
|
cet->startblk = CET_START_BLK_SLC(this->mtdSize, this);
|
|
} else {
|
|
cet->startblk = (uint32_t) (this->mtdSize >> this->bbt_erase_shift)-1;
|
|
}
|
|
} else {
|
|
/* 512th MB used by CET */
|
|
if (cet->maxblks > BBT_SLC_PARTITION/this->blockSize) {
|
|
printk(KERN_INFO "brcmnandCET: Not enough space to store CET, disabling CET\n");
|
|
cet->flags = BRCMNAND_CET_DISABLED;
|
|
return -1;
|
|
}
|
|
#if defined(CONFIG_MIPS_BRCM)
|
|
cet->startblk = CET_START_BLK_SLC(this->mtdSize, this);
|
|
#else
|
|
cet->startblk = CET_START_BLK_SLC((1<<29), this);
|
|
#endif
|
|
}
|
|
}
|
|
if (gdebug) {
|
|
printk(KERN_INFO "brcmnandCET: start blk = %x, numblks = %x\n", cet->startblk, cet->numblks);
|
|
}
|
|
|
|
/* Init memory based CET */
|
|
cet->memtbl = (struct brcmnand_cet_memtable *) vmalloc(cet->numblks*sizeof(struct brcmnand_cet_memtable));
|
|
if (cet->memtbl == NULL) {
|
|
printk(KERN_ERR "brcmnandCET: vmalloc failed %s\n", __FUNCTION__);
|
|
cet->flags = BRCMNAND_CET_DISABLED;
|
|
return -1;
|
|
}
|
|
for (i = 0; i < cet->numblks; i++) {
|
|
cet->memtbl[i].isdirty = 0;
|
|
cet->memtbl[i].blk = -1;
|
|
cet->memtbl[i].bitvec = NULL;
|
|
}
|
|
ret = search_cet_blks(mtd, cet, 0);
|
|
if (unlikely(gClearCET == 1)) { /* kernel cmdline showcet */
|
|
cmdline_showcet(mtd, cet);
|
|
}
|
|
if (unlikely(gClearCET == 2)) { /* kernel cmdline resetcet */
|
|
if (cmdline_resetcet(mtd, cet) < 0) {
|
|
cet->flags = BRCMNAND_CET_DISABLED;
|
|
return -1;
|
|
}
|
|
}
|
|
if (unlikely(gClearCET == 3)) { /* kernel cmdline disable */
|
|
cet->flags = BRCMNAND_CET_DISABLED;
|
|
ret = -1;
|
|
}
|
|
//cet_printpg_oob(mtd, cet, 3);
|
|
switch(cet->flags) {
|
|
case BRCMNAND_CET_DISABLED:
|
|
printk(KERN_INFO "brcmnandCET: Status -> Disabled\n");
|
|
break;
|
|
case BRCMNAND_CET_LAZY:
|
|
printk(KERN_INFO "brcmnandCET: Status -> Deferred\n");
|
|
break;
|
|
case BRCMNAND_CET_LOADED:
|
|
printk(KERN_INFO "brcmnandCET: Status -> Loaded\n");
|
|
break;
|
|
default:
|
|
printk(KERN_INFO "brcmnandCET: Status -> Fatal error CET disabled\n");
|
|
cet->flags = BRCMNAND_CET_DISABLED;
|
|
break;
|
|
}
|
|
if (unlikely(gdebug)) {
|
|
cet_printpg_oob(mtd, cet, 3);
|
|
cet_printblk_oob(mtd, cet);
|
|
}
|
|
|
|
INIT_DELAYED_WORK(&cet->cet_flush, sync_cet);
|
|
cet->mtd = mtd;
|
|
schedule_delayed_work(&cet->cet_flush, CET_SYNC_FREQ);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* brcmnand_cet_erasecallback: Called every time there is an erase due to
|
|
* userspace activity
|
|
*
|
|
* @param mtd MTD device structure
|
|
* @param addr Address of the block that was erased by fs/userspace
|
|
*
|
|
* Assumption: cet->flag != BRCMNAND_CET_DISABLED || BRCMNAND_CET_LAZY
|
|
* is checked by the caller
|
|
* flag == BRCMNAND_CET_DISABLED => CET not being used
|
|
* flag == BRCMNAND_CET_LAZY => correctable error count is 0 so need of callback
|
|
*
|
|
* TODO Optimize, add comments, check all return paths
|
|
*/
|
|
int brcmnand_cet_erasecallback(struct mtd_info *mtd, u_int32_t addr)
|
|
{
|
|
struct brcmnand_chip *this = (struct brcmnand_chip *) mtd->priv;
|
|
struct brcmnand_cet_descr *cet = this->cet;
|
|
uint32_t page = 0;
|
|
int blkbegin, blk, i, ret, retlen, pg_idx = 0, numzeros = 0, byte, gdebug = 0;
|
|
uint32_t *ptr;
|
|
unsigned int pos;
|
|
loff_t origaddr = addr;
|
|
|
|
/* Find out which entry in the memtbl does the addr map to */
|
|
page = (uint32_t) (addr >> this->page_shift);
|
|
blk = page/(this->blockSize<<3);
|
|
if (unlikely(cet->memtbl[blk].blk == -1)) {
|
|
printk(KERN_INFO "brcmnandCET: %s invalid block# in CET\n", __FUNCTION__);
|
|
return -1;
|
|
}
|
|
blkbegin = cet->memtbl[blk].blk;
|
|
/* Start page of the block */
|
|
addr = ((loff_t) blkbegin) << this->bbt_erase_shift;
|
|
if (cet->memtbl[blk].bitvec == NULL) {
|
|
if (gdebug) {
|
|
printk(KERN_INFO "DEBUG -> brcmnandCET: bitvec is null, reloading\n");
|
|
}
|
|
cet->memtbl[blk].bitvec = (char *) vmalloc(this->blockSize);
|
|
if (cet->memtbl[blk].bitvec == NULL) {
|
|
printk(KERN_INFO "brcmnandCET: %s vmalloc failed\n", __FUNCTION__);
|
|
return -1;
|
|
}
|
|
memset(cet->memtbl[blk].bitvec, 0xff, sizeof(this->blockSize));
|
|
/* Read an entire block */
|
|
for (i = 0; i < mtd->erasesize/mtd->writesize; i++) {
|
|
if (gdebug) {
|
|
printk(KERN_INFO "DEBUG -> brcmnandCET: Reading page %d\n", i);
|
|
}
|
|
ret = mtd->read(mtd, addr, this->pageSize, &retlen, (uint8_t *) (cet->memtbl[blk].bitvec+pg_idx));
|
|
if (ret < 0 || (retlen != this->pageSize)) {
|
|
vfree(cet->memtbl[blk].bitvec);
|
|
return -1;
|
|
}
|
|
pg_idx += mtd->writesize;
|
|
addr += this->pageSize;
|
|
}
|
|
}
|
|
page = (uint32_t) ((origaddr & (~(mtd->erasesize-1))) >> this->page_shift);
|
|
pos = page % (this->blockSize<<3);
|
|
byte = pos / (1<<3);
|
|
ptr = (uint32_t *) ((char *)cet->memtbl[blk].bitvec+byte);
|
|
/* numpages/8bits per byte/4byte per uint32 */
|
|
for (i = 0; i < ((mtd->erasesize/mtd->writesize)>>3)>>2; i++) {
|
|
/* Count the number of 0s for in the bitvec */
|
|
numzeros += bitcount(~ptr[i]);
|
|
}
|
|
if (likely(numzeros == 0)) {
|
|
if (gdebug) {
|
|
printk(KERN_INFO "DEBUG -> brcmnandCET: returning 0 numzeros = 0\n");
|
|
}
|
|
return 0;
|
|
}
|
|
if (cet->cerr_count < numzeros) {
|
|
if (gdebug) {
|
|
printk(KERN_ERR "brcmnandCET: Erroneous correctable error count");
|
|
}
|
|
return -1;
|
|
}
|
|
cet->cerr_count -= numzeros;
|
|
/* Make bits corresponding to this block all 1s */
|
|
memset(cet->memtbl[blk].bitvec+byte, 0xff, (mtd->erasesize/mtd->writesize)>>3);
|
|
cet->memtbl[blk].isdirty = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* brcmnand_cet_update: Called every time a single correctable error is
|
|
* encountered.
|
|
* @param mtd MTD device structure
|
|
* @param from Page address at which correctable error occured
|
|
* @param status Return status
|
|
* 1 => This page had a correctable errror in past,
|
|
* therefore, return correctable error to filesystem
|
|
* 0 => First occurence of a correctable error for
|
|
* this page. return a success to the filesystem
|
|
*
|
|
* Check the in memory CET bitvector to see if this page (loff_t from)
|
|
* had a correctable error in past, if not set this page's bit to '0'
|
|
* in the bitvector.
|
|
*
|
|
*/
|
|
int brcmnand_cet_update(struct mtd_info *mtd, loff_t from, int *status)
|
|
{
|
|
struct brcmnand_chip *this = (struct brcmnand_chip *) mtd->priv;
|
|
struct brcmnand_cet_descr *cet = this->cet;
|
|
int gdebug = 0, ret, blk, byte, bit, retlen = 0, blkbegin, i;
|
|
uint32_t page = 0;
|
|
unsigned int pg_idx = 0, pos = 0;
|
|
unsigned char c, mask;
|
|
|
|
if (gdebug) {
|
|
printk(KERN_INFO "DEBUG -> brcmnandCET: Inside %s\n", __FUNCTION__);
|
|
}
|
|
if (cet->flags == BRCMNAND_CET_LAZY) {
|
|
/* Force creation of the CET and the mem table */
|
|
ret = search_cet_blks(mtd, cet, 1);
|
|
if (ret < 0) {
|
|
cet->flags = BRCMNAND_CET_DISABLED;
|
|
return ret;
|
|
}
|
|
cet->flags = BRCMNAND_CET_LOADED;
|
|
}
|
|
/* Find out which entry in memtbl does the from address map to */
|
|
page = (uint32_t) (from >> this->page_shift);
|
|
/* each bit is one page << 3 for 8 bits per byte */
|
|
blk = page/(this->blockSize<<3);
|
|
if (unlikely(cet->memtbl[blk].blk == -1)) {
|
|
printk(KERN_INFO "brcmnandCET: %s invalid block# in CET\n", __FUNCTION__);
|
|
return -1;
|
|
}
|
|
blkbegin = cet->memtbl[blk].blk;
|
|
/* Start page of the block */
|
|
from = ((loff_t) blkbegin) << this->bbt_erase_shift;
|
|
/* If bitvec == NULL, load the block from flash */
|
|
if (cet->memtbl[blk].bitvec == NULL) {
|
|
if (gdebug) {
|
|
printk(KERN_INFO "DEBUG -> brcmnandCET: bitvec null .... loading ...\n");
|
|
}
|
|
cet->memtbl[blk].bitvec = (char *) vmalloc(this->blockSize);
|
|
if (cet->memtbl[blk].bitvec == NULL) {
|
|
printk(KERN_ERR "brcmnandCET: %s vmalloc failed\n", __FUNCTION__);
|
|
return -1;
|
|
}
|
|
memset(cet->memtbl[blk].bitvec, 0xff, this->blockSize);
|
|
/* Read an entire block */
|
|
if (gdebug) {
|
|
printk(KERN_INFO "DEBUG -> brcmnandCET: Reading pages starting @ %x\n", (unsigned int) from);
|
|
}
|
|
for (i = 0; i < mtd->erasesize/mtd->writesize; i++) {
|
|
ret = mtd->read(mtd, from, this->pageSize, &retlen, (uint8_t *) (cet->memtbl[blk].bitvec+pg_idx));
|
|
if (ret < 0 || (retlen != this->pageSize)) {
|
|
vfree(cet->memtbl[blk].bitvec);
|
|
return -1;
|
|
}
|
|
pg_idx += mtd->writesize;
|
|
from += this->pageSize;
|
|
}
|
|
}
|
|
pos = page % (this->blockSize<<3);
|
|
byte = pos / (1<<3);
|
|
bit = pos % (1<<3);
|
|
c = cet->memtbl[blk].bitvec[byte];
|
|
mask = 1<<bit;
|
|
if ((c & mask) == mask) { /* First time error mark it but return a good status */
|
|
*status = 0;
|
|
c = (c & ~mask);
|
|
cet->memtbl[blk].bitvec[byte] = c;
|
|
cet->memtbl[blk].isdirty = 1;
|
|
} else {
|
|
*status = 1; /* This page had a previous error so return a bad status */
|
|
}
|
|
cet->cerr_count++;
|
|
#if 0
|
|
printk(KERN_INFO "DEBUG -> count = %d, byte = %d, bit = %d, blk = %x status = %d c = %d addr = %x\n", cet->cerr_count\
|
|
, byte, bit, blk, *status, cet->memtbl[blk].bitvec[byte], cet->memtbl[blk].bitvec+byte);
|
|
printk(KERN_INFO "DEBUG -> CET: Exiting %s\n", __FUNCTION__);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* brcmnand_cet_prepare_reboot Call flush_memcet to flush any in-mem dirty data
|
|
*
|
|
* @param mtd MTD device structure
|
|
*
|
|
* Flush any pending in-mem CET blocks to flash before reboot
|
|
*/
|
|
int brcmnand_cet_prepare_reboot(struct mtd_info *mtd)
|
|
{
|
|
int gdebug = 0;
|
|
struct brcmnand_chip *this = (struct brcmnand_chip *) mtd->priv;
|
|
struct brcmnand_cet_descr *cet = this->cet;
|
|
|
|
#if 0
|
|
// Disable for MLC
|
|
if (NAND_IS_MLC(this)) {
|
|
return 0;
|
|
}
|
|
#endif
|
|
if (unlikely(gdebug)) {
|
|
printk(KERN_INFO "DEBUG -> brcmnandCET: flushing pending CET\n");
|
|
}
|
|
if (unlikely(cet->flags == BRCMNAND_CET_DISABLED)) {
|
|
return 0;
|
|
}
|
|
flush_memcet(mtd);
|
|
|
|
return 0;
|
|
}
|
|
|
|
EXPORT_SYMBOL(brcmnand_cet_update);
|
|
|
|
#endif
|