NAND FLASH驱动

S3C6410的 NAND 驱动以 platform驱动的形式存在。
 
static int __init s3c_nand_init(void)
{
	printk("S3C NAND Driver, (c) 2008 Samsung Electronics\n");

	platform_driver_register(&s3c2450_nand_driver);
	platform_driver_register(&s3c6400_nand_driver);
	return platform_driver_register(&s3c6410_nand_driver);
}

static void __exit s3c_nand_exit(void)
{
	platform_driver_unregister(&s3c2450_nand_driver);
	platform_driver_unregister(&s3c6400_nand_driver);
	platform_driver_unregister(&s3c6410_nand_driver);
}


 
static struct platform_driver s3c6410_nand_driver = {
	.probe		= s3c6410_nand_probe,
	.remove		= s3c_nand_remove,
	.suspend	= s3c_nand_suspend,
	.resume		= s3c_nand_resume,
	.driver		= {
		.name	= "s3c6410-nand",
		.owner	= THIS_MODULE,
	},
};

static int s3c6410_nand_probe(struct platform_device *dev)
{
	return s3c_nand_probe(dev, TYPE_S3C6410);
}

 

其中:

enum s3c_cpu_type {
	TYPE_S3C2450,	/* including s3c2416 */
	TYPE_S3C6400,
	TYPE_S3C6410,	/* including s3c6430/31 */
};


 

/* s3c_nand_probe
 *
 * called by device layer when it finds a device matching
 * one our driver can handled. This code checks to see if
 * it can allocate all necessary resources then calls the
 * nand layer to look for devices
 */
