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

        就像题目所说,本文有两个内容要讲,第一是分析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.\n");
		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驱动程序框架分析

 

而在文章的最后我将我的驱动程序全部贴上,希望对大家有用:

 

/*
*可参考 drivers\mtd\nand\s3c2410.c
*    和 drivers\mtd\nand\at91_nand.c
*/

#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/clk.h>

#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/nand_ecc.h>
#include <linux/mtd/partitions.h>

#include <asm/io.h>

#include <asm/arch/regs-nand.h>
#include <asm/arch/nand.h>

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");



 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值