uClinux 2.6(bf561)中的NorFlash驱动实现分析:全文

来源:互联网 发布:普天网络会所 编辑:程序博客网 时间:2024/05/17 07:30
 
   
下文开发环境为:AM29LV320MT(4M,16位),uclinux-2.6,VDSP4.5,bf561
1   NorFlash基本参数
在uClinux启动时可以看到类似的提示:
BF561 EZKIT Map: Found 1 x16 devices at 0x0 in 16-bit bank
它的输出是由:
       printk(KERN_INFO "%s: Found %d x%d devices at 0x%x in %d-bit bank/n",
              map->name, cfi->interleave, cfi->device_type*8, base,
              map->bankwidth*8);
进行控制的。这行信息揭示了NorFlash的一些基本参数:
l         map->bankwidth:MTD设备的接口总线宽度,其值可以为1,2或4。
l         cfi->interleave:交错数,几块芯片平行连接成一块芯片,使bankwidth变大。
l         cfi->device_type:单个芯片的总线宽度,其值为1、2或4。
l         device_addr:nor flash设备的总线地址(寻址)范围视具体芯片以及其采用的位宽而定。
对于位扩展来讲:bankwidth=device_type*interleave
如富士通的29LV650:其容量是8Mbyte,共128个sector,每个sector的大小是64 kbyte。如果选择位宽为x8,设备总线的每个地址代表了一个byte的存储单元,其总线地址范围为8M(0x000000~0x7fffff)。如果选择位宽为x16,设备总线的每个地址代表了两个byte的存储单元,固其总线地址范围为4M(0x000000~0x3fffff)。
再如intel的E29F128:其容量为16Mbyte,共128个sector,每个sector的大小是128Kbyte。如果选择位宽为x8,设备总线的每个地址代表了一个byte的存储单元,其总线地址范围为16M(0x000000~0xffffff)。如果选择位宽为x16,这时候设备的A0脚不可用,所以不能访问到奇地址的存储单元,而只能0、2、4...地址的来访问,其总线地址范围为8M(0x000000~0xffffff的偶地址)。
在uClinux进行芯片检测时就充分考虑了各种可能的接线方式:
static int genprobe_new_chip(struct map_info *map, struct chip_probe *cp,
                   struct cfi_private *cfi)
{
     int min_chips = (map_bankwidth(map)/4?:1); /* At most 4-bytes wide. */
     int max_chips = map_bankwidth(map); /* And minimum 1 */
     int nr_chips, type;
 
     for (nr_chips = max_chips; nr_chips >= min_chips; nr_chips >>= 1) {
 
         if (!cfi_interleave_supported(nr_chips))
             continue;
 
         cfi->interleave = nr_chips;
 
         /* Minimum device size. Don't look for one 8-bit device
            in a 16-bit bus, etc.
            每个芯片的位宽 = (总线宽度 / 芯片数量)。
*/
         type = map_bankwidth(map) / nr_chips;
        
         for (; type <= CFI_DEVICETYPE_X32; type<<=1) {
              cfi->device_type = type;
 
              if (cp->probe_chip(map, 0, NULL, cfi))
                   return 1;
         }
     }
     return 0;
}
在向norflash发送命令并读取flash的响应结果时则考虑到设备地址的问题:
 
/*
 * Returns the command address according to the given geometry.
 */
static inline uint32_t cfi_build_cmd_addr(uint32_t cmd_ofs, int interleave, int type)
{
     return (cmd_ofs * type) * interleave;
}
 
