在linux系统中,提供了MTD(内存技术设备)系统来建立Flash针对linux的统一,抽象接口,MTD将文件系统与底层的Flash存取器进行了隔离,使得Flash驱动工程师无需关心Flash作为字符设备和块设备与Linux内核接口(由MTD层完成)
在引入MTD后,Linux系统中Flash设备驱动及接口可分四层
(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字符设备定义在mtdchar.c中实现,通过注册一系列file_operations的函数(lseek,open,clos,read,write等),可实现设备读写控制,MTD 块设备则是定义在一个描述MTD 块设备的结构 mtdblk_dev ,并声明了一个名为 mtdblks 的指针数组,这个数组 中的每个mtdblk_dev 和 mtd_table 中的每一个mtd_info 一一对应。
(4)块设备节点:通过mknod在/dev子目录下建立MTD字符设备节点和MTD块设备节点
LInux MTD系统接口
在引入MTD后,底层Flash驱动直接与MTD原始设备层交互,利用其提供的接口注册设备和分区
用于描述MTD原始设备的数据结构是mtd_info,这其中定义了大量关于MTD的数据和操作函数,mtd_info是表示MTD原始设备结构体,每个分区也被认为是一个mtd_info,这些mtd_info指针被存放在mtd_table的数组里,这个结构体定义如下:
struct mtd_info {
u_char type;//内存技术类型
uint32_t flags;//标志位
uint64_t size; // MTD设备大小Total size of the MTD
unsigned int erasesize_shift;
unsigned int writesize_shift;
unsigned int erasesize_mask;
unsigned int writesize_mask;
// Kernel-only stuff starts here.
const char *name;
int index;
struct nand_ecclayout *ecclayout;
int numeraseregions;
struct mtd_erase_region_info *eraseregions;
int (*erase) (struct mtd_info *mtd, struct erase_info *instr);
int (*point) (struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, void **virt, resource_size_t *phys);
void (*unpoint) (struct mtd_info *mtd, loff_t from, size_t len);
unsigned long (*get_unmapped_area) (struct mtd_info *mtd,
unsigned long len,
unsigned long offset,
unsigned long flags);
struct backing_dev_info *backing_dev_info;
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);
int (*panic_write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);
int (*read_oob) (struct mtd_info *mtd, loff_t from,
struct mtd_oob_ops *ops);
int (*write_oob) (struct mtd_info *mtd, loff_t to,
struct mtd_oob_ops *ops);
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);
int (*writev) (struct mtd_info *mtd, const struct kvec *vecs, unsigned long count, loff_t to, size_t *retlen);
void (*sync) (struct mtd_info *mtd);
int (*lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
int (*unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
int (*suspend) (struct mtd_info *mtd);
void (*resume) (struct mtd_info *mtd);
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;
struct mtd_ecc_stats ecc_stats;
int subpage_sft;
void *priv;
struct module *owner;
struct device dev;
int usecount;
int (*get_device) (struct mtd_info *mtd);
void (*put_device) (struct mtd_info *mtd);
};
mtd_info的type字段给出了底层物理设备的类型,包括MTD_RAM,MTD_ROM,MTD_NORFLASH,MTD_NANSFLASH等
flags字段标志可以是MTD_WRITEABLE,MTD_BIT_WRITEABLE,MTD_NO_ERASE,MTD_POWERUP_LOCK等的组合
mtd_info中的read(),write(),read_oob(),write_oob(),erase()是MTD设备驱动要实现的主要函数,在Linux中MTD下层实现了针对NOR Flash和NAND Flash通用的mtd_info成员函数
某些内存技术带有额外数据(OOB),例如NAND Flash没512字节就会有16个字节的“额外数据”
Flash驱动中使用如下两个函数注册和注销MTD设备
int add_mtd_device(struct mtd_info *mtd);
int del_mtd_device(struct mtd_info *mtd);
下面代码中mtd_part结构体用于表示分区,其mtd_info成员结构体用于描述分区,它会被加入到mtd_table(定义为struct mtd_info *mtd_table[MAX_MTD_DEVICES])中,其大部分成员由其主分区mtd_part->master决定,各种函数也指向主分区相应的函数
struct mtd_part {
struct mtd_info mtd;//分区信息(大部分由master决定)
struct mtd_info *master;//该分区的主分区
uint64_t offset;//该分区的偏移地址
int index;// 主分区号
struct list_head list;
int registered;
};
mtd_partition(分区数组)会在MTD原始设备层调用add_mtd_partitions()时传递分区信息用,定义如下:
struct mtd_partition {
char *name; //标示字符串
uint64_t size; //分区大小
uint64_t offset; //主MTD内偏移地址
uint32_t mask_flags; //掩码标志
struct nand_ecclayout *ecclayout; //OOB布局out of band layout for this partition (NAND only)
struct mtd_info **mtdp;
};
Flash驱动中使用如下两个函数注册和注销分区
int add_mtd_partitions(struct mtd_info *master,struct mtd_partition *parts,int nbparts)
int del_mtd_partitions(struct mtd_info *master);
add_mtd_partitions()会对每一个新建立的分区建立一个mtd_part结构体,并将其加入mtd_partions(分区数组)中,并调用add_mtd_device()将此分区作为MTD设备键入mtd_table,成功时返回0,否则返回-ENOMEM
del_mtd_partitions()的作用是对于mtd_partitions上的每一个分区,如果它的主分区是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;
uint64_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++) {
slave = add_one_partition(master, parts + i, i, cur_offset);
if (!slave)
return -ENOMEM;
cur_offset = slave->offset + slave->mtd.size;
}
return 0;
}
static struct mtd_part *add_one_partition(struct mtd_info *master,
const struct mtd_partition *part, int partno,
uint64_t cur_offset)
{
struct mtd_part *slave;
slave = kzalloc(sizeof(*slave), GFP_KERNEL);
if (!slave) {
printk(KERN_ERR"memory allocation error while creating partitions for \"%s\"\n",
master->name);
del_mtd_partitions(master);
return NULL;
}
list_add(&slave->list, &mtd_partitions);
slave->mtd.type = master->type;
slave->mtd.flags = master->flags & ~part->mask_flags;
slave->mtd.size = part->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 = part->name;
slave->mtd.owner = master->owner;
slave->mtd.backing_dev_info = master->backing_dev_info;
...
}
MTD用户空间编程
drives/mtd/mtdchar.c文件实现了MTD字符设备接口,通过它用户可以直接操作Flash设备,通过read(),write系统调用可以读写Flash,通过一系列IOCTL命令可以获得Flash设备信息,擦除Flash,读写NAND的OOB等
NOR Flash驱动
在Linux中,实现了针对CFI(公共Flash接口),JEDEC等接口的通用NOR驱动,这一层驱动直接面对mtd_info的成员函数,这使得NOR的芯片级驱动变得十分简单,只需定义具体内存映射情况结构体mao_info并使用指定调用do_mao_probe()探测mtd_info()就可以了
NOR Flash驱动的核心是定义map_info结构体,他指定NOR Flash的基址,位宽,大小等信息,然后调用do_map_probe()探测芯片就可以了,map_info原型如下:
struct map_info {
const char *name;
unsigned long size;
resource_size_t phys;
#define NO_XIP (-1UL)
void __iomem *virt;
void *cached;
int bankwidth;
#ifdef CONFIG_MTD_COMPLEX_MAPPINGS
map_word (*read)(struct map_info *, unsigned long);
void (*copy_from)(struct map_info *, void *, unsigned long, ssize_t);
void (*write)(struct map_info *, const map_word, unsigned long);
void (*copy_to)(struct map_info *, unsigned long, const void *, ssize_t);
#endif
void (*inval_cache)(struct map_info *, unsigned long, ssize_t);
void (*set_vpp)(struct map_info *, int);
unsigned long pfow_base;
unsigned long map_priv_1;
unsigned long map_priv_2;
void *fldrv_priv;
struct mtd_chip_driver *fldrv;
};
NOR Flash驱动在Linux中实现,主要工作如下:
(1)定义map_info的实例,初始化其中的成员,根据目标板的情况name,size,bankwidth和phys赋值
(2)如果Flash需要分区,则定义mtd_partition数组,将实际电路板中Flash分区信息记录在其中
(3)以map_info和探测的接口类型(如“ch_probe”,"jedec_probe"等)为参数调用do_map_probe()探测Flash得到mtd_info
do_map_probe()函数原型如下:
struct mtd_info *do_map_probe(const char *name,struct map_info *map);
第一个为探测接口类型,常见的如下
do_map_probe("cfi_probe",&xxx_map_info);
do_map_probe("jedec_probe",&xxx_map_info);
do_map_probe("map_rom",&xxx_map_info);
do_map_probe()会根据传入参数name(接口类型),通过get_mtd_chip_driver()得到具体的MTD驱动,如下所示:
struct mtd_info *do_map_probe(const char *name, struct map_info *map)
{
struct mtd_chip_driver *drv;
struct mtd_info *ret;
drv = get_mtd_chip_driver(name);
if (!drv && !request_module("%s", name))
drv = get_mtd_chip_driver(name);//通过接口类型找到驱动
if (!drv)
return NULL;
ret = drv->probe(map);
module_put(drv->module);
if (ret)
return ret;
return NULL;
}
(4)在模块初始化时以mtd_info为参数调用add_mtd_device()或以mtd_info,partition数组为参数调用add_mtd_partitions()注册设备或分区
(5)在模块卸载时调用“反函数”来删除设备或分区
下面是一个NOR Flash的驱动
#define WINDOW_SIZE ...
#define WINDOW_ADDR ...
static struct map_info xxx_map = {//定义并初始化map_info
.name = "xxx_Flash",
.size = WINDOW_SIZE,//大小
.bankwidth = 1,//总线宽度
.phys = WINDOW_ADDR//物理地址
};
static struct mtd_partition xxx_partitions[] = {//定义数组用于分区
{
.name = "Drive A",
.offset = 0,//分区偏移地址
.size = 0x0e0000//分区大小
},
...
};
#define NUM_PARTITIONS ARRAY_SIZE(xxx_partitions)
static struct mtd_info *mymtd;
static int __init init_xxx_map(void)
{
int rc = 0;
xxx_map.virt = ioremap_nocache(xxx_map.phys,xxx_map.size);//物理地址映射为虚拟地址
...
mymtd = do_map_probe("jedec_probe",&xxx_map);//探测NOR Flash得到mtd_info
mymtd->owner = THIS_MODULE;
add_mtd_partitions(mymtd,xxx_partitions,NUM_PARTTITIONS);//添加分区信息
...
}
struct void __exit cleanup_xxx_map(void0
{
if(mymtd){
del_mtd_partitions(mymtd);//删除分区
map_destroy(mymtd);
}
...
}
NAND Flash驱动
和NOR Flash非常类似,在Linux内核MTD的下层已经实现了通用的NAND驱动(在driver/mtd/nand/nand_bash.c中)即实现了mtd_info结构体的成员函数
MTD使用nand_chip数据结构表示一个NAND Flash芯片,这个结构体中包含了关于NAND Flash的地址信息,读写方法,ECC模式等,这个结构体原型如下:
struct nand_chip {
void __iomem *IO_ADDR_R;
void __iomem *IO_ADDR_W;
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);
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;
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;
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中的cmd_ctrl(),dev_ready(),ready_byte(),write_buf()等成员(如果不赋值就使用nand_base.c中的默认函数),注意将mtd_info的priv指向nand_chip
(3)以mtd_info为参数调用nand_scan()函数探测NAND Flash的存在得到mtd_info,nand_scan()函数原型如下:
int nand_scan(struct mtd_info *mtd,int maxchips);
(4)如果要分区则以mtd_info,mtd_partition为参数调用add_mtd_partitions()函数,添加分区
下面是一个NAND Flash设备驱动模块
#define CHIP_PHYSICAL_ADDRESS ...
#define NUM_PARTITIONS 2
static const struct mtd_partition partition_info[] = {
{
.name = "NAND FS 0",
.offset = 0,
.size = 8 * 1024 * 1024},
{
.name = "NAND FS 1",
.offset = MTDPART_OFS_APPEND,//表示接着上面结束地址
.size = MTDPART_SIZ_FULL}
};
static int __init au1xxx_nand_init(void)
{
struct nand_chip *this;
u16 boot_swapboot = 0;
int retval;
u32 mem_staddr;
u32 nand_phys;
//初始化结构体(分配内存)
au1550_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL);
if (!au1550_mtd) {
printk("Unable to allocate NAND MTD dev structure.\n");
return -ENOMEM;
}
this = (struct nand_chip *)(&au1550_mtd[1]);
//初始化成员函数
memset(au1550_mtd, 0, sizeof(struct mtd_info));
memset(this, 0, sizeof(struct nand_chip));
au1550_mtd->priv = this;
au1550_mtd->owner = THIS_MODULE;
au_writel(0, MEM_STNDCTL);
#ifdef CONFIG_MIPS_PB1550
au_writel(au_readl(GPIO2_DIR) & ~(1 << 6), GPIO2_DIR);
boot_swapboot = (au_readl(MEM_STSTAT) & (0x7 << 1)) | ((bcsr->status >> 6) & 0x1);
switch (boot_swapboot) {
case 0:
case 2:
case 8:
case 0xC:
case 0xD:
nand_width = 0;
break;
case 1:
case 9:
case 3:
case 0xE:
case 0xF:
nand_width = 1;
break;
default:
printk("Pb1550 NAND: bad boot:swap\n");
retval = -EINVAL;
goto outmem;
}
#endif
#ifdef NAND_STCFG
if (NAND_CS == 0) {
au_writel(NAND_STCFG, MEM_STCFG0);
au_writel(NAND_STTIME, MEM_STTIME0);
au_writel(NAND_STADDR, MEM_STADDR0);
}
if (NAND_CS == 1) {
au_writel(NAND_STCFG, MEM_STCFG1);
au_writel(NAND_STTIME, MEM_STTIME1);
au_writel(NAND_STADDR, MEM_STADDR1);
}
if (NAND_CS == 2) {
au_writel(NAND_STCFG, MEM_STCFG2);
au_writel(NAND_STTIME, MEM_STTIME2);
au_writel(NAND_STADDR, MEM_STADDR2);
}
if (NAND_CS == 3) {
au_writel(NAND_STCFG, MEM_STCFG3);
au_writel(NAND_STTIME, MEM_STTIME3);
au_writel(NAND_STADDR, MEM_STADDR3);
}
#endif
mem_staddr = 0x00000000;
if (((au_readl(MEM_STCFG0) & 0x7) == 0x5) && (NAND_CS == 0))
mem_staddr = au_readl(MEM_STADDR0);
else if (((au_readl(MEM_STCFG1) & 0x7) == 0x5) && (NAND_CS == 1))
mem_staddr = au_readl(MEM_STADDR1);
else if (((au_readl(MEM_STCFG2) & 0x7) == 0x5) && (NAND_CS == 2))
mem_staddr = au_readl(MEM_STADDR2);
else if (((au_readl(MEM_STCFG3) & 0x7) == 0x5) && (NAND_CS == 3))
mem_staddr = au_readl(MEM_STADDR3);
if (mem_staddr == 0x00000000) {
printk("Au1xxx NAND: ERROR WITH NAND CHIP-SELECT\n");
kfree(au1550_mtd);
return 1;
}
nand_phys = (mem_staddr << 4) & 0xFFFC0000;
p_nand = (void __iomem *)ioremap(nand_phys, 0x1000);
if (NAND_CS == 0)
nand_width = au_readl(MEM_STCFG0) & (1 << 22);
if (NAND_CS == 1)
nand_width = au_readl(MEM_STCFG1) & (1 << 22);
if (NAND_CS == 2)
nand_width = au_readl(MEM_STCFG2) & (1 << 22);
if (NAND_CS == 3)
nand_width = au_readl(MEM_STCFG3) & (1 << 22);
this->dev_ready = au1550_device_ready;
this->select_chip = au1550_select_chip;
this->cmdfunc = au1550_command;
this->chip_delay = 30;
this->ecc.mode = NAND_ECC_SOFT;
this->options = NAND_NO_AUTOINCR;
if (!nand_width)
this->options |= NAND_BUSWIDTH_16;
this->read_byte = (!nand_width) ? au_read_byte16 : au_read_byte;
au1550_write_byte = (!nand_width) ? au_write_byte16 : au_write_byte;
this->read_word = au_read_word;
this->write_buf = (!nand_width) ? au_write_buf16 : au_write_buf;
this->read_buf = (!nand_width) ? au_read_buf16 : au_read_buf;
this->verify_buf = (!nand_width) ? au_verify_buf16 : au_verify_buf;
if (nand_scan(au1550_mtd, 1)) {//探测NAND Flash
retval = -ENXIO;
goto outio;
}
//注册分区
add_mtd_partitions(au1550_mtd, partition_info, ARRAY_SIZE(partition_info));
return 0;
outio:
iounmap((void *)p_nand);
outmem:
kfree(au1550_mtd);
return retval;
}
最后强调的是,在NAND芯片驱动中,如果nand_chip中没有赋值,将使用默认值(nand_base.c中的默认值),下面是nand_chip中的nand_ecc_ctrl结构体类型成员ecc赋值,定义OOB的分布模式
static struct nand_ecclayout nand_oob_8 = {
.eccbytes = 3,
.eccpos = {0, 1, 2},
.oobfree = {
{.offset = 3,
.length = 2},
{.offset = 6,
.length = 2}}
};
static struct nand_ecclayout nand_oob_16 = {
.eccbytes = 6,
.eccpos = {0, 1, 2, 3, 6, 7},
.oobfree = {
{.offset = 8,
. length = 8}}
};
static struct nand_ecclayout nand_oob_64 = {
.eccbytes = 24,
.eccpos = {
40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63},
.oobfree = {
{.offset = 2,
.length = 38}}
};
static struct nand_ecclayout nand_oob_128 = {
.eccbytes = 48,
.eccpos = {
80, 81, 82, 83, 84, 85, 86, 87,
88, 89, 90, 91, 92, 93, 94, 95,
96, 97, 98, 99, 100, 101, 102, 103,
104, 105, 106, 107, 108, 109, 110, 111,
112, 113, 114, 115, 116, 117, 118, 119,
120, 121, 122, 123, 124, 125, 126, 127},
.oobfree = {
{.offset = 2,
.length = 78}}
};
NOR Flash驱动实例
针对S3C2410等平台而言。外接NOR Flash的情况下,由于NOR Flash直接映射到CPU的内存空间,为了使用NOR Flash驱动,我们只需要在BSP的板文件中添加相应的信息, 例如NOR Flash所在的物理地址和到校,分区信息,总线宽度等,这些信息以platform资源和数据的形式呈现,如下:
static struct resource nor_resource = {
.start = OMAP_CS0_PHYS,
.end = OMAP_CS0_PHYS + SZ_32M - 1,
.flags = IORESOURCE_MEM,//表示地址
};
static struct mtd_partition nor_partitions[] = {//分区结构体
{
.name = "bootloader",
.offset = 0,
.size = SZ_128K,
.mask_flags = MTD_WRITEABLE,
},
{
.name = "params",
.offset = MTDPART_OFS_APPEND,
.size = SZ_128K,
.mask_flags = 0,
},
{
.name = "kernel",
.offset = MTDPART_OFS_APPEND,
.size = SZ_2M,
.mask_flags = 0
},
{
.name = "rootfs",
.offset = MTDPART_OFS_APPEND,
.size = MTDPART_SIZ_FULL,
.mask_flags = 0
},
};
static struct flash_platform_data nor_data = {
.map_name = "cfi_probe",
.width = 2,
.parts = nor_partitions,
.nr_parts = ARRAY_SIZE(nor_partitions),
};
static struct platform_device nor_device = {
.name = "omapflash",//应该和驱动里的(physmap.c)一致
.id = 0,
.dev = {
.platform_data = &nor_data,
},
.num_resources = 1,//资源数
.resource = &nor_resource,//资源结构体
};
下面来总结一下:
由于引入了MTD系统以及MTD下层的通用NOR和NAND驱动,Linux中NOR和NAND Flash芯片级驱动的设计难度大大降低,对于NOR驱动工作仅仅只需在BSP中添加相关的platform信息。
在串口驱动中,讲解了tty_driver到uart_driver的角色转换,在Flash驱动中,讲解了mtd_info向map_info/nand_chip的转移,可以说,Linux驱动这种分层设计思想是贯穿个中Linux驱动框架的。
在引入MTD后,Linux系统中Flash设备驱动及接口可分四层
(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字符设备定义在mtdchar.c中实现,通过注册一系列file_operations的函数(lseek,open,clos,read,write等),可实现设备读写控制,MTD 块设备则是定义在一个描述MTD 块设备的结构 mtdblk_dev ,并声明了一个名为 mtdblks 的指针数组,这个数组 中的每个mtdblk_dev 和 mtd_table 中的每一个mtd_info 一一对应。
(4)块设备节点:通过mknod在/dev子目录下建立MTD字符设备节点和MTD块设备节点
LInux MTD系统接口
在引入MTD后,底层Flash驱动直接与MTD原始设备层交互,利用其提供的接口注册设备和分区
用于描述MTD原始设备的数据结构是mtd_info,这其中定义了大量关于MTD的数据和操作函数,mtd_info是表示MTD原始设备结构体,每个分区也被认为是一个mtd_info,这些mtd_info指针被存放在mtd_table的数组里,这个结构体定义如下:
struct mtd_info {
u_char type;//内存技术类型
uint32_t flags;//标志位
uint64_t size; // MTD设备大小Total size of the MTD
unsigned int erasesize_shift;
unsigned int writesize_shift;
unsigned int erasesize_mask;
unsigned int writesize_mask;
// Kernel-only stuff starts here.
const char *name;
int index;
struct nand_ecclayout *ecclayout;
int numeraseregions;
struct mtd_erase_region_info *eraseregions;
int (*erase) (struct mtd_info *mtd, struct erase_info *instr);
int (*point) (struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, void **virt, resource_size_t *phys);
void (*unpoint) (struct mtd_info *mtd, loff_t from, size_t len);
unsigned long (*get_unmapped_area) (struct mtd_info *mtd,
unsigned long len,
unsigned long offset,
unsigned long flags);
struct backing_dev_info *backing_dev_info;
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);
int (*panic_write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);
int (*read_oob) (struct mtd_info *mtd, loff_t from,
struct mtd_oob_ops *ops);
int (*write_oob) (struct mtd_info *mtd, loff_t to,
struct mtd_oob_ops *ops);
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);
int (*writev) (struct mtd_info *mtd, const struct kvec *vecs, unsigned long count, loff_t to, size_t *retlen);
void (*sync) (struct mtd_info *mtd);
int (*lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
int (*unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
int (*suspend) (struct mtd_info *mtd);
void (*resume) (struct mtd_info *mtd);
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;
struct mtd_ecc_stats ecc_stats;
int subpage_sft;
void *priv;
struct module *owner;
struct device dev;
int usecount;
int (*get_device) (struct mtd_info *mtd);
void (*put_device) (struct mtd_info *mtd);
};
mtd_info的type字段给出了底层物理设备的类型,包括MTD_RAM,MTD_ROM,MTD_NORFLASH,MTD_NANSFLASH等
flags字段标志可以是MTD_WRITEABLE,MTD_BIT_WRITEABLE,MTD_NO_ERASE,MTD_POWERUP_LOCK等的组合
mtd_info中的read(),write(),read_oob(),write_oob(),erase()是MTD设备驱动要实现的主要函数,在Linux中MTD下层实现了针对NOR Flash和NAND Flash通用的mtd_info成员函数
某些内存技术带有额外数据(OOB),例如NAND Flash没512字节就会有16个字节的“额外数据”
Flash驱动中使用如下两个函数注册和注销MTD设备
int add_mtd_device(struct mtd_info *mtd);
int del_mtd_device(struct mtd_info *mtd);
下面代码中mtd_part结构体用于表示分区,其mtd_info成员结构体用于描述分区,它会被加入到mtd_table(定义为struct mtd_info *mtd_table[MAX_MTD_DEVICES])中,其大部分成员由其主分区mtd_part->master决定,各种函数也指向主分区相应的函数
struct mtd_part {
struct mtd_info mtd;//分区信息(大部分由master决定)
struct mtd_info *master;//该分区的主分区
uint64_t offset;//该分区的偏移地址
int index;// 主分区号
struct list_head list;
int registered;
};
mtd_partition(分区数组)会在MTD原始设备层调用add_mtd_partitions()时传递分区信息用,定义如下:
struct mtd_partition {
char *name; //标示字符串
uint64_t size; //分区大小
uint64_t offset; //主MTD内偏移地址
uint32_t mask_flags; //掩码标志
struct nand_ecclayout *ecclayout; //OOB布局out of band layout for this partition (NAND only)
struct mtd_info **mtdp;
};
Flash驱动中使用如下两个函数注册和注销分区
int add_mtd_partitions(struct mtd_info *master,struct mtd_partition *parts,int nbparts)
int del_mtd_partitions(struct mtd_info *master);
add_mtd_partitions()会对每一个新建立的分区建立一个mtd_part结构体,并将其加入mtd_partions(分区数组)中,并调用add_mtd_device()将此分区作为MTD设备键入mtd_table,成功时返回0,否则返回-ENOMEM
del_mtd_partitions()的作用是对于mtd_partitions上的每一个分区,如果它的主分区是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;
uint64_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++) {
slave = add_one_partition(master, parts + i, i, cur_offset);
if (!slave)
return -ENOMEM;
cur_offset = slave->offset + slave->mtd.size;
}
return 0;
}
static struct mtd_part *add_one_partition(struct mtd_info *master,
const struct mtd_partition *part, int partno,
uint64_t cur_offset)
{
struct mtd_part *slave;
slave = kzalloc(sizeof(*slave), GFP_KERNEL);
if (!slave) {
printk(KERN_ERR"memory allocation error while creating partitions for \"%s\"\n",
master->name);
del_mtd_partitions(master);
return NULL;
}
list_add(&slave->list, &mtd_partitions);
slave->mtd.type = master->type;
slave->mtd.flags = master->flags & ~part->mask_flags;
slave->mtd.size = part->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 = part->name;
slave->mtd.owner = master->owner;
slave->mtd.backing_dev_info = master->backing_dev_info;
...
}
MTD用户空间编程
drives/mtd/mtdchar.c文件实现了MTD字符设备接口,通过它用户可以直接操作Flash设备,通过read(),write系统调用可以读写Flash,通过一系列IOCTL命令可以获得Flash设备信息,擦除Flash,读写NAND的OOB等
NOR Flash驱动
在Linux中,实现了针对CFI(公共Flash接口),JEDEC等接口的通用NOR驱动,这一层驱动直接面对mtd_info的成员函数,这使得NOR的芯片级驱动变得十分简单,只需定义具体内存映射情况结构体mao_info并使用指定调用do_mao_probe()探测mtd_info()就可以了
NOR Flash驱动的核心是定义map_info结构体,他指定NOR Flash的基址,位宽,大小等信息,然后调用do_map_probe()探测芯片就可以了,map_info原型如下:
struct map_info {
const char *name;
unsigned long size;
resource_size_t phys;
#define NO_XIP (-1UL)
void __iomem *virt;
void *cached;
int bankwidth;
#ifdef CONFIG_MTD_COMPLEX_MAPPINGS
map_word (*read)(struct map_info *, unsigned long);
void (*copy_from)(struct map_info *, void *, unsigned long, ssize_t);
void (*write)(struct map_info *, const map_word, unsigned long);
void (*copy_to)(struct map_info *, unsigned long, const void *, ssize_t);
#endif
void (*inval_cache)(struct map_info *, unsigned long, ssize_t);
void (*set_vpp)(struct map_info *, int);
unsigned long pfow_base;
unsigned long map_priv_1;
unsigned long map_priv_2;
void *fldrv_priv;
struct mtd_chip_driver *fldrv;
};
NOR Flash驱动在Linux中实现,主要工作如下:
(1)定义map_info的实例,初始化其中的成员,根据目标板的情况name,size,bankwidth和phys赋值
(2)如果Flash需要分区,则定义mtd_partition数组,将实际电路板中Flash分区信息记录在其中
(3)以map_info和探测的接口类型(如“ch_probe”,"jedec_probe"等)为参数调用do_map_probe()探测Flash得到mtd_info
do_map_probe()函数原型如下:
struct mtd_info *do_map_probe(const char *name,struct map_info *map);
第一个为探测接口类型,常见的如下
do_map_probe("cfi_probe",&xxx_map_info);
do_map_probe("jedec_probe",&xxx_map_info);
do_map_probe("map_rom",&xxx_map_info);
do_map_probe()会根据传入参数name(接口类型),通过get_mtd_chip_driver()得到具体的MTD驱动,如下所示:
struct mtd_info *do_map_probe(const char *name, struct map_info *map)
{
struct mtd_chip_driver *drv;
struct mtd_info *ret;
drv = get_mtd_chip_driver(name);
if (!drv && !request_module("%s", name))
drv = get_mtd_chip_driver(name);//通过接口类型找到驱动
if (!drv)
return NULL;
ret = drv->probe(map);
module_put(drv->module);
if (ret)
return ret;
return NULL;
}
(4)在模块初始化时以mtd_info为参数调用add_mtd_device()或以mtd_info,partition数组为参数调用add_mtd_partitions()注册设备或分区
(5)在模块卸载时调用“反函数”来删除设备或分区
下面是一个NOR Flash的驱动
#define WINDOW_SIZE ...
#define WINDOW_ADDR ...
static struct map_info xxx_map = {//定义并初始化map_info
.name = "xxx_Flash",
.size = WINDOW_SIZE,//大小
.bankwidth = 1,//总线宽度
.phys = WINDOW_ADDR//物理地址
};
static struct mtd_partition xxx_partitions[] = {//定义数组用于分区
{
.name = "Drive A",
.offset = 0,//分区偏移地址
.size = 0x0e0000//分区大小
},
...
};
#define NUM_PARTITIONS ARRAY_SIZE(xxx_partitions)
static struct mtd_info *mymtd;
static int __init init_xxx_map(void)
{
int rc = 0;
xxx_map.virt = ioremap_nocache(xxx_map.phys,xxx_map.size);//物理地址映射为虚拟地址
...
mymtd = do_map_probe("jedec_probe",&xxx_map);//探测NOR Flash得到mtd_info
mymtd->owner = THIS_MODULE;
add_mtd_partitions(mymtd,xxx_partitions,NUM_PARTTITIONS);//添加分区信息
...
}
struct void __exit cleanup_xxx_map(void0
{
if(mymtd){
del_mtd_partitions(mymtd);//删除分区
map_destroy(mymtd);
}
...
}
NAND Flash驱动
和NOR Flash非常类似,在Linux内核MTD的下层已经实现了通用的NAND驱动(在driver/mtd/nand/nand_bash.c中)即实现了mtd_info结构体的成员函数
MTD使用nand_chip数据结构表示一个NAND Flash芯片,这个结构体中包含了关于NAND Flash的地址信息,读写方法,ECC模式等,这个结构体原型如下:
struct nand_chip {
void __iomem *IO_ADDR_R;
void __iomem *IO_ADDR_W;
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);
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;
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;
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中的cmd_ctrl(),dev_ready(),ready_byte(),write_buf()等成员(如果不赋值就使用nand_base.c中的默认函数),注意将mtd_info的priv指向nand_chip
(3)以mtd_info为参数调用nand_scan()函数探测NAND Flash的存在得到mtd_info,nand_scan()函数原型如下:
int nand_scan(struct mtd_info *mtd,int maxchips);
(4)如果要分区则以mtd_info,mtd_partition为参数调用add_mtd_partitions()函数,添加分区
下面是一个NAND Flash设备驱动模块
#define CHIP_PHYSICAL_ADDRESS ...
#define NUM_PARTITIONS 2
static const struct mtd_partition partition_info[] = {
{
.name = "NAND FS 0",
.offset = 0,
.size = 8 * 1024 * 1024},
{
.name = "NAND FS 1",
.offset = MTDPART_OFS_APPEND,//表示接着上面结束地址
.size = MTDPART_SIZ_FULL}
};
static int __init au1xxx_nand_init(void)
{
struct nand_chip *this;
u16 boot_swapboot = 0;
int retval;
u32 mem_staddr;
u32 nand_phys;
//初始化结构体(分配内存)
au1550_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL);
if (!au1550_mtd) {
printk("Unable to allocate NAND MTD dev structure.\n");
return -ENOMEM;
}
this = (struct nand_chip *)(&au1550_mtd[1]);
//初始化成员函数
memset(au1550_mtd, 0, sizeof(struct mtd_info));
memset(this, 0, sizeof(struct nand_chip));
au1550_mtd->priv = this;
au1550_mtd->owner = THIS_MODULE;
au_writel(0, MEM_STNDCTL);
#ifdef CONFIG_MIPS_PB1550
au_writel(au_readl(GPIO2_DIR) & ~(1 << 6), GPIO2_DIR);
boot_swapboot = (au_readl(MEM_STSTAT) & (0x7 << 1)) | ((bcsr->status >> 6) & 0x1);
switch (boot_swapboot) {
case 0:
case 2:
case 8:
case 0xC:
case 0xD:
nand_width = 0;
break;
case 1:
case 9:
case 3:
case 0xE:
case 0xF:
nand_width = 1;
break;
default:
printk("Pb1550 NAND: bad boot:swap\n");
retval = -EINVAL;
goto outmem;
}
#endif
#ifdef NAND_STCFG
if (NAND_CS == 0) {
au_writel(NAND_STCFG, MEM_STCFG0);
au_writel(NAND_STTIME, MEM_STTIME0);
au_writel(NAND_STADDR, MEM_STADDR0);
}
if (NAND_CS == 1) {
au_writel(NAND_STCFG, MEM_STCFG1);
au_writel(NAND_STTIME, MEM_STTIME1);
au_writel(NAND_STADDR, MEM_STADDR1);
}
if (NAND_CS == 2) {
au_writel(NAND_STCFG, MEM_STCFG2);
au_writel(NAND_STTIME, MEM_STTIME2);
au_writel(NAND_STADDR, MEM_STADDR2);
}
if (NAND_CS == 3) {
au_writel(NAND_STCFG, MEM_STCFG3);
au_writel(NAND_STTIME, MEM_STTIME3);
au_writel(NAND_STADDR, MEM_STADDR3);
}
#endif
mem_staddr = 0x00000000;
if (((au_readl(MEM_STCFG0) & 0x7) == 0x5) && (NAND_CS == 0))
mem_staddr = au_readl(MEM_STADDR0);
else if (((au_readl(MEM_STCFG1) & 0x7) == 0x5) && (NAND_CS == 1))
mem_staddr = au_readl(MEM_STADDR1);
else if (((au_readl(MEM_STCFG2) & 0x7) == 0x5) && (NAND_CS == 2))
mem_staddr = au_readl(MEM_STADDR2);
else if (((au_readl(MEM_STCFG3) & 0x7) == 0x5) && (NAND_CS == 3))
mem_staddr = au_readl(MEM_STADDR3);
if (mem_staddr == 0x00000000) {
printk("Au1xxx NAND: ERROR WITH NAND CHIP-SELECT\n");
kfree(au1550_mtd);
return 1;
}
nand_phys = (mem_staddr << 4) & 0xFFFC0000;
p_nand = (void __iomem *)ioremap(nand_phys, 0x1000);
if (NAND_CS == 0)
nand_width = au_readl(MEM_STCFG0) & (1 << 22);
if (NAND_CS == 1)
nand_width = au_readl(MEM_STCFG1) & (1 << 22);
if (NAND_CS == 2)
nand_width = au_readl(MEM_STCFG2) & (1 << 22);
if (NAND_CS == 3)
nand_width = au_readl(MEM_STCFG3) & (1 << 22);
this->dev_ready = au1550_device_ready;
this->select_chip = au1550_select_chip;
this->cmdfunc = au1550_command;
this->chip_delay = 30;
this->ecc.mode = NAND_ECC_SOFT;
this->options = NAND_NO_AUTOINCR;
if (!nand_width)
this->options |= NAND_BUSWIDTH_16;
this->read_byte = (!nand_width) ? au_read_byte16 : au_read_byte;
au1550_write_byte = (!nand_width) ? au_write_byte16 : au_write_byte;
this->read_word = au_read_word;
this->write_buf = (!nand_width) ? au_write_buf16 : au_write_buf;
this->read_buf = (!nand_width) ? au_read_buf16 : au_read_buf;
this->verify_buf = (!nand_width) ? au_verify_buf16 : au_verify_buf;
if (nand_scan(au1550_mtd, 1)) {//探测NAND Flash
retval = -ENXIO;
goto outio;
}
//注册分区
add_mtd_partitions(au1550_mtd, partition_info, ARRAY_SIZE(partition_info));
return 0;
outio:
iounmap((void *)p_nand);
outmem:
kfree(au1550_mtd);
return retval;
}
最后强调的是,在NAND芯片驱动中,如果nand_chip中没有赋值,将使用默认值(nand_base.c中的默认值),下面是nand_chip中的nand_ecc_ctrl结构体类型成员ecc赋值,定义OOB的分布模式
static struct nand_ecclayout nand_oob_8 = {
.eccbytes = 3,
.eccpos = {0, 1, 2},
.oobfree = {
{.offset = 3,
.length = 2},
{.offset = 6,
.length = 2}}
};
static struct nand_ecclayout nand_oob_16 = {
.eccbytes = 6,
.eccpos = {0, 1, 2, 3, 6, 7},
.oobfree = {
{.offset = 8,
. length = 8}}
};
static struct nand_ecclayout nand_oob_64 = {
.eccbytes = 24,
.eccpos = {
40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63},
.oobfree = {
{.offset = 2,
.length = 38}}
};
static struct nand_ecclayout nand_oob_128 = {
.eccbytes = 48,
.eccpos = {
80, 81, 82, 83, 84, 85, 86, 87,
88, 89, 90, 91, 92, 93, 94, 95,
96, 97, 98, 99, 100, 101, 102, 103,
104, 105, 106, 107, 108, 109, 110, 111,
112, 113, 114, 115, 116, 117, 118, 119,
120, 121, 122, 123, 124, 125, 126, 127},
.oobfree = {
{.offset = 2,
.length = 78}}
};
NOR Flash驱动实例
针对S3C2410等平台而言。外接NOR Flash的情况下,由于NOR Flash直接映射到CPU的内存空间,为了使用NOR Flash驱动,我们只需要在BSP的板文件中添加相应的信息, 例如NOR Flash所在的物理地址和到校,分区信息,总线宽度等,这些信息以platform资源和数据的形式呈现,如下:
static struct resource nor_resource = {
.start = OMAP_CS0_PHYS,
.end = OMAP_CS0_PHYS + SZ_32M - 1,
.flags = IORESOURCE_MEM,//表示地址
};
static struct mtd_partition nor_partitions[] = {//分区结构体
{
.name = "bootloader",
.offset = 0,
.size = SZ_128K,
.mask_flags = MTD_WRITEABLE,
},
{
.name = "params",
.offset = MTDPART_OFS_APPEND,
.size = SZ_128K,
.mask_flags = 0,
},
{
.name = "kernel",
.offset = MTDPART_OFS_APPEND,
.size = SZ_2M,
.mask_flags = 0
},
{
.name = "rootfs",
.offset = MTDPART_OFS_APPEND,
.size = MTDPART_SIZ_FULL,
.mask_flags = 0
},
};
static struct flash_platform_data nor_data = {
.map_name = "cfi_probe",
.width = 2,
.parts = nor_partitions,
.nr_parts = ARRAY_SIZE(nor_partitions),
};
static struct platform_device nor_device = {
.name = "omapflash",//应该和驱动里的(physmap.c)一致
.id = 0,
.dev = {
.platform_data = &nor_data,
},
.num_resources = 1,//资源数
.resource = &nor_resource,//资源结构体
};
下面来总结一下:
由于引入了MTD系统以及MTD下层的通用NOR和NAND驱动,Linux中NOR和NAND Flash芯片级驱动的设计难度大大降低,对于NOR驱动工作仅仅只需在BSP中添加相关的platform信息。
在串口驱动中,讲解了tty_driver到uart_driver的角色转换,在Flash驱动中,讲解了mtd_info向map_info/nand_chip的转移,可以说,Linux驱动这种分层设计思想是贯穿个中Linux驱动框架的。