linux块设备之nand flash

1790阅读 0评论2014-10-30 644924073
分类:LINUX

   Flash是一种能够长期存储数据的设备,即使在不加电的情况下,数据也不会丢失,一般有两种:
1、norFlash:地址线和数据线分开,可以芯片内执行(XIP)而不必在把代码读到系统的RAM中,因而速度比较块。是随机存储介质,可以对字节进行操作,处理少量数据的时候的速度快于nand,适合用于数据较小的场合。
2、nandFlash:地址线和数据线复用,可以象磁盘一样通过接口升级,被称为固态硬盘。所有的操作都是以块和页为单位,所以在进行大量数据读写时,Nand Flash的速度快于norFlash.
   在linux系统中提供了MTD系统来建立Flash针对liinux系统,抽象接口,将文件系统与底层的存储进行隔离。MTK中间层细分为四层,按从上到下的依次为:设备节点、MTD设备层、MTD原始设备层和硬件驱动下层,其层次结构为:
1.硬件驱动层:Flash硬件驱动层负责Flash硬件设备的读、写、擦除,Linux MTD设备的norFlash芯片驱动位于driver/mtd/chips子目录,nandflash的驱动位于drivers/mtd/nand子目录。
2.MTD原始设备层:MTD原始设备层由两部分组成,一部分是MTD原始设备的通用代码,另一部分是各个特定Flash的数据,如分区。
3.MTD设备层:基于MTD原始设备,Linux系统可以定义出MTD的块设备的结构和字符设备,构成MTD设备层,MTD字符设备定义。
4.设备节点:通过mknod在/dev子目录下建立MTD字符设备节点和块设备节点,用户通过访问此设备节点。
通过源码分析,我们知道当上层要求对Flash进行读写时,它会像设备层发出请求,设备层得读写函数会调用原始设备层中的读写函数,即mtd_info结构体来描述原始设备的操作函数,各种信息。

点击(此处)折叠或打开

  1. struct mtd_info *mtd_table[MAX_MTD_DEVICES];
每个原始设备可能分成多个设备分区,设备分区是将一个内存分成多个块,每个块设备分区用一个mtd_part来描述,所有的分区组成一个链表mtd_partitions

点击(此处)折叠或打开

  1. static LIST_HEAD(mtd_partitions);
MTD原始设备到具体的设备之间存在一些映射关系数据在drivers/mtd/mpas目录下对应的文件中,这些映射数据包括分区信息、IO映射以及特定函数的映射等。这种映射关系用到map_info描述。在MTD设备层中,MTD字符设备通过file_iperation函数来操作,这些函数都是通过原始设备层得操作来实现的。MTD块设备实现了快设备的接口函数,所有的块设备组成一个mtdblks[MAX_MTD_DEVICES]。下面来看看mtdblk_dev的数据结构,代表一个闪存块设备。

点击(此处)折叠或打开

  1. static struct mtdblk_dev {
  2.     struct mtd_info *mtd;               //下层原始设备层的mtd设备结构
  3.     int count;
  4.     struct mutex cache_mutex;
  5.     unsigned char *cache_data;         //缓冲区数据地址
  6.     unsigned long cache_offset;        //在缓冲区中读写位置偏移
  7.     unsigned int cache_size;
  8.     enum { STATE_EMPTY, STATE_CLEAN, STATE_DIRTY } cache_state;//缓冲区状态
  9. } *mtdblks[MAX_MTD_DEVICES];
结构mtd_info描述了一个MTD原始设备,每个分区也被实现为一个mtd_info,这个mtd_info的指针被存在mtd_table的数组中,结构体mtd_info