/*
 * Transforms the CFI command for the given geometry (bus width & interleave).
*/
map_word cfi_build_cmd(u_long cmd, struct map_info *map, struct cfi_private *cfi)
{
     map_word val = { {0} };
     int wordwidth, words_per_bus, chip_mode, chips_per_word;
     unsigned long onecmd;
     int i;
 
     /* We do it this way to give the compiler a fighting chance
        of optimising away all the crap for 'bankwidth' larger than
        an unsigned long, in the common case where that support is
        disabled */
     if (map_bankwidth_is_large(map)) {
         wordwidth = sizeof(unsigned long);
         words_per_bus = (map_bankwidth(map)) / wordwidth; // i.e. normally 1
     } else {
         wordwidth = map_bankwidth(map);
         words_per_bus = 1;
     }
 
     chip_mode = map_bankwidth(map) / cfi_interleave(cfi);
     chips_per_word = wordwidth * cfi_interleave(cfi) / map_bankwidth(map);
 
     /* First, determine what the bit-pattern should be for a single
        device, according to chip mode and endianness... */
     switch (chip_mode) {
     default: BUG();
     case 1:
         onecmd = cmd;
         break;
     case 2:
         onecmd = cpu_to_cfi16(cmd);
         break;
     case 4:
         onecmd = cpu_to_cfi32(cmd);
         break;
     }
 
     /* Now replicate it across the size of an unsigned long, or
        just to the bus width as appropriate */
     switch (chips_per_word) {
     default: BUG();
#if BITS_PER_LONG >= 64
     case 8:
         onecmd |= (onecmd << (chip_mode * 32));
#endif
     case 4:
         onecmd |= (onecmd << (chip_mode * 16));
     case 2:
         onecmd |= (onecmd << (chip_mode * 8));
     case 1:
         ;
     }
 
     /* And finally, for the multi-word case, replicate it
        in all words in the structure */
     for (i=0; i < words_per_bus; i++) {
         val.x[i] = onecmd;
     }
 
     return val;
}
 
/*
 * Sends a CFI command to a bank of flash for the given geometry.
 *
 * Returns the offset in flash where the command was written.
 * If prev_val is non-null, it will be set to the value at the command address,
 * before the command was written.
 */
uint32_t cfi_send_gen_cmd(u_char cmd, uint32_t cmd_addr, uint32_t base,
                   struct map_info *map, struct cfi_private *cfi,
                   int type, map_word *prev_val)
{
     map_word val;
     uint32_t addr = base + cfi_build_cmd_addr(cmd_addr, cfi_interleave(cfi), type);
 
     val = cfi_build_cmd(cmd, map, cfi);
 
     if (prev_val)
         *prev_val = map_read(map, addr);
 
     map_write(map, val, addr);
 
     return addr - base;
}
 
2   chips和maps
现在的NorFlash大多支持CFI或者JEDEC这样的规范,根据这些规范可以自动检测出芯片的一些参数并进行读写,所以uClinux将这些规范的驱动实现单独放在mtd/chips目录下。也就是说这个目录下放的是芯片的通用驱动代码,这些代码本身是不会主动去检测芯片的存在的,它仅仅是注册了一个驱动供其它模块调用。
在通用规范的基础上,每个不同的厂商有不同的实现,且芯片在不同的系统中可能有不同的参数,如基地址,位宽等。uClinux将这部分独立出来放在mtd/maps下。这个目录下的代码将根据芯片实现的不同规范调用相应的chips驱动。因此chips下的模块必须先于maps下的模块初始化。
在include/linux/mtd/map.h中有一段相关的说明:
/* The map stuff is very simple. You fill in your struct map_info with
   a handful of routines for accessing the device, making sure they handle
   paging etc. correctly if your device needs it. Then you pass it off
  to a chip probe routine -- either JEDEC or CFI probe or both -- via
   do_map_probe(). If a chip is recognised, the probe code will invoke the
   appropriate chip driver (if present) and return a struct mtd_info.
   At which point, you fill in the mtd->module with your own module
   address, and register it with the MTD core code. Or you could partition
   it and register the partitions instead, or keep it for your own private
   use; whatever.
 
   The mtd->priv field will point to the struct map_info, and any further
   private data required by the chip driver is linked from the
   mtd->priv->fldrv_priv field. This allows the map driver to get at
   the destructor function map->fldrv_destroy() when it's tired
   of living.
*/
也就是说,只要在map_info结构体中填上norflash的具体参数,然后调用do_map_probe进行芯片的检测,如果检测通过,它将返回一个mtd结构体,这样就可以在系统中使用了。
3   chip_driver注册
uClinux将CFI和JEDEC的实现都放在mtd/chips目录下,并将之称为chip_driver,供上层的maps调用。
chip_driver都要求填充一个叫做mtd_chip_driver的结构体(include/linux/mtd/map.h):
struct mtd_chip_driver {
     struct mtd_info *(*probe)(struct map_info *map);
     void (*destroy)(struct mtd_info *);
     struct module *module;
     char *name;
     struct list_head list;
};
这个结构体主要就两个内容,一个是name,另一个是probe函数指针。上层的map将根据name来调用所需要的chip_driver。
下面看看CFI的实现(driver/mtd/chips/cfi_probe.c)。
 
