在读者学习本章之前,最好了解Nand Flash读写过程和操作,可以参考:Nand Flash裸机操作。
一开始想在本章写eMMC框架和设备驱动,但是没有找到关于eMMC设备驱动具体写法,所以本章仍继续完成Nand Flash设备驱动,eMMC这个坑留在以后填。如果读者开发板为eMMC,本节驱动可能无法正常执行。
在裸机操作中,读者应了解Nand Flash时序图、Nand Flash片选、读写和擦除等操作,在此不再赘述。
一、Nand Flash驱动分析
Nand Flash设备驱动放在drivers/mtd/nand目录下,mtd(memory technology device,存储技术设备)是用于访问存储设备(ROM、flash)的子系统。mtd的主要目的是为了使新的存储设备的驱动更加简单,因此它在硬件和顶层之间提供了一个抽象的接口。
读者可在此目录下任意选择一个单板驱动文件进行分析,我选择的是davinci_nand.c文件。
Nand Flash和Nor Flash文件链接:
https://files.cnblogs.com/files/Lioker/18_nand_nor.zip
首先来看它的入口函数:
static int __init nand_davinci_init(void)
{
return platform_driver_probe(&nand_davinci_driver, nand_davinci_probe);
}
我们进入platform_driver的probe函数中,看看它是如何初始化
static int __init nand_davinci_probe(struct platform_device *pdev)
{
struct davinci_nand_pdata *pdata = pdev->dev.platform_data;
struct davinci_nand_info *info;
struct resource *res1;
struct resource *res2;
void __iomem *vaddr;
void __iomem *base;
int ret;
uint32_t val;
nand_ecc_modes_t ecc_mode;
struct mtd_partition *mtd_parts = NULL;
int mtd_parts_nb = ; ...
/* 初始化硬件,如设置TACLS、TWRPH0、TWRPH1等 */
platform_set_drvdata(pdev, info);
...
/* 配置mtd_info结构体,它是nand_chip的抽象 */
info->mtd.priv = &info->chip;
info->mtd.name = dev_name(&pdev->dev);
info->mtd.owner = THIS_MODULE; info->mtd.dev.parent = &pdev->dev; /* 配置nand_chip结构体 */
info->chip.IO_ADDR_R = vaddr;
info->chip.IO_ADDR_W = vaddr;
info->chip.chip_delay = ;
info->chip.select_chip = nand_davinci_select_chip;
...
/* Set address of hardware control function */
info->chip.cmd_ctrl = nand_davinci_hwcontrol;
info->chip.dev_ready = nand_davinci_dev_ready; /* Speed up buffer I/O */
info->chip.read_buf = nand_davinci_read_buf;
info->chip.write_buf = nand_davinci_write_buf; /* Use board-specific ECC config */
ecc_mode = pdata->ecc_mode; ret = -EINVAL;
switch (ecc_mode) {
case NAND_ECC_NONE:
case NAND_ECC_SOFT: /* 启动软件ECC */
pdata->ecc_bits = ;
break;
case NAND_ECC_HW: /* 启动硬件ECC */
...
break;
default:
ret = -EINVAL;
goto err_ecc;
}
info->chip.ecc.mode = ecc_mode; /* 使能nand clk */
info->clk = clk_get(&pdev->dev, "aemif");
...
ret = clk_enable(info->clk);
...
val = davinci_nand_readl(info, A1CR_OFFSET + info->core_chipsel * );
...
/* 扫描Nand Flash */
ret = nand_scan_ident(&info->mtd, pdata->mask_chipsel ? : , NULL);
...
/* second phase scan */
ret = nand_scan_tail(&info->mtd);
/* 以上nand_scan_ident()和nand_scan_tail()两步可以使用nand_scan()代替 */ if (mtd_has_cmdlinepart()) {
static const char *probes[] __initconst = {
"cmdlinepart", NULL
};
/* 设置分区 */
mtd_parts_nb = parse_mtd_partitions(&info->mtd, probes,
&mtd_parts, );
} if (mtd_parts_nb <= ) {
mtd_parts = pdata->parts;
mtd_parts_nb = pdata->nr_parts;
} /* Register any partitions */
if (mtd_parts_nb > ) {
ret = mtd_device_register(&info->mtd, mtd_parts,
mtd_parts_nb);
if (ret == )
info->partitioned = true;
} /* If there's no partition info, just package the whole chip
* as a single MTD device.
*/
if (!info->partitioned)
ret = mtd_device_register(&info->mtd, NULL, ) ? -ENODEV : ;
...
return ret;
}
probe()函数所做的有以下几点:
1. 初始化硬件,如设置TACLS、TWRPH0、TWRPH1等
2. 配置mtd_info结构体,它是nand_chip等底层Flash结构体的抽象,用于描述MTD设备,定义了MTD数据和操作函数
3. 配置nand_chip结构体
4. 启动软件ECC
5. 使用clk_get()和clk_enable()获取并使能Nand Flash时钟
6. 使用nand_scan()扫描Nand Flash
7. 使用parse_mtd_partitions()解析命令行中设置的分区。若命令行中没有设置mtdparts返回0;若设置了并且解析没问题,那么返回分区的个数,否则返回小于0的数
8. 使用mtd_device_register()注册Nand Flash分区
其中,
1. nand_scan()函数调用关系如下:
nand_scan(&info->mtd, pdata->mask_chipsel ? : );
-> nand_scan_ident(mtd, maxchips, NULL);
/* 获取Nand Flash存储器类型 */
-> nand_get_flash_type(mtd, chip, busw, &nand_maf_id, &nand_dev_id, table);
-> nand_scan_tail(mtd); /* 设置Nand Flash底层读写擦除等函数 */
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, int *dev_id, struct nand_flash_dev *type)
{
int i, maf_idx;
u8 id_data[];
int ret; /* Select the device */
chip->select_chip(mtd, ); /* nand_chip的片选函数 */ /*
* Reset the chip, required by some chips (e.g. Micron MT29FxGxxxxx)
* after power-up
*/
chip->cmdfunc(mtd, NAND_CMD_RESET, -, -); /* Send the command for reading device ID */
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -); /* Read manufacturer and device IDs */
*maf_id = chip->read_byte(mtd); /* 调用read_byte函数读取厂家ID */
*dev_id = chip->read_byte(mtd); /* 设备ID */ /* Try again to make sure, as some systems the bus-hold or other
* interface concerns can cause random data which looks like a
* possibly credible NAND flash to appear. If the two results do
* not match, ignore the device completely.
*/ chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -); for (i = ; i < ; i++)
id_data[i] = chip->read_byte(mtd); /* 打印参数信息 */
if (id_data[] != *maf_id || id_data[] != *dev_id) {
printk(KERN_INFO "%s: second ID read did not match "
"%02x,%02x against %02x,%02x\n", __func__,
*maf_id, *dev_id, id_data[], id_data[]);
return ERR_PTR(-ENODEV);
}
...
/* 校验产品ID */
if (!type)
type = nand_flash_ids; for (; type->name != NULL; type++)
if (*dev_id == type->id)
break;
...
}
代码中第46行可看出,nand_flash_ids[]数组是个全局变量,通过循环匹配设备ID,确定Nand Flash的大小、位数等规格。其定义如下:
struct nand_flash_dev nand_flash_ids[] = { #ifdef CONFIG_MTD_NAND_MUSEUM_IDS
{"NAND 1MiB 5V 8-bit", 0x6e, , , 0x1000, },
{"NAND 2MiB 5V 8-bit", 0x64, , , 0x1000, },
{"NAND 4MiB 5V 8-bit", 0x6b, , , 0x2000, },
{"NAND 1MiB 3,3V 8-bit", 0xe8, , , 0x1000, },
{"NAND 1MiB 3,3V 8-bit", 0xec, , , 0x1000, },
{"NAND 2MiB 3,3V 8-bit", 0xea, , , 0x1000, },
{"NAND 4MiB 3,3V 8-bit", 0xd5, , , 0x2000, },
{"NAND 4MiB 3,3V 8-bit", 0xe3, , , 0x2000, },
{"NAND 4MiB 3,3V 8-bit", 0xe5, , , 0x2000, },
{"NAND 8MiB 3,3V 8-bit", 0xd6, , , 0x2000, },
...
};
2. 我们如果不传入命令行参数,parse_mtd_partitions()函数没有作用,就需要自己构建分区表。mtd_device_register()函数传入参数中应有分区表
int mtd_device_register(struct mtd_info *master, const struct mtd_partition *parts, int nr_parts)
{
return parts ? add_mtd_partitions(master, parts, nr_parts) :
add_mtd_device(master);
}
参数struct mtd_partition *parts即为分区表,其定义和示例如下:
/* 定义 */
struct mtd_partition {
char *name; /* 分区名,如bootloader、params、kernel和root */
uint64_t size; /* 分区大小*/
uint64_t offset; /* 分区所在的偏移值 */
uint32_t mask_flags; /* 掩码标识 */
struct nand_ecclayout *ecclayout; /* oob布局 */
}; /* 示例 */
static const struct mtd_partition partition_info[] = {
{
.name = "NAND FS 0",
.offset = ,
.size = * * },
{
.name = "NAND FS 1",
.offset = MTDPART_OFS_APPEND, /* 接着上一个 */
.size = MTDPART_SIZ_FULL /* 余下的所有空间 */ }
};
简单分析完了Nand Flash设备驱动,接下来我们来分析MTD子系统框架。
二、MTD子系统框架分析
在开发板中ls /dev/mtd*,我们可以看到MTD设备既有块设备也有字符设备,块设备(mtdblockx)针对文件系统,字符设备(mtdx)针对格式化等操作。
在上一节中,我们知道了mtd_info是nand_chip等底层Flash结构体的抽象,因此我们可以得到如下框架。
在上一节中,我们知道了mtd_device_register()函数最终调用add_mtd_device(master)函数添加MTD设备。根据上图可以确定添加的是MTD原始设备。
add_mtd_device()函数定义如下:
int add_mtd_device(struct mtd_info *mtd)
{
struct mtd_notifier *not; /* MTD通知结构体,用于添加删除mtd_info */
int i, error;
...
/* 配置mtd_info */
mtd->index = i;
mtd->usecount = ;
...
mtd->erasesize_mask = ( << mtd->erasesize_shift) - ;
mtd->writesize_mask = ( << mtd->writesize_shift) - ;
...
mtd->dev.type = &mtd_devtype;
mtd->dev.class = &mtd_class;
mtd->dev.devt = MTD_DEVT(i);
dev_set_name(&mtd->dev, "mtd%d", i);
dev_set_drvdata(&mtd->dev, mtd);
device_register(&mtd->dev); /* 创建device */
...
list_for_each_entry(not, &mtd_notifiers, list)
not->add(mtd); /* 调用mtd_notifier的add函数 */ return ;
}
其中,struct mtd_notifier定义如下:
struct mtd_notifier {
void (*add)(struct mtd_info *mtd);
void (*remove)(struct mtd_info *mtd);
struct list_head list;
};
struct mtd_notifier的注册注销函数定义如下:
/* 注册函数 */
void register_mtd_user (struct mtd_notifier *new)
{
struct mtd_info *mtd;
mutex_lock(&mtd_table_mutex);
list_add(&new->list, &mtd_notifiers);
__module_get(THIS_MODULE); mtd_for_each_device(mtd)
new->add(mtd); mutex_unlock(&mtd_table_mutex);
} /* 注销函数 */
int unregister_mtd_user (struct mtd_notifier *old)
{
struct mtd_info *mtd;
mutex_lock(&mtd_table_mutex);
module_put(THIS_MODULE); mtd_for_each_device(mtd)
old->remove(mtd); list_del(&old->list);
mutex_unlock(&mtd_table_mutex);
return ;
}
至此,各个结构体层次已经出来了,如下图所示:
既然mtd_info是nand_chip等底层Flash结构体的抽象,那么用于表示Nor Flash的结构体是什么呢,第三节我们就来分析这个问题。
三、Nor Flash驱动分析
进入drivers/mtd/目录中,Nor Flash和Nand Flash一样,必然会有自己的目录。根据排除法确定Nor Flash设备驱动文件所在的目录为maps。
读者可在此目录下任意选择一个单板驱动文件进行分析,我选择的是dc21285.c文件。
首先来看它的入口函数:
static int __init init_dc21285(void)
{
int nrparts; /* Determine bankwidth */
switch (*CSR_SA110_CNTL & (<<)) {
...
case SA110_CNTL_ROMWIDTH_32:
dc21285_map.bankwidth = ;
dc21285_map.read = dc21285_read32;
dc21285_map.write = dc21285_write32;
dc21285_map.copy_to = dc21285_copy_to_32;
break;
...
}
...
/* 根据Nor Flash物理地址映射Nor Flash空间 */
dc21285_map.virt = ioremap(DC21285_FLASH, **); /* DC21285_FLASH为物理起始地址,16*1024*1024为FLASH大小 */
...
/* NOR有两种规范
* 1. jedec:内核中定义有jedec_table结构体,里面存放有NOR Flash的大小、名字等信息。如果内核中没有定义我们使用的NOR Flash,就必须手动添加
* 2. cfi:common flash interface,是新的NOR Flash规范,Flash本身包含有属性,和Nand Flash相同
*/
if (machine_is_ebsa285()) {
dc21285_mtd = do_map_probe("cfi_probe", &dc21285_map);
} else {
dc21285_mtd = do_map_probe("jedec_probe", &dc21285_map);
} if (!dc21285_mtd) {
iounmap(dc21285_map.virt);
return -ENXIO;
} dc21285_mtd->owner = THIS_MODULE; nrparts = parse_mtd_partitions(dc21285_mtd, probes, &dc21285_parts, );
mtd_device_register(dc21285_mtd, dc21285_parts, nrparts);
...
return ;
}
通过此函数,我们可以知道表示Nor Flash的结构体为struct map_info dc21285_map。
init()函数所做的有以下几点:
1. 申请mtd_info结构体内存空间
2. 申请并配置map_info结构体
3. 映射与map_info->phys物理地址对应的map_info->virt虚拟内存,其大小为Flash真实大小,它放在map_info->size
4. 使用do_map_probe()设置map_info结构体
5. 使用parse_mtd_partitions()解析命令行中设置的分区。若命令行中没有设置mtdparts返回0;若设置了并且解析没问题,那么返回分区的个数,否则返回小于0的数
6. 使用mtd_device_register()注册Nor Flash分区
do_map_probe()函数调用关系如下:
dc21285_mtd = do_map_probe("cfi_probe", &dc21285_map);
-> struct mtd_chip_driver *drv = get_mtd_chip_driver(name);
-> list_for_each(pos, &chip_drvs_list)
if (!strcmp(this->name, name)) /* 匹配驱动 */
return this; /* 返回的是mtd_chip_driver *drv */
-> ret = drv->probe(map); /* 调用驱动的probe()函数返回mtd_info */
四、Nand Flash驱动和Nor Flash驱动编写
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/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/nand_ecc.h>
#include <linux/mtd/partitions.h> #include <asm/io.h>
//#include <asm/arch/regs-nand.h>
//#include <asm/arch/nand.h> struct itop_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 *itop_nand;
static struct mtd_info *itop_mtd;
static struct itop_nand_regs *nand_regs;
static struct clk *clk; static struct mtd_partition itop_nand_part[] = {
[] = {
.name = "bootloader",
.size = 0x00080000,
.offset = ,
},
[] = {
.name = "params",
.offset = MTDPART_OFS_APPEND,
.size = 0x00020000,
},
[] = {
.name = "kernel",
.offset = MTDPART_OFS_APPEND,
.size = 0x00400000,
},
[] = {
.name = "root",
.offset = MTDPART_OFS_APPEND,
.size = MTDPART_SIZ_FULL,
}
}; static void itop_nand_select_chip(struct mtd_info *mtd, int chipnr)
{
if (chipnr == -)
{
/* 取消选中: NFCONT[1]设为1 */
nand_regs->nfcont |= ( << );
}
else
{
/* 选中: NFCONT[1]设为0 */
nand_regs->nfcont &= ~( << );
}
} static void itop_nand_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl)
{
if (ctrl & NAND_CLE)
{
/* MFDCMMD = cmd */
nand_regs->nfcmd = cmd;
}
else
{
/* NFDADDR = cmd */
nand_regs->nfaddr = cmd;
}
} static int itop_nand_device_ready(struct mtd_info *mtd)
{
/* 判断NFSTAT[0]: 1 表示ready */
return (nand_regs->nfstat & ( << ));
} static int itop_nand_init(void)
{
/* 1. 分配nand_chip */
itop_nand = kzalloc(sizeof(struct nand_chip), GFP_KERNEL); /* 2. 设置nand_chip */
itop_mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
itop_mtd->owner = THIS_MODULE;
itop_mtd->priv = itop_nand; nand_regs = ioremap(0x4E000000, sizeof(struct itop_nand_regs)); itop_nand->select_chip = itop_nand_select_chip;
itop_nand->cmd_ctrl = itop_nand_cmd_ctrl; /* 命令最后调用它 */
itop_nand->IO_ADDR_R = &nand_regs->nfdata; /* 读数据最后调用它 */
itop_nand->IO_ADDR_W = &nand_regs->nfdata; /* 写数据 */
itop_nand->dev_ready = itop_nand_device_ready; /* 状态位 */
itop_nand->ecc.mode = NAND_ECC_SOFT; /* 开启ECC */ /* 3. 硬件相关的操作 */
/* 注意使能时钟 */
clk = clk_get(NULL, "nand");
clk_enable(clk);
nand_regs->nfconf = (( << ) | ( << ) | ( << ));
nand_regs->nfcont = (( << ) | ( << )); /* 4. nand_scan() */
nand_scan(itop_mtd, ); /* 5. mtd_device_register() */
mtd_device_register(itop_mtd, itop_nand_part, ); return ;
} static void itop_nand_exit(void)
{
kfree(itop_nand);
kfree(itop_mtd);
iounmap(nand_regs);
} module_init(itop_nand_init);
module_exit(itop_nand_exit); MODULE_LICENSE("GPL");
Nor Flash驱动源代码(有可能部分开发板中没有Nor 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/mtd/mtd.h>
#include <linux/mtd/map.h>
#include <linux/mtd/partitions.h> #include <asm/io.h> static struct map_info *nor_map;
static struct mtd_info *nor_mtd; static struct mtd_partition nor_part[] = {
[] = {
.name = "bootloader",
.size = 0x00080000,
.offset = ,
}, [] = {
/* 没有那么大内存 */
#if 0
.name = "params",
.offset = MTDPART_OFS_APPEND,
.size = 0x00020000,
},
[] = {
.name = "kernel",
.offset = MTDPART_OFS_APPEND,
.size = 0x00400000,
},
[] = {
#endif
.name = "root",
.offset = MTDPART_OFS_APPEND,
.size = MTDPART_SIZ_FULL,
}
}; static int itop_nor_init(void)
{
/* 分配空间 */
nor_map = kzalloc(sizeof(struct map_info), GFP_KERNEL);
nor_mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL); nor_map->bankwidth = ;
nor_map->name = "itop_nor";
nor_map->phys = ;
nor_map->size = 0x100000;
nor_map->virt = ioremap(nor_map->phys, nor_map->size); nor_mtd = do_map_probe("cfi_probe", nor_map); if (!nor_mtd)
nor_mtd = do_map_probe("jedec_probe", nor_map); if (!nor_mtd) {
iounmap(nor_map->virt);
kfree(nor_mtd);
kfree(nor_map);
return -ENXIO;
} nor_mtd->owner = THIS_MODULE; /* 2表示分区个数 */
mtd_device_register(nor_mtd, nor_part, ); return ;
} static void itop_nor_exit(void)
{
if (nor_mtd) {
kfree(nor_map);
kfree(nor_mtd);
iounmap(nor_map->virt);
}
} module_init(itop_nor_init);
module_exit(itop_nor_exit); MODULE_LICENSE("GPL");
Makefile:
KERN_DIR = /work/itop4412/tools/linux-3.5 all:
make -C $(KERN_DIR) M=`pwd` modules clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order obj-m += nand.o nor.o
下一章 十九、eMMC驱动框架分析