点击(此处)折叠或打开

  1. struct mtd_info {
  2.     u_char type;              //内存技术的类型
  3.     u_int32_t flags;         //标志位
  4.     u_int32_t size;         //mtd设备的大小
  5.     u_int32_t erasesize;
  6.     u_int32_t writesize;

  7.     u_int32_t oobsize; //oob块大小
  8.     u_int32_t oobavail; //每个块oob数据量

  9.     // Kernel-only stuff starts here.
  10.     char *name;
  11.     int index;

  12.     struct nand_ecclayout *ecclayout;

  13.     int numeraseregions;//可变擦除区域的数据,如果为0,意味着整个设备
  14.     struct mtd_erase_region_info *eraseregions;

  15.     int (*erase) (struct mtd_info *mtd, struct erase_info *instr);

  16.     /* This stuff for eXecute-In-Place */
  17.     int (*point) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char **mtdbuf);

  18.     /* We probably shouldn't allow XIP if the unpoint isn't a NULL */
  19.     void (*unpoint) (struct mtd_info *mtd, u_char * addr, loff_t from, size_t len);


  20.     int (*read) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
  21.     int (*write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);

  22.     int (*read_oob) (struct mtd_info *mtd, loff_t from,
  23.              struct mtd_oob_ops *ops);
  24.     int (*write_oob) (struct mtd_info *mtd, loff_t to,
  25.              struct mtd_oob_ops *ops);

  26.     int (*get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
  27.     int (*read_fact_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
  28.     int (*get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
  29.     int (*read_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
  30.     int (*write_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
  31.     int (*lock_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len);

  32.     /* kvec-based read/write methods.
  33.      NB: The 'count' parameter is the number of _vectors_, each of
  34.      which contains an (ofs, len) tuple.
  35.     */
  36.     int (*writev) (struct mtd_info *mtd, const struct kvec *vecs, unsigned long count, loff_t to, size_t *retlen);

  37.     /* Sync */
  38.     void (*sync) (struct mtd_info *mtd);

  39.     /* Chip-supported device locking */
  40.     int (*lock) (struct mtd_info *mtd, loff_t ofs, size_t len);
  41.     int (*unlock) (struct mtd_info *mtd, loff_t ofs, size_t len);

  42.     /* Power Management functions */
  43.     int (*suspend) (struct mtd_info *mtd);
  44.     void (*resume) (struct mtd_info *mtd);

  45.     /* Bad block management functions */
  46.     int (*block_isbad) (struct mtd_info *mtd, loff_t ofs);
  47.     int (*block_markbad) (struct mtd_info *mtd, loff_t ofs);

  48.     struct notifier_block reboot_notifier; /* default mode before reboot */

  49.     /* ECC status information */
  50.     struct mtd_ecc_stats ecc_stats;
  51.     /* Subpage shift (NAND) */
  52.     int subpage_sft;

  53.     void *priv;                    //指向map_info结构

  54.     struct module *owner;
  55.     int usecount;

  56.     int (*get_device) (struct mtd_info *mtd);
  57.     void (*put_device) (struct mtd_info *mtd);
  58. };
设备层得mtdblock设备的notifier声明:

点击(此处)折叠或打开

  1. struct mtd_notifier {
  2.     void (*add)(struct mtd_info *mtd);
  3.     void (*remove)(struct mtd_info *mtd);
  4.     struct list_head list;
  5. };
mtd_part结构是用于描述MTD原始设备分区的,结构mtd_part中的list成员链成一个链表mtd_partions.每个mtd_part结构中的mtd_info结构用于描述本分区被加入mtd_table数组中,其中mtd_info结构大部分成员由住分区mtd_part->master决定,各种函数也指向主分区的相应函数。

点击(此处)折叠或打开

  1. /* Our partition linked list */
  2. static LIST_HEAD(mtd_partitions);//MTD原始设备分区的链表

  3. /* Our partition node structure */
  4. struct mtd_part {
  5.     struct mtd_info mtd;          //分区信息
  6.     struct mtd_info *master;     //该分区的主分区
  7.     u_int32_t offset;           //该分区的偏移地址
  8.     int index; //分区号
  9.     struct list_head list;
  10.     int registered;
  11. };
结构mtd_partition描述mtd设备分区的结构,在MTD原始设备层调用add_mtd_partions时传递分区信息使用

点击(此处)折叠或打开

  1. struct mtd_partition {
  2.     char *name;            //分区名
  3.     u_int32_t size;            //分区大小
  4.     u_int32_t offset;        /* offset within the master MTD space */
  5.     u_int32_t mask_flags;        /* master MTD flags to mask out for this partition */
  6.     struct nand_ecclayout *ecclayout;    /* out of band layout for this partition (NAND only)*/
  7.     struct mtd_info **mtdp;        /* pointer to store the MTD object */
  8. };
下面来看看各个层次的代码架构,首先来看看设备驱动层,注册一个平台驱动会,调用probe函数。

点击(此处)折叠或打开

  1. static int __init s3c2410_nand_init(void)
  2. {
  3.     printk("S3C24XX NAND Driver, (c) 2004 Simtec Electronics\n");

  4.     platform_driver_register(&s3c2412_nand_driver);
  5.     platform_driver_register(&s3c2440_nand_driver);
  6.     return platform_driver_register(&s3c2410_nand_driver);
  7. }

点击(此处)折叠或打开

  1. static int s3c24xx_nand_probe(struct platform_device *pdev,
  2.              enum s3c_cpu_type cpu_type)
  3. {
  4.     struct s3c2410_platform_nand *plat = to_nand_plat(pdev);
  5.     struct s3c2410_nand_info *info;
  6.     struct s3c2410_nand_mtd *nmtd;
  7.     struct s3c2410_nand_set *sets;
  8.     struct resource *res;
  9.     int err = 0;
  10.     int size;
  11.     int nr_sets;
  12.     int setno;

  13.     pr_debug("s3c2410_nand_probe(%p)\n", pdev);

  14.     info = kmalloc(sizeof(*info), GFP_KERNEL);
  15.     if (info == NULL) {
  16.         dev_err(&pdev->dev, "no memory for flash info\n");
  17.         err = -ENOMEM;
  18.         goto exit_error;
  19.     }

  20.     memzero(info, sizeof(*info));
  21.     platform_set_drvdata(pdev, info);

  22.     spin_lock_init(&info->controller.lock);
  23.     init_waitqueue_head(&info->controller.wq);

  24.     /* get the clock source and enable it */

  25.     info->clk = clk_get(&pdev->dev, "nand");
  26.     if (IS_ERR(info->clk)) {
  27.         dev_err(&pdev->dev, "failed to get clock");
  28.         err = -ENOENT;
  29.         goto exit_error;
  30.     }

  31.     clk_enable(info->clk);

  32.     /* allocate and map the resource */

  33.     /* currently we assume we have the one resource */
  34.     res = pdev->resource;
  35.     size = res->end - res->start + 1;

  36.     info->area = request_mem_region(res->start, size, pdev->name);

  37.     if (info->area == NULL) {
  38.         dev_err(&pdev->dev, "cannot reserve register region\n");
  39.         err = -ENOENT;
  40.         goto exit_error;
  41.     }

  42.     info->device = &pdev->dev;
  43.     info->platform = plat;
  44.     info->regs = ioremap(res->start, size);
  45.     info->cpu_type = cpu_type;

  46.     if (info->regs == NULL) {
  47.         dev_err(&pdev->dev, "cannot reserve register region\n");
  48.         err = -EIO;
  49.         goto exit_error;
  50.     }

  51.     dev_dbg(&pdev->dev, "mapped registers at %p\n", info->regs);

  52.     /* initialise the hardware */

  53.     err = s3c2410_nand_inithw(info, pdev);
  54.     if (err != 0)
  55.         goto exit_error;

  56.     sets = (plat != NULL) ? plat->sets : NULL;
  57.     nr_sets = (plat != NULL) ? plat->nr_sets : 1;

  58.     info->mtd_count = nr_sets;

  59.     /* allocate our information */

  60.     size = nr_sets * sizeof(*info->mtds);
  61.     info->mtds = kmalloc(size, GFP_KERNEL);
  62.     if (info->mtds == NULL) {
  63.         dev_err(&pdev->dev, "failed to allocate mtd storage\n");
  64.         err = -ENOMEM;
  65.         goto exit_error;
  66.     }

  67.     memzero(info->mtds, size);

  68.     /* initialise all possible chips */

  69.     nmtd = info->mtds;

  70.     for (setno = 0; setno < nr_sets; setno++, nmtd++) {
  71.         pr_debug("initialising set %d (%p, info %p)\n", setno, nmtd, info);

  72.         s3c2410_nand_init_chip(info, nmtd, sets);

  73.         nmtd->scan_res = nand_scan(&nmtd->mtd, (sets) ? sets->nr_chips : 1);

  74.         if (nmtd->scan_res == 0) {
  75.             s3c2410_nand_add_partition(info, nmtd, sets);
  76.         }

  77.         if (sets != NULL)
  78.             sets++;
  79.     }

  80.     if (allow_clk_stop(info)) {
  81.         dev_info(&pdev->dev, "clock idle support enabled\n");
  82.         clk_disable(info->clk);
  83.     }

  84.     pr_debug("initialised ok\n");
  85.     return 0;

  86.  exit_error:
  87.     s3c2410_nand_remove(pdev);

  88.     if (err == 0)
  89.         err = -EINVAL;
  90.     return err;
  91. }
上面主要完成
1.分配一个s3c2410_nand_info结构,并初始化等待队列,获取时钟,内存映射
2.s3c2410_nand_inithw对nand控制器初始化
3.s3c2410_nand_init_chip
4.nand_scan扫描nand flash
5.s3c2410_nand_add_partition添加分区

点击(此处)折叠或打开

  1. int add_mtd_device(struct mtd_info *mtd)
  2. {
  3.     int i;

  4.     BUG_ON(mtd->writesize == 0);
  5.     mutex_lock(&mtd_table_mutex);

  6.     for (i=0; i < MAX_MTD_DEVICES; i++)
  7.         if (!mtd_table[i]) {
  8.             struct list_head *this;

  9.             mtd_table[i] = mtd;
  10.             mtd->index = i;
  11.             mtd->usecount = 0;

  12.             /* Some chips always power up locked. Unlock them now */
  13.             if ((mtd->flags & MTD_WRITEABLE)
  14.              && (mtd->flags & MTD_STUPID_LOCK) && mtd->unlock) {
  15.                 if (mtd->unlock(mtd, 0, mtd->size))
  16.                     printk(KERN_WARNING
  17.                      "%s: unlock failed, "
  18.                      "writes may not work\n",
  19.                      mtd->name);
  20.             }

  21.             DEBUG(0, "mtd: Giving out device %d to %s\n",i, mtd->name);
  22.             /* No need to get a refcount on the module containing
  23.              the notifier, since we hold the mtd_table_mutex */
  24.             list_for_each(this, &mtd_notifiers) {
  25.                 struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list);
  26.                 not->add(mtd);
  27.             }

  28.             mutex_unlock(&mtd_table_mutex);
  29.             /* We _know_ we aren't being removed, because
  30.              our caller is still holding us here. So none
  31.              of this try_ nonsense, and no bitching about it
  32.              either. :) */
  33.             __module_get(THIS_MODULE);
  34.             return 0;
  35.         }

  36.     mutex_unlock(&mtd_table_mutex);
  37.     return 1;
  38. }
这个函数主要是搜索mtd_notifiers链表,匹配后调用add函数。在代码中看看mtd_notifiers链表是在那初始化的。

点击(此处)折叠或打开

  1. void register_mtd_user (struct mtd_notifier *new)
  2. {
  3.     int i;

  4.     mutex_lock(&mtd_table_mutex);

  5.     list_add(&new->list, &mtd_notifiers);//将MTD块设备的通知结构实例加入到全局链表mtd_notifiers

  6.      __module_get(THIS_MODULE);

  7.     for (i=0; i< MAX_MTD_DEVICES; i++)//对每个MTD块设备调用MTD通知结构实例设备函数
  8.         if (mtd_table[i])
  9.             new->add(mtd_table[i]);

  10.     mutex_unlock(&mtd_table_mutex);
  11. }
将其家人mtd_notifirs队列,然后对每个MTD原始设备执行addh函数,下面来看看其总体的框架图

点击(此处)折叠或打开

  1. int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
  2. {
  3.     int ret, i;

  4.     /* Register the notifier if/when the first device type is
  5.      registered, to prevent the link/init ordering from fucking
  6.      us over. */
  7.     if (!blktrans_notifier.list.next)//如果不存在,就注册MTD块设备
  8.         register_mtd_user(&blktrans_notifier);

  9.     tr->blkcore_priv = kzalloc(sizeof(*tr->blkcore_priv), GFP_KERNEL);
  10.     if (!tr->blkcore_priv)
  11.         return -ENOMEM;

  12.     mutex_lock(&mtd_table_mutex);

  13.     ret = register_blkdev(tr->major, tr->name);
  14.     if (ret) {
  15.         printk(KERN_WARNING "Unable to register %s block device on major %d: %d\n",
  16.          tr->name, tr->major, ret);
  17.         kfree(tr->blkcore_priv);
  18.         mutex_unlock(&mtd_table_mutex);
  19.         return ret;
  20.     }
  21.     spin_lock_init(&tr->blkcore_priv->queue_lock);
  22. //创建请求队列,并赋予块设备特定的请求处理函数
  1.     tr->blkcore_priv->rq = blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock);
  2.     if (!tr->blkcore_priv->rq) {
  3.         unregister_blkdev(tr->major, tr->name);
  4.         kfree(tr->blkcore_priv);
  5.         mutex_unlock(&mtd_table_mutex);
  6.         return -ENOMEM;
  7.     }

  8.     tr->blkcore_priv->rq->queuedata = tr; //赋予MTD块设备操作函数集
  9.     blk_queue_hardsect_size(tr->blkcore_priv->rq, tr->blksize);
  10.     tr->blkshift = ffs(tr->blksize) - 1;

  11.     tr->blkcore_priv->thread = kthread_run(mtd_blktrans_thread, tr,
  12.             "%sd", tr->name);//创建线程
  13.     if (IS_ERR(tr->blkcore_priv->thread)) {
  14.         blk_cleanup_queue(tr->blkcore_priv->rq);
  15.         unregister_blkdev(tr->major, tr->name);
  16.         kfree(tr->blkcore_priv);
  17.         mutex_unlock(&mtd_table_mutex);
  18.         return PTR_ERR(tr->blkcore_priv->thread);
  19.     }

  20.     INIT_LIST_HEAD(&tr->devs);//初始化设备的链表
  21.     list_add(&tr->list, &blktrans_majors);

  22.     for (i=0; i<MAX_MTD_DEVICES; i++) {
  23.         if (mtd_table[i] && mtd_table[i]->type != MTD_ABSENT)
  24.             tr->add_mtd(tr, mtd_table[i]);//创建MTD设备结构并初始化,然后加入到MTD设备链表中
  25.     }

  26.     mutex_unlock(&mtd_table_mutex);

  27.     return 0;
  28. }
其中函数mtd_blktrans_request是MTD设备的请求处理函数,当请求队列中的请求需要设备处理时调用这个函数。在MTD设备中,函数mtd_blktrans_request唤醒了块设备线程来处理。

点击(此处)折叠或打开

  1. static void mtd_blktrans_request(struct request_queue *rq)
  2. {
  3.     struct mtd_blktrans_ops *tr = rq->queuedata;
  4.     wake_up_process(tr->blkcore_priv->thread);
  5. }
线程函数mtd_blktrans_thread处理块设备的读写请求

点击(此处)折叠或打开

  1. static int mtd_blktrans_thread(void *arg)
  2. {
  3.     struct mtd_blktrans_ops *tr = arg;
  4.     struct request_queue *rq = tr->blkcore_priv->rq;

  5.     /* we might get involved when memory gets low, so use PF_MEMALLOC */
  6.     current->flags |= PF_MEMALLOC | PF_NOFREEZE;

  7.     spin_lock_irq(rq->queue_lock);
  8.     while (!kthread_should_stop()) {
  9.         struct request *req;
  10.         struct mtd_blktrans_dev *dev;
  11.         int res = 0;

  12.         req = elv_next_request(rq);//从块设备的请求队列中得到下一个请求

  13.         if (!req) {//如果请求不存在,将设备的等待线程加入等待队列中
  14.             set_current_state(TASK_INTERRUPTIBLE);
  15.             spin_unlock_irq(rq->queue_lock);
  16.             schedule();//调度让CPU有机会执行等待线程
  17.             spin_lock_irq(rq->queue_lock);
  18.             continue;
  19.         }

  20.         dev = req->rq_disk->private_data;//得到请求的设备
  21.         tr = dev->tr;

  22.         spin_unlock_irq(rq->queue_lock);

  23.         mutex_lock(&dev->lock);
  24.         res = do_blktrans_request(tr, dev, req);//处理请求
  25.         mutex_unlock(&dev->lock);

  26.         spin_lock_irq(rq->queue_lock);

  27.         end_request(req, res);//从请求队列删去请求
  28.     }
  29.     spin_unlock_irq(rq->queue_lock);

  30.     return 0;
  31. }
其整个的流程为:
下面来看看是怎么调用add分区的,由register_mtd_user注册结构mtd_notifier实现,函数注册MTD设备,通过分配硬盘结构来激活每个MTD设备。

点击(此处)折叠或打开

  1. static struct mtd_notifier blktrans_notifier = {
  2.     .add = blktrans_notify_add,
  3.     .remove = blktrans_notify_remove,
  4. };
函数blktrans_notify_add通过MTD将设备加入到链表blktrans_majors中,并分配处理每个MTD分区对应的通用硬盘结构

点击(此处)折叠或打开

  1. static void blktrans_notify_add(struct mtd_info *mtd)
  2. {
  3.     struct list_head *this;

  4.     if (mtd->type == MTD_ABSENT) //设备不存在
  5.         return;

  6.     list_for_each(this, &blktrans_majors) {//遍历每个MTD主设备
  7.         struct mtd_blktrans_ops *tr = list_entry(this, struct mtd_blktrans_ops, list);

  8.         tr->add_mtd(tr, mtd);
  9.     }
  10. }
函数mtdblock_add_mtd分配MTD块设备结构,初始化后加入MTD块设备链表

点击(此处)折叠或打开

  1. static void blktrans_notify_add(struct mtd_info *mtd)
  2. {
  3.     struct list_head *this;

  4.     if (mtd->type == MTD_ABSENT)
  5.         return;

  6.     list_for_each(this, &blktrans_majors) {
  7.         struct mtd_blktrans_ops *tr = list_entry(this, struct mtd_blktrans_ops, list);

  8.         tr->add_mtd(tr, mtd);
  9.     }

  10. }
其主要的流程为:
下面来看看内核中怎么添加的分区表,通过 s3c_device_nand.dev.platform_data = &smdk_nand_info;
然后在probe中会用到

点击(此处)折叠或打开

  1. static struct mtd_partition smdk_default_nand_part[] = {
  2.     [0] = {
  3.         .name    = "Boot Agent",
  4.         .size    = SZ_16K,
  5.         .offset    = 0,
  6.     },
  7.     [1] = {
  8.         .name    = "S3C2410 flash partition 1",
  9.         .offset = 0,
  10.         .size    = SZ_2M,
  11.     },
  12.     [2] = {
  13.         .name    = "S3C2410 flash partition 2",
  14.         .offset = SZ_4M,
  15.         .size    = SZ_4M,
  16.     },
  17.     [3] = {
  18.         .name    = "S3C2410 flash partition 3",
  19.         .offset    = SZ_8M,
  20.         .size    = SZ_2M,
  21.     },
  22.     [4] = {
  23.         .name    = "S3C2410 flash partition 4",
  24.         .offset = SZ_1M * 10,
  25.         .size    = SZ_4M,
  26.     },
  27.     [5] = {
  28.         .name    = "S3C2410 flash partition 5",
  29.         .offset    = SZ_1M * 14,
  30.         .size    = SZ_1M * 10,
  31.     },
  32.     [6] = {
  33.         .name    = "S3C2410 flash partition 6",
  34.         .offset    = SZ_1M * 24,
  35.         .size    = SZ_1M * 24,
  36.     },
  37.     [7] = {
  38.         .name    = "S3C2410 flash partition 7",
  39.         .offset = SZ_1M * 48,
  40.         .size    = SZ_16M,
  41.     }
  42. };

点击(此处)折叠或打开

  1. static struct s3c2410_platform_nand smdk_nand_info = {
  2.     .tacls        = 20,
  3.     .twrph0        = 60,
  4.     .twrph1        = 20,
  5.     .nr_sets    = ARRAY_SIZE(smdk_nand_sets),
  6.     .sets        = smdk_nand_sets,
  7. };

  8. /* devices we initialise */

  9. static struct platform_device __initdata *smdk_devs[] = {
  10.     &s3c_device_nand,
  11.     &smdk_led4,
  12.     &smdk_led5,
  13.     &smdk_led6,
  14.     &smdk_led7,
  15. };



上一篇:linux之块设备驱动
下一篇:概述Linux网络设备驱动结构