主题 : tiny210--U-Boot实现NAND的擦除(型号:K9GAG08U0F) 复制链接 | 浏览器收藏 | 打印
级别: 新手上路
UID: 70046
精华: 1
发帖: 39
金钱: 250 两
威望: 50 点
贡献值: 1 点
综合积分: 98 分
注册时间: 2012-05-15
最后登录: 2013-10-04
楼主  发表于: 2013-03-11 16:14

 tiny210--U-Boot实现NAND的擦除(型号:K9GAG08U0F)

管理提醒: 本帖被 xoom 执行加亮操作(2013-03-12)

(我是菜鸟,贡献点微薄之力)

1、NAND的初始化:

#if defined(CONFIG_CMD_NAND)

puts("NAND:  ");

nand_init(); /* go init the NAND */

#endif

调用函数:

nand_init_chip(&nand_info, &nand_chip, base_address);             参数base_address=0xB0E000000。

函数功能:

设置nand_chip结构体:

nand->IO_ADDR_R = 0xB0E000000;

还有调用board_nand_init函数,这个函数和芯片操作息息相关,也是设置nand_chip结构体,设置初始化好了NAND。

现在看看核心代码:

先获取NAND的ID号的1st Cycle 和2nd Cycle,

然后通过nand_flash_ids[]数组来匹配,找出我们芯片对应的成员:

for (i = 0; nand_flash_ids.name != NULL; i++) {

if (tmp == nand_flash_ids.id) {

type = &nand_flash_ids;

break;

}

}

找出我们芯片对应的成员:

{"NAND 2GiB 3,3V 8-bit", 0xD5, 0, 2048, 0, LP_OPTIONS},



nand_flash_ids[]结构体原型:

struct nand_flash_dev {

char *name;

int id;

unsigned long pagesize;

unsigned long chipsize;

unsigned long erasesize;

unsigned long options;

};





再获取NAND的ID号的3st Cycle 和4nd Cycle,这次的数据主要是体现该芯片的内部信息,如页大小,有多少块等,然后用这些值来设置nand->ecc结构体:
这里说明一下芯片内部一个信息:

Cell Type:SLC / MLC

bit2&bit3表示的是芯片的类型,是SLC还是某种MLC:

Bit2,bit3=0x00 : SLC,简单说就是内部单个存储单元,存储一位的数据,所能表示的数值只有0,1,也就需要两种不同的电压来表示,所以叫做2 Level的Cell。


Bit2,bit3=0x01/0x10/0x11 : 4 /8/16 Level Cell,都叫做MLC,其含义是内部单个存储单元设计成可以表示多个,即4/8/16个不同的电压,对应地,可以表示2,3,4位的数据。 这类的MLC的nand flash,由于单个存储单元,要存储更多的数据,所以内部结构更复杂,读取和写入数据的逻辑更复杂,相对数据出错的几率也比SLC要大。

所以,一般MLC的使用,都需要检错和纠错能力更强的硬件或软件算法,以保证数据的正确性。

软件实现此类的多位数据的检错和纠错的效率相对较低,一般是硬件本身就已经提供此功能。

对应的其为硬件ECC,也就是Linux内核MTD中的HW_ECC。




遇到的问题:读取设备ID为0,说明我们的NNAD读操作有误,经过检测得到解决办法:

在读操作之前没有先复位NAND,导致操作NAND出错,在NAND操作前添加代码:

/*Reset*/


s3c_nand_hwcontrol(0,NAND_CMD_RESET, NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);

s3c_nand_device_ready(0);

s3c_nand_hwcontrol(0, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE);


/*******/

(后来发现,这部分可以不加,因为后面代码还会在读一次,那次就有复位)

这样打印的设备ID就是NAND: ID=d5



由于六次ID数据包含了该芯片的很多信息,所以打印出来:

1st Cycle(Maker Code)=ecH,

2st Cycle(Device Code)=d5H,

3st Cycle(cellinfo)=94H,

4st Cycle(Page Size, Block Size,Redundant Area Size)=76H,

5st Cycle(Plane Number, ECC Level, Organization.)=54H,