static struct mtd_chip_driver cfi_chipdrv = {
     .probe        = cfi_probe,
     .name         = "cfi_probe",
     .module       = THIS_MODULE
};
 
static int __init cfi_probe_init(void)
{
     register_mtd_chip_driver(&cfi_chipdrv);
     return 0;
}
上面的register_mtd_chip_driver函数位于driver/mtd/chips/chipreg.c,用于注册一个chip_driver。如下所示:
static LIST_HEAD(chip_drvs_list);
 
void register_mtd_chip_driver(struct mtd_chip_driver *drv)
{
     spin_lock(&chip_drvs_lock);
     list_add(&drv->list, &chip_drvs_list);
     spin_unlock(&chip_drvs_lock);
}
其实就是将mtd_chip_driver结构体放在一个单链表中!
4   maps定义
maps用于定义norflash芯片的具体参数及其读写回调函数。在driver/mtd/maps/ezkit561.c中实现了一个CFI Flash的示例,因为AM29lv320支持CFI接口,所以就直接在此文件的基础上修改。
下面就是map_info这个结构体的定义(include/linux/mtd/map.h):
struct map_info {
     char *name;
     unsigned long size;
     unsigned long phys;
#define NO_XIP (-1UL)
 
     void __iomem *virt;
     void *cached;
 
     int bankwidth; /* in octets. This isn't necessarily the width
                of actual bus cycles -- it's the repeat interval
               in bytes, before you are talking to the first chip again.
               */
 
#ifdef CONFIG_MTD_COMPLEX_MAPPINGS
     map_word (*read)(struct map_info *, unsigned long);
     void (*copy_from)(struct map_info *, void *, unsigned long, ssize_t);
 
     void (*write)(struct map_info *, const map_word, unsigned long);
     void (*copy_to)(struct map_info *, unsigned long, const void *, ssize_t);
 
     /* We can perhaps put in 'point' and 'unpoint' methods, if we really
        want to enable XIP for non-linear mappings. Not yet though. */
#endif
     /* It's possible for the map driver to use cached memory in its
        copy_from implementation (and _only_ with copy_from). However,
        when the chip driver knows some flash area has changed contents,
        it will signal it to the map driver through this routine to let
        the map driver invalidate the corresponding cache as needed.
        If there is no cache to care about this can be set to NULL. */
     void (*inval_cache)(struct map_info *, unsigned long, ssize_t);
 
     /* set_vpp() must handle being reentered -- enable, enable, disable
        must leave it enabled. */
     void (*set_vpp)(struct map_info *, int);
 
