http://www.crifan.com/files/doc/docbook/nand_get_type/release/html/nand_get_type.html
Linux MTD下获取Nand flash各个参数的过程的详细解析
版本:v1.1
摘要
本文主要介绍了Nand Flash的一些背景知识和术语,然后详尽分析了Linux的MTD中的nand_base.c中的nand_get_flash_type函数每一步骤的功能。
2012-08-09
修订历史 | ||
---|---|---|
修订 1.0 | 2009-07-28 | crl |
| ||
修订 1.1 | 2012-08-09 | crl |
|
版权 © 2012 Crifan, http://crifan.com
目录
插图清单
目录
摘要
下面是Linux MTD中,获取nand flash型号,各个参数,以及硬件特性的函数,其实也就是nand_get_flash_type
,下面对其详细解析:
此处的编程,不是写软件,写代码,而是对于硬件来说的,可以理解为对硬件编程,只不过其工具是硬件内部的逻辑,而不是你用的软件。对Nand Flash的编程,本质上就是实现写操作,将数据写到Nand Flash里面去,所以对于nand flash,可以简单的理解为 program编程=write写(数据)。
这个词,本来没啥好说的,接触多了,自然就知道了。但是对于和我类似,最开始接触的时候,就是没搞懂这个词的具体含义。其中文翻译,一般称作,数据手册,意思就是,一个关于描述硬件各个硬件特性,参数以及/或者如何操作,如何使用的文档。
这个是Linux MTD中,关于块大小和页大小的别名,第一次见到的时候,把我搞糊涂了,后来才慢慢明白的。因为,nand 操作的写基本单位页,所以,writesize,对应的就是pagesize,页大小。而擦除操作的基本单位是blocksize,块大小,所以也叫它erasesize。在此简单提一下这几个名词,方便和我遇到类似问题的朋友。
nand flash中每一页对应一块区域,用于存放校验的ECC数据和其他一些信息,比如上层文件系统放的和自己文件系统相关的数据。这个区域,在Linux MTD相关系统中,被称作oob(out of band),可以翻译为带外,也就是nand flash的一个页,可以称作一个band,band之外,对应的就是指那个多出来的,特殊的区域了。而nand flash的datasheet中,一般成为spare area,可译为空闲区域,另外,在ID的含义解释中也叫做redundant area,可译为冗余区域,归根结底,都是一个含义。不要被搞糊涂了就好。
nand flash硬件中的一块地方,名字叫做register,实际就是一个数据缓存,一个buffer,用于存放那些从flash读出来或者将要写入到flash中的。其实叫做页缓存,更合适,更容易明白其含义。此页寄存器的大小=页大小+ oob 大小,即pagesize+oob,对于常见的页是2KB的,此页寄存器就是2KB+64=2112字节。
对于chip,其实任何某个型号的flash,都可以称其是一个chip,但是实际上,此处的chip,是针对内部来说的,也就是某型号的flash,内部有几个chip,比如下面会举例说到的,三星的2GB的K9WAG08U1A芯片(可以理解为外部芯片/型号)内部装了2个单片是1GB的K9K8G08U0A,此时就称 K9WAG08U1A内部有2个chip,而有些单个的chip,内部又包含多个plane,比如上面的K9K8G08U0A内部包含4个单片是2Gb的Plane。只有搞清楚了此处的chip和plane的关系,才能明白后面提到的多页(Multi Plane / Multi Page)编程和交互(interleave)编程的含义。
摘要
详细代码可以在这里找到:linux/drivers/mtd/nand/nand_base.c
2407/* 2408 * Get the flash and manufacturer id and lookup if the type is supported 2409 */ 2410static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd, 2411 struct nand_chip *chip, 2412 int busw, int *maf_id) 2413{ 2414 struct nand_flash_dev *type = NULL; 2415 int i, dev_id, maf_idx; 2416 int tmp_id, tmp_manf; 2417 2418 /* Select the device */ 2419 chip->select_chip(mtd, 0); 2420 2421 /* 2422 * Reset the chip, required by some chips (e.g. Micron MT29FxGxxxxx) 2423 * after power-up 2424 */ 2425 chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); 2426 2427 /* Send the command for reading device ID */ 2428 chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); 2429 2430 /* Read manufacturer and device IDs */ 2431 *maf_id = chip->read_byte(mtd); 2432 dev_id = chip->read_byte(mtd); 2433 2434 /* Try again to make sure, as some systems the bus-hold or other 2435 * interface concerns can cause random data which looks like a 2436 * possibly credible NAND flash to appear. If the two results do 2437 * not match, ignore the device completely. 2438 */ 2439 2440 chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); 2441 2442 /* Read manufacturer and device IDs */ 2443 2444 tmp_manf = chip->read_byte(mtd); 2445 tmp_id = chip->read_byte(mtd); 2446 2447 if (tmp_manf != *maf_id || tmp_id != dev_id) { 2448 printk(KERN_INFO "%s: second ID read did not match " 2449 "%02x,%02x against %02x,%02x\n", __func__, 2450 *maf_id, dev_id, tmp_manf, tmp_id); 2451 return ERR_PTR(-ENODEV); 2452 } 2453 2454 /* Lookup the flash id */ 2455 for (i = 0; nand_flash_ids[i].name != NULL; i++) { 2456 if (dev_id == nand_flash_ids[i].id) { 2457 type = &nand_flash_ids[i]; 2458 break; 2459 } 2460 } 2461 2462 if (!type) 2463 return ERR_PTR(-ENODEV); 2464 2465 if (!mtd->name) 2466 mtd->name = type->name; 2467 2468 chip->chipsize = (uint64_t)type->chipsize << 20; 2469 2470 /* Newer devices have all the information in additional id bytes */ 2471 if (!type->pagesize) { 2472 int extid; 2473 /* The 3rd id byte holds MLC / multichip data */ 2474 chip->cellinfo = chip->read_byte(mtd); 2475 /* The 4th id byte is the important one */ 2476 extid = chip->read_byte(mtd); 2477 /* Calc pagesize */ 2478 mtd->writesize = 1024 << (extid & 0x3); 2479 extid >>= 2; 2480 /* Calc oobsize */ 2481 mtd->oobsize = (8 << (extid & 0x01)) * (mtd->writesize >> 9); 2482 extid >>= 2; 2483 /* Calc blocksize. Blocksize is multiples of 64KiB */ 2484 mtd->erasesize = (64 * 1024) << (extid & 0x03); 2485 extid >>= 2; 2486 /* Get buswidth information */ 2487 busw = (extid & 0x01) ? NAND_BUSWIDTH_16 : 0; 2488 2489 } else { 2490 /* 2491 * Old devices have chip data hardcoded in the device id table 2492 */ 2493 mtd->erasesize = type->erasesize; 2494 mtd->writesize = type->pagesize; 2495 mtd->oobsize = mtd->writesize / 32; 2496 busw = type->options & NAND_BUSWIDTH_16; 2497 } 2498 2499 /* Try to identify manufacturer */ 2500 for (maf_idx = 0; nand_manuf_ids[maf_idx].id != 0x0; maf_idx++) { 2501 if (nand_manuf_ids[maf_idx].id == *maf_id) 2502 break; 2503 } 2504 2505 /* 2506 * Check, if buswidth is correct. Hardware drivers should set 2507 * chip correct ! 2508 */ 2509 if (busw != (chip->options & NAND_BUSWIDTH_16)) { 2510 printk(KERN_INFO "NAND device: Manufacturer ID:" 2511 " 0x%02x, Chip ID: 0x%02x (%s %s)\n", *maf_id, 2512 dev_id, nand_manuf_ids[maf_idx].name, mtd->name); 2513 printk(KERN_WARNING "NAND bus width %d instead %d bit\n", 2514 (chip->options & NAND_BUSWIDTH_16) ? 16 : 8, 2515 busw ? 16 : 8); 2516 return ERR_PTR(-EINVAL); 2517 } 2518 2519 /* Calculate the address shift from the page size */ 2520 chip->page_shift = ffs(mtd->writesize) - 1; 2521 /* Convert chipsize to number of pages per chip -1. */ 2522 chip->pagemask = (chip->chipsize >> chip->page_shift) - 1; 2523 2524 chip->bbt_erase_shift = chip->phys_erase_shift = 2525 ffs(mtd->erasesize) - 1; 2526 if (chip->chipsize & 0xffffffff) 2527 chip->chip_shift = ffs((unsigned)chip->chipsize) - 1; 2528 else 2529 chip->chip_shift = ffs((unsigned)(chip->chipsize >> 32)) + 32 - 1; 2530 2531 /* Set the bad block position */ 2532 chip->badblockpos = mtd->writesize > 512 ? 2533 NAND_LARGE_BADBLOCK_POS : NAND_SMALL_BADBLOCK_POS; 2534 2535 /* Get chip options, preserve non chip based options */ 2536 chip->options &= ~NAND_CHIPOPTIONS_MSK; 2537 chip->options |= type->options & NAND_CHIPOPTIONS_MSK; 2538 2539 /* 2540 * Set chip as a default. Board drivers can override it, if necessary 2541 */ 2542 chip->options |= NAND_NO_AUTOINCR; 2543 2544 /* Check if chip is a not a samsung device. Do not clear the 2545 * options for chips which are not having an extended id. 2546 */ 2547 if (*maf_id != NAND_MFR_SAMSUNG && !type->pagesize) 2548 chip->options &= ~NAND_SAMSUNG_LP_OPTIONS; 2549 2550 /* Check for AND chips with 4 page planes */ 2551 if (chip->options & NAND_4PAGE_ARRAY) 2552 chip->erase_cmd = multi_erase_cmd; 2553 else 2554 chip->erase_cmd = single_erase_cmd; 2555 2556 /* Do not replace user supplied command function ! */ 2557 if (mtd->writesize > 512 && chip->cmdfunc == nand_command) 2558 chip->cmdfunc = nand_command_lp; 2559 2560 printk(KERN_INFO "NAND device: Manufacturer ID:" 2561 " 0x%02x, Chip ID: 0x%02x (%s %s)\n", *maf_id, dev_id, 2562 nand_manuf_ids[maf_idx].name, type->name); 2563 2564 return type; 2565}
选中芯片,才能对其操作。 | ||||
发送ReadID的命令:0x90,去取得芯片的ID信息 | ||||
根据datasheet中的定义,第一个字节,简称byte1,是生产厂商的信息,不同的厂商,对应不同的数字。而byte2是芯片类型,不同的nand flash芯片,对应不同的设备ID,也就是一个字节的数字。 关于读取出来的ID的具体含义,可以参考三星K9K8G08U0A的datasheet中解释: | ||||
再次发送ReadID命令,其目的,上面注释代码中说了,有些特殊的系统中,第一次读取的信息,看起来是很正常,但是实际是错的,所以这里读两次,正常的设备,肯定都会一样的,如果两次不一样,那么说明设备有问题,也就直接函数返回了。 | ||||
下面根据读取出来的flash ID,也就是具体flash芯片,或叫做设备ID,不同的数值,对应不同的容量和物理参数的flash。 其中,nand_flash_ids是个预先定义好的数组,其定义在: /* * Chip ID list * * Name. ID code, pagesize, chipsize in MegaByte, eraseblock size, options * * Pagesize; 0, 256, 512 * 0 get this information from the extended chip ID + 256 256 Byte page size * 512 512 Byte page size */ struct nand_flash_dev nand_flash_ids[] = { ...... /* 4 Gigabit */ {"NAND 512MiB 1,8V 8-bit", 0xAC, 0, 512, 0, LP_OPTIONS}, {"NAND 512MiB 3,3V 8-bit", 0xDC, 0, 512, 0, LP_OPTIONS}, {"NAND 512MiB 1,8V 16-bit", 0xBC, 0, 512, 0, LP_OPTIONS16}, {"NAND 512MiB 3,3V 16-bit", 0xCC, 0, 512, 0, LP_OPTIONS16}, /* 8 Gigabit */ {"NAND 1GiB 1,8V 8-bit", 0xA3, 0, 1024, 0, LP_OPTIONS}, {"NAND 1GiB 3,3V 8-bit", 0xD3, 0, 1024, 0, LP_OPTIONS}, {"NAND 1GiB 1,8V 16-bit", 0xB3, 0, 1024, 0, LP_OPTIONS16}, {"NAND 1GiB 3,3V 16-bit", 0xC3, 0, 1024, 0, LP_OPTIONS16}, ...... }
在结构体数组nand_flash_ids[]中,预先定义了,目前所支持的很多类型Nand Flash的具体物理参数,主要是上面结构体中的页大小pagesize,芯片大小chipsize,块大小erasesize,而id变量表示此类型的芯片,用哪个数字来表示。 | ||||
此处通过刚读取到的设备ID,去和预先定义好的那个结构体数组nand_flash_ids[]中的每一个ID去比较,如果相等,那么说明支持此款nand falsh,而其他的信息,就可以直接从后面几项中直接获得了。
而对于旧的一些nand flash,在表项中其pagesize不是0,就可以直接可以从上面的预定义的表里面获得了。 比如,对于常见的三星的型号为K9K8G08U0A的nand flash,其设备号是0xD3,找到匹配的表项就是: {"NAND 1GiB 3,3V 8-bit", 0xD3, 0, 1024, 0, LP_OPTIONS}, 因此也就知道,其容量是1024MB,设备相关物理特性是1GiB 3,3V 8-bit了。 而关于pagesize和块大小erasesize此处都是0,就只能另外从后面读取的ID中获得了。 | ||||
此处由于上面表中的chipsize是MB=210Bytes为单位的,所以要左移20位,换算成byte单位 | ||||
解释下面代码第三个字节之前,要先把图标帖出来,才更容易看得懂具体的解释: 由表中定义可以看出:
| ||||
读取4th ID 4th ID的含义,如图: | ||||
Page Size,如图 2.4 “Nand Flash 第4个ID的具体含义”所示,页大小,是bit0和bit1组合起来所表示的。 extid & 0x3,就是取得bit0和bit1的值,而左移1024位,是因为上面表中的单位是KB=210=1024。
| ||||
Redundant Area Size(byte/512byte) 前面介绍过了,此处的oob,就是datasheet中的redundant area size就是linux中的oob大小。 上面表中的意思是,512个byte,对应8还是16个字节的redundant area。 之所以是512字节对应多少个,是因为以前的nand flash页大小是512(除了最早的好像是256之外),所以估计是硬件设计就这样设计了。512个字节对应多少个冗余的数据用作oob,而后来的页大小,对应的是512的整数倍,比如2K,4K等 所以,此处可以按照每个512对应几个字节的oob,然后再算页大小是512的多少倍,即: 此处的extid & 0x01算出来的值,对应上面的8或16,而mtd->writesize >> 9,其实就是 mtd->writesize /512,到此,才算清楚,为何此处oob是这么算的。 | ||||
Block Size 具体算法很清楚,算出是64KB的多少倍,得出总大小。 此处之所以是64KB为基础,是因为已知最小的blocksize,就是64KB的。 | ||||
Organization X8/X16,表示的是,硬件I/O位宽(Bus Width)是8位的还是16位的。 目前大多数,都是X8的。 | ||||
旧的nand flash的一些参数,是知道设备ID后,可以直接从表中读取出来的。 | ||||
根据读取出来的生长厂商的ID,去和表中对应项匹配,找到是哪家的nand flash芯片。 其中,nand_manuf_ids和上面nand_flash_ids类似,也是个预先定义好的数组,其定义和nand_flash_ids同文件 /* * Manufacturer ID list */ struct nand_manufacturers nand_manuf_ids[] = { {NAND_MFR_TOSHIBA, "Toshiba"}, {NAND_MFR_SAMSUNG, "Samsung"}, {NAND_MFR_FUJITSU, "Fujitsu"}, {NAND_MFR_NATIONAL, "National"}, {NAND_MFR_RENESAS, "Renesas"}, {NAND_MFR_STMICRO, "ST Micro"}, {NAND_MFR_HYNIX, "Hynix"}, {NAND_MFR_MICRON, "Micron"}, {NAND_MFR_AMD, "AMD"}, {0x0, "Unknown"} }; | ||||
检测你的驱动中的关于位宽的定义,是否和硬件所一致。 | ||||
此处计算的pagesize,blocksize等的shift,是为了后期的对这些值的除操作更加高效而做的。 对于代码中的除操作,如果直接只是/pagesize,则没有直接算出其是2的多少次方,然后用位移操作,更加高效。因此,此处直接计算好是多少个shift,以后的除于pagesize,blocksize,就可以直接用对应的位移操作了。 | ||||
算出mask,为了后期保证传入的地址不越界,所以会对其mask一下。 | ||||
这段,貌似是新的kernel里面新加的,而且把chip->chipsize定义换成uint64_t了,支持超过4GB大小的nand flash了 否则,如果你正好是4GB,对于旧的代码chip->chipsize是uint32_t类型的,那么正好就变成0了。 此处之所以去chip->chipsize & 0xffffffff判断是超过4GB,看起来,估计是ffs函数最多支持32位,所以,没法计算超过此大小的ffs。
| ||||
设置坏块的标记位置。 关于nand flash的small block和large block,据我了解,好像就是对应的small pagesize和large pagesize,而此处的大小,是针对于旧的nand flash,其页大小pagesize是512 所以,Small block就是页大小是512B的nand flash,而larger block就是新的,页大小大于512B的,比如2KB,4KB等的nand flash。 下面的宏定义在 /* * Constants for oob configuration */ #define NAND_SMALL_BADBLOCK_POS 5 #define NAND_LARGE_BADBLOCK_POS 0 约定俗成的,small block的nand ,坏块标记在byte5,而large block的nand flash在byte0。 关于坏块标记,实际情况更复杂些:
| ||||
获得上面nand id表中的默认设置的那些option:LP_OPTIONS(如果是X16则是,LP_OPTIONS16)。 | ||||
自动增加页数??? 没太搞懂啥意思,估计是cache program/read相关的吧,目前据我了解的,好像页只有cache program/read,能和auto increment pages有点关系。 | ||||
如果有extentID且不是三星的nand flash,则清除掉上面我们默认设置的那些参数:LP_OPTIONS。 | ||||
一种特殊的nand flash,AND chip。所以,也要赋值给特殊的擦除函数。 具体关于此类nand flash的介绍,感兴趣的自己参考上面drivers\mtd\nand\nand_ids.c中nand_flash_ids数组中的解释。 | ||||
如果nand flash的页大小是大于512B,也就是2KB,4KB等新的,被称作large block或largepage的nand flash,此处的lp,应该也就是large page的缩写。 此类的nand flash比旧的,在发送地址的时候,多一个地址周期。具体参考datasheet。 | ||||
终于检测完所有需要的信息了。最后打印出nand flash的相关信息。 | ||||
活干完了,就可以return回家了,呵呵。 |