static int s3c_nand_probe(struct platform_device *pdev, enum s3c_cpu_type cpu_type)
{	
	struct s3c_nand_mtd_info *plat_info = pdev->dev.platform_data;
	struct mtd_partition *partition_info = (struct mtd_partition *)plat_info->partition;
	struct nand_chip *nand;
	struct resource *res;
	int err = 0;
	int ret = 0;
	int i, j, size;

#if defined(CONFIG_MTD_NAND_S3C_HWECC)
	struct nand_flash_dev *type = NULL;
	u_char tmp;
	u_char dev_id;
#endif

	/* get the clock source and enable it */

	s3c_nand.clk = clk_get(&pdev->dev, "nand");
	if (IS_ERR(s3c_nand.clk)) {
		dev_err(&pdev->dev, "failed to get clock");
		err = -ENOENT;
		goto exit_error;
	}

	clk_enable(s3c_nand.clk);

	/* allocate and map the resource */

	/* currently we assume we have the one resource */
	res  = pdev->resource;
	size = res->end - res->start + 1;
	//申请I/O内存,设备通常会提供一组寄存器来用于控制设备、读写设备和获取设备状态,这些寄存器为与I/O空间,也可能位于内存空间。当位于IO空间时,通常称为IO端口,位于内存空间时,对应的内存空间称为 IO 内存。
 //在内核中访问I/O 内存前,需要首先使用ioremap()函数将设备所处的物理地址映射到虚拟地址。ioremap()函数原型如下:
 void *ioremap(unsigned long offset, unsigned long size); ioremap()与vmlloc()类似,也需要建立页表,但是它并不进行vmlloc()函数中所执行的内存分配行为。ioremap()返回一个特殊的虚拟地址。。。。。在设备的物理地址被映射到虚拟地址之后,尽管尅有直接通过指针访问这些地址,但是可以使用linux内核的一组函数来完成设备内存映射的虚拟地址的读写。
	s3c_nand.area = request_mem_region(res->start, size, pdev->name);

	if (s3c_nand.area == NULL) {
		dev_err(&pdev->dev, "cannot reserve register region\n");
		err = -ENOENT;
		goto exit_error;
	}

	s3c_nand.cpu_type   = cpu_type;
	s3c_nand.device     = &pdev->dev;
	s3c_nand.regs       = ioremap(res->start, size);

	if (s3c_nand.regs == NULL) {
		dev_err(&pdev->dev, "cannot reserve register region\n");
		err = -EIO;
		goto exit_error;
	}

	/* allocate memory for MTD device structure and private data */
	s3c_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL);

	if (!s3c_mtd) {
		printk("Unable to allocate NAND MTD dev structure.\n");
		return -ENOMEM;
	}

	/* Get pointer to private data */
	nand = (struct nand_chip *) (&s3c_mtd[1]);

	/* Initialize structures */
	memset((char *) s3c_mtd, 0, sizeof(struct mtd_info));
	memset((char *) nand, 0, sizeof(struct nand_chip));

	/* Link the private data with the MTD structure */
	s3c_mtd->priv = nand;

	for (i = 0; i < plat_info->chip_nr; i++) {
		nand->IO_ADDR_R		= (char *)(s3c_nand.regs + S3C_NFDATA);
		nand->IO_ADDR_W		= (char *)(s3c_nand.regs + S3C_NFDATA);
		nand->cmd_ctrl		= s3c_nand_hwcontrol;
		nand->dev_ready		= s3c_nand_device_ready;		
		nand->scan_bbt		= s3c_nand_scan_bbt;
		nand->options		= 0;

#if defined(CONFIG_MTD_NAND_S3C_CACHEDPROG)
		nand->options		|= NAND_CACHEPRG;
#endif

#if defined(CONFIG_MTD_NAND_S3C_HWECC)
		nand->ecc.mode		= NAND_ECC_HW;
		nand->ecc.hwctl		= s3c_nand_enable_hwecc;
		nand->ecc.calculate	= s3c_nand_calculate_ecc;
		nand->ecc.correct	= s3c_nand_correct_data;
		
		s3c_nand_hwcontrol(0, NAND_CMD_READID, NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
		s3c_nand_hwcontrol(0, 0x00, NAND_CTRL_CHANGE | NAND_NCE | NAND_ALE);
		s3c_nand_hwcontrol(0, 0x00, NAND_NCE | NAND_ALE);
		s3c_nand_hwcontrol(0, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE);
		s3c_nand_device_ready(0);

		tmp = readb(nand->IO_ADDR_R); /* Maf. ID */
		dev_id = tmp = readb(nand->IO_ADDR_R); /* Device ID */

		for (j = 0; nand_flash_ids[j].name != NULL; j++) {
			if (tmp == nand_flash_ids[j].id) {
				type = &nand_flash_ids[j];
				break;
			}
		}

		if (!type) {
			printk("Unknown NAND Device.\n");
			goto exit_error;
		}
		
		nand->cellinfo = readb(nand->IO_ADDR_R);	/* the 3rd byte */
		tmp = readb(nand->IO_ADDR_R);			/* the 4th byte */

		if (!type->pagesize) {
			if (((nand->cellinfo >> 2) & 0x3) == 0) {
				nand_type = S3C_NAND_TYPE_SLC;				
				nand->ecc.size = 512;
				nand->ecc.bytes	= 4;

				if ((1024 << (tmp & 0x3)) > 512) {
					nand->ecc.read_page = s3c_nand_read_page_1bit;
					nand->ecc.write_page = s3c_nand_write_page_1bit;
					nand->ecc.read_oob = s3c_nand_read_oob_1bit;
					nand->ecc.write_oob = s3c_nand_write_oob_1bit;
					nand->ecc.layout = &s3c_nand_oob_64;
				} else {
					nand->ecc.layout = &s3c_nand_oob_16;
				}
			} else {
				nand_type = S3C_NAND_TYPE_MLC;
				nand->options |= NAND_NO_SUBPAGE_WRITE;	/* NOP = 1 if MLC */
				nand->ecc.read_page = s3c_nand_read_page_4bit;
				nand->ecc.write_page = s3c_nand_write_page_4bit;
				nand->ecc.size = 512;
				nand->ecc.bytes = 8;	/* really 7 bytes */
				nand->ecc.layout = &s3c_nand_oob_mlc_64;



				if((1024 << (tmp & 0x3)) > 2048)
					nand->ecc.layout = &s3c_nand_oob_mlc_128;		
			}
		} else {
/*
			nand_type = S3C_NAND_TYPE_SLC;
			nand->ecc.size = 512;
			nand->cellinfo = 0;
			nand->ecc.bytes = 4;
			nand->ecc.layout = &s3c_nand_oob_16;
*/
			nand_type = S3C_NAND_TYPE_MLC;
			nand->options |= NAND_NO_SUBPAGE_WRITE;	/* NOP = 1 if MLC */
			nand->ecc.read_page = s3c_nand_read_page_4bit;
			nand->ecc.write_page = s3c_nand_write_page_4bit;
			nand->ecc.size = 512;
			nand->ecc.bytes = 8;	/* really 7 bytes */
			nand->ecc.layout = &s3c_nand_oob_mlc_64;

			if(dev_id==0xd5)
				nand->ecc.layout = &s3c_nand_oob_mlc_128;		
		}

		printk("S3C NAND Driver is using hardware ECC.\n");
#else
		nand->ecc.mode = NAND_ECC_SOFT;
		printk("S3C NAND Driver is using software ECC.\n");
#endif
		if (nand_scan(s3c_mtd, 1)) {
			ret = -ENXIO;
			goto exit_error;
		}

		/* Register the partitions */
		add_mtd_partitions(s3c_mtd, partition_info, plat_info->mtd_part_nr);
	}

	pr_debug("initialized ok\n");
	return 0;

exit_error:
	kfree(s3c_mtd);

	return ret;
}


