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
上面这些信息的出处在
关于其大小的确定,在datashee的 Memory mapping 一节:
在开发板的硬件硬件手册有 更详细的说明。
***************************************************************************************************************
下面的内容参考 了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.等的静态映射都看 的清楚明白了了。