MTD系列 - linux内核底层nand驱动解析

前言:
 前几篇文章基本都没有涉及到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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值