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/