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\
}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;}Mtdcore.c中的add_mtd_device来添加分区