drivers/mtd/nand/s3c_nand.c是一个platform驱动,我们在S3C6410的BSP中只需要添加相关的NAND的platform的设备和分区信息即可。


在BSP中添加相应的信息如下:[ 下面有关于这部分的分析]

static void __init smdk6410_map_io(void)
{
	s3c_device_nand.name = "s3c6410-nand";

	s3c64xx_init_io(smdk6410_iodesc, ARRAY_SIZE(smdk6410_iodesc));
	s3c24xx_init_clocks(12000000);
	s3c24xx_init_uarts(smdk6410_uartcfgs, ARRAY_SIZE(smdk6410_uartcfgs));
}


smdk6410_map_io() 在下面出现。

MACHINE_START(SMDK6410, "SMDK6410")
	/* Maintainer: Ben Dooks <[email protected]> */
	.phys_io	= S3C_PA_UART & 0xfff00000,
	.io_pg_offst	= (((u32)S3C_VA_UART) >> 18) & 0xfffc,
	.boot_params	= S3C64XX_PA_SDRAM + 0x100,

	.init_irq	= s3c6410_init_irq,
	.map_io		= smdk6410_map_io,
	.init_machine	= smdk6410_machine_init,
	.timer		= &s3c24xx_timer,
MACHINE_END


 

struct platform_device s3c_device_nand = {
	.name		  = "s3c-nand",
	.id		  = -1,
	.num_resources	  = ARRAY_SIZE(s3c_nand_resource),
	.resource	  = s3c_nand_resource,
};


其中关于资源地信息如下:

/* NAND Controller */

static struct resource s3c_nand_resource[] = {
	[0] = {
		.start = S3C64XX_PA_NAND,
		.end   = S3C64XX_PA_NAND + S3C64XX_SZ_NAND - 1,
		.flags = IORESOURCE_MEM,
	}
};


在宏定义的文件/* linux/arch/arm/mach-s3c6400/include/mach/map.h 中,有

/* NAND flash controller */
#define S3C64XX_PA_NAND	   	(0x70200000)
#define S3C64XX_SZ_NAND	   	SZ_1M

 

上面这些信息的出处在

NAND FLASH驱动_第1张图片

关于其大小的确定,在datashee的 Memory mapping 一节:

NAND FLASH驱动_第2张图片

 

在开发板的硬件硬件手册有 更详细的说明。

***************************************************************************************************************

下面的内容参考 了ARM LINUX 静态映射分析

内核提供了一个比较重要的结构体, machine_desc,这个结构体在内核移植的过程中,起着重要的作用。内核通过它来进行体系架构相关部分的初始话。

struct machine_desc {
	/*
	 * Note! The first four elements are used
	 * by assembler code in head.S, head-common.S
	 */
	unsigned int		nr;		/* architecture number	*/
	unsigned int		phys_io;	/* start of physical io	*/
	unsigned int		io_pg_offst;	/* byte offset for io 
						 * page tabe entry	*/

