Flash驱动

Flash 在嵌入式系统中是必不可少的,它是 BootLoader、Linux 内核和文件系统的最佳载体。在 Linux 内核中,引入了 MTD 层为 NOR Flash 和 NAND Flash 设备提供统一的接口,从而使得 Flash 驱动的设计工作大为简化。

Linux MTD 系统层次
在 Linux 系统中,提供了 MTD(Memory Technology Device,内存技术设备)系统来建立 Flash 针对 Linux 的统一、抽象的接口。MTD 将文件系统与底层的 Flash 存储器进行了隔离,使 Flash 驱动工程师无须关心 Flash作为字符设备和块设备与 Linux 内核的 接口。

下面列出了linux MTD系统的架构:


如上图所示,在引入 MTD后,Linux 系统中的 Flash 设备驱动及接口可分为 4 层,从上到下依次是:设备节点、MTD 设备层、MTD原始设备层和硬件驱动层,这 4 层的作用如下:

1.硬件驱动层:

Flash 硬件驱动层负责 Flash 硬件设备的读,擦除,写、Linux MTD设备的 NOR Flash 芯片驱动位于 drivers/mtd/chips 子目录下,NAND 型 Flash的驱动程序则位于/drivers/mtd/nand子目录下。
2.MTD 原始设备层:

MTD 原始设备层由两部分组成,一部分是 MTD 原始设备的通用代码,另一部分是各个特定的 Flash 的数据,例如分区。
3.MTD 设备层:

基于 MTD 原始设备,Linux 系统可以定义出 MTD 的块设备(主设备号 31)和字符设备(设备号 90),构成 MTD 设备层。MTD 字符设备的定义在 mtdchar.c 中实现,通过注册一系列 file_operation 函数(lseek、open、close、read、write、ioctl)可实现对 MTD 设备的读写和控制。MTD块设备则是定义了一个描述 MTD 块设备的结构 mtdblk_dev,并声明了一个名为 mtdblks 的指针数组,这数组中的每一个 mtdblk_dev 和 mtd_table 中的每一个 mtd_info 一一对应。
4.设备节点:
通过 mknod 在/dev 子目录下建立 MTD 字符设备节点(主设备号为 90)和 MTD 块设备节点(主设备号为 31),用户通过访问此设备节点即可访问 MTD字符设备和块设备。


Linux MTD 系统接口


如上图所示,在引入 MTD 后,底层 Flash 驱动直接与 MTD 原始设备层交互,利用其提供的接口注册设备和分区。用于描述 MTD 原始设备的数据结构是mtd_info,这其中定义了大量关于 MTD 的数据和操作函数,这个结构体的定义如下代码所示。mtd_info 是表示MTD 原始设备的结构体,每个分区也被认为是一个 mtd_info,例如,如果有两个 MTD 原始设备,而每个上有 3 个分区,在系统中就将共有 6 个 mtd_info 结构体,这些 mtd_info的指针被存放在名为 mtd_table 的数组里

mtd_info的定义:

struct mtd_info {
	u_char type;// 内存技术的类型
	u_int32_t flags;//标志位
	u_int32_t size;	 //mtd 设备的大小

