十八、Nand Flash驱动和Nor Flash驱动

 

在读者学习本章之前,最好了解Nand Flash读写过程和操作,可以参考:Nand Flash裸机操作

 

一开始想在本章写eMMC框架和设备驱动,但是没有找到关于eMMC设备驱动具体写法,所以本章仍继续完成Nand Flash设备驱动,eMMC这个坑留在以后填。如果读者开发板为eMMC,本节驱动可能无法正常执行。

 

 

在裸机操作中,读者应了解Nand Flash时序图、Nand Flash片选、读写和擦除等操作,在此不再赘述。

一、Nand Flash驱动分析

Nand Flash设备驱动放在drivers/mtd/nand目录下,mtd(memory technology device,存储技术设备)是用于访问存储设备(ROM、flash)的子系统。mtd的主要目的是为了使新的存储设备的驱动更加简单,因此它在硬件和顶层之间提供了一个抽象的接口。

读者可在此目录下任意选择一个单板驱动文件进行分析,我选择的是davinci_nand.c文件。

Nand Flash和Nor Flash文件链接:

https://files.cnblogs.com/files/Lioker/18_nand_nor.zip

 

首先来看它的入口函数:

1 static int __init nand_davinci_init(void)
2 {
3     return platform_driver_probe(&nand_davinci_driver, nand_davinci_probe);
4 }

我们进入platform_driver的probe函数中,看看它是如何初始化

  1 static int __init nand_davinci_probe(struct platform_device *pdev)
  2 {
  3     struct davinci_nand_pdata    *pdata = pdev->dev.platform_data;
  4     struct davinci_nand_info    *info;
  5     struct resource            *res1;
  6     struct resource            *res2;
  7     void __iomem            *vaddr;
  8     void __iomem            *base;
  9     int                ret;
 10     uint32_t            val;
 11     nand_ecc_modes_t        ecc_mode;
 12     struct mtd_partition        *mtd_parts = NULL;
 13     int                mtd_parts_nb = 0;
 14 
 15 ...
 16     /* 初始化硬件,如设置TACLS、TWRPH0、TWRPH1等 */
 17     platform_set_drvdata(pdev, info);
 18 ...
 19     /* 配置mtd_info结构体,它是nand_chip的抽象 */
 20     info->mtd.priv        = &info->chip;
 21     info->mtd.name        = dev_name(&pdev->dev);
 22     info->mtd.owner        = THIS_MODULE;
 23 
 24     info->mtd.dev.parent    = &pdev->dev;
 25 
 26     /* 配置nand_chip结构体 */
 27     info->chip.IO_ADDR_R    = vaddr;
 28     info->chip.IO_ADDR_W    = vaddr;
 29     info->chip.chip_delay    = 0;
 30     info->chip.select_chip    = nand_davinci_select_chip;
 31 ...
 32     /* Set address of hardware control function */
 33     info->chip.cmd_ctrl    = nand_davinci_hwcontrol;
 34     info->chip.dev_ready    = nand_davinci_dev_ready;
 35 
 36     /* Speed up buffer I/O */
 37     info->chip.read_buf     = nand_davinci_read_buf;
 38     info->chip.write_buf    = nand_davinci_write_buf;
 39 
 40     /* Use board-specific ECC config */
 41     ecc_mode        = pdata->ecc_mode;
 42 
 43     ret = -EINVAL;
 44     switch (ecc_mode) {
 45     case NAND_ECC_NONE:
 46     case NAND_ECC_SOFT:        /* 启动软件ECC */
 47         pdata->ecc_bits = 0;
 48         break;
 49     case NAND_ECC_HW:        /* 启动硬件ECC */
 50 ...
 51         break;
 52     default:
 53         ret = -EINVAL;
 54         goto err_ecc;
 55     }
 56     info->chip.ecc.mode = ecc_mode;
 57 
 58     /* 使能nand clk */
 59     info->clk = clk_get(&pdev->dev, "aemif");
 60 ...
 61     ret = clk_enable(info->clk);
 62 ...
 63     val = davinci_nand_readl(info, A1CR_OFFSET + info->core_chipsel * 4);
 64 ...
 65     /* 扫描Nand Flash */
 66     ret = nand_scan_ident(&info->mtd, pdata->mask_chipsel ? 2 : 1, NULL);
 67 ...
 68     /* second phase scan */
 69     ret = nand_scan_tail(&info->mtd);
 70     /* 以上nand_scan_ident()和nand_scan_tail()两步可以使用nand_scan()代替 */
 71     
 72     if (mtd_has_cmdlinepart()) {
 73         static const char *probes[] __initconst = {
 74             "cmdlinepart", NULL
 75         };
 76     /* 设置分区 */
 77         mtd_parts_nb = parse_mtd_partitions(&info->mtd, probes,
 78                             &mtd_parts, 0);
 79     }
 80 
 81     if (mtd_parts_nb <= 0) {
 82         mtd_parts = pdata->parts;
 83         mtd_parts_nb = pdata->nr_parts;
 84     }
 85 
 86     /* Register any partitions */
 87     if (mtd_parts_nb > 0) {
 88         ret = mtd_device_register(&info->mtd, mtd_parts,
 89                       mtd_parts_nb);
 90         if (ret == 0)
 91             info->partitioned = true;
 92     }
 93 
 94     /* If there's no partition info, just package the whole chip
 95      * as a single MTD device.
 96      */
 97     if (!info->partitioned)
 98         ret = mtd_device_register(&info->mtd, NULL, 0) ? -ENODEV : 0;
 99 ...
100     return ret;
101 }
View Code