	const char		*name;		/* architecture name	*/
	unsigned long		boot_params;	/* tagged list		*/

	unsigned int		video_start;	/* start of video RAM	*/
	unsigned int		video_end;	/* end of video RAM	*/

	unsigned int		reserve_lp0 :1;	/* never has lp0	*/
	unsigned int		reserve_lp1 :1;	/* never has lp1	*/
	unsigned int		reserve_lp2 :1;	/* never has lp2	*/
	unsigned int		soft_reboot :1;	/* soft reboot		*/
	void			(*fixup)(struct machine_desc *,
					 struct tag *, char **,
					 struct meminfo *);
	void			(*map_io)(void);/* IO mapping function	*/
	void			(*init_irq)(void);
	struct sys_timer	*timer;		/* system tick timer	*/
	void			(*init_machine)(void);
};


machine_desc 通过宏 MACHINE_START 进行初始化。

MACHINE_START(SMDK6410, "SMDK6410")
	/* Maintainer: Ben Dooks <[email protected]> */
	.phys_io	= S3C_PA_UART & 0xfff00000,
	.io_pg_offst	= (((u32)S3C_VA_UART) >> 18) & 0xfffc,
	.boot_params	= S3C64XX_PA_SDRAM + 0x100,

	.init_irq	= s3c6410_init_irq,
	.map_io		= smdk6410_map_io,
	.init_machine	= smdk6410_machine_init,
	.timer		= &s3c24xx_timer,
MACHINE_END


其中 MACHINE_START 宏定义如下:

#define MACHINE_START(_type,_name)			\
static const struct machine_desc __mach_desc_##_type	\
 __used							\
 __attribute__((__section__(".arch.info.init"))) = {	\
	.nr		= MACH_TYPE_##_type,		\
	.name		= _name,

#define MACHINE_END				\
};
 
其中,MACHINE_TYPE 宏的是用了 GCC 扩展。在预编译时,进行字符替换。
 
在mach-types.h文件中 
 

#define MACH_TYPE_SMDK6410             1626

上面 map_io函数,即是,内核提供给用户创建IO资源到内核虚拟地址静态映射表的 接口函数。在系统初始化 ,被调用

start_kernel()->setup_arch(&command_line)->paging_init(mdesc)>devicemaps_init()-> mdesc->map_io() 中调用。

 

而init_irq 的初始化流程为:

start_kernrl()->init_IRQ()->init_arch_irq()->setup_arch()->init_arch_irq

而上面的init_arch_irq 指针的赋值则在   

start_kernel()->setup_arch()-》

	 * Set up various architecture-specific pointers
	 */
	init_arch_irq = mdesc->init_irq;
	system_timer = mdesc->timer;
	init_machine = mdesc->init_machine;


接下来,继续分析 smdk6410_map_io()

static void __init smdk6410_map_io(void)
{
	s3c_device_nand.name = "s3c6410-nand";

	s3c64xx_init_io(smdk6410_iodesc, ARRAY_SIZE(smdk6410_iodesc));
	s3c24xx_init_clocks(12000000);
	s3c24xx_init_uarts(smdk6410_uartcfgs, ARRAY_SIZE(smdk6410_uartcfgs));
}

 

/* read cpu identification code */

void __init s3c64xx_init_io(struct map_desc *mach_desc, int size)
{
	unsigned long idcode;

	/* initialise the io descriptors we need for initialisation */
	iotable_init(s3c_iodesc, ARRAY_SIZE(s3c_iodesc));
	iotable_init(mach_desc, size);

	idcode = __raw_readl(S3C_VA_SYS + 0x118);
	s3c_init_cpu(idcode, cpu_ids, ARRAY_SIZE(cpu_ids));
}


 

/*
 * Create the architecture specific mappings
 */
void __init iotable_init(struct map_desc *io_desc, int nr)
{
	int i;

	for (i = 0; i < nr; i++)
		create_mapping(io_desc + i);
}


由上可知, smdk6410_map_io()最终 调用create_mapping()建立映射表。其参数为:

struct map_desc {
	unsigned long virtual;
	unsigned long pfn;
	unsigned long length;
	unsigned int type;
};

 

create_mapping()就是根据 map_desc提供的信息创建线性映射表的。

。。。。。。。

来看下,S3C6410是怎么创建map_desc结构体的。

/* Initial IO mappings */

static struct map_desc s3c6410_iodesc[] __initdata = {
	IODESC_ENT(LCD),
	IODESC_ENT(SROMC),
	IODESC_ENT(HOSTIFB),
	IODESC_ENT(OTG),
	IODESC_ENT(OTGSFR),
};

 

#define IODESC_ENT(x) { (unsigned long)S3C64XX_VA_##x, __phys_to_pfn(S3C64XX_PA_##x), S3C64XX_SZ_##x, MT_DEVICE }


展开后,等价于:

static struct map_desc s3c6410_iodesc[] __initdata = {
	.virtual    S3C64XX_VA_GPIO),
	.pfn        __phys_to_pfn(S3C64XX_PA_GPIO),
	.length     S3C64XX_SZ_GPIO,
	.type      MTD_DEVICE,
......................
	};

 


至此,我们已经清楚看到了GPIO被静态映射的过程,由于我么在前面的静态映射中,已经做好了相关的映射,所以 在写GPIO相关的驱动的时候。可以直接配置引脚的原因。

s3c_gpio_cfgpin(S3C64XX_GPN(0), S3C_GPIO_SFN(1));

 

2、最终还会调用s3c_init_cpu()->cpu->map_io( );
static struct cpu_table *cpu;



 

static struct cpu_table *cpu;
 
 
/* per-cpu initialisation function table. */

struct cpu_table {
	unsigned long	idcode;
	unsigned long	idmask;
	void		(*map_io)(void);
	void		(*init_uarts)(struct s3c2410_uartcfg *cfg, int no);
	void		(*init_clocks)(int xtal);
	int		(*init)(void);
	const char	*name;
};

在cpu.c文件
 

/* table of supported CPUs */

static const char name_s3c6400[] = "S3C6400"; static const char name_s3c6410[] = "S3C6410";

 

static struct cpu_table cpu_ids[] __initdata = {
	{
		.idcode		= 0x36400000,
		.idmask		= 0xfffff000,
		.map_io		= s3c6400_map_io,
		.init_clocks	= s3c6400_init_clocks,
		.init_uarts	= s3c6400_init_uarts,
		.init		= s3c6400_init,
		.name		= name_s3c6400,
	}, {
		.idcode		= 0x36410100,
		.idmask		= 0xffffff00,
		.map_io		= s3c6410_map_io,
		.init_clocks	= s3c6410_init_clocks,
		.init_uarts	= s3c6410_init_uarts,
		.init		= s3c6410_init,
		.name		= name_s3c6410,
	},
};
 
/* s3c6410_map_io
 *
 * register the standard cpu IO areas
*/

void __init s3c6410_map_io(void)
{
	iotable_init(s3c6410_iodesc, ARRAY_SIZE(s3c6410_iodesc));

	/* initialise device information early */
	s3c6410_default_sdhci0();
	s3c6410_default_sdhci1();

	/* the i2c devices are directly compatible with s3c2440 */
	s3c_i2c0_setname("s3c2440-i2c");
	s3c_i2c1_setname("s3c2440-i2c");

	/* set our idle function */
	s3c64xx_idle = s3c6410_idle;
}
 
 
/* Initial IO mappings */

static struct map_desc s3c6410_iodesc[] __initdata = {
	IODESC_ENT(LCD),
	IODESC_ENT(SROMC),
	IODESC_ENT(HOSTIFB),
	IODESC_ENT(OTG),
	IODESC_ENT(OTGSFR),
};
 
和前面分析是一样的情况:

/* Initial IO mappings */

static struct map_desc s3c6410_iodesc[] __initdata = { LCD\SROMC HOSTIFB\ OTG\ OTGSFR.等的静态映射都看 的清楚明白了了。


   

你可能感兴趣的:(NAND FLASH驱动)