MTD中的nand驱动初步分析---面向u-boot

之前提到nand驱动的初始化分析,有一个结构体 struct mtd_info始终贯穿这些代码

再来分析一下这个结构体的基本功能,如何初始化,如何使用


一、分析过程

看看结构体的出现和使用方式

第一次出现在文件\u-boot-sunxi-sunxi\drivers\mtd\nand\nand.c内:

#ifndef CONFIG_SYS_NAND_SELF_INIT
static void nand_init_chip(int i)
{
struct mtd_info *mtd= &nand_info[i];
struct nand_chip *nand = &nand_chip[i];
ulong base_addr = base_address[i];
int maxchips = CONFIG_SYS_NAND_MAX_CHIPS;


if (maxchips < 1)
maxchips = 1;


mtd->priv = nand;
nand->IO_ADDR_R = nand->IO_ADDR_W = (void  __iomem *)base_addr;


if (board_nand_init(nand))
return;


if (nand_scan(mtd, maxchips))
return;


nand_register(i);
}
#endif

看这句代码:

struct mtd_info *mtd = &nand_info[i];  

结构体指向全局变量nand_info,这个变量就是nand设备的信息


再看初始化:

mtd->priv = nand;

mtd的私有数据就是一个struct nand_chip类型的结构体


从编程的角度来说,一个硬件驱动应该有两个面,一个面向上层,提供接口;一个面向底层,提供硬件操作

广义上来看:

struct mtd_info就是面向上层,提供数据接口

struct nand_chip面向nand设备,提供硬件接口

假如:mtd->priv = nand; 初始化为另外一种设备的结构体,例如nor flash,那么mtd就是一种nor  flash的驱动,

用户实现nor flash相关的操作即可。


struct   mtd_info结构体来自linux内核的MTD子系统,u-boot使用时进行了一些简化使用,毕竟不是操作系统,很多问题可以不用考虑

MTD的全称是memory  technology  device,主要针对是用于访问memory设备(ROM、flash),其目的就是简化驱动的更新,

例如cubieboard接了一个nand flash,型号是K9GBG08U0A,如果没有这个驱动如何简单添加呢?

从人的正常思考角度,加了一个nand,无外乎读,写,刷新几种主要操作,而MTD就提供了这几种操作,

用户在使用时需要实现这个接口就可以了,至于数据的格式,支持什么文件格式yaffs,ext3,ext4用户无须关心

MTD上层已经实现了,用户提供读写,刷新等等基本操作就可以了。大大简化了一个驱动的开发工作量。


基于这种思路,来看看struct   mtd_info结构体

struct mtd_info {
u_char type;     // 设备类型,指示这是一种什么设备,MTD支持多种设备

u_int32_t flags;
uint64_t size;/* Total size of the MTD */   // 总容量,假设板上有5颗nand flash,容量之和就是这个数


/* "Major" erase size for the device. Na茂ve users may take this
* to be the only erase size available, or may use the more detailed
* information below if they desire
*/
u_int32_t erasesize;  // 刷新大小,对于nand,一块为单位刷新,对于cubieboard上的K9GBG08U0A,一个块就是1M大小

                            //对于nand来说,这个大小就是块大小了
/* Minimal writable flash unit size. In case of NOR flash it is 1 (even
* though individual bits can be cleared), in case of NAND flash it is
* one NAND page (or half, or one-fourths of it), in case of ECC-ed NOR
* it is of ECC block size, etc. It is illegal to have writesize = 0.
* Any driver registering a struct mtd_info must ensure a writesize of
* 1 or larger.
*/
u_int32_t writesize;  // 一次写的最小字节数,对于nand而言,一次写一页或半页,等等


u_int32_t oobsize;   /* Amount of OOB data per block (e.g. 16) */
u_int32_t oobavail;  /* Available OOB bytes per block */


/* Kernel-only stuff starts here. */
const char *name;
int index;


/* ecc layout structure pointer - read only ! */
struct nand_ecclayout *ecclayout;


/* Data for variable erase regions. If numeraseregions is zero,
* it means that the whole device has erasesize as given above.
*/
int numeraseregions;
struct mtd_erase_region_info *eraseregions;


/*
* Erase is an asynchronous operation.  Device drivers are supposed
* to call instr->callback() whenever the operation completes, even
* if it completes with a failure.
* Callers are supposed to pass a callback function and wait for it
* to be called before writing to the block.
*/
int (*erase) (struct mtd_info *mtd, struct erase_info *instr);  // 刷新函数指针,用户实现自己的函数之后,初始化到这个指针


/* This stuff for eXecute-In-Place */
/* phys is optional and may be set to NULL */
int (*point) (struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, void **virt, phys_addr_t *phys);


/* We probably shouldn't allow XIP if the unpoint isn't a NULL */
void (*unpoint) (struct mtd_info *mtd, loff_t from, size_t len);




int (*read) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);      // 读函数指针,用户实现自己的函数之后,初始化到这个指针
int (*write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf); // 写函数指针,用户实现自己的函数之后,初始化到这个指针