	u_int32_t erasesize;//主要的擦除块大小(同一个
                            //mtd 设备可能有数种不同的 erasesize)
	/* 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;//写的大小?

	u_int32_t oobsize;   // Amount of OOB data per block (e.g. 16)// oob 数据大小
	u_int32_t oobavail;  // Available OOB bytes per block 每个块中可用的oob的大小

	// Kernel-only stuff starts here.
	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;//不同 erasesize 的区域的数目(通常是 1)
	struct mtd_erase_region_info *eraseregions;
        //此 routine 用于将一个 erase_info 加入 erase queue
	int (*erase) (struct mtd_info *mtd, struct erase_info *instr);

	/* This stuff for eXecute-In-Place *//*针对 eXecute-In-Place */
	int (*point) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char **mtdbuf);

	/* We probably shouldn't allow XIP if the unpoint isn't a NULL *//* 如果 unpoint 为空,不允许 XIP */
	void (*unpoint) (struct mtd_info *mtd, u_char * addr, loff_t from, size_t len);

        //读 Flash
	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);//写 Flash

	int (*read_oob) (struct mtd_info *mtd, loff_t from,
			 struct mtd_oob_ops *ops);//读 out-of-band
	int (*write_oob) (struct mtd_info *mtd, loff_t to,
			 struct mtd_oob_ops *ops);//写 out-of-band

	/*
	 * 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.
	 */
        //下面是读取flash中的寄存器,只读
	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);

	/* kvec-based read/write methods.
	   NB: The 'count' parameter is the number of _vectors_, each of
	   which contains an (ofs, len) tuple.
	*//* iovec-based 读写函数,对于 NAND Flash 需要针对它定义 */
	int (*writev) (struct mtd_info *mtd, const struct kvec *vecs, unsigned long count, loff_t to, size_t *retlen);

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

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

	/* Power Management functions *//* 电源管理函数*/
	int (*suspend) (struct mtd_info *mtd);
	void (*resume) (struct mtd_info *mtd);

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

	struct notifier_block reboot_notifier;  /* default mode before reboot */

	/* 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_info 的 type 字段

给出底层物理设备的类型,包括 MTD_RAM(内存)、MTD_ROM(只读内存)、MTD_ NORFlash(nor)、MTD_NANDFlash(nand)、MTD_PEROM 等,用于表示底层的设备是个什么设备。

flags 字段

包括 MTD_ERASEABLE(可擦除)、MTD_WRITEB_WRITEABLE(可编程)、MTD_XIP(可片内执行)、MTD_OOB(NAND 带外数据)、MTD_ECC(支持自动 ECC)等。某些内存技术支持带外数据(OOB),例如,NAND Flash 每 512字节就会有 16 个字节的
“额外数据” 用于存放纠错码或元数据。这是因为,所有 Flash器件都受位交换现象的困扰,而 NAND 发生的概率比 NOR 大,因此NAND 厂商推荐在使用 NAND 的时候最好要使用 ECC(Error Checking and Correcting),汉明码是最简单的 ECC。
ecctype 字段

表明了 ECC 的类型,包括 MTD_ECC_NONE(不支持自动 ECC)、MTD_ECC_ RS_DiskOnChip(DiskOnChip 上自动 ECC)、MTD_ECC_SW(Toshiba & Samsung 设备 SW ECC)

mtdcore.c (路径drivers\mtd)中定义了 MTD 设备数组:

struct mtd_info *mtd_table[MAX_MTD_DEVICES];

EXPORT_SYMBOL_GPL(mtd_table);
最多可以有 MAX_MTD_DEVICES(默认定义为 32)个设备,每个 MTD 分区也算一个 MTD 设备。
mtd_info 中的 read()、write()、read_ecc()、write_ecc()、read_oob()、write_oob()是 MTD 设备驱动要实现的主要函数,后面我们将看到,在 NOR 和 NAND 的驱动代码中几乎看不到 mtd_info 的成员函数(也即这些成员函数对于 Flash 芯片驱动是透明的),这是因为 Linux 在 MTD 的下层实现了针对 NOR Hash 和 NAND Hash 的通用的mtd_info 成员函数

Flash 驱动中使用如下两个函数注册和注销 MTD 设备(路径:drivers\mtd\Mtdcore.c ):

int add_mtd_device(struct mtd_info *mtd);
int del_mtd_device (struct mtd_info *mtd);
下面代码所示的 mtd_part 结构体用于描述分区,其 mtd_info 结构体成员用于描述本分区,它会被加入到 mtd_table 中,其大部分成员由其主分区 mtd_part->master 决定,各种函数也指向主分区的相应函数,而主分区(其大小涵盖所有分区)则不作为一个 MTD原始设备加入 mtd_table。

struct mtd_part {
	struct mtd_info mtd;//分区的信息(大部分由其 master 决定)
	struct mtd_info *master;//该分区的主分区
	u_int32_t offset;//该分区的偏移地址
	int index;//分区号
	struct list_head list;
	int registered;
};
mtd_partition 会在 MTD 原始设备层调用 add_mtd_partions()时传递分区信息用,这个结构体的定义如下所示。
struct mtd_partition {
	char *name;			/* 标识字符串 */
	u_int32_t size;			/* 分区大小 */
	u_int32_t offset;		/* 主 MTD 空间内的偏移*/
	u_int32_t mask_flags;		/* 掩码标志 */
	struct nand_ecclayout *ecclayout;	/* out of band layout for this partition (NAND only)*/
	struct mtd_info **mtdp;		/* pointer to store the MTD object */
};
Flash 驱动中使用如下两个函数注册和注销分区:
int add_mtd_partitions(struct mtd_info *, const struct mtd_partition *, int);
int del_mtd_partitions(struct mtd_info *);
add_mtd_partitions()会对每一个新建分区建立一个新的 mtd_part 结构体,将其加入 mtd_ partitions 中,并调用 add_mtd_device()将此分区作为 MTD 设备加入 mtd_table。成功时返回 0,如果分配 mtd_part 时内存不足,则返回-ENOMEM。
del_mtd_partitions()的作用是对于 mtd_partitions 上的每一个分区,如果它的主分区是 master(参数 master 是被删除分区的主分区)则将它从 mtd_partitions 和 mtd_table,中删除并释放掉,这个函数会调用 del_mtd_device()。
add_mtd_partitions()中新建的 mtd_part 需要依赖传入的 mtd_partition 参数对其进行初始化,代码如下所示:

int add_mtd_partitions(struct mtd_info *master,
		       const struct mtd_partition *parts,
		       int nbparts)
{
	struct mtd_part *slave;
	u_int32_t cur_offset = 0;
	int i;

	printk (KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n", nbparts, master->name);

	for (i = 0; i < nbparts; i++) {

		/* allocate the partition structure */
		slave = kzalloc (sizeof(*slave), GFP_KERNEL);
		if (!slave) {
			printk ("memory allocation error while creating partitions for \"%s\"\n",
				master->name);
			del_mtd_partitions(master);
			return -ENOMEM;
		}
		list_add(&slave->list, &mtd_partitions);

		/* 设置该分区的 MTD 对象 mtd_info*/
		slave->mtd.type = master->type;
		slave->mtd.flags = master->flags & ~parts[i].mask_flags;
		slave->mtd.size = parts[i].size;
		slave->mtd.writesize = master->writesize;
		slave->mtd.oobsize = master->oobsize;
		slave->mtd.oobavail = master->oobavail;
		slave->mtd.subpage_sft = master->subpage_sft;

		slave->mtd.name = parts[i].name;
		slave->mtd.owner = master->owner;

		slave->mtd.read = part_read;
		slave->mtd.write = part_write;

		if(master->point && master->unpoint){
			slave->mtd.point = part_point;
			slave->mtd.unpoint = part_unpoint;
		}

		if (master->read_oob)
			slave->mtd.read_oob = part_read_oob;
		if (master->write_oob)
			slave->mtd.write_oob = part_write_oob;
		if(master->read_user_prot_reg)
			slave->mtd.read_user_prot_reg = part_read_user_prot_reg;
		if(master->read_fact_prot_reg)
			slave->mtd.read_fact_prot_reg = part_read_fact_prot_reg;
		if(master->write_user_prot_reg)
			slave->mtd.write_user_prot_reg = part_write_user_prot_reg;
		if(master->lock_user_prot_reg)
			slave->mtd.lock_user_prot_reg = part_lock_user_prot_reg;
		if(master->get_user_prot_info)
			slave->mtd.get_user_prot_info = part_get_user_prot_info;
		if(master->get_fact_prot_info)
			slave->mtd.get_fact_prot_info = part_get_fact_prot_info;
		if (master->sync)
			slave->mtd.sync = part_sync;
		if (!i && master->suspend && master->resume) {
				slave->mtd.suspend = part_suspend;
				slave->mtd.resume = part_resume;
		}
		if (master->writev)
			slave->mtd.writev = part_writev;
		if (master->lock)
			slave->mtd.lock = part_lock;
		if (master->unlock)
			slave->mtd.unlock = part_unlock;
		if (master->block_isbad)
			slave->mtd.block_isbad = part_block_isbad;
		if (master->block_markbad)
			slave->mtd.block_markbad = part_block_markbad;
		slave->mtd.erase = part_erase;
		slave->master = master;
		slave->offset = parts[i].offset;
		slave->index = i;

		if (slave->offset == MTDPART_OFS_APPEND)
			slave->offset = cur_offset;
		if (slave->offset == MTDPART_OFS_NXTBLK) {
			slave->offset = cur_offset;
			if ((cur_offset % master->erasesize) != 0) {
				/* Round up to next erasesize */
				slave->offset = ((cur_offset / master->erasesize) + 1) * master->erasesize;
				printk(KERN_NOTICE "Moving partition %d: "
				       "0x%08x -> 0x%08x\n", i,
				       cur_offset, slave->offset);
			}
		}
		if (slave->mtd.size == MTDPART_SIZ_FULL)
			slave->mtd.size = master->size - slave->offset;
		cur_offset = slave->offset + slave->mtd.size;

		printk (KERN_NOTICE "0x%08x-0x%08x : \"%s\"\n", slave->offset,
			slave->offset + slave->mtd.size, slave->mtd.name);

		/* let's do some sanity checks */
		if (slave->offset >= master->size) {
				/* let's register it anyway to preserve ordering */
			slave->offset = 0;
			slave->mtd.size = 0;
			printk ("mtd: partition \"%s\" is out of reach -- disabled\n",
				parts[i].name);
		}
		if (slave->offset + slave->mtd.size > master->size) {
			slave->mtd.size = master->size - slave->offset;
			printk ("mtd: partition \"%s\" extends beyond the end of device \"%s\" -- size truncated to %#x\n",
				parts[i].name, master->name, slave->mtd.size);
		}
		if (master->numeraseregions>1) {
			/* Deal with variable erase size stuff */
			int i;
			struct mtd_erase_region_info *regions = master->eraseregions;

			/* Find the first erase regions which is part of this partition. */
			for (i=0; i < master->numeraseregions && slave->offset >= regions[i].offset; i++)
				;

			for (i--; i < master->numeraseregions && slave->offset + slave->mtd.size > regions[i].offset; i++) {
				if (slave->mtd.erasesize < regions[i].erasesize) {
					slave->mtd.erasesize = regions[i].erasesize;
				}
			}
		} else {
			/* Single erase size */
			slave->mtd.erasesize = master->erasesize;
		}

		if ((slave->mtd.flags & MTD_WRITEABLE) &&
		    (slave->offset % slave->mtd.erasesize)) {
			/* Doesn't start on a boundary of major erase size */
			/* FIXME: Let it be writable if it is on a boundary of _minor_ erase size though */
			slave->mtd.flags &= ~MTD_WRITEABLE;
			printk ("mtd: partition \"%s\" doesn't start on an erase block boundary -- force read-only\n",
				parts[i].name);
		}
		if ((slave->mtd.flags & MTD_WRITEABLE) &&
		    (slave->mtd.size % slave->mtd.erasesize)) {
			slave->mtd.flags &= ~MTD_WRITEABLE;
			printk ("mtd: partition \"%s\" doesn't end on an erase block -- force read-only\n",
				parts[i].name);
		}

		slave->mtd.ecclayout = master->ecclayout;
		if (master->block_isbad) {
			uint32_t offs = 0;

			while(offs < slave->mtd.size) {
				if (master->block_isbad(master,
							offs + slave->offset))
					slave->mtd.ecc_stats.badblocks++;
				offs += slave->mtd.erasesize;
			}
		}

		if(parts[i].mtdp)
		{	/* store the object pointer (caller may or may not register it */
			*parts[i].mtdp = &slave->mtd;
			slave->registered = 0;
		}
		else
		{
			/* register our partition */
			add_mtd_device(&slave->mtd);
			slave->registered = 1;
		}
	}

	return 0;
}
最后,为了使系统能支持 MTD 字符设备与块设备及 MTD 分区,在编译内核时应该包括相应的配置选项,如下所示。


NAND Flash 驱动

如上图所示,Linux 内核在 MTD的 下 层 实 现 了 通 用 的 NAND 驱 动 ( 主 要 通 过drivers/mtd/nand/nand_base.c文件实现),因此芯片级的 NAND 驱动不再需要实现 mtd_info 中的 read()、write()、read_oob()、write_oob()等成员函数,而主体转移到了nand_chip 数据结构。
MTD 使用 nand_chip 数据结构表示一个 NAND Flash 芯片,这个结构体中包含了关于 NAND Flash 的地址信息、读写方法、ECC 模式、硬件控制等一系列底层机制,其定义如下所示:

struct nand_chip {
	void  __iomem	*IO_ADDR_R;//读 8 根 I/O 线的地址
	void  __iomem	*IO_ADDR_W;//写 8 根 I/O 线的地址

	uint8_t		(*read_byte)(struct mtd_info *mtd);//从芯片读一个字节
	u16		(*read_word)(struct mtd_info *mtd);//从芯片读一个字
	void		(*write_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);//将缓冲区内容写入芯片
	void		(*read_buf)(struct mtd_info *mtd, uint8_t *buf, int len);//将芯片数据读到缓冲区
	int		(*verify_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);//验证芯片和写入缓冲区中的数据
	void		(*select_chip)(struct mtd_info *mtd, int chip);//控制 CE 信号
	int		(*block_bad)(struct mtd_info *mtd, loff_t ofs, int getchip);//检查是否为坏块
	int		(*block_markbad)(struct mtd_info *mtd, loff_t ofs);//标志坏块
	void		(*cmd_ctrl)(struct mtd_info *mtd, int dat,
				    unsigned int ctrl);
	int		(*dev_ready)(struct mtd_info *mtd);
	void		(*cmdfunc)(struct mtd_info *mtd, unsigned command, int column, int page_addr);//命令处理函数
	int		(*waitfunc)(struct mtd_info *mtd, struct nand_chip *this);
	void		(*erase_cmd)(struct mtd_info *mtd, int page);//擦除命令处理
	int		(*scan_bbt)(struct mtd_info *mtd);//扫描坏块
	int		(*errstat)(struct mtd_info *mtd, struct nand_chip *this, int state, int status, int page);
	int		(*write_page)(struct mtd_info *mtd, struct nand_chip *chip,
				      const uint8_t *buf, int page, int cached, int raw);

	int		chip_delay;
	unsigned int	options;

	int		page_shift;
	int		phys_erase_shift;
	int		bbt_erase_shift;
	int		chip_shift;//芯片特定的延迟
	int		numchips;
	unsigned long	chipsize;
	int		pagemask;
	int		pagebuf;
	int		subpagesize;
	uint8_t		cellinfo;
	int		badblockpos;

	nand_state_t	state;//芯片状态,如输入/输出

	uint8_t		*oob_poi;
	struct nand_hw_control  *controller;
	struct nand_ecclayout	*ecclayout;

	struct nand_ecc_ctrl ecc;
	struct nand_buffers *buffers;
	struct nand_hw_control hwcontrol;

	struct mtd_oob_ops ops;

	uint8_t		*bbt;
	struct nand_bbt_descr	*bbt_td;
	struct nand_bbt_descr	*bbt_md;

	struct nand_bbt_descr	*badblock_pattern;

	void		*priv;
};
先附上一副图:

与 NOR Flash 类似,由于有了 MTD 层,完成一个 NAND Flash 驱动在 Linux 中的工作量也很小,如上图所示,主要的工作如下。
(1)如果 Flash 要分区,则定义 mtd_partition 数组,将实际电路板中 Flash 分区信息记录于其中。
(2)在模块加载时分配和 nand_chip 的内存,根据目标板NAND 控制器的特殊情况初始化 nand_chip 中hwcontrol()、dev_ready()、
calculate_ecc()、correct_data()、read_byte()、write_byte()等成员函数(如果不赋值会使用 nand_base.c 中的默认函数),注意将 mtd_info 的 priv 置为 nand_chip。

(3) mtd_info 为参数调用 nand_scan()函数探测 NAND Flash是否存在,该函数的原型为:

int nand_scan (struct mtd_info *mtd, int maxchips);
nand_scan()函数会读取 NAND 芯片 ID,并根据 mtd->priv 即 nand_chip 中的成员初始化 mtd_info。
(4)如果要分区,则以 mtd_info 和 mtd_partition 为参数调用 add_mtd_partitions(),添加分区信息。

下面所示为一个简单的 NAND Flash 设备驱动模板:

#define CHIP_PHYSICAL_ADDRESS ...
#define NUM_PARTITIONS 2
static struct mtd_partition partition_info[] =
{
  {
    .name = "Flash partition 1", 
    .offset = 0, 
    .size = 8 * 1024 * 1024
  },

  {
    .name = "Flash partition 2", 
    .offset = MTDPART_OFS_NEXT, 
    .size = MTDPART_SIZ_FULL
  } ,
};
int __init board_init(void)
{
  struct nand_chip *this;
  int err = 0;
  /* 为 MTD 设备结构体和 nand_chip 分配内存 */
  board_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip),GFP_KERNEL);
  if (!board_mtd)
  {
    printk("Unable to allocate NAND MTD device structure.\n");
    err = - ENOMEM;
    goto out;
  }
  /* 初始化结构体 */
  memset((char*)board_mtd,0,sizeof(struct mtd_info) + sizeof(struct nand_chip));
  /* 映射物理地址 */
  baseaddr = (unsigned long)ioremap(CHIP_PHYSICAL_ADDRESS, 1024);
  if (!baseaddr)
  {
    printk("Ioremap to access NAND chip failed\n");
    err = - EIO;
    goto out_mtd;
  }
  /* 获得私有数据(nand_chip)指针 */
  this = (struct nand_chip*)(&board_mtd[1]);
  /* 将 nand_chip 赋予 mtd_info 私有指针 */
  board_mtd->priv = this;
  /* 设置 NAND Flash 的 I/O 基地址 */
  this->IO_ADDR_R = baseaddr;
  this->IO_ADDR_W = baseaddr;
  /* 硬件控制函数 */
  this->hwcontrol = board_hwcontrol;
  /* 从数据手册获知命令延迟时间 */
  this->chip_delay = CHIP_DEPENDEND_COMMAND_DELAY;
  /* 初始化设备 ready 函数 */
  this->dev_ready = board_dev_ready;
  this->eccmode = NAND_ECC_SOFT;
  /* 扫描以确定设备的存在 */
  if (nand_scan(board_mtd, 1))
  {
    err = - ENXIO;
    goto out_ior;
  }
  //添加分区
  add_mtd_partitions(board_mtd, partition_info, NUM_PARTITIONS);
  goto out;
  out_ior: iounmap((void*)baseaddr);
  out_mtd: kfree(board_mtd);
  out: return err;
}
static void _ _exit board_cleanup(void)
{
  /* 释放资源,注销设备 */
  nand_release(board_mtd);
  /* unmap 物理地址 */
  iounmap((void*)baseaddr);
  /* 释放 MTD 设备结构体 */
  kfree(board_mtd);
}

  /* GPIO 方式的硬件控制 */
