嵌入式Linux——nand flash 驱动(4):AT91驱动分析以及写自己的nand驱动

2019-07-13 02:20发布

        就像题目所说,本文有两个内容要讲,第一是分析AT91的nand驱动程序,而第二个就是写自己的驱动程序。可能有人会问我们前面不是已经分析过s3c2440的nand驱动了吗?那为啥还要分析AT91 的那?因为AT91的程序简单,他的驱动程序更直接的去设置相应的函数或者寄存器,所以更利于我们去读懂同时也更利于我们去模仿写自己的驱动程序。 下面我们言归正传在分析我们的drivers/mtd/nand/at91_nand.c 同样我们先从入口函数开始分析: static int __init at91_nand_init(void) { return platform_driver_register(&at91_nand_driver); } 同样是注册一个平台驱动结构体,用于和平台设备的名字进行比较,而他的名字为:.name = "at91_nand"。这里我们认为名字匹配,来进入probe函数来分析其驱动程序的编写,因为分析需要,我将部分不重要的判断给删了 static int __init at91_nand_probe(struct platform_device *pdev) { struct at91_nand_host *host; struct mtd_info *mtd; /* 定义mtd_info结构体 */ struct nand_chip *nand_chip; /* 定义nand_chip结构体,这里将mtd_info和nand_chip分开写其实是为了更方便的设置 */ int res; #ifdef CONFIG_MTD_PARTITIONS struct mtd_partition *partitions = NULL; /* 如果定义了分区,对分区进行设置 */ int num_partitions = 0; #endif /* Allocate memory for the device structure (and zero it) */ host = kzalloc(sizeof(struct at91_nand_host), GFP_KERNEL); /* 为host分配内存空间 */ host->io_base = ioremap(pdev->resource[0].start, pdev->resource[0].end - pdev->resource[0].start + 1); /* 对nand的寄存器进行重映射 */ mtd = &host->mtd; nand_chip = &host->nand_chip; host->board = pdev->dev.platform_data; nand_chip->priv = host; /* link the private data structures */ mtd->priv = nand_chip; /* 将nand_chip结构体放到mtd_info的私有数据中,以实现mtd_info对nand_chip的调用 */ mtd->owner = THIS_MODULE; /* Set address of NAND IO lines */ nand_chip->IO_ADDR_R = host->io_base;                 /* 设置读缓存地址 */ nand_chip->IO_ADDR_W = host->io_base; /* 设置写缓存地址 */ nand_chip->cmd_ctrl = at91_nand_cmd_ctrl; /* 设置写地址/命令函数 */ nand_chip->dev_ready = at91_nand_device_ready; /* 就绪函数 */ nand_chip->ecc.mode = NAND_ECC_SOFT; /* enable ECC */ /* 设置ECC为软件检测 */ nand_chip->chip_delay = 20; /* 20us command delay time */ platform_set_drvdata(pdev, host);                             at91_nand_enable(host);                                   /* 设置nand片选信号 */ /* Scan to find existance of the device */ if (nand_scan(mtd, 1)) { /* 扫描nand */ res = -ENXIO; goto out; } #ifdef CONFIG_MTD_PARTITIONS if (host->board->partition_info) partitions = host->board->partition_info(mtd->size, &num_partitions); #ifdef CONFIG_MTD_CMDLINE_PARTS else { mtd->name = "at91_nand"; num_partitions = parse_mtd_partitions(mtd, part_probes, &partitions, 0); } #endif if ((!partitions) || (num_partitions == 0)) { printk(KERN_ERR "at91_nand: No parititions defined, or unsupported device. "); res = ENXIO; goto release; } res = add_mtd_partitions(mtd, partitions, num_partitions); /* 添加分区 */ #else res = add_mtd_device(mtd); #endif if (!res) return res; release: nand_release(mtd); out: at91_nand_disable(host); platform_set_drvdata(pdev, NULL); iounmap(host->io_base); kfree(host); return res; } 从上面我们可以很清楚的看出一个驱动程序的大体步骤: 1.分配一个nand_chip结构体 2.设置nand_chip结构体,对nand_chip中的选项进行设置,其实就是对各个寄存器进行操作。 3.硬件相关的设置:如对IO口进行重映射。 4.nand_scan调用mtd_info结构体 5.添加分区:add_mtd_partitions   有了上面的步骤我们开始写自己的nand驱动程序. 首先我们要做的第一件事就是开nand的总开关/* 0.设置nand的总开关 */ nand_clk = clk_get(NULL,"nand"); clk_enable(nand_clk); /* 这个设置相当于将CLKCON的bit[4]=1 */ 只有开启nand在CLKCON的总开关才可以对nand进行后续的设置。 接着我们将分配一个nand_chip结构体的空间: /* 1.分配一个nand_chip结构体 */ s3c_nand = kzalloc(sizeof(struct nand_chip),GFP_KERNEL); 然后就是nand_chip结构体,因为在设置nand_chip中会用到nand寄存器虚拟地址的设置,所以先对nand寄存器进行地址重映射然后设置: s3c_nand_regs = ioremap(0x4e000000,sizeof(struct s3c_nand_reg)); /* nand寄存器地址重映射 */ /* 2.设置nand_chip结构体 */ /* 设置nand_chip是给nand_scan函数使用的,如果不知道怎么设置, * 先看nand_scan函数怎么使用,他应该提供: * 选中,发命令,发地址,发数据判断状态的功能。 */ s3c_nand->select_chip = s3c2440_select_chip; /* 片选,默认函数不能提供次功能,要自己写 */ s3c_nand->cmd_ctrl = s3c2440_cmd_ctrl ; /* 写命令/地址,此函数可以参考AT91 */ s3c_nand->IO_ADDR_R = &s3c_nand_regs->nfdata;/* 读数据的地址,NFDATA 的虚拟地址 */ s3c_nand->IO_ADDR_W = &s3c_nand_regs->nfdata;/* 写数据的地址,NFDATA 的虚拟地址 */ s3c_nand->dev_ready = s3c2440_dev_ready; /* 就绪函数 */ s3c_nand->ecc.mode = NAND_ECC_SOFT; /* 设置nand的ecc检测模式为软件设置 */ 而nand的寄存器地址放在结构体s3c_nand_reg中: struct s3c_nand_reg{ unsigned long nfconf; /* nand配置寄存器 */ unsigned long nfcont; /* nand控制寄存器 */ unsigned long nfcmd; /* nand命令寄存器 */ unsigned long nfaddr; /* nand地址寄存器 */ unsigned long nfdata; /* nand数据寄存器 */ unsigned long nfmeccd0; unsigned long nfmeccd1; unsigned long nfseccd; unsigned long nfstat; /* nand状态寄存器 */ unsigned long nfestat0; unsigned long nfestat1; unsigned long nfmecc0; unsigned long nfmecc1; unsigned long nfsecc; unsigned long nfsblk; unsigned long nfeblk; }; 而片选函数:s3c_nand->select_chip = s3c2440_select_chip为: /* 片选控制函数 */ static void s3c2440_select_chip(struct mtd_info *mtd, int chip) { if(chip == -1) { /* 取消片选:NFCONT[1]=1 */ s3c_nand_regs->nfcont |= (1<<1); }else{ /* 选中 :NFCONT[1]=0 */ s3c_nand_regs->nfcont &= ~(1<<1); } } 而写命令/地址函数:s3c2440_cmd_ctrl /* 写命令/地址函数 */ static void s3c2440_cmd_ctrl(struct mtd_info *mtd, int dat,unsigned int ctrl) { if(ctrl & NAND_CLE){ /* 发命令:NFCMMD = dat */ s3c_nand_regs->nfcmd = dat; }else{ /* 发地址:NFADDR = dat */ s3c_nand_regs->nfaddr = dat; } } 等待就绪函数:s3c2440_dev_ready /* 等待就绪函数 */ int s3c2440_dev_ready(struct mtd_info *mtd) { /* 返回NFSTAT的bit[0] */ return s3c_nand_regs->nfstat & (1<<0); } 写完上面的函数,接下来就是与硬件相关的操作了,其实就是设置TACLS,TWRPH0 ,TWRPH1 的值,以及使能nand /* 3.硬件相关的设置 */ /* *HCLK = 100MHz; *TACLS:发出ALE/CLE之后多长时间才发出nWE信号,从nand手册可知ALE/CLE与nWE可以同时发出所以 * TACLS=0 *TWRPH0:nWE的脉冲宽度,HCLK*(TWRPH0+1),从nand的手册可知脉冲宽度要大于12ns,所以 * TWRPH0>=1; *TWRPH1:nWE变为高电平后多长时间ALE/CLE能变为低电平,HCLK*(TWRPH1+1), * 从nand的手册可知这段时间要大于5ns,所以 * TWRPH1 >= 0; */ #define TACLS 0 #define TWRPH0 1 #define TWRPH1 0 s3c_nand_regs->nfconf |= (TACLS << 12) | (TWRPH0 << 8) | (TWRPH1 << 4); /* * NFCONT的bit[1]=1 : 取消片选 * bit[0]=1 : 使能nand flash控制器 */ s3c_nand_regs->nfcont = (1<<1) | (1<<0); 而最后要写的就是nand_scan函数和add_mtd_partitions函数 /* 4.使用:nand_scan */ s3c_mtd = kzalloc(sizeof(struct mtd_info),GFP_KERNEL); s3c_mtd->owner = THIS_MODULE; s3c_mtd->priv = s3c_nand; //私有数据 nand_scan(s3c_mtd, 1); //参数二为最大芯片数 /* 5.分区:add_mtd_partitions */ /* 如果不想分区用add_mtd_device(s3c_mtd)就可以了 */ add_mtd_partitions(s3c_mtd,s3c_nand_parts, 4); 而要添加的分区信息为: static struct mtd_partition s3c_nand_parts[] = { [0] = { .name = "bootloader", /* 分区名 */ .size = 0x00040000, /* 分区大小 */ .offset = 0, /* 偏移值 */ }, [1] = { .name = "params", .size = 0x00020000, .offset = MTDPART_OFS_APPEND, }, [2] = { .name = "kernel", .size = 0x00020000, .offset = MTDPART_OFS_APPEND, }, [3] = { .name = "root", .size = MTDPART_SIZ_FULL, .offset = MTDPART_OFS_APPEND, } };   上面就是一个nand驱动程序了。下面我会将一些我认为比较好的文章的连接放到这里,希望对你有用:    

【详解】如何编写Linux下Nand Flash驱动超级推荐这个,他写的特详细。

    24.Linux-Nand Flash驱动(分析MTD层并制作NAND驱动)  

基于MTD的NANDFLASH设备驱动底层实现原理分析(一)

Linux NAND FLASH驱动程序框架分析

  而在文章的最后我将我的驱动程序全部贴上,希望对大家有用:   /* *可参考 driversmtd ands3c2410.c * 和 driversmtd andat91_nand.c */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct s3c_nand_reg{ unsigned long nfconf; unsigned long nfcont; unsigned long nfcmd; unsigned long nfaddr; unsigned long nfdata; unsigned long nfmeccd0; unsigned long nfmeccd1; unsigned long nfseccd; unsigned long nfstat; unsigned long nfestat0; unsigned long nfestat1; unsigned long nfmecc0; unsigned long nfmecc1; unsigned long nfsecc; unsigned long nfsblk; unsigned long nfeblk; }; static struct s3c_nand_reg *s3c_nand_regs; static struct nand_chip *s3c_nand; static struct mtd_info *s3c_mtd; static struct clk *nand_clk; static struct mtd_partition s3c_nand_parts[] = { [0] = { .name = "bootloader", .size = 0x00040000, .offset = 0, }, [1] = { .name = "params", .size = 0x00020000, .offset = MTDPART_OFS_APPEND, }, [2] = { .name = "kernel", .size = 0x00020000, .offset = MTDPART_OFS_APPEND, }, [3] = { .name = "root", .size = MTDPART_SIZ_FULL, .offset = MTDPART_OFS_APPEND, } }; /* 片选控制函数 */ static void s3c2440_select_chip(struct mtd_info *mtd, int chip) { if(chip == -1) { /* 取消片选:NFCONT[1]=1 */ s3c_nand_regs->nfcont |= (1<<1); }else{ /* 选中 :NFCONT[1]=0 */ s3c_nand_regs->nfcont &= ~(1<<1); } } /* 写命令/地址函数 */ static void s3c2440_cmd_ctrl(struct mtd_info *mtd, int dat,unsigned int ctrl) { if(ctrl & NAND_CLE){ /* 发命令:NFCMMD = dat */ s3c_nand_regs->nfcmd = dat; }else{ /* 发地址:NFADDR = dat */ s3c_nand_regs->nfaddr = dat; } } /* 等待就绪函数 */ int s3c2440_dev_ready(struct mtd_info *mtd) { /* 返回NFSTAT的bit[0] */ return s3c_nand_regs->nfstat & (1<<0); } static int s3c_nand_init(void) { /* 0.设置nand的总开关 */ nand_clk = clk_get(NULL,"nand"); clk_enable(nand_clk); /* 这个设置相当于将CLKCON的bit[4]=1 */ /* 1.分配一个nand_chip结构体 */ s3c_nand = kzalloc(sizeof(struct nand_chip),GFP_KERNEL); s3c_nand_regs = ioremap(0x4e000000,sizeof(struct s3c_nand_reg)); /* 2.设置nand_chip结构体 */ /* 设置nand_chip是给nand_scan函数使用的,如果不知道怎么设置, * 先看nand_scan函数怎么使用,他应该提供: * 选中,发命令,发地址,发数据判断状态的功能。 */ s3c_nand->select_chip = s3c2440_select_chip; /* 片选,默认函数不能提供次功能,要自己写 */ s3c_nand->cmd_ctrl = s3c2440_cmd_ctrl ; /* 写命令/地址,此函数可以参考AT91 */ s3c_nand->IO_ADDR_R = &s3c_nand_regs->nfdata;/* 读数据的地址,NFDATA 的虚拟地址 */ s3c_nand->IO_ADDR_W = &s3c_nand_regs->nfdata;/* 写数据的地址,NFDATA 的虚拟地址 */ s3c_nand->dev_ready = s3c2440_dev_ready; /* 就绪函数 */ s3c_nand->ecc.mode = NAND_ECC_SOFT; /* 设置nand的ecc检测模式为软件设置 */ /* 3.硬件相关的设置 */ /* *HCLK = 100MHz; *TACLS:发出ALE/CLE之后多长时间才发出nWE信号,从nand手册可知ALE/CLE与nWE可以同时发出所以 * TACLS=0 *TWRPH0:nWE的脉冲宽度,HCLK*(TWRPH0+1),从nand的手册可知脉冲宽度要大于12ns,所以 * TWRPH0>=1; *TWRPH1:nWE变为高电平后多长时间ALE/CLE能变为低电平,HCLK*(TWRPH1+1), * 从nand的手册可知这段时间要大于5ns,所以 * TWRPH1 >= 0; */ #define TACLS 0 #define TWRPH0 1 #define TWRPH1 0 s3c_nand_regs->nfconf |= (TACLS << 12) | (TWRPH0 << 8) | (TWRPH1 << 4); /* * NFCONT的bit[1]=1 : 取消片选 * bit[0]=1 : 使能nand flash控制器 */ s3c_nand_regs->nfcont = (1<<1) | (1<<0); /* 4.使用:nand_scan */ s3c_mtd = kzalloc(sizeof(struct mtd_info),GFP_KERNEL); s3c_mtd->owner = THIS_MODULE; s3c_mtd->priv = s3c_nand; //私有数据 nand_scan(s3c_mtd, 1); //参数二为最大芯片数 /* 5.分区:add_mtd_partitions */ /* 如果不想分区用add_mtd_device(s3c_mtd)就可以了 */ add_mtd_partitions(s3c_mtd,s3c_nand_parts, 4); return 0; } static void s3c_nand_exit(void) { kfree(s3c_mtd); iounmap(s3c_nand_regs); kfree(s3c_nand); del_mtd_partitions(s3c_mtd); } module_init(s3c_nand_init); module_exit(s3c_nand_exit); MODULE_LICENSE("GPL");