嵌入式Linux驱动学习【8】—— Nand Flash

1 Nand Flash

原理图。
在这里插入图片描述
引脚:

引脚功能
IO0~IO7数据输入输出
CLE命令锁存
ALE地址锁存
nCE芯片使能
nRE读使能
nWE写使能
R/nB就绪/忙

操作nand flash基本流程,传输命令–>地址–>数据,以读操作为例,时序图:
在这里插入图片描述
读操作主要有以下步骤:
1)选中芯片(NFCONT寄存器第1位)
2)清除RnB(NFSTAT寄存器第4位)
3)发出命令0x00(NFCMMD寄存器)
4)发出列地址(2次)(NFADDR)
5)发出页(行)地址(3次)(NFADDR)
6)发出命令0x30(NFCMMD寄存器)
7)等待RnB(NFSTAT寄存器第0位)
8)读数据(NFDATA寄存器)
9)取消片选(NFCONT寄存器第1位)

2 框架

2.1 框架

在这里插入图片描述

2.2 设备

在内核移植中,建立了mtd_partition结构体,描述了内核分区。在mini2440_machine_init中,将smdk2410_devices信息注册到platform。

static struct mtd_partition mini2440_default_nand_part[] = {
	[0] = {
		.name	= "supervivi",
		.size	= 0x00040000,
		.offset	= 0,
	},
	[1] = {
		.name	= "param",
		.offset = 0x00040000,
		.size	= 0x00020000,
	},
	[2] = {
		.name	= "Kernel",
		.offset = 0x00060000,
		.size	= 0x00500000,
	},
	[3] = {
		.name	= "root",
		.offset = 0x00560000,
		.size	= 1024 * 1024 * 1024, //
	},
	[4] = {
		.name	= "nand",
		.offset = 0x00000000,
		.size	= 1024 * 1024 * 1024, //
	}
};

2.3 驱动

在drivers/mtd/nand/s3c2410.c中,s3c2410_nand_init注册platform驱动s3c24xx_nand_driver,所以,当与设备相匹配时,会调用probe函数。

static int s3c24xx_nand_probe(struct platform_device *pdev)
{
	...
	err = s3c2410_nand_inithw(info);//初始化硬件
	...
	for (setno = 0; setno < nr_sets; setno++, nmtd++) {

		s3c2410_nand_init_chip(info, nmtd, sets);//初始化芯片

		nmtd->scan_res = nand_scan_ident(&nmtd->mtd,
						 (sets) ? sets->nr_chips : 1);//扫描nand flash

		if (nmtd->scan_res == 0) {
			s3c2410_nand_update_chip(info, nmtd);
			nand_scan_tail(&nmtd->mtd);
			s3c2410_nand_add_partition(info, nmtd, sets);//添加分区
		}

		if (sets != NULL)
			sets++;
	}
		...
}

nand_scan_ident–>nand_get_flash_type,与nand_flash_ids[]进行匹配,获取芯片类型信息。struct nand_flash_dev nand_flash_ids[ ]是全局类型,定义了一些NAND芯片的类型。
s3c2410_nand_add_partition()->add_mtd_partitions() -> add_one_partition()->add_mtd_device()创建分区。

int add_mtd_device(struct mtd_info *mtd)
{
	for (i=0; i < MAX_MTD_DEVICES; i++)
		if (!mtd_table[i]) {
			struct mtd_notifier *not;
			...
			list_for_each_entry(not, &mtd_notifiers, list)
				not->add(mtd);
		}
}

这里的mtd_notifiers,通过查找,看到是在register_mtd_user添加的。

void register_mtd_user (struct mtd_notifier *new)
{
	...
	list_add(&new->list, &mtd_notifiers);
    ...
}

而register_mtd_user,有两处调用:

mtdoops_console_init in mtdoops.c (drivers\mtd) : 	register_mtd_user(&mtdoops_notifier);
register_mtd_blktrans in mtd_blkdevs.c (drivers\mtd) : 		register_mtd_user(&blktrans_notifier);

以register_mtd_blktrans为例

static struct mtd_notifier blktrans_notifier = {
	.add = blktrans_notify_add,
	.remove = blktrans_notify_remove,
};


int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
{
    ...
	if (!blktrans_notifier.list.next)
		register_mtd_user(&blktrans_notifier);
	...
	list_add(&tr->list, &blktrans_majors);
	...
}

