Nand-flash存储器是flash存储器的一种,其内部采用非线性宏单元模式,为固态大容量内存的实现提供了廉价有效的解决方案。Nand-flash存储器具有容量较大,改写速度快等优点,适用于大量数据的存储,因而在业界得到了越来越广泛的应用,如嵌入式产品中包括数码相机、MP3随身听记忆卡、体积小巧的U盘等。
本人使用的是韦东山老师的JZ2440开发板,CPU是S3C2440A,上面所使用的NAND FLASH芯片为K9F2G08U0C。
在S3C2440A芯片手册第6章NAND FLASH CONTORLLER中提到引脚配置相关内容:
查看开发板上的nand flash即K9F2G08U0C芯片手册有如下信息:
可以得知K9F2G08U0C的信息:
1. Page Size为2KByte
2. 8bit bus width
2. 5 address cycles
所以对于我们的S3C2440A芯片应该设置引脚NCON为1(即接上拉电阻),引脚GPG13为1(即接上拉电阻),引脚GPG14为1(即接上拉电阻),引脚GPG15为0(即接上下拉电阻)。这些只需要硬件上进行配置即可。原理图连接如下:
在K9F2G08U0C芯片手册中有如下定义:
该引脚指示程序、擦除或随机读取操作正在进行中,完成后返回到高状态。连接到S3C2440的RnB引脚,可以通过判断S3C2440的NFSTAT寄存器的bit0位,为0表示该NAND Flash正忙,此时不可操作。
高电平有效。
高电平有效。
连接到S3C2440的nFCE引脚,可以通过设置S3C2440的NFCNT寄存器的bit1位,为0表示选中该NAND Flash芯片,为1表示取消选中。
在信号脉冲上升沿进行命令、地址和数据的写。
在信号脉冲上升沿进行读出8个i/o数据。
当不使用S3C2440A的NAND FLASH控制器来操作NAND FLASH时,我们需要执行以下步骤来读ID:
1. 使能CE片选
2. 使能CLE
3. 发送0X90命令,并发出WE写脉冲
4. 复位CLE,然后使能ALE
5. 发送0X00地址,并发出WE写脉冲
6. 设CLE和ALE为低电平
7. while判断nRE(读使能)是否为低电平
8. 读出8个I/O的数据,并发出RE上升沿脉冲
S3C2440A中有个NAND FLASH控制器,它会自动控制CLE,ALE那些控制引脚,我们只需要配置控制器,就可以直接写命令,写地址,读写数据到它的寄存器中便能完成(读写数据之前需要判断RnB脚),如下图所示:
当使用NAND FLASH控制器时,我们读ID就只需要如下几步:
1. 设置寄存器NFCONT(0x4E000004)的bit1=0,使能片选
2. 写入寄存器NFCMMD(0x4E000008)=0X90,发送命令
3. 写入寄存器NFADDR(0x4E00000C)=0X00,发送地址
4. while判断nRE(读使能)是否为低电平
5. 读取寄存器NFDATA(0x4E000010),来读取数据
在开发板UBOOT操作如下:
可以看到读出正确的厂家ID和设备ID了。退出读ID、读数据或写数据都执行“mw.b 0x4e000008 0xff”即可。
读操作的时序如下:
K9F2G08U0C芯片实际可用容量为256M字节=128k*2k字节,页大小为2k字节(对应column Address),总共有128k(对应row Address)页,所以使用8个i/o发地址时需要发以下方式的地址(发5个地址周期):
所以row Address=128k=2^17(A27~A11),所以column Address=2k=2^11( A10~A0)。
接下来分析内核(linux-2.6.22.6)是如何使用NAND Flash的。内核自带的驱动在drivers/mtd/nand/s3c2410.c。
该驱动使用platform平台设备驱动构造,当与设备匹配后会调用到platform_driver->probe即s3c2412_nand_probe()函数,部分代码如下:
static int s3c24xx_nand_probe(struct platform_device *pdev, enum s3c_cpu_type cpu_type)
{
... ...
err = s3c2410_nand_inithw(info, pdev); //初始化硬件hardware,设置TACLS 、TWRPH0、TWRPH1通信时序等
s3c2410_nand_init_chip(info, nmtd, sets); //初始化芯片
nmtd->scan_res = nand_scan(&nmtd->mtd, (sets) ? sets->nr_chips : 1); //扫描nandflash
... ...
s3c2410_nand_add_partition(info, nmtd, sets); //调用add_mtd_partitions()来添加mtd分区
... ...
}
其中nand_scan()函数调用如下:
nand_scan() //扫描nandflash
nand_set_defaults() //设置nand_chip默认函数
nand_scan_ident()
nand_get_flash_type() //获取flash存储器的类型
nand_scan_tail() //构造mtd设备的成员(实现对nandflash的读,写,擦除等)
nand_get_flash_type()函数部分代码如下:
static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,struct nand_chip *chip,int busw, int *maf_id)
{
struct nand_flash_dev *type = NULL;
int i, dev_id, maf_idx;
chip->select_chip(mtd, 0); //调用nand_chip结构体的成员select_chip使能flash片选
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); //调用nand_chip结构体的成员cmdfunc,发送读id命令,最后数据保存在mtd结构体里
*maf_id = chip->read_byte(mtd); // 获取厂家ID,
dev_id = chip->read_byte(mtd); //获取设备ID
/*for循环匹配nand_flash_ids[]数组,找到对应的nandflash信息*/
for (i = 0; nand_flash_ids[i].name != NULL; i++)
{
if (dev_id == nand_flash_ids[i].id) //匹配设备ID
{
type = &nand_flash_ids[i];
break;
}
}
... ...
/*匹配成功,便打印nandflash参数 */
printk(KERN_INFO "NAND device: Manufacturer ID:"
" 0x%02x, Chip ID: 0x%02x (%s %s)\n", *maf_id,
dev_id, nand_manuf_ids[maf_idx].name, mtd->name);
... ...
}
nand_chip结构体就是保存与硬件操作相关的函数(重点)。与nand_flash_ids[]数组进行ID匹配,数组有如下红框部分:
正好有设备ID 0xDA,所以启动内核时的打印信息如下:
nand_scan_tail()函数可以看出,全都是对struct nand_chip的成员的补充(nand_set_defaults() 里填充了一部分操作函数),struct nand_chip结构如下:
struct nand_chip {
void __iomem *IO_ADDR_R; /* 需要读出数据的nandflash地址 */
void __iomem *IO_ADDR_W; /* 需要写入数据的nandflash地址 */
/* 从芯片中读一个字节 */
uint8_t (*read_byte)(struct mtd_info *mtd);
/* 从芯片中读一个字 */
u16 (*read_word)(struct mtd_info *mtd);
/* 将缓冲区内容写入nandflash地址, len:数据长度*/
void (*write_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);
/* 读nandflash地址至缓冲区, 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);
/* 选中芯片,当chip==0表示选中,chip==-1时表示取消选中 */
void (*select_chip)(struct mtd_info *mtd, int chip);
/* 检测是否有坏块 */
int (*block_bad)(struct mtd_info *mtd, loff_t ofs, int getchip);
/* 标记坏块 */
int (*block_markbad)(struct mtd_info *mtd, loff_t ofs);
/* 命令、地址控制函数 , dat :要传输的命令/地址 */
/*当ctrl的bit[1]==1: 表示要发送的dat是命令
bit[2]==1: 表示要发送的dat是地址
bit[0]==1:表示使能nand , ==0:表示禁止nand
具体可以参考内核的nand_command_lp()函数,它会调用这个cmd_crtl函数实现功能*/
void (*cmd_ctrl)(struct mtd_info *mtd, int dat,unsigned int ctrl);
/* 设备是否就绪,当该函数返回的RnB引脚的数据等于1,表示nandflash已就绪 */
int (*dev_ready)(struct mtd_info *mtd);
/* 实现命令发送,最终调用nand_chip -> cmd_ctrl来实现 */
void (*cmdfunc)(struct mtd_info *mtd, unsigned command, int column, int page_addr);
/*等待函数,通过nand_chip ->dev_ready来等待nandflash是否就绪 */
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; /* 由板决定的延迟时间 */
/* 与具体的NAND芯片相关的一些选项,默认为8位宽nand,
比如设置为NAND_BUSWIDTH_16,表示nand的总线宽为16 */
unsigned int options;
/* 用位表示的NAND芯片的page大小,如某片NAND芯片
* 的一个page有512个字节,那么page_shift就是9
*/
int page_shift;
/* 用位表示的NAND芯片的每次可擦除的大小,如某片NAND芯片每次可
* 擦除16K字节(通常就是一个block的大小),那么phys_erase_shift就是14
*/
int phys_erase_shift;
/* 用位表示的bad block table的大小,通常一个bbt占用一个block,
* 所以bbt_erase_shift通常与phys_erase_shift相等
*/
int bbt_erase_shift;
/* 用位表示的NAND芯片的容量 */
int chip_shift;
/* NADN FLASH芯片的数量 */
int numchips;
/* NAND芯片的大小 */
uint64_t 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; /* ECC布局 */
/* ECC校验结构体,若不设置, ecc.mode默认为NAND_ECC_NONE(无ECC校验) */
/*可以为硬件ECC和软件ECC校验,比如:设置ecc.mode=NAND_ECC_SOFT(软件ECC校验)*/
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;
};
s3c2410_nand_add_partition()函数代码如下:
static int s3c2410_nand_add_partition(struct s3c2410_nand_info *info,
struct s3c2410_nand_mtd *mtd,
struct s3c2410_nand_set *set)
{
if (set == NULL)
return add_mtd_device(&mtd->mtd); //创建1个分区mtd设备
if (set->nr_partitions > 0 && set->partitions != NULL) {
return add_mtd_partitions(&mtd->mtd, set->partitions, set->nr_partitions); //创建多个分区mtd设备
}
return add_mtd_device(&mtd->mtd);
}
其中add_mtd_partitions()函数主要实现多个分区(如常见的4个分区方式:bootloader、params、kernel、root)创建,也就是多次调用add_mtd_device(),当只设置nand_flash为一个分区时,就直接调用add_mtd_device()即可。
add_mtd_partitions()函数中有如下代码:
/***************************************************
* master:要创建的mtd设备
* parts:分区信息的数组,它的结构体是mtd_partition
* nbparts:要创建的分区个数
**************************************************/
int add_mtd_partitions(struct mtd_info *master,
const struct mtd_partition *parts,
int nbparts)
{
... ...
printk (KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n", nbparts, master->name); //打印信息
for (i = 0; i < nbparts; i++) //循环次数为要创建的分区个数
{
... ...
printk (KERN_NOTICE "0x%08x-0x%08x : \"%s\"\n", slave->offset,
slave->offset + slave->mtd.size, slave->mtd.name); //打印信息
... ...
add_mtd_device(&slave->mtd); //创建一个分区
}
}
上面打印信息在内核启动时可以看到(修改过数组参数的内核):
对于s3c2410.c这个内核自带的nand flash驱动文件其中的打印参数实际使用到了下面的数组(位于arch/arm/plat-s3c24xx/common-smdk.c中,修改内核源码过后的分区):
static struct mtd_partition smdk_default_nand_part[] = {
[0] = {
.name = "bootloader",
.size = 0x00040000,
.offset = 0,
},
[1] = {
.name = "params",
.offset = MTDPART_OFS_APPEND,
.size = 0x00020000,
},
[2] = {
.name = "kernel",
.offset = MTDPART_OFS_APPEND,
.size = 0x00200000,
},
[3] = {
.name = "root",
.offset = MTDPART_OFS_APPEND,
.size = MTDPART_SIZ_FULL,
}
};
接下来我们看创建一个分区 add_mtd_device()函数是怎么操作的。
add_mtd_device()函数部分代码如下:
int add_mtd_device(struct mtd_info *mtd) //创建一个mtd设备
{
struct list_head *this;
... ...
for (i=0; i < MAX_MTD_DEVICES; i++)
{
... ...
mtd_table[i] = mtd; //将这个mtd_info填充到全局数组mtd_table里
... ...
list_for_each(this, &mtd_notifiers) //遍历mtd_notifiers链表
{
struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list); //通过list_head找到struct mtd_notifier *not
not->add(mtd); //最后调用mtd_notifier的add()函数,里面进行创建设备节点
}
}
... ...
}
首先将这个mtd_info结构体指针存到到全局数组mtd_table里,接着遍历mtd_notifiers链表,调用链表里的每一个mtd_notifier结构的成员add()函数。
查找“mtd_notifiers”定位到mtd_notifiers链表是在register_mtd_user()里添加的:
实际就是在该函数里创建设备节点(创建字符设备或或块设备、创建类操作在其他驱动模块里进行的),后面会分析到。
void register_mtd_user (struct mtd_notifier *new)
{
... ...
list_add(&new->list, &mtd_notifiers);
... ...
for (i=0; i< MAX_MTD_DEVICES; i++) //循环调用mtd_notifier->add函数
if (mtd_table[i])
new->add(mtd_table[i]);
}
如上图,找到被drivers/mtd/mtdchar.c、drivers/mtd/mtd_blkdevs.c调用。这里有一个巧妙的运用,无论是mtdchar.c和mtd_blkdevs.c(调用了register_mtd_user)先被加载还是s3c2410.c(调用了add_mtd_device)先被加载,后来加载的驱动文件都会调用到mtd_notifier->add()函数。
到这里整理一下思路:
1. mtdchar.c、mtd_blkdevs.c两个文件调用register_mtd_user()函数为mtd_notifiers链表添加结构
2. 驱动程序s3c2410.c调用add_mtd_device()函数来遍历mtd_notifiers链表里的每一个结构的add()函数
所以mtd_notifiers链表里有两个结构,如果驱动程序s3c2410.c创建4个分区就会调用4次add_mtd_device()函数来遍历mtd_notifiers链表,也就是会调用8次add()函数(两种,一种是mtdchar.c里添加的add函数,一种是mtd_blkdevs.c里添加的add函数),我们先来看结果,在开发板上执行“ls -l /dev/mtd*”如下:
可以看到总共有4个分区,对于每个分区分别创建了两个字符设备节点和一个块设备节点,因为mtd层既提供了字符设备的操作接口(mtdchar.c), 也实现了块设备的操作接口(mtd_blkdevs.c)。接下来看是如何创建这些设备的。
创建字符设备源头在mtdchar.c入口函数中:
static int __init init_mtdchar(void)
{
if (register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops)) {
printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n",
MTD_CHAR_MAJOR);
return -EAGAIN;
} //创建字符设备
mtd_class = class_create(THIS_MODULE, "mtd"); //创建类
if (IS_ERR(mtd_class)) {
printk(KERN_ERR "Error creating mtd class.\n");
unregister_chrdev(MTD_CHAR_MAJOR, "mtd");
return PTR_ERR(mtd_class);
}
register_mtd_user(¬ifier);
return 0;
}
当add_mtd_device()时遍历链表,调用链表结构之一not->add(mtd)指向的add()函数是mtd_notify_add(),如下:
static void mtd_notify_add(struct mtd_info* mtd)
{
if (!mtd)
return;
/*其中MTD_CHAR_MAJOR主设备定义为90 */
class_device_create(mtd_class, NULL, MKDEV(MTD_CHAR_MAJOR, mtd->index*2),NULL, "mtd%d", mtd->index);
//创建mtd%d字符设备节点
class_device_create(mtd_class, NULL,MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1),NULL, "mtd%dro", mtd->index);
//创建mtd%dro字符设备节点
}
该函数创建了两个字符设备(mtd%d, mtd%dro ),其中ro的字符设备表示为只读。
创建块设备源头在mtdblock.c入口函数中:
/*drivers/mtd/mtdblock.c文件中*/
static int __init init_mtdblock(void)
{
return register_mtd_blktrans(&mtdblock_tr);
}
/*drivers/mtd/mtd_blkdevs.c文件中*/
int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
{
... ...
register_mtd_user(&blktrans_notifier);
... ...
tr->blkcore_priv = kzalloc(sizeof(*tr->blkcore_priv), GFP_KERNEL); //分配
... ...
ret = register_blkdev(tr->major, tr->name); //这里创建块设备
... ...
tr->blkcore_priv->rq = blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock); //初始化队列
}
当add_mtd_device()时遍历链表,调用链表结构之一not->add(mtd)指向的add()函数是blktrans_notify_add(),如下:
static struct mtd_notifier blktrans_notifier = {
.add = blktrans_notify_add,
.remove = blktrans_notify_remove,
};
blktrans_notify_add函数又有如下调用:
blktrans_notify_add
tr->add_mtd(指向mtdblock_add_mtd)
mtdblock_add_mtd
add_mtd_blktrans_dev
add_mtd_blktrans_dev()函数部分代码如下:
int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
{
... ...
gd = alloc_disk(1 << tr->part_bits); //分配一个gendisk结构体
gd->major = tr->major; //设置gendisk的主设备号
gd->first_minor = (new->devnum) << tr->part_bits; //设置gendisk的起始此设备号
gd->fops = &mtd_blktrans_ops; //设置操作函数
... ...
gd->queue = tr->blkcore_priv->rq; //设置请求队列
add_disk(gd); //向内核注册gendisk结构体
}
显然在内核中,mtd已经帮我们做了整个框架,而我们的nand flash驱动只需要以下几步即可:
1. 设置mtd_info结构体成员
主要是实现对nandflash的read()、write()、read_oob()、write_oob()、erase()等操作,属于软件的部分,它会通过它的成员priv来找到对应的nand_chip结构体,来调用与硬件相关的操作。
2. 设置nand_chip结构体成员
它是mtd_info结构体的priv成员,主要是对MTD设备中的nandflash硬件相关的描述。当我们不设置nand_chip的成员时,以下的成员就会被mtd自动设为默认值,代码位于:nand_scan()->nand_scan_ident()->nand_set_defaults()。
我们需要设置nand_chip的成员如下:
IO_ADDR_R (提供读数据)
IO_ADDR_W (提供写数据)
select_chip (提供片选使能/禁止)
cmd_ctrl (提供写命令/地址)
dev_ready (提供nandflash的RnB脚,来判断是否就绪)
ecc.mode (设置ECC为硬件校验/软件校验)
其它成员会通过nand_scan()->nand_scan_ident()->nand_set_defaults()来设置为默认值。
3. 设置硬件相关(设置nand控制器时序等)
4 通过nand_scan()来扫描nandflash
5 通过add_mtd_partitions()来添加分区,创建MTD字符/块设备
驱动程序s3c_nand.c完整代码如下:
/* 参考
* drivers\mtd\nand\s3c2410.c
* drivers\mtd\nand\at91_nand.c
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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 = "bootloader",
.size = 0x00040000,
.offset = 0,
},
[1] = {
.name = "params",
.offset = MTDPART_OFS_APPEND,
.size = 0x00020000,
},
[2] = {
.name = "kernel",
.offset = MTDPART_OFS_APPEND,
.size = 0x00200000,
},
[3] = {
.name = "root",
.offset = MTDPART_OFS_APPEND,
.size = MTDPART_SIZ_FULL,
}
};
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. 创建4个分区 */
add_mtd_partitions(s3c_mtd, s3c_nand_parts, 4);
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");
Makefile代码如下:
KERN_DIR = /work/system/linux-2.6.22.6 //内核目录
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += s3c_nand.o
内核:linux-2.6.22.6
编译器:arm-linux-gcc-3.4.5
环境:ubuntu9.10
(必须设置为NFS自动挂载文件系统启动,从网络文件系统下载去掉nandflash的内核 到内存并启动)
1. 重新配置内核,去掉内核自带的usbmouse鼠标驱动。在linux-2.6.22.6内核目录下执行:
make menuconfig
2. 配置步骤如下:
Device Drivers --->
<*> Memory Technology Device (MTD) support --->
<*> NAND Device Support --->
< > NAND Flash support for S3C2410/S3C2440 SoC
1. 编译内核与模块
make uImage
2. 将linux-2.6.22.6/arch/arm/boot下的uImage烧写到开发板
cp /work/system/linux-2.6.22.6/arch/arm/boot/uImage /work/nfs_root/first_fs
3. 在开发板上通过网络文件系统下载新内核到内存,并启动
nfs 0x30000000 192.168.1.13:/work/nfs_root/first_fs/uImage
bootm 30000000
查看设备执行如下:
ls /dev/mtd*
1. 首先编译自己写的nand flash驱动。在ubuntu驱动文件目录下执行:
make
2. 在开发板驱动文件目录下执行:
insmod s3c_nand.ko
输出如下:
3. 查看设备
ls /dev/mtd*
4. 挂载分区
mount dev/mtdblock3 /mnt (挂载分区4到/mnt,假如nandflash里面没有内容,应该先进行格式化才能挂载成功)
5. 格式化
5.1 安装zlib
下载zlib-1.2.3.tar.gz,ubuntu进入该文件目录,执行以下命令:
tar xvf zlib-1.2.3.tar.gz
cd zlib-1.2.3
./configure --shared --prefix=/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux
修改生成的Makefile,修改以下几项,都加上了“arm-linux-”前缀:
CC=arm-linux-gcc
LDSHARED=arm-linux-gcc -shared -Wl,-soname,libz.so.1
CPP=arm-linux-gcc -E
AR=arm-linux-ar rc
RANLIB=arm-linux-ranlib
最后执行以下命令编译、安装:
make
make install
5.2 安装mtd-utils
下载mtd-utils-05.07.23,ubuntu进入该文件目录,执行以下命令:
cd mtd-utils-05.07.23/util
vi Makefile (将”#CROSS=arm-linux-”改为”CROSS=arm-linux-”)
make (该目录下就会生成许多工具)
5.3 拷贝格式化工具
cp flash_erase flash_eraseall /work/nfs_root/first_fs/bin/
5.4 擦除分区(这里假设擦除文件系统分区)
flash_eraseall dev/mtd3 (如果之前有挂载,需要先卸载umount /mnt)
6. 重新挂载,执行以下命令:
mount -t yaffs /dev/mtdblock3 /mnt (因为当前的mtdblock3为空,mount命令无法自动获取mtdblock3的文件类型所以指定格式为yaffs )
这样就与上一节十六、Linux驱动之块设备驱动使用内存一样,能以文件操作形式使用nand flash了!