static void board_hwcontrol(struct mtd_info *mtd, int cmd)
{
  switch (cmd)
  {
    case NAND_CTL_SETCLE:
      /* Set CLE pin high */
      break;
    case NAND_CTL_CLRCLE:
      /* Set CLE pin low */
      break;
    case NAND_CTL_SETALE:
      /* Set ALE pin high */
      break;
    case NAND_CTL_CLRALE:
      /* Set ALE pin low */
      break;
    case NAND_CTL_SETNCE:
      /* Set nCE pin low */
      break;
    case NAND_CTL_CLRNCE:
      /* Set nCE pin high */
      break;
   }
}
/* 返回设备 ready 状态 */
static int board_dev_ready(struct mtd_info *mtd)
{
    return xxx_read_ready_bit();
}

S3C2410 NAND 控制器硬件描述

S3C2410 处理器集成了一个 NAND 控制器,它提供如下引脚。
1.D[7:0]:数据/命令/地址 I/O 端口(数据、命令、地址复用)。
2.CLE:命令锁存使能(数据线上 NAND Flash 命令有效,输出)。
3.ALE:地址锁存使能(数据线上 NAND Flash 地址有效,输出)。
4.nFCE:NAND Flash 片选(输出)。
5.nFRE:NAND Flash 读使能(输出)。
6.nFWE:NAND Flash 写使能(输出)。
7.R/nB:NAND Flash 准备好/忙(输入)。