     unsigned long map_priv_1;
     unsigned long map_priv_2;
     void *fldrv_priv;
     struct mtd_chip_driver *fldrv;
};
在ezkit561.c中将之填充为:
struct map_info ezkit561_map = {
     .name = "BF561 EZKIT Map",
     .phys = EZKIT561_FLASH_BASE,
     .size = EZKIT561_FLASH_SIZE,
     .bankwidth = 2,        /* 16 bit */
};
5   驱动加载
norflash驱动模块的初始化代码为:
int __init init_ezkit561_flash(void)
{
     printk(KERN_NOTICE "ezkit561 map: mapping %ld MiB flash at 0x%x/n",
            EZKIT561_FLASH_SIZE / 0x100000, EZKIT561_FLASH_BASE);
 
// 将物理地址映射到linux的内核空间,在uClinux中,直接返回EZKIT561_FLASH_BASE的值。
     ezkit561_map.virt = ioremap(EZKIT561_FLASH_BASE, EZKIT561_FLASH_SIZE);
 
     if (!ezkit561_map.virt) {
         printk("init_ezkit561_flash: failed to ioremap/n");
         return -EIO;
     }
    // 填充read, write, copy_from, copy_to几个函数指针为默认值。
     simple_map_init(&ezkit561_map);
 
     ezkit561_mtd = do_map_probe("cfi_probe", &ezkit561_map);
     if (ezkit561_mtd) {
         ezkit561_mtd->owner = THIS_MODULE;
         return add_mtd_partitions(ezkit561_mtd, ezkit561_parts,
                        EZKIT561_PART_COUNT);
     }
 
     return -ENXIO;
}
它其实就是检测系统中的norflash芯片,如果存在此芯片则将这个芯片上的分区添加到系统的mtdblock列表中。
6   do_map_probe
这个函数位于drivers/mtd/chips/chipreg.c,用于查找指定名称的chip_driver,并调用它完成芯片的检测。
     /* Hide all the horrid details, like some silly person taking
        get_module_symbol() away from us, from the caller. */
 
struct mtd_info *do_map_probe(const char *name, struct map_info *map)
{
     struct mtd_chip_driver *drv;
     struct mtd_info *ret;
 
     drv = get_mtd_chip_driver(name);
 
     if (!drv && !request_module("%s", name))
         drv = get_mtd_chip_driver(name);
 
     if (!drv)
         return NULL;
 
     ret = drv->probe(map);
 
     /* We decrease the use count here. It may have been a
        probe-only module, which is no longer required from this
        point, having given us a handle on (and increased the use
        count of) the actual driver code.
        其实它啥也没做!除非定义了CONFIG_MODULE_UNLOAD
     */
     module_put(drv->module);
 
     if (ret)
         return ret;
 
     return NULL;
}
很简单,程序开始转到drv中定义的probe函数,对于cfi_probe来讲,就是cfi_probe函数(drivers/mtd/chips/cfi_probe.c)。
7   cfi_probe
 
struct mtd_info *cfi_probe(struct map_info *map)
{
     /*
      * Just use the generic probe stuff to call our CFI-specific
      * chip_probe routine in all the possible permutations, etc.
      */
     return mtd_do_chip_probe(map, &cfi_chip_probe);
}
在这里使用了一个新的结构体chip_probe(include/linux/mtd/gen_probe.h):
struct chip_probe {
     char *name;
     int (*probe_chip)(struct map_info *map, __u32 base,
               unsigned long *chip_map, struct cfi_private *cfi);
};
在cfi_probe.c中将之填充为:
static struct chip_probe cfi_chip_probe = {
     .name         = "CFI",
     .probe_chip   = cfi_probe_chip
};
cfi_probe_chip就是具体检测norflash的函数。
实际程序转到driver/mtd/chips/gen_probe.c继续:
struct mtd_info *mtd_do_chip_probe(struct map_info *map, struct chip_probe *cp)
{
     struct mtd_info *mtd = NULL;
     struct cfi_private *cfi;
 
     /* First probe the map to see if we have CFI stuff there. */
     cfi = genprobe_ident_chips(map, cp);
 
     if (!cfi)
         return NULL;
 