/* In blackbox flight recorder like scenarios we want to make successful
  writes in interrupt context. panic_write() is only intended to be
  called when its known the kernel is about to panic and we need the
  write to succeed. Since the kernel is not going to be running for much
  longer, this function can break locks and delay to ensure the write
  succeeds (but not sleep). */


int (*panic_write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);


int (*read_oob) (struct mtd_info *mtd, loff_t from,
struct mtd_oob_ops *ops);
int (*write_oob) (struct mtd_info *mtd, loff_t to,
struct mtd_oob_ops *ops);


/*
* Methods to access the protection register area, present in some
* flash devices. The user data is one time programmable but the
* factory data is read only.
*/
int (*get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
int (*read_fact_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
int (*get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
int (*read_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
int (*write_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
int (*lock_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len);


/* XXX U-BOOT XXX */
#if 0
/* kvec-based read/write methods.
  NB: The 'count' parameter is the number of _vectors_, each of
  which contains an (ofs, len) tuple.
*/
int (*writev) (struct mtd_info *mtd, const struct kvec *vecs, unsigned long count, loff_t to, size_t *retlen);
#endif


/* Sync */
void (*sync) (struct mtd_info *mtd);


/* Chip-supported device locking */
int (*lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
int (*unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);


/* Bad block management functions */
int (*block_isbad) (struct mtd_info *mtd, loff_t ofs);
int (*block_markbad) (struct mtd_info *mtd, loff_t ofs);


/* XXX U-BOOT XXX */
#if 0
struct notifier_block reboot_notifier;  /* default mode before reboot */
#endif


/* ECC status information */
struct mtd_ecc_stats ecc_stats;
/* Subpage shift (NAND) */
int subpage_sft;


void *priv;   // 这个指针就是用来指向某一种设备的结构体,实现硬件层与软件层的代码结合


struct module *owner;
int usecount;


/* If the driver is something smart, like UBI, it may need to maintain
* its own reference counting. The below functions are only for driver.
* The driver may register its callbacks. These callbacks are not
* supposed to be called by MTD users */
int (*get_device) (struct mtd_info *mtd);
void (*put_device) (struct mtd_info *mtd);
};


再来看看mtd里面这些参数是怎么初始化的,从代码流程分析一下

调用关系

nand_scan --> nand_scan_ident --> nand_get_flash_type

nand_scan 如何调用的需要读我前面一篇文章:http://blog.csdn.net/andy_wsj/article/details/9335755

nand_get_flash_type函数里面设置了几个变量,看看代码:

static const struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,
 struct nand_chip *chip,
 int busw,
 int *maf_id, int *dev_id,
 const struct nand_flash_dev *type)
{
int i, maf_idx;
u8 id_data[8];
int ret;


/* Select the device */
chip->select_chip(mtd, 0);   //   片选函数,用户驱动自己实现


/*
* Reset the chip, required by some chips (e.g. Micron MT29FxGxxxxx)
* after power-up
*/
chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);   // 命令操作函数----复位,用户驱动自己实现


/* Send the command for reading device ID */
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);   // 命令操作函数----读ID


/* Read manufacturer and device IDs */
*maf_id = chip->read_byte(mtd);    // 厂商ID
*dev_id = chip->read_byte(mtd);    // 设备id


/* Try again to make sure, as some systems the bus-hold or other
* interface concerns can cause random data which looks like a
* possibly credible NAND flash to appear. If the two results do
* not match, ignore the device completely.
*/


chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);


for (i = 0; i < 2; i++)    //  读两个字节,参考nand芯片资料,第1、2个字节就是厂商id和设备id
id_data[i] = chip->read_byte(mtd);


if (id_data[0] != *maf_id || id_data[1] != *dev_id) {
printk(KERN_INFO "%s: second ID read did not match "
      "%02x,%02x against %02x,%02x\n", __func__,
      *maf_id, *dev_id, id_data[0], id_data[1]);
return ERR_PTR(-ENODEV);
}


if (!type)                                            // 这个指针调用时传的是NULL,那么将指向nand_flash_ids
type = nand_flash_ids;         // nand_flash_ids就是MTD支持的nand设备列表,在文件\u-boot-sunxi-sunxi\drivers\mtd\nand\nand_ids.c内

                                                                  // cubieboard使用的K9GBG08U0A也在这个列表内,如果不再,自己定义,传过来也可以


for (; type->name != NULL; type++)  // 通过设备id找到对应的设备
if (*dev_id == type->id)
break;


chip->onfi_version = 0;
if (!type->name || !type->pagesize) {                           // 如果找到设备,看看是不是ONFI标准芯片,这是一个intel推出的nand接口标准
/* Check is chip is ONFI compliant */                        // 需要定义宏CONFIG_SYS_NAND_ONFI_DETECTION
ret = nand_flash_detect_onfi(mtd, chip, &busw);   //  不用看资料,三星的芯片不会去凑intel的标准,不是让人宰吗?没有定义上述宏,调用返回值是0
if (ret)                                                                               //  最后我忍不住看看K9GBG08U0A的资料,没有任何地方提到ONFI 

goto ident_done;                                                          
}


       // 若不是ONFI flash ,通过以下代码初始化

chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);


/* Read entire ID string */


for (i = 0; i < 8; i++)
id_data[i] = chip->read_byte(mtd);  // 读取8个字节id信息,这个就要了解nand的id信息组成和作用了,不清楚的可以去网上找一找


if (!type->name)
return ERR_PTR(-ENODEV);


if (!mtd->name)  //  初始化设备名, 操作mtd
mtd->name = type->name;


chip->chipsize = (uint64_t)type->chipsize << 20;   // 芯片容量,按MB计算


if (!type->pagesize && chip->init_size) {                                     // 若用户自己定义了初始化函数chip->init_size,则使用用户的初始化
/* set the pagesize, oobsize, erasesize by the driver*/   
busw = chip->init_size(mtd, chip, id_data);
} else if (!type->pagesize) {
int extid;
/* The 3rd id byte holds MLC / multichip data */
chip->cellinfo = id_data[2];
/* The 4th id byte is the important one */
extid = id_data[3];


/*
* Field definitions are in the following datasheets:
* Old style (4,5 byte ID): Samsung K9GAG08U0M (p.32)
* New style   (6 byte ID): Samsung K9GBG08U0M (p.40)
*
* Check for wraparound + Samsung ID + nonzero 6th byte
* to decide what to do.
*/
if (id_data[0] == id_data[6] && id_data[1] == id_data[7] &&
id_data[0] == NAND_MFR_SAMSUNG &&
(chip->cellinfo & NAND_CI_CELLTYPE_MSK) &&
id_data[5] != 0x00) {
/* Calc pagesize */
mtd->writesize = 2048 << (extid & 0x03);
extid >>= 2;
/* Calc oobsize */
switch (extid & 0x03) {
case 1:
mtd->oobsize = 128;
break;
case 2:
mtd->oobsize = 218;
break;
case 3:
mtd->oobsize = 400;
break;
default:
mtd->oobsize = 436;
break;
}
extid >>= 2;
/* Calc blocksize */
mtd->erasesize = (128 * 1024) <<
(((extid >> 1) & 0x04) | (extid & 0x03));
busw = 0;
} else {
/* Calc pagesize */
mtd->writesize = 1024 << (extid & 0x03);
extid >>= 2;
/* Calc oobsize */
mtd->oobsize = (8 << (extid & 0x01)) *
(mtd->writesize >> 9);
extid >>= 2;
/* Calc blocksize. Blocksize is multiples of 64KiB */
mtd->erasesize = (64 * 1024) << (extid & 0x03);
extid >>= 2;
/* Get buswidth information */
busw = (extid & 0x01) ? NAND_BUSWIDTH_16 : 0;
}
} else {
/*
* Old devices have chip data hardcoded in the device id table
*/
mtd->erasesize = type->erasesize;
mtd->writesize = type->pagesize;
mtd->oobsize = mtd->writesize / 32;
busw = type->options & NAND_BUSWIDTH_16;


/*
* Check for Spansion/AMD ID + repeating 5th, 6th byte since
* some Spansion chips have erasesize that conflicts with size
* listed in nand_ids table
* Data sheet (5 byte ID): Spansion S30ML-P ORNAND (p.39)
*/
if (*maf_id == NAND_MFR_AMD && id_data[4] != 0x00 &&
id_data[5] == 0x00 && id_data[6] == 0x00 &&
id_data[7] == 0x00 && mtd->writesize == 512) {
mtd->erasesize = 128 * 1024;
mtd->erasesize <<= ((id_data[3] & 0x03) << 1);
}
}
/* Get chip options, preserve non chip based options */
chip->options |= type->options;


/* Check if chip is a not a samsung device. Do not clear the
* options for chips which are not having an extended id.
*/
if (*maf_id != NAND_MFR_SAMSUNG && !type->pagesize)
chip->options &= ~NAND_SAMSUNG_LP_OPTIONS;

ident_done:

      ............篇幅关系,部分代码略............


return type;
}