8.NCON:输入,NAND Flash 内存地址步长选择,0:表示 3 步长地址,1:表示 4 步长地址(NAND Flash 中地址要通过 d[7:0]送多次,每送一次就为一步长)。


S3C2410 对 NAND Flash 的操作通过 NFCONF、NFCMD、NFADDR、NFDATA、NFSTAT 和 NFECC 这 6 个寄存器来完成。

1.NFCONF:NAND Flash 配置寄存器,用于使能 NAND Flash 控制器、初始化ECC、并设置 NAND Flash 片选信号 nFCE 为 1(即不选中)。
2.NFCMD:NAND Flash 命令寄存器,对于 page 大小不一样的不同型号的NAND,其操作命令可能不一样。本节的例子芯片为 K9F1208U0M,在其数据手册中该芯片命令集的有关描述。
3.NFADDR:NAND Flash 地址寄存器。
4.NFDATA:NAND Flash 数据寄存器,8 位。
5.NFSTAT:NAND Flash 状态寄存器,位 0 如果为 0 表示设备忙,否则表示设备准备好。
6.NFECC:NAND Flash 校验寄存器。S3C2410 NAND 控制器内置的 ECC 生成模块执行以下任务:当写入数据时,ECC 生成模块产生一个 ECC 码;当读数据时,ECC 生成模块产生一个 ECC 码,驱动中可以将该 ECC 码与原先存入 OOB 中的校验码进行比较。