probe()函数所做的有以下几点:

1. 初始化硬件,如设置TACLS、TWRPH0、TWRPH1等

2. 配置mtd_info结构体,它是nand_chip等底层Flash结构体的抽象,用于描述MTD设备,定义了MTD数据和操作函数

3. 配置nand_chip结构体

4. 启动软件ECC

5. 使用clk_get()和clk_enable()获取并使能Nand Flash时钟

6. 使用nand_scan()扫描Nand Flash

7. 使用parse_mtd_partitions()解析命令行中设置的分区。若命令行中没有设置mtdparts返回0;若设置了并且解析没问题,那么返回分区的个数,否则返回小于0的数

8. 使用mtd_device_register()注册Nand Flash分区

 

其中,

1. nand_scan()函数调用关系如下:

nand_scan(&info->mtd, pdata->mask_chipsel ? 2 : 1);
  -> nand_scan_ident(mtd, maxchips, NULL);
    /* 获取Nand Flash存储器类型 */
    -> nand_get_flash_type(mtd, chip, busw, &nand_maf_id, &nand_dev_id, table);
  -> nand_scan_tail(mtd);    /* 设置Nand Flash底层读写擦除等函数 */

nand_get_flash_type()函数定义如下:

 1 static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd, struct nand_chip *chip, int busw, int *maf_id, int *dev_id, struct nand_flash_dev *type)
 2 {
 3     int i, maf_idx;
 4     u8 id_data[8];
 5     int ret;
 6 
 7     /* Select the device */
 8     chip->select_chip(mtd, 0);        /* nand_chip的片选函数 */
 9 
10     /*
11      * Reset the chip, required by some chips (e.g. Micron MT29FxGxxxxx)
12      * after power-up
13      */
14     chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
15 
16     /* Send the command for reading device ID */
17     chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
18 
19     /* Read manufacturer and device IDs */
20     *maf_id = chip->read_byte(mtd);        /* 调用read_byte函数读取厂家ID */
21     *dev_id = chip->read_byte(mtd);        /* 设备ID */
22 
23     /* Try again to make sure, as some systems the bus-hold or other
24      * interface concerns can cause random data which looks like a
25      * possibly credible NAND flash to appear. If the two results do
26      * not match, ignore the device completely.
27      */
28 
29     chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
30 
31     for (i = 0; i < 2; i++)
32         id_data[i] = chip->read_byte(mtd);
33 
34     /* 打印参数信息 */
35     if (id_data[0] != *maf_id || id_data[1] != *dev_id) {
36         printk(KERN_INFO "%s: second ID read did not match "
37                "%02x,%02x against %02x,%02x\n", __func__,
38                *maf_id, *dev_id, id_data[0], id_data[1]);
39         return ERR_PTR(-ENODEV);
40     }
41 ...
42     /* 校验产品ID */
43     if (!type)
44         type = nand_flash_ids;
45 
46     for (; type->name != NULL; type++)
47         if (*dev_id == type->id)
48             break;
49 ...
50 }

代码中第46行可看出,nand_flash_ids[]数组是个全局变量,通过循环匹配设备ID,确定Nand Flash的大小、位数等规格。其定义如下:

struct nand_flash_dev nand_flash_ids[] = {

#ifdef CONFIG_MTD_NAND_MUSEUM_IDS
    {"NAND 1MiB 5V 8-bit",      0x6e, 256, 1, 0x1000, 0},
    {"NAND 2MiB 5V 8-bit",      0x64, 256, 2, 0x1000, 0},
    {"NAND 4MiB 5V 8-bit",      0x6b, 512, 4, 0x2000, 0},
    {"NAND 1MiB 3,3V 8-bit",    0xe8, 256, 1, 0x1000, 0},
    {"NAND 1MiB 3,3V 8-bit",    0xec, 256, 1, 0x1000, 0},
    {"NAND 2MiB 3,3V 8-bit",    0xea, 256, 2, 0x1000, 0},
    {"NAND 4MiB 3,3V 8-bit",    0xd5, 512, 4, 0x2000, 0},
    {"NAND 4MiB 3,3V 8-bit",    0xe3, 512, 4, 0x2000, 0},
    {"NAND 4MiB 3,3V 8-bit",    0xe5, 512, 4, 0x2000, 0},
    {"NAND 8MiB 3,3V 8-bit",    0xd6, 512, 8, 0x2000, 0},
...
};

 

2. 我们如果不传入命令行参数,parse_mtd_partitions()函数没有作用,就需要自己构建分区表。mtd_device_register()函数传入参数中应有分区表

int mtd_device_register(struct mtd_info *master, const struct mtd_partition *parts, int nr_parts)
{
    return parts ? add_mtd_partitions(master, parts, nr_parts) :
        add_mtd_device(master);
}

参数struct mtd_partition *parts即为分区表,其定义和示例如下:

/* 定义 */
struct mtd_partition {
    char *name;             /* 分区名,如bootloader、params、kernel和root */
    uint64_t size;          /* 分区大小*/
    uint64_t offset;        /* 分区所在的偏移值 */
    uint32_t mask_flags;    /* 掩码标识 */
    struct nand_ecclayout *ecclayout;    /* oob布局 */
};

/* 示例 */
static const struct mtd_partition partition_info[] = {
    {
        .name = "NAND FS 0",
        .offset = 0,
        .size = 8 * 1024 * 1024 },
    {
        .name = "NAND FS 1",
        .offset = MTDPART_OFS_APPEND,    /* 接着上一个 */
        .size = MTDPART_SIZ_FULL         /* 余下的所有空间 */ }
};

 

 

简单分析完了Nand Flash设备驱动,接下来我们来分析MTD子系统框架。

二、MTD子系统框架分析

在开发板中ls /dev/mtd*,我们可以看到MTD设备既有块设备也有字符设备,块设备(mtdblockx)针对文件系统,字符设备(mtdx)针对格式化等操作。

在上一节中,我们知道了mtd_info是nand_chip等底层Flash结构体的抽象,因此我们可以得到如下框架。

在上一节中,我们知道了mtd_device_register()函数最终调用add_mtd_device(master)函数添加MTD设备。根据上图可以确定添加的是MTD原始设备。

add_mtd_device()函数定义如下:

 1 int add_mtd_device(struct mtd_info *mtd)
 2 {
 3     struct mtd_notifier *not;    /* MTD通知结构体,用于添加删除mtd_info */
 4     int i, error;
 5 ...
 6     /* 配置mtd_info */
 7     mtd->index = i;
 8     mtd->usecount = 0;
 9 ...
10     mtd->erasesize_mask = (1 << mtd->erasesize_shift) - 1;
11     mtd->writesize_mask = (1 << mtd->writesize_shift) - 1;
12 ...
13     mtd->dev.type = &mtd_devtype;
14     mtd->dev.class = &mtd_class;
15     mtd->dev.devt = MTD_DEVT(i);
16     dev_set_name(&mtd->dev, "mtd%d", i);
17     dev_set_drvdata(&mtd->dev, mtd);
18     device_register(&mtd->dev);        /* 创建device */
19 ...
20     list_for_each_entry(not, &mtd_notifiers, list)
21         not->add(mtd);        /* 调用mtd_notifier的add函数 */
22 
23     return 0;
24 }

其中,struct mtd_notifier定义如下:

struct mtd_notifier {
    void (*add)(struct mtd_info *mtd);
    void (*remove)(struct mtd_info *mtd);
    struct list_head list;
};

struct mtd_notifier的注册注销函数定义如下:

/* 注册函数 */
void register_mtd_user (struct mtd_notifier *new)
{
    struct mtd_info *mtd;
    mutex_lock(&mtd_table_mutex);
    list_add(&new->list, &mtd_notifiers);
    __module_get(THIS_MODULE);

    mtd_for_each_device(mtd)
        new->add(mtd);

    mutex_unlock(&mtd_table_mutex);
}

/* 注销函数 */
int unregister_mtd_user (struct mtd_notifier *old)
{
    struct mtd_info *mtd;
    mutex_lock(&mtd_table_mutex);
    module_put(THIS_MODULE);

    mtd_for_each_device(mtd)
        old->remove(mtd);

    list_del(&old->list);
    mutex_unlock(&mtd_table_mutex);
    return 0;
}

 

至此,各个结构体层次已经出来了,如下图所示:

 

 

既然mtd_info是nand_chip等底层Flash结构体的抽象,那么用于表示Nor Flash的结构体是什么呢,第三节我们就来分析这个问题。

三、Nor Flash驱动分析

进入drivers/mtd/目录中,Nor Flash和Nand Flash一样,必然会有自己的目录。根据排除法确定Nor Flash设备驱动文件所在的目录为maps。

读者可在此目录下任意选择一个单板驱动文件进行分析,我选择的是dc21285.c文件。

首先来看它的入口函数:

 1 static int __init init_dc21285(void)
 2 {
 3     int nrparts;
 4 
 5     /* Determine bankwidth */
 6     switch (*CSR_SA110_CNTL & (3<<14)) {
 7 ...
 8         case SA110_CNTL_ROMWIDTH_32:
 9             dc21285_map.bankwidth = 4;
10             dc21285_map.read = dc21285_read32;
11             dc21285_map.write = dc21285_write32;
12             dc21285_map.copy_to = dc21285_copy_to_32;
13             break;
14 ...
15     }
16 ...
17     /* 根据Nor Flash物理地址映射Nor Flash空间 */
18     dc21285_map.virt = ioremap(DC21285_FLASH, 16*1024*1024);    /* DC21285_FLASH为物理起始地址,16*1024*1024为FLASH大小 */
19 ...
20     /* NOR有两种规范
21      * 1. jedec:内核中定义有jedec_table结构体,里面存放有NOR Flash的大小、名字等信息。如果内核中没有定义我们使用的NOR Flash,就必须手动添加
22      * 2. cfi:common flash interface,是新的NOR Flash规范,Flash本身包含有属性,和Nand Flash相同
23      */
24     if (machine_is_ebsa285()) {
25         dc21285_mtd = do_map_probe("cfi_probe", &dc21285_map);
26     } else {
27         dc21285_mtd = do_map_probe("jedec_probe", &dc21285_map);
28     }
29 
30     if (!dc21285_mtd) {
31         iounmap(dc21285_map.virt);
32         return -ENXIO;
33     }
34 
35     dc21285_mtd->owner = THIS_MODULE;
36 
37     nrparts = parse_mtd_partitions(dc21285_mtd, probes, &dc21285_parts, 0);
38     mtd_device_register(dc21285_mtd, dc21285_parts, nrparts);
39 ...
40     return 0;
41 }

通过此函数,我们可以知道表示Nor Flash的结构体为struct map_info dc21285_map。

 

init()函数所做的有以下几点:

1. 申请mtd_info结构体内存空间

2. 申请并配置map_info结构体

3. 映射与map_info->phys物理地址对应的map_info->virt虚拟内存,其大小为Flash真实大小,它放在map_info->size

4. 使用do_map_probe()设置map_info结构体

5. 使用parse_mtd_partitions()解析命令行中设置的分区。若命令行中没有设置mtdparts返回0;若设置了并且解析没问题,那么返回分区的个数,否则返回小于0的数

6. 使用mtd_device_register()注册Nor Flash分区

 

do_map_probe()函数调用关系如下:

1 dc21285_mtd = do_map_probe("cfi_probe", &dc21285_map);
2   -> struct mtd_chip_driver *drv = get_mtd_chip_driver(name);
3     -> list_for_each(pos, &chip_drvs_list)
4          if (!strcmp(this->name, name))        /* 匹配驱动 */
5            return this;            /* 返回的是mtd_chip_driver *drv */
6   -> ret = drv->probe(map);        /* 调用驱动的probe()函数返回mtd_info */

 

 

四、Nand Flash驱动和Nor Flash驱动编写

