MTD系列 - linux内核底层nand驱动解析
<br />前言:<br /> 前几篇文章基本都没有涉及到mtd原始设备层以下的内容,其实mtd块设备层和mtd原始设备层的分界线很明显,只是通过<br /> mtd_table[]和mtd_notifiers链表来联系,具体怎么联系的请参考上一篇文章。<br /> 本文中将详细分析linux内核中nand设备注册和驱动注册,同时文中会穿插关于nand坏块管理的部分,另外在适当的地方会<br />
前言:
前几篇文章基本都没有涉及到mtd原始设备层以下的内容,其实mtd块设备层和mtd原始设备层的分界线很明显,只是通过
mtd_table[]和mtd_notifiers链表来联系,具体怎么联系的请参考上一篇文章。
本文中将详细分析linux内核中nand设备注册和驱动注册,同时文中会穿插关于nand坏块管理的部分,另外在适当的地方会
讲解DMA原理和其在nand驱动程序中的应用。
* OS : linux2.6.29
* SOC : pxa935
* NAND : Hynix(512MB 1.8V 16-bit) - H8BCS0UN0MCR
* Author: 李枝果/lizgo 2010-11-9 lizhiguo0532@163.com
* note : 本文中涉及的内容基本都是平台相关的,读者可只关注共性的东西
2.6版本的linux内核驱动模型中流传着一个时髦的词:“platform”,其中也存在platform device和platform driver,
内核中使用platform bus统一管理这些设备和驱动,所以注册包括device和driver的注册。
一、platform device注册
MACHINE_START和MACHINE_END定义的结构体是平台相关的,这里定义如下:
MACHINE_START(BENZGLB, "Benzglb")
.phys_io = 0x40000000,
.boot_params = 0xa0000100,
.io_pg_offst = (io_p2v(0x40000000) >> 18) & 0xfffc,
.map_io = pxa_map_io,
// start_kernel()-->setup_arch()-->paging_init()-->devicemaps_init()-->pxa_map_io
.init_irq = pxa3xx_init_irq,
// 在setup_arch()中被赋值给全局函数指针init_arch_irq
// start_kernel()-->init_IRQ()-->"init_arch_irq()"间接调用pxa3xx_init_irq
.timer = &pxa_timer,
// 在setup_arch()中被赋值给全局struct sys_timer对象指针system_timer
// start_kernel()-->time_init-->"system_timer->init()"间接调用pxa_timer.init = pxa_timer_init()
.init_machine = benzglb_init,
// 在setup_arch()中被赋值给全局函数指针init_machine
// 由于arch_initcall(customize_machine),所以在start_kernel()-->rest_init()
// -->kernel_init()-->do_basic_setup()-->do_initcalls()的第3个等级上被调用
// init.h
MACHINE_END
/*******************************
其中的宏定义于文件:arch/arm/include/asm/mach/arch.h
#define MACHINE_START(_type,_name) /
static const struct machine_desc __mach_desc_##_type /
__used /
__attribute__((__section__(".arch.info.init"))) = { /
.nr = MACH_TYPE_##_type, /
.name = _name,
#define MACHINE_END /
};
将上面的展开,实际上就是定义了一个结构体:
static const struct machine_desc __mach_desc_BENZGLB __used /
__attribute__((__section__(".arch.info.init"))) = {
.nr = MACH_TYPE_BENZGLB,
.name = "Benzglb",
.phys_io = 0x40000000,
.boot_params = 0xa0000100,
.io_pg_offst = (io_p2v(0x40000000) >> 18) & 0xfffc,
.map_io = pxa_map_io,
.init_irq = pxa3xx_init_irq,
.timer = &pxa_timer,
.init_machine = benzglb_init,
};
*******************************/
benzglb_init()函数可谓是重量级的了,初始化了很多东西。但是是在哪里调用该函数的呢?或许从上面的注释你也可以
注意到了,下面就再来wlak一下:
start_kernel()
--> setup_arch()
--> ...
--> init_machine = mdesc->init_machine;
--> ...
init_machine是文件arch/arm/kernel/setup.c中的静态全局变量,定义和调用如下:
static void (*init_machine)(void) __initdata;
static int __init customize_machine(void)
{
/* customizes platform devices, or adds new ones */
if (init_machine)
init_machine();
return 0;
}
arch_initcall(customize_machine); // init.h initcall3
可以看到customize_machine()函数将会在do_initcalls()的第3个等级上被调用,接着就会调用函数init_machine(),
也就是函数benzglb_init():
do_initcalls()
--> customize_machine()
--> init_machine() == benzglb_init()
--> benzina_init_nand() // 该函数中我们只关注nand初始化
看来有必要将benzina_init_nand()全部列出来看一看了:
static struct pxa3xx_nand_platform_data benzina_nand_info;
/******
struct pxa3xx_nand_platform_data {
struct mtd_partition *parts;
unsigned int nr_parts;
};
******/
static void __init benzina_init_nand(void)
{
benzina_nand_info.parts = android_256m_v75_partitions; // nand分区数组
benzina_nand_info.nr_parts = ARRAY_SIZE(android_256m_v75_partitions); // nand分区数目
pxa3xx_device_nand.dev.platform_data = &benzina_nand_info;
platform_device_register(&pxa3xx_device_nand);
}
android_256m_v75_partitions定义于arch/arm/mach-pxa/include/mach/part_table.h中,这是一个
struct mtd_partition类型结构体的数组,描述了系统上nand分区情况:name、offset、size等。
pxa3xx_device_nand结构体对象是struct platform_device类型:
static u64 pxa3xx_nand_dma_mask = DMA_BIT_MASK(32);
static struct resource pxa3xx_resource_nand[] = {
[0] = {
.start = 0x43100000,
.end = 0x431000ff,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_NAND,
.end = IRQ_NAND,
.flags = IORESOURCE_IRQ,
},
};
struct platform_device pxa3xx_device_nand = {
.name = "pxa3xx-nand",
.id = -1,
.dev = {
.dma_mask = &pxa3xx_nand_dma_mask,
.coherent_dma_mask = DMA_BIT_MASK(32),
},
.resource = pxa3xx_resource_nand, // see up
.num_resources = ARRAY_SIZE(pxa3xx_resource_nand),
};
struct platform_device结构体的定义位于文件include/linux/platform_device.h中:
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource * resource;
};
benzina_init_nand()函数中将描述nand分区的结构体benzina_nand_info与描述nand device的结构体
联系起来:
pxa3xx_device_nand.dev.platform_data = &benzina_nand_info;
最后调用函数platform_device_register(&pxa3xx_device_nand)将nand的平台设备注册进系统的设备树内。
对于注册的过程这里就不跟踪了,如果有兴趣,可参考我的另篇文章或自行分析。
二、platform driver注册
该部分的内容位于文件drivers/mtd/nand/pxa3xx_nand.c
static struct platform_driver pxa3xx_nand_driver = {
.driver = {
.name = "pxa3xx-nand",
},
.probe = pxa3xx_nand_probe,
.remove = pxa3xx_nand_remove,
#ifdef CONFIG_PM // 电源管理的部分
.suspend = pxa3xx_nand_suspend,
.resume = pxa3xx_nand_resume,
#endif
};
static int __init pxa3xx_nand_init(void)
{
...
return platform_driver_register(&pxa3xx_nand_driver);
}
module_init(pxa3xx_nand_init);
这里说明一点:device和driver的注册其实是没有先后之分的,device注册的时候除了将自己挂在platform bus上外,另外
会去遍历该bus上的所有drivers,直到匹配到(device和driver的名字相同)一个driver为止。而driver注册的时候,也是除
了将自己挂在platfrom bus上之外,另外也会去遍历该bus上的所有设备区匹配,这里和前面不同的是,它会去找到多有该
driver可以管理到的设备为止。下面就跟踪一下driver注册时,如何调用到probe函数的,又如何传递了
struct platform_device的参数:
platform_driver_register(&pxa3xx_nand_driver)
--> drv->driver.bus = &platform_bus_type
--> drv->driver.probe = platform_drv_probe
--> ...
--> driver_register(&drv->driver)
--> driver_find(drv->name, drv->bus) // 线检查是否已经注册过了
--> bus_add_driver(drv)
--> driver_attach(drv)
--> bus_for_each_dev(drv->bus, NULL, drv, __driver_attach)
--> fn(dev, data) = __driver_attach(dev, data) // dev - each device on platfrom bus
// data - driver
--> driver_probe_device(drv, dev)
--> drv->bus->match(dev, drv) = platform_match(dev, drv) // 名字匹配
--> really_probe(dev, drv)
--> drv->probe(dev) = platform_drv_probe(dev)
--> static int platform_drv_probe(struct device *_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
return drv->probe(dev);
}
-->pxa3xx_nand_probe(&pxa3xx_device_nand);
...
到这个过程中,我们就还可以看到,driver注册的时候,会优先使用platform bus
的probe函数,如果它的probe函数为NULL,那么就使用注册driver的probe函数
(前提是要存在probe函数)
三、pxa3xx_nand_probe()函数分析
由于内容加多,参见文档:pxa3xx_nand_probe.c
四、底层几个关键结构体的联系
static struct mtd_info *monahans_mtd = NULL;
struct nand_chip *this;
struct pxa3xx_nand_info *info;
struct dfc_context dfc_context =
{
.dfc_mode = &dfc_mode,
};
static struct dfc_flash_info hynix4GbX16 =
{
.timing = {
.tCH = 10, /* tCH, Enable signal hold time */
.tCS = 35, /* tCS, Enable signal setup time */
.tWH = 15, /* tWH, ND_nWE high duration */
.tWP = 25, /* tWP, ND_nWE pulse time */
.tRH = 15, /* tRH, ND_nRE high duration */
.tRP = 25, /* tRP, ND_nRE pulse width */
/* tR = tR+tRR+tWB+1, ND_nWE high to ND_nRE low for read */
.tR = 25000,
/* tWHR, ND_nWE high to ND_nRE low delay for status read */
.tWHR = 60,
.tAR = 10, /* tAR, ND_ALE low to ND_nRE low delay */
},
.enable_arbiter = 1, /* Data flash bus arbiter enable */
.page_per_block = 64, /* Pages per block */
.row_addr_start = 1, /* third cycle start, Row address start position */
.read_id_bytes = 4, /* Returned ID bytes */
.dfc_mode = 0, /* NAND mode */
.ncsx = 0,
.page_size = 2048, /* Page size in bytes */
.oob_size = 64, /* OOB size in bytes */
.flash_width = 16, /* Width of Flash memory */
.dfc_width = 16, /* Width of flash controller */
.num_blocks = 4096, /* Number of physical blocks in Flash */ //modified sunqidong
.chip_id = 0xbcad, //modified sunqidong
.read_prog_cycles = 5, /* Read, Program Cycles */
/* command codes */
.read1 = 0x3000, /* Read */
.read2 = 0x0050, /* Read1 unused, current DFC don't support */
.program = 0x1080, /* Write, two cycle command */ //modified sunqidong
.read_status = 0x0070, /* Read status */
.read_id = 0x0090, /* Read ID */
.erase = 0xD060, /* Erase, two cycle command */
.reset = 0x00FF, /* Reset */
.lock = 0x002A, /* Lock whole flash */
.unlock = 0x2423, /* Unlock, two cycle command, supporting partial unlock */
.lock_status = 0x007A, /* Read block lock status */
.addr2ndcb1 = HYNIX4GbX16Addr2NDCB1,
.ndbbr2addr = HYNIX4GbX16NDBBR2Addr,
};
/ 这几个底层关键结构体的联系
context->flash_info = &hynix4GbX16
// 分配mtd_info、nand_chip、pxa3xx_nand_info的空间
monahans_mtd = kzalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip) +
sizeof(struct pxa3xx_nand_info) , GFP_KERNEL);
...
this = (struct nand_chip *)((void *)monahans_mtd + sizeof(struct mtd_info));
info = (struct pxa3xx_nand_info *)((void *)this + sizeof(struct nand_chip));
...
monahans_mtd->priv = this;
this->priv = info;
...
info->context = &dfc_context
...
更多推荐
所有评论(0)