Linux NAND FLASH驱动分析(一)

  最近一直在忙着工作上的事情,好久都没有更新博客了,发现最近思想是比较混乱的。学任何东西都坚持不下去,既然选择驱动开发这条路就要坚持下去。

  之前分析了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  调用平台总线驱动的matchdrv->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 mtd_partition smdk_default_nand_part[] = { 
    [0] = {
        .name= "bootloader",
        .size=SZ_512K,
        .offset= 0,
    },
    [1] = {
       .name= "kernel",
       .offset      = MTDPART_OFS_APPEND,
       .size       =SZ_2M,
    },
    [2] = {
       .name= "fs",
       .offset      = MTDPART_OFS_APPEND,
       .size       =MTDPART_SIZ_FULL,
    },

 };

包含的设置

static struct s3c2410_nand_set smdk_nand_sets[] = {
    [0] = {
        .name = "NAND",
        .nr_chips = 1,
        .nr_partitions = ARRAY_SIZE(smdk_default_nand_part),
       .partitions = smdk_default_nand_part,
    },
 };

static struct s3c2410_platform_nand
smdk_nand_info = {
    .tacls = 20,
    .twrph0 = 60,
    .twrph1 = 20,
   .nr_sets = ARRAY_SIZE(smdk_nand_sets),
 // 1
   .sets=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,
  },
};





你可能感兴趣的:(Linux驱动)