就像题目所说,本文有两个内容要讲,第一是分析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驱动程序了。下面我会将一些我认为比较好的文章的连接放到这里,希望对你有用:
24.Linux-Nand Flash驱动(分析MTD层并制作NAND驱动)
而在文章的最后我将我的驱动程序全部贴上,希望对大家有用:
/*
*可参考 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");