     map->fldrv_priv = cfi;
     /* OK we liked it. Now find a driver for the command set it talks */
 
     mtd = check_cmd_set(map, 1); /* First the primary cmdset */
     if (!mtd)
         mtd = check_cmd_set(map, 0); /* Then the secondary */
}
这个函数实际做了两件事,一个是调用cfi_probe_chip(回调函数)检测芯片是否存在,另一个就是检查它所支持的命令集并生成mtd结构体。
8   check_cmd_set
这个函数位于driver/mtd/chips/gen_probe.c,它直接转向cfi_cmdset_0002(此函数位于driver/mtd/chips/cfi_cmdset_0002.c)继续运行,正是在这个函数中,分配了一个mtd struct并在其中填上相应的值或者回调函数。
struct mtd_info *cfi_cmdset_0002(struct map_info *map, int primary)
{
     struct cfi_private *cfi = map->fldrv_priv;
     struct mtd_info *mtd;
     int i;
 
     mtd = kmalloc(sizeof(*mtd), GFP_KERNEL);
     if (!mtd) {
         printk(KERN_WARNING "Failed to allocate memory for MTD device/n");
         return NULL;
     }
     memset(mtd, 0, sizeof(*mtd));
     mtd->priv = map;
     mtd->type = MTD_NORFLASH;
 
     /* Fill in the default mtd operations */
 
     return cfi_amdstd_setup(mtd);
}
 
9   驱动的使用
在norflash驱动加载的最后有几行语句:
     if (ezkit561_mtd) {
         ezkit561_mtd->owner = THIS_MODULE;
         return add_mtd_partitions(ezkit561_mtd, ezkit561_parts,
                        EZKIT561_PART_COUNT);
     }
通过add_mtd_partitions(drivers/mtd/mtdpart.c)函数可以在系统的分区表中添加相应的分区,这样在需要对分区进行读写的时候自然就可以找到相应的mtd并调用其函数来完成相应的操作。
/*
 * This function, given a master MTD object and a partition table, creates
 * and registers slave MTD objects which are bound to the master according to
 * the partition definitions.
 * (Q: should we register the master MTD object as well?)
 */
 
int add_mtd_partitions(struct mtd_info *master,
                const struct mtd_partition *parts,
                int nbparts)
{
     struct mtd_part *slave;
     u_int32_t cur_offset = 0;
     int i;
 
     printk (KERN_NOTICE "Creating %d MTD partitions on /"%s/":/n", nbparts, master->name);
 
     for (i = 0; i < nbparts; i++) {
 
         /* allocate the partition structure */
         slave = kmalloc (sizeof(*slave), GFP_KERNEL);
         if (!slave) {
              printk ("memory allocation error while creating partitions for /"%s/"/n",
                   master->name);
              del_mtd_partitions(master);
              return -ENOMEM;
         }
         memset(slave, 0, sizeof(*slave));
/* 添加到分区列表中,但在这里mtd_partitions这个链表的作用仅仅是在slave分配失败时可以删除已经分配的结构体,对于系统的其它部分没有任何影响! */
         list_add(&slave->list, &mtd_partitions);
 
         /* set up the MTD object for this partition */
         slave->mtd.type = master->type;
 
         if(parts[i].mtdp)
         {    /* store the object pointer (caller may or may not register it */
              *parts[i].mtdp = &slave->mtd;
              slave->registered = 0;
         }
         else
         {
              /* register our partition */
              add_mtd_device(&slave->mtd);
              slave->registered = 1;
         }
     }
 
     return 0;
}
在函数的最后,通过add_mtd_device,可以将一个mtd结构体添加到mtd_table的指针数组中,这样在系统中就可以看到/dev/mtdblock0,/dev/mtdblock1,…这样的设备了。因为是一个数组,所以在系统中定义了最大的数组元素
#define MAX_MTD_DEVICES 16
所以最多只有mtdblock15。
 
原创粉丝点击