所以上述add_mtd_device最终调用了blktrans_notify_add。

static void blktrans_notify_add(struct mtd_info *mtd)
{
	struct mtd_blktrans_ops *tr;

	if (mtd->type == MTD_ABSENT)
		return;

	list_for_each_entry(tr, &blktrans_majors, list)
		tr->add_mtd(tr, mtd);
}

blktrans_majors在register_mtd_blktrans添加。

static struct mtd_blktrans_ops mtdblock_tr = {
	.name		= "mtdblock",
	.major		= 31,
	.part_bits	= 0,
	.blksize 	= 512,
	.open		= mtdblock_open,
	.flush		= mtdblock_flush,
	.release	= mtdblock_release,
	.readsect	= mtdblock_readsect,
	.writesect	= mtdblock_writesect,
	.add_mtd	= mtdblock_add_mtd,
	.remove_dev	= mtdblock_remove_dev,
	.owner		= THIS_MODULE,
};

static int __init init_mtdblock(void)
{
	mutex_init(&mtdblks_lock);

	return register_mtd_blktrans(&mtdblock_tr);
}

所以上述not->add(mtd)的大致流程

not->add(mtd)  --->                //即blktrans_notify_add
	tr->add_mtd(tr, mtd);  --->   //即mtdblock_add_mtd
		add_mtd_blktrans_dev --->
			add_disk(gd);       //向内核注册gendisk结构体

3 程序

描述:编写nand flash驱动,建立分区,通过挂载,测试驱动功能。

#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/cpufreq.h>

#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/nand_ecc.h>
#include <linux/mtd/partitions.h>

#include <asm/io.h>

#include <plat/regs-nand.h>
#include <plat/nand.h>

struct s3c_nand_regs {
	unsigned long nfconf  ;
	unsigned long nfcont  ;
	unsigned long nfcmd   ;
	unsigned long nfaddr  ;
	unsigned long nfdata  ;
	unsigned long nfeccd0 ;
	unsigned long nfeccd1 ;
	unsigned long nfeccd  ;
	unsigned long nfstat  ;
	unsigned long nfestat0;
	unsigned long nfestat1;
	unsigned long nfmecc0 ;
	unsigned long nfmecc1 ;
	unsigned long nfsecc  ;
	unsigned long nfsblk  ;
	unsigned long nfeblk  ;
};

static struct nand_chip *s3c_nand;
static struct mtd_info *s3c_mtd;
static struct s3c_nand_regs *s3c_nand_regs;

static struct mtd_partition s3c_nand_parts[] = {
	[0] = {
		.name	= "supervivi",
		.size	= 0x00040000,
		.offset	= 0,
	},
	[1] = {
		.name	= "param",
		.offset = 0x00040000,
		.size	= 0x00020000,
	},
	[2] = {
		.name	= "Kernel",
		.offset = 0x00060000,
		.size	= 0x00500000,
	},
	[3] = {
		.name	= "root",
		.offset = 0x00560000,
		.size	= 1024 * 1024 * 1024, //
	},
	[4] = {
		.name	= "nand",
		.offset = 0x00000000,
		.size	= 1024 * 1024 * 1024, //
	}
};


static void s3c2440_select_chip(struct mtd_info *mtd, int chipnr)
{
	if (chipnr == -1)
	{
		/* 取消选中: NFCONT[1]设为1 */
		s3c_nand_regs->nfcont |= (1<<1);		
	}
	else
	{
		/* 选中: NFCONT[1]设为0 */
		s3c_nand_regs->nfcont &= ~(1<<1);
	}
}

static void s3c2440_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl)
{
	if (ctrl & NAND_CLE)
	{
		/* 发命令: NFCMMD=dat */
		s3c_nand_regs->nfcmd = dat;
	}
	else
	{
		/* 发地址: NFADDR=dat */
		s3c_nand_regs->nfaddr = dat;
	}
}


static int s3c2440_dev_ready(struct mtd_info *mtd)
{
	return (s3c_nand_regs->nfstat & (1<<0));
}