通过 S3C2410 的 NAND 控制器访问 NAND Flash 的一般流程如下。
(1)设置 NAND Flash 配置寄存器 NFCONF。
(2)在 NFCMD 寄存器中写入 NAND Flash 命令。
(3)在 NFADDR 寄存器中写入地址。
(4)读/写数据,通过 NFSTAT 寄存器检查 NAND Flash 状态,在读/写操作后 R/nB 信号应该被检查。

nand_chip 初始化和成员函数
nand_chip 是 NAND Flash 驱动的核心数据结构,这个结构体中的成员直接对应着NAND Flash 的底层操作,针对具体的 NAND 控制器情况,本驱动中初始化了write_buf()、read_buf()、select_chip()、chip_delay()及几个 ECC 相关的成员函数。

下面分析一下nandchip的驱动实现:

路径: drivers\mtd\nand\S3c2410.c

首先是初始化和卸载函数:

static int __init s3c2410_nand_init(void)
{
	printk("S3C24XX NAND Driver, (c) 2004 Simtec Electronics\n");

	platform_driver_register(&s3c2412_nand_driver);
	platform_driver_register(&s3c2440_nand_driver);
	return platform_driver_register(&s3c2410_nand_driver);
}

static void __exit s3c2410_nand_exit(void)
{
	platform_driver_unregister(&s3c2412_nand_driver);
	platform_driver_unregister(&s3c2440_nand_driver);
	platform_driver_unregister(&s3c2410_nand_driver);
}