6st Cycle(Device Technology, EDO, Interface.)=43H,



我们的芯片是MLC会调用下面代码:

nand_type = S3C_NAND_TYPE_MLC;

nand->options |= NAND_NO_SUBPAGE_WRITE; /* NOP = 1 if MLC */

nand->ecc.read_page = s3c_nand_read_page_4bit;

nand->ecc.write_page = s3c_nand_write_page_4bit;

nand->ecc.size = 512;

nand->ecc.bytes = 8; /* really 7 bytes */

nand->ecc.layout = &s3c_nand_oob_mlc_64;





到了函数nand_scan(struct mtd_info *mtd, int maxchips)

Mtd:nand_info;

Maxchips:1



函数核心代码:

nand_set_defaults(chip, busw); //设置NAND的操作函数集



type = nand_get_flash_type(mtd, chip, busw, &nand_maf_id, &nand_dev_id, table);

// 读取NAND的型号,并且最终根据{"NAND 2GiB 3,3V 8-bit", 0xD5, 0, 2048, 0, LP_OPTIONS},来设置nand_chip结构体。





Uboot已经定义好了NAND命令:

U_BOOT_CMD(

nand, CONFIG_SYS_MAXARGS, 1, do_nand,

"NAND sub-system",

"info - show available NAND devices\n"

"nand device [dev] - show or set current device\n"

"nand read - addr off|partition size\n"

"nand write - addr off|partition size\n"

"    read/write 'size' bytes starting at offset 'off'\n"

"    to/from memory address 'addr', skipping bad blocks.\n"

......

)

所以nand的操作都是从do_nand函数开始的:




启动结果:

NAND:  NAND device: Manufacturer ID: 0xec, Chip ID: 0xd5 (Samsung NAND 2GiB 3,3V 8-bit)

NAND bus width 8 instead 16 bit




随便测试一下:

210 # nand write.e

no devices available

解决:

屏蔽nand_base函数里:

#if 0

        if (IS_ERR(type)) {

#ifndef CONFIG_SYS_NAND_QUIET_TEST

                printk(KERN_WARNING "No NAND device found!!!\n");

#endif

                chip->select_chip(mtd, -1);

                return PTR_ERR(type);

        }

#endif


到此NAND的初始化就结束了。现在不如NAND的三大重头戏:


1、nand的擦除函数的实现:

核心代码:

if (strncmp(cmd, "erase", 5) == 0 || strncmp(cmd, "scrub", 5) == 0) {

nand_erase_options_t opts;//核心结构体

struct nand_chip *chip = meminfo->priv;  //取出初始化设置好的nand_chip;

int ret = meminfo->block_isbad(meminfo, erase.addr);  //检测坏块

result = meminfo->erase(meminfo, &erase); //擦除函数

现在看看 meminfo->erase(meminfo, &erase),就是函数nand_erase(struct mtd_info *mtd, struct erase_info *instr)。

参数:mtd结构体,这个负责调用nand_chip的擦除函数,擦除大小信息放在instr结构体。

根据下面的问题:

问题:

210 # nand erase 0 100000

nand_curr_device=0


NAND erase: device 0 offset 0x0, size 0x100000

Skipping bad block at  0x00000000                                          

BUG: failure at nand_base.c:187/nand_select_chip()!

经过打印知道问题所在:

if (busw != (chip->options & NAND_BUSWIDTH_16)) {

printk(KERN_INFO "NAND device: Manufacturer ID:"

       " 0x%02x, Chip ID: 0x%02x (%s %s)\n", *maf_id,

       *dev_id, nand_manuf_ids[maf_idx].name, mtd->name);

printk(KERN_WARNING "NAND bus width %d instead %d bit\n",

       (chip->options & NAND_BUSWIDTH_16) ? 16 : 8,

       busw ? 16 : 8);

return ERR_PTR(-EINVAL);

}

由于我们执行这个函数,进入if语句了,这样就会返回,导致后面的chip->page_shift = ffs(mtd->writesize) - 1;没有执行,这样chip->page_shift = 0,这问题就大了,在检测坏块的时候nand_block_bad函数:

chipnr = (int)(ofs >> chip->chip_shift);

chip->select_chip(mtd, chipnr);

而select_chip:

static void nand_select_chip(struct mtd_info *mtd, int chipnr)

{

struct nand_chip *chip = mtd->priv;


switch (chipnr) {

case -1:

chip->cmd_ctrl(mtd, NAND_CMD_NONE, 0 | NAND_CTRL_CHANGE);

break;

case 0:

break;


default:

BUG();

}

}

这明显就出错。




在if (busw != (chip->options & NAND_BUSWIDTH_16)) 就返回了,导致后面的chip->page_shift没有操作,结果为0。这样,

chipnr = (int)(ofs >> chip->chip_shift)得到的chipnr就有问题,nand_select_chip函数的switch函数就会跑到default:BUG();



所以屏蔽这部分:

#if 0

        if (busw != (chip->options & NAND_BUSWIDTH_16)) {

                printk(KERN_INFO "NAND device: Manufacturer ID:"

                       " 0x%02x, Chip ID: 0x%02x (%s %s)\n", *maf_id,

                       *dev_id, nand_manuf_ids[maf_idx].name, mtd->name);

                printk(KERN_WARNING "NAND bus width %d instead %d bit\n",

                       (chip->options & NAND_BUSWIDTH_16) ? 16 : 8,

                       busw ? 16 : 8);

//              return ERR_PTR(-EINVAL);

        }

#endif


函数nand_flash_detect_non_onfi是个很重要的函数,我们的芯片重要信息设置(修改的重点):

if (!type->pagesize) {
        int extid;
        chip->cellinfo = chip->read_byte(mtd);
        extid = chip->read_byte(mtd);
        //mtd->writesize = 1024 << (extid & 0x3);
我们的NAND一页是8k,所以修改:
  mtd->writesize = 2048<< (extid & 0x3);
        extid >>= 2;
        //mtd->oobsize = (8 << (extid & 0x01)) * (mtd->writesize >> 9);
我们的NAND每一页obb为512B,改为:
  mtd->oobsize =512;
        extid >>= 2;
        //mtd->erasesize = (64 * 1024) << (extid & 0x03);
问题:
我们的NAND是以128K为一倍的,所以:
  mtd->erasesize = (128 * 1024) << (extid & 0x03);
        extid >>= 2;
        *busw = (extid & 0x01) ? NAND_BUSWIDTH_16 : 0;
}



启动结果(少了一句):

NAND:  NAND device: Manufacturer ID: 0xec, Chip ID: 0xd5 (Samsung NAND 2GiB 3,3V 8-bit)

操作结果:

#nand erase 0 10000000

NAND erase: device 0 offset 0x0, size 0x10000000

Skipping bad block at  0x00800000                                          

Skipping bad block at  0x00880000                                          

Erasing at 0xff80000 -- 100% complete.

OK

擦除函数到这里就解决。



[ 此帖被2012shiyi在2013-03-12 10:41重新编辑 ]
Cya,Reggie.
级别: 侠客
UID: 81867
精华: 1
发帖: 67
金钱: 390 两
威望: 78 点
贡献值: 1 点
综合积分: 154 分
注册时间: 2012-11-14
最后登录: 2014-10-30
1楼  发表于: 2013-04-25 20:03
Take a look at this link:
http://aiothome.net/read.php?tid=28878

the github link will take you to our mini210s 16bit ecc driver source code for linux.  You should be able to grab the ecc layout from s5p_nand_mlc.c
Cya,Reggie.
级别: 侠客
UID: 81867
精华: 1
发帖: 67
金钱: 390 两
威望: 78 点
贡献值: 1 点
综合积分: 154 分
注册时间: 2012-11-14
最后登录: 2014-10-30
2楼  发表于: 2013-04-25 20:05
I think you also have other problems, the u-boot source code that you're working on is quite old, it seems like you might be better off transplanting any code you've got from your own editing and putting it into a new version of u-boot, then you won't need to hack the code your using.