static int s3c_nand_init(void)
{
	struct clk *clk;
	
	/* 1. 分配一个nand_chip结构体 */
	s3c_nand = kzalloc(sizeof(struct nand_chip), GFP_KERNEL);

	s3c_nand_regs = ioremap(0x4E000000, sizeof(struct s3c_nand_regs));
	
	/* 2. 设置nand_chip */
	/* 设置nand_chip是给nand_scan函数使用的, 如果不知道怎么设置, 先看nand_scan怎么使用 
	 * 它应该提供:选中,发命令,发地址,发数据,读数据,判断状态的功能
	 */
	s3c_nand->select_chip = s3c2440_select_chip;
	s3c_nand->cmd_ctrl    = s3c2440_cmd_ctrl;
	s3c_nand->IO_ADDR_R   = &s3c_nand_regs->nfdata;
	s3c_nand->IO_ADDR_W   = &s3c_nand_regs->nfdata;
	s3c_nand->dev_ready   = s3c2440_dev_ready;
	s3c_nand->ecc.mode    = NAND_ECC_SOFT;
	
	/* 3. 硬件相关的设置: 根据NAND FLASH的手册设置时间参数 */
	/* 使能NAND FLASH控制器的时钟 */
	clk = clk_get(NULL, "nand");
	clk_enable(clk);              /* CLKCON'bit[4] */
	
	/* HCLK=100MHz
	 * TACLS:  发出CLE/ALE之后多长时间才发出nWE信号, 从NAND手册可知CLE/ALE与nWE可以同时发出,所以TACLS=0
	 * TWRPH0: nWE的脉冲宽度, HCLK x ( TWRPH0 + 1 ), 从NAND手册可知它要>=12ns, 所以TWRPH0>=1
	 * TWRPH1: nWE变为高电平后多长时间CLE/ALE才能变为低电平, 从NAND手册可知它要>=5ns, 所以TWRPH1>=0
	 */
#define TACLS    0
#define TWRPH0   1
#define TWRPH1   0
	s3c_nand_regs->nfconf = (TACLS<<12) | (TWRPH0<<8) | (TWRPH1<<4);

	/* NFCONT: 
	 * BIT1-设为1, 取消片选 
	 * BIT0-设为1, 使能NAND FLASH控制器
	 */
	s3c_nand_regs->nfcont = (1<<1) | (1<<0);
	
	/* 4. 使用: nand_scan */
	s3c_mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
	s3c_mtd->owner = THIS_MODULE;
	s3c_mtd->priv  = s3c_nand;
	
	nand_scan(s3c_mtd, 1);  /* 识别NAND FLASH, 构造mtd_info */
	
	/* 5. add_mtd_partitions */
	add_mtd_partitions(s3c_mtd, s3c_nand_parts, 4);
	
	//add_mtd_device(s3c_mtd);
	return 0;

}

static void s3c_nand_exit(void)
{
	del_mtd_partitions(s3c_mtd);
	kfree(s3c_mtd);
	iounmap(s3c_nand_regs);
	kfree(s3c_nand);
}

module_init(s3c_nand_init);
module_exit(s3c_nand_exit);

MODULE_LICENSE("GPL");


4 测试

1)make menuconfig去掉内核自带的NAND FLASH驱动

-> Device Drivers
	-> Memory Technology Device (MTD) support
		-> NAND Device Support
			< >   NAND Flash support for S3C2410/S3C2440 SoC

2)make uImage
使用新内核启动, 并且使用NFS作为根文件系统。
3)insmod s3c_nand.ko
查看:ls -l /dev/mtd*
查看分区信息:cat /proc/partitions
此时看到/dev/mtdblock*是字符设备,
执行mdev -s,若不执行,直接挂接,出现问题:
mount: mounting /dev/mtdblock3 on /mnt/ failed: Block device required
4)格式化 (参考下面编译工具)
flash_eraseall /dev/mtd3 // yaffs
5)挂接
mount -t yaffs /dev/mtdblock3 /mnt
6)在/mnt目录下建文件
卸载目录,重新启动,再挂接,还能看到建立的文件。

编译工具:
1)tar xjf mtd-utils-05.07.23.tar.bz2
2)cd mtd-utils-05.07.23/util
修改Makefile:
#CROSS=arm-linux-
改为
CROSS=arm-linux-
3)make
4)cp flash_erase flash_eraseall /work/nfs_root/first_fs/bin/


版权声明:本文为chen06130原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。