这里初始化了:

mtd->name

mtd->erasesize

mtd->writesize 

mtd->oobsize

这些都是描述nand的数据


那么操作函数在哪初始化呢?

nand_scan --> nand_scan_tail

如果上面的操作成功,那么就会调用函数nand_scan_tail初始化结构体定义的各种操作,

这个函数在文件\u-boot-sunxi-sunxi\drivers\mtd\nand\nand_base.c内,

函数比较长,截取其中一部分:

int nand_scan_tail(struct mtd_info *mtd)
{
      .........略去N行..........

/* Fill in remaining MTD driver data */
mtd->type = MTD_NANDFLASH;
mtd->flags = (chip->options & NAND_ROM) ? MTD_CAP_ROM :
MTD_CAP_NANDFLASH;
mtd->erase = nand_erase;
mtd->point = NULL;
mtd->unpoint = NULL;
mtd->read = nand_read;
mtd->write = nand_write;
mtd->read_oob = nand_read_oob;
mtd->write_oob = nand_write_oob;
mtd->sync = nand_sync;

mtd->lock = NULL;
mtd->unlock = NULL;
mtd->block_isbad = nand_block_isbad;
mtd->block_markbad = nand_block_markbad;



/* propagate ecc.layout to mtd_info */
mtd->ecclayout = chip->ecc.layout;


/* Check, if we should skip the bad block table scan */
if (chip->options & NAND_SKIP_BBTSCAN)
chip->options |= NAND_BBT_SCANNED;


return 0;
}

可以看出,调用这个函数之后,一个MTD类型的nand驱动就建立了

上层,例如文件系统在写一个文件会调用mtd->write,从而进入函数nand_write();

至于mtd子系统如何与上层打交道,驱动工程师可以不关心。

就像我们知道一个CPU管脚的用法就可以了,不关心其内部电路结构也可以使用它。

当然,你要提升系统性能的时候,才需要每个点都去深究,那是是高级内容了。


这些初始化完成之后,通过以下调用路径加入处理链表中

nand_init --> nand_init_chip --> nand_register --> add_mtd_device

代码就是

#ifdef CONFIG_MTD_DEVICE
/*
* Add MTD device so that we can reference it later
* via the mtdcore infrastructure (e.g. ubi).
*/
add_mtd_device(mtd);
#endif


二、总结

如果需要初始化mtd模块

1、需要定义宏CONFIG_MTD_DEVICE

2、需要查找nand flash芯片资料,获取设备id,查找mtd是否默认支持,

      若不支持,考虑自行添加确保初始化正确

3、初始化基本的操作,读、写、刷新等等




你可能感兴趣的:(u-boot学习)