最近一直在忙着工作上的事情,好久都没有更新博客了,发现最近思想是比较混乱的。学任何东西都坚持不下去,既然选择驱动开发这条路就要坚持下去。
之前分析了Linux块设备驱动,是以内存块来模拟的虚拟块设备。现在重点来分析Linux NAND FLASH驱动,移植过uboot的朋友都知道,在uboot中NAND FLASH驱动正是从Linux内核中移植过去的,Linux内核中对FLASH的驱动都是以MTD来实现的,现在就来揭秘MTD驱动的神秘面纱。
在Linux内核启动阶段,总能看到这样的打印信息(“S3C24XX NAND Driver, (c) 2004 Simtec Electronics”),没错,这就是NAND FLASH驱动中打印出来,我们就顺藤摸瓜的分析下去。
注:Linux内核版本为2.6.36.4
在drivers/mtd/nand/s3c2410.c中的s3c2410_nand_init函数即为驱动的入口。
s3c2410_nand_init
--> platform_driver_register(&s3c24xx_nand_driver)
主要工作就是注册了一个平台驱动。以下为该平台驱动的实例:
static struct platform_driver s3c24xx_nand_driver = {
.probe= s3c24xx_nand_probe,
.remove= s3c24xx_nand_remove,
.suspend= s3c24xx_nand_suspend,
.resume= s3c24xx_nand_resume,
.id_table= s3c24xx_driver_ids,
.driver= {
.name= "s3c24xx-nand",
.owner= THIS_MODULE,
},
};
platform_driver_register
--> driver_register
--> bus_add_driver
--> driver_attach
--> __driver_attach
--> driver_match_device 调用平台总线驱动的match(drv->bus->match ? drv->bus->match(dev, drv) : 1;)
--> platform_match
--> platform_match_id 将用platform_driver.id_table的name与平台设备的name进行匹配
--> driver_probe_device
--> really_probe
--> 最终调用平台驱动的probe方法:s3c24xx_nand_probe
下面就从这个函数开始分析:
s3c24xx_nand_probe
/* NAND的探测函数:
common-smdk2440.c
NAND FLASH的分区表
};
包含的设置
static struct s3c2410_nand_set smdk_nand_sets[] = {
static int s3c24xx_nand_probe(struct platform_device *pdev)
{
struct s3c2410_platform_nand *plat = to_nand_plat(pdev);
enum s3c_cpu_type cpu_type;
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;
// 得到CPU的类型,对于S3C2440:TYPE_S3C2440
cpu_type = platform_get_device_id(pdev)->driver_data;
// 动态申请一个s3c2410_nand_info结构
info = kzalloc(sizeof(*info), GFP_KERNEL);
if (info == NULL) {
dev_err(&pdev->dev, "no memory for flash info\n");
err = -ENOMEM;
goto exit_error;
}
// pdev->dev.drv_data = info
platform_set_drvdata(pdev, info);
// 初始化硬件控制器的锁和等待队列
spin_lock_init(&info->controller.lock);
init_waitqueue_head(&info->controller.wq);
// 获取设备的时钟
info->clk = clk_get(&pdev->dev, "nand");
if (IS_ERR(info->clk)) {
dev_err(&pdev->dev, "failed to get clock\n");
err = -ENOENT;
goto exit_error;
}
// 使能时钟
clk_enable(info->clk);
// 获取平台设备的资源及大小
/*
static struct resource s3c_nand_resource[] = {
[0] = {
.start =S3C_PA_NAND,
.end =S3C_PA_NAND + SZ_1M,
.flags =IORESOURCE_MEM,
}
};
struct platform_device s3c_device_nand = {
.name = "s3c2410-nand",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_nand_resource),
.resource = s3c_nand_resource,
};
*/
res = pdev->resource;
size = resource_size(res);
// 检查资源是否可用
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;
}
// 设置s3c2410_nand_info结构
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;
}
// 初始化NAND FLASH控制器
err = s3c2410_nand_inithw(info);
if (err != 0)
goto exit_error;
// 获取NAND FLASH的配置及配置的种类
sets = (plat != NULL) ? plat->sets : NULL;
nr_sets = (plat != NULL) ? plat->nr_sets : 1;
info->mtd_count = nr_sets;// 1
// 计算需要的s3c2410_nand_mtd的空间
size = nr_sets * sizeof(*info->mtds);
// 动态申请空间
info->mtds = kzalloc(size, GFP_KERNEL);
if (info->mtds == NULL) {
dev_err(&pdev->dev, "failed to allocate mtd storage\n");
err = -ENOMEM;
goto exit_error;
}
// nmtd指向临时申请的s3c2410_nand_mtd空间
nmtd = info->mtds;
for (setno = 0; setno < nr_sets; setno++, nmtd++) {
// 对s3c2410_nand_mtd内嵌的nand_chip进行初始化
s3c2410_nand_init_chip(info, nmtd, sets);
// 对NAND设备进行扫描
nmtd->scan_res = nand_scan_ident(&nmtd->mtd,
(sets) ? sets->nr_chips : 1,NULL);
// 扫描成功
if (nmtd->scan_res == 0) {
s3c2410_nand_update_chip(info, nmtd);// 跟新s3c2410_nand_mtd内嵌的nand_chip成员
nand_scan_tail(&nmtd->mtd);
s3c2410_nand_add_partition(info, nmtd, sets);// 添加分区
}
if (sets != NULL)
sets++;
}
err = s3c2410_nand_cpufreq_register(info);
if (err < 0) {
dev_err(&pdev->dev, "failed to init cpufreq support\n");
goto exit_error;
}
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:
s3c24xx_nand_remove(pdev);
if (err == 0)
err = -EINVAL;
return err;
}
涉及的数据结构:
struct s3c2410_nand_info {
struct nand_hw_control controller;
struct s3c2410_nand_mtd *mtds;
struct s3c2410_platform_nand *platform;
struct device *device;
struct resource *area;
struct clk *clk;
void __iomem *regs;
void __iomem *sel_reg;
int sel_bit;
int mtd_count;
unsigned long save_sel;
unsigned long clk_rate;
enum s3c_cpu_type cpu_type;
#ifdef CONFIG_CPU_FREQ
struct notifier_block freq_transition;
#endif
};
struct nand_hw_control {
spinlock_t lock;
struct nand_chip *active;
wait_queue_head_t wq;
};
struct s3c2410_nand_mtd {
struct mtd_info mtd;
struct nand_chip chip;
struct s3c2410_nand_set *set;
struct s3c2410_nand_info *info;
int scan_res;
};
描述NAND FLASH的平台信息
struct s3c2410_platform_nand {
/* 控制器的时序信息,所有的都是以纳秒为单位 */
inttacls; 从CLE/ALE到nWE/nOE 信号的有效时间
inttwrph0; nWE/nOE的有效时间
inttwrph1; 从nWE/nOE失效到CLE/ALE释放的时间
unsigned intignore_unset_ecc:1;
intnr_sets; 支持多少种配置
struct s3c2410_nand_set *sets; 配置集合
void(*select_chip)(struct s3c2410_nand_set *, int chip); 选择芯片的函数指针
};
用于描述S3C2410平台的NAND FLASH 配置
struct s3c2410_nand_set {
unsigned intdisable_ecc:1; 是否禁止ECC校验
unsigned intflash_bbt:1; 是否使用FLASH来存储坏块表
unsigned intoptions; 可选的标志
int nr_chips; 有多少片NAND FLASH
int nr_partitions; 有多个分区
char *name; 名字
int *nr_map;
struct mtd_partition*partitions; MTD分区表指针
struct nand_ecclayout*ecc_layout; ECC布局图指针
};
用来描述一个MTD分区
struct mtd_partition {
char *name; 分区的名字
uint64_t size; 分区的大小
uint64_t offset; 在整个MTD设备中的偏移地址
uint32_t mask_flags; 主MTD设备掩码对于该分区不起左右的掩码位
struct nand_ecclayout *ecclayout; ECC布局
};
用于描述ECC布局
struct nand_ecclayout {
__u32 eccbytes; 用于ECC校验的数据字节数
__u32 eccpos[64]; ECC数据的存放位置
__u32 oobavail; OOB区可用大小
struct nand_oobfree oobfree[8]; 用来描述一个OOB空闲区
};
用来描述OOB空闲区
struct nand_oobfree {
__u32 offset; 空闲区的偏移地址
__u32 length; 空闲取的长度
};
在/arch/arm/plat-s3c24xx/common-smdk.c中有s3c2410_platform_nand的实例:smdk_nand_info
static struct s3c2410_platform_nandsmdk_nand_info = {
.tacls = 20,
.twrph0 = 60,
.twrph1 = 20,
.nr_sets = ARRAY_SIZE(smdk_nand_sets), // = 1支持一种配置
.sets =smdk_nand_sets,
};
static struct s3c2410_nand_setsmdk_nand_sets[] = {
[0] = {
.name = "NAND",
.nr_chips = 1, 只有一块NAND FLASH
.nr_partitions= ARRAY_SIZE(smdk_default_nand_part), 该NAND FLASH上分区数
.partitions=smdk_default_nand_part, NAND FLASH上的分区表
},
};
static struct mtd_partitionsmdk_default_nand_part[] = {
[0] = {
.name = "Boot Agent",
.size = SZ_16K,
.offset = 0,
},
[1] = {
.name = "S3C2410 flash partition 1",
.offset = 0,
.size = SZ_2M,
},
[2] = {
.name = "S3C2410 flash partition 2",
.offset = SZ_4M,
.size = SZ_4M,
},
[3] = {
.name = "S3C2410 flash partition 3",
.offset = SZ_8M,
.size = SZ_2M,
},
};