Nand Flash驱动源代码:

  1 #include <linux/module.h>
  2 #include <linux/types.h>
  3 #include <linux/init.h>
  4 #include <linux/kernel.h>
  5 #include <linux/string.h>
  6 #include <linux/ioport.h>
  7 #include <linux/platform_device.h>
  8 #include <linux/delay.h>
  9 #include <linux/err.h>
 10 #include <linux/slab.h>
 11 #include <linux/clk.h>
 12 #include <linux/mtd/mtd.h>
 13 #include <linux/mtd/nand.h>
 14 #include <linux/mtd/nand_ecc.h>
 15 #include <linux/mtd/partitions.h>
 16 
 17 #include <asm/io.h>
 18 //#include <asm/arch/regs-nand.h>
 19 //#include <asm/arch/nand.h>
 20 
 21 struct itop_nand_regs
 22 {
 23     unsigned long nfconf  ;
 24     unsigned long nfcont  ;
 25     unsigned long nfcmd   ;
 26     unsigned long nfaddr  ;
 27     unsigned long nfdata  ;
 28     unsigned long nfeccd0 ;
 29     unsigned long nfeccd1 ;
 30     unsigned long nfeccd  ;
 31     unsigned long nfstat  ;
 32     unsigned long nfestat0;
 33     unsigned long nfestat1;
 34     unsigned long nfmecc0 ;
 35     unsigned long nfmecc1 ;
 36     unsigned long nfsecc  ;
 37     unsigned long nfsblk  ;
 38     unsigned long nfeblk  ;
 39 };
 40 
 41 static struct nand_chip *itop_nand;
 42 static struct mtd_info *itop_mtd;
 43 static struct itop_nand_regs *nand_regs;
 44 static struct clk *clk;
 45 
 46 static struct mtd_partition itop_nand_part[] = {
 47     [0] = {
 48         .name   = "bootloader",
 49         .size   = 0x00080000,
 50         .offset    = 0,
 51     },
 52     [1] = {
 53         .name   = "params",
 54         .offset = MTDPART_OFS_APPEND,
 55         .size   = 0x00020000,
 56     },
 57     [2] = {
 58         .name   = "kernel",
 59         .offset = MTDPART_OFS_APPEND,
 60         .size   = 0x00400000,
 61     },
 62     [3] = {
 63         .name   = "root",
 64         .offset = MTDPART_OFS_APPEND,
 65         .size   = MTDPART_SIZ_FULL,
 66     }
 67 };
 68 
 69 static void itop_nand_select_chip(struct mtd_info *mtd, int chipnr)
 70 {
 71     if (chipnr == -1)
 72     {
 73         /* 取消选中: NFCONT[1]设为1 */
 74         nand_regs->nfcont |= (1 << 1);
 75     }
 76     else
 77     {
 78         /* 选中: NFCONT[1]设为0 */
 79         nand_regs->nfcont &= ~(1 << 1);
 80     }
 81 }
 82 
 83 static void itop_nand_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl)
 84 {
 85     if (ctrl & NAND_CLE)
 86     {
 87         /* MFDCMMD = cmd */
 88         nand_regs->nfcmd = cmd;
 89     }
 90     else
 91     {
 92         /* NFDADDR = cmd */
 93         nand_regs->nfaddr = cmd;
 94     }
 95 }
 96 
 97 static int itop_nand_device_ready(struct mtd_info *mtd)
 98 {
 99     /* 判断NFSTAT[0]: 1 表示ready */
100     return (nand_regs->nfstat & (1 << 0));
101 }
102 
103 static int itop_nand_init(void)
104 {
105     /* 1. 分配nand_chip */
106     itop_nand = kzalloc(sizeof(struct nand_chip), GFP_KERNEL);
107 
108     /* 2. 设置nand_chip */
109     itop_mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
110     itop_mtd->owner = THIS_MODULE;
111     itop_mtd->priv = itop_nand;
112 
113     nand_regs = ioremap(0x4E000000, sizeof(struct itop_nand_regs));
114     
115     itop_nand->select_chip = itop_nand_select_chip;
116     itop_nand->cmd_ctrl     = itop_nand_cmd_ctrl;            /* 命令最后调用它    */
117     itop_nand->IO_ADDR_R    = &nand_regs->nfdata;             /* 读数据最后调用它    */
118     itop_nand->IO_ADDR_W    = &nand_regs->nfdata;            /* 写数据    */
119     itop_nand->dev_ready    = itop_nand_device_ready;        /* 状态位    */
120     itop_nand->ecc.mode     = NAND_ECC_SOFT;                /* 开启ECC    */
121 
122     /* 3. 硬件相关的操作 */
123     /* 注意使能时钟 */
124     clk = clk_get(NULL, "nand");
125     clk_enable(clk);
126     nand_regs->nfconf = ((0 << 12) | (1 << 8) | (0 << 4));
127     nand_regs->nfcont = ((1 << 1) | (1 << 0));    
128 
129     /* 4. nand_scan() */
130     nand_scan(itop_mtd, 1);
131 
132     /* 5. mtd_device_register() */
133     mtd_device_register(itop_mtd, itop_nand_part, 4);
134 
135     return 0;
136 }
137 
138 static void itop_nand_exit(void)
139 {
140     kfree(itop_nand);
141     kfree(itop_mtd);
142     iounmap(nand_regs);
143 }
144 
145 module_init(itop_nand_init);
146 module_exit(itop_nand_exit);
147 
148 MODULE_LICENSE("GPL");
View Code