module_init(s3c2410_nand_init);
module_exit(s3c2410_nand_exit);
这里支持很多的2410,2440,2412各个CPU,选2440看下:

定义如下:

static struct platform_driver s3c2440_nand_driver = {
	.probe		= s3c2440_nand_probe,//当和设备匹配后调用
	.remove		= s3c2410_nand_remove,
	.suspend	= s3c24xx_nand_suspend,//电源管理相关
	.resume		= s3c24xx_nand_resume,
	.driver		= {
		.name	= "s3c2440-nand",
		.owner	= THIS_MODULE,
	},
};
看下probe是如何对nand初始化的:

static int s3c2440_nand_probe(struct platform_device *dev)
{
	return s3c24xx_nand_probe(dev, TYPE_S3C2440);
}
继续:

static int s3c24xx_nand_probe(struct platform_device *pdev,
			      enum s3c_cpu_type cpu_type)
{
	struct s3c2410_platform_nand *plat = to_nand_plat(pdev);//控制器的时序信息和片选操作
	struct s3c2410_nand_info *info;
	struct s3c2410_nand_mtd *nmtd;
	struct s3c2410_nand_set *sets;
	struct resource *res;
	int err = 0;
	int size;
	int nr_sets;
	int setno;

	pr_debug("s3c2410_nand_probe(%p)\n", pdev);