Nor Flash驱动源代码(有可能部分开发板中没有Nor Flash):

 1 #include <linux/module.h>
 2 #include <linux/types.h>
 3 #include <linux/init.h>
 4 #include <linux/kernel.h>
 5 #include <linux/string.h>
 6 #include <linux/ioport.h>
 7 #include <linux/platform_device.h>
 8 #include <linux/delay.h>
 9 #include <linux/err.h>
10 #include <linux/slab.h>
11 #include <linux/clk.h>
12 #include <linux/mtd/mtd.h>
13 #include <linux/mtd/map.h>
14 #include <linux/mtd/partitions.h>
15 
16 #include <asm/io.h>
17 
18 static struct map_info *nor_map;
19 static struct mtd_info *nor_mtd;
20 
21 static struct mtd_partition nor_part[] = {
22     [0] = {
23         .name   = "bootloader",
24         .size   = 0x00080000,
25         .offset    = 0,
26     },
27     
28     [1] = {
29     /* 没有那么大内存 */
30     #if 0
31         .name   = "params",
32         .offset = MTDPART_OFS_APPEND,
33         .size   = 0x00020000,
34     },
35     [2] = {
36         .name   = "kernel",
37         .offset = MTDPART_OFS_APPEND,
38         .size   = 0x00400000,
39     },
40     [3] = {
41     #endif
42         .name   = "root",
43         .offset = MTDPART_OFS_APPEND,
44         .size   = MTDPART_SIZ_FULL,
45     }
46 };
47 
48 static int itop_nor_init(void)
49 {
50     /* 分配空间 */
51     nor_map = kzalloc(sizeof(struct map_info), GFP_KERNEL);
52     nor_mtd  = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
53 
54     nor_map->bankwidth = 2;
55     nor_map->name = "itop_nor";
56     nor_map->phys = 0;
57     nor_map->size = 0x100000;
58     nor_map->virt = ioremap(nor_map->phys, nor_map->size);
59 
60     nor_mtd = do_map_probe("cfi_probe", nor_map);
61 
62     if (!nor_mtd)
63         nor_mtd = do_map_probe("jedec_probe", nor_map);
64 
65     if (!nor_mtd) {
66         iounmap(nor_map->virt);
67         kfree(nor_mtd);
68         kfree(nor_map);
69         return -ENXIO;
70     }
71 
72     nor_mtd->owner = THIS_MODULE;
73 
74     /* 2表示分区个数 */
75     mtd_device_register(nor_mtd, nor_part, 2);
76 
77     return 0;
78 }
79 
80 static void itop_nor_exit(void)
81 {
82     if (nor_mtd) {
83         kfree(nor_map);
84         kfree(nor_mtd);
85         iounmap(nor_map->virt);
86     }
87 }
88 
89 module_init(itop_nor_init);
90 module_exit(itop_nor_exit);
91 
92 MODULE_LICENSE("GPL");
View Code

Makefile:

 1 KERN_DIR = /work/itop4412/tools/linux-3.5
 2 
 3 all:
 4     make -C $(KERN_DIR) M=`pwd` modules
 5 
 6 clean:
 7     make -C $(KERN_DIR) M=`pwd` modules clean
 8     rm -rf modules.order
 9 
10 obj-m    += nand.o nor.o
View Code

 

 

下一章  十九、eMMC驱动框架分析

 

转载于:https://www.cnblogs.com/Lioker/p/11254902.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值