	info = kmalloc(sizeof(*info), GFP_KERNEL);//给s3c2410_nand_info分配内存
	if (info == NULL) {
		dev_err(&pdev->dev, "no memory for flash info\n");
		err = -ENOMEM;
		goto exit_error;
	}

	memzero(info, sizeof(*info));
	platform_set_drvdata(pdev, info);//将s3c2410_nand_info放到platform dev上

	spin_lock_init(&info->controller.lock);//初始化锁
	init_waitqueue_head(&info->controller.wq);//初始化等待队列

	/* get the clock source and enable it */

	info->clk = clk_get(&pdev->dev, "nand");//获取nandflash的时钟
	if (IS_ERR(info->clk)) {
		dev_err(&pdev->dev, "failed to get clock");
		err = -ENOENT;
		goto exit_error;
	}

	clk_enable(info->clk);//使能它

	/* allocate and map the resource */

	/* currently we assume we have the one resource */
	res  = pdev->resource;
	size = res->end - res->start + 1;

	info->area = request_mem_region(res->start, size, pdev->name);//分配资源

	if (info->area == NULL) {
		dev_err(&pdev->dev, "cannot reserve register region\n");
		err = -ENOENT;
		goto exit_error;
	}

	info->device     = &pdev->dev;
	info->platform   = plat;
	info->regs       = ioremap(res->start, size);
	info->cpu_type   = cpu_type;

	if (info->regs == NULL) {
		dev_err(&pdev->dev, "cannot reserve register region\n");
		err = -EIO;
		goto exit_error;
	}

	dev_dbg(&pdev->dev, "mapped registers at %p\n", info->regs);

	/* initialise the hardware */

	err = s3c2410_nand_inithw(info, pdev);//硬件时序的初始化
	if (err != 0)
		goto exit_error;

	sets = (plat != NULL) ? plat->sets : NULL;
	nr_sets = (plat != NULL) ? plat->nr_sets : 1;

	info->mtd_count = nr_sets;

	/* allocate our information */

	size = nr_sets * sizeof(*info->mtds);
	info->mtds = kmalloc(size, GFP_KERNEL);
	if (info->mtds == NULL) {
		dev_err(&pdev->dev, "failed to allocate mtd storage\n");
		err = -ENOMEM;
		goto exit_error;
	}

	memzero(info->mtds, size);

	/* initialise all possible chips */

	nmtd = info->mtds;

	for (setno = 0; setno < nr_sets; setno++, nmtd++) {
		pr_debug("initialising set %d (%p, info %p)\n", setno, nmtd, info);

		s3c2410_nand_init_chip(info, nmtd, sets);//读写函数和硬件ecc的初始化

		nmtd->scan_res = nand_scan(&nmtd->mtd, (sets) ? sets->nr_chips : 1);//扫描是否有这个nand设备,就用到了上面初始化读写函数中的指针了

		if (nmtd->scan_res == 0) {
			s3c2410_nand_add_partition(info, nmtd, sets);//最终调用到了drivers\mtd\
Mtdcore.c中的add_mtd_device来添加分区
}if (sets != NULL)sets++;}if (allow_clk_stop(info)) {dev_info(&pdev->dev, "clock idle support enabled\n");clk_disable(info->clk);}pr_debug("initialised ok\n");return 0; exit_error:s3c2410_nand_remove(pdev);if (err == 0)err = -EINVAL;return err;}








  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值