大牛的文章,吐血推荐。
————————————————————————————————————————————————————————
AM3359 中利用GPMC控制器驱动FIFO
开发板:英蓓特科技有限公司的Mini8600B
处理器:TI公司的AM3359
操作系统:Linux3.2.0
FIFO芯片:IDT7205L20
硬件连接图:
一、GPMC module 简介
1、 GPMC简介
GPMC用于控制external memory的读写访问,GPMC的地址为0x0000 0000到0x1fff ff ff,最大支持512MB的片外存储器访问;GPMC寄存器的基地址为0x5000 0000,用于配置GPMC模块。GPMC能提供最大100MHz的外部时钟频率(gpmc_clk)。
支持的设备或存储器:
NAND flash与NOR flash结构的device/memory:
(1)8bit与16bit的同步/异步读写device/memory
(2)16bit地址数据复用的NOR flash(pSRAM)
(3)8bit与16bit的NAND flash
(4)其他支持的设备:使用NOR interface protocol进行通信,
Technical Reference Manual上有这么一句:Other supported device type interact with the GPMC through the NOR interface protocol.
2、NOR Interface ProtocolNOR flash architecture, introduced in 1988, is a flash technology. Unlike NAND, which is a sequential access device, NOR is directly addressable; i.e., it is designed to be a random access device. NOR is best suited to devices used to store and run code or firmware, usually in small capacities. While NOR has fast read capabilities it has slow write and erase functions compared to NAND architecture.
3、GPMC编程模式
如上图所示,使用GPMC驱动外设时,要先对其进行初始化和配置。GPMC支持软件复位操作,通过GPMC_SYSCONFIG寄存器的配置来实现。接下来配置GPMC,NOR类型的device的配置要比NAND简单得多,主要配置GPMC_CONFIG1_i到GPMC_CONFIG7_i这七个寄存器,这些寄存器包含了memory类型(NAND、NOR)的配置,片选配置,时序配置等信息,关于片选配置,下面会详细描述。需要注意的是,在配置GPMC这七个片选寄存器之前,要先使GPMC_CONFIG7_i的CSVALID disabled,Technical Reference Manual上有这么一句:Chip-select configuration (base and mask address or any protocol and timing settings) must be performed while the associated chip-select is disabled through the GPMC_CONFIG7_i[6] CSVALID bit. In addition, a chip-select configuration can only be disabled if there is no ongoing access to that chip-select. 片选寄存器配置完之后应当enable CSVALID。
片寻配置(chip-select configuration):GPMC最大支持512MB的片外寻址,被分成8片,每片的大小可选以下数值:16M、32M、64M、128M、256M,但8片大小的总和不能大于512MB。可通过GPMC_CONFIG7_i来配置片寻基址、片寻大小等,片寻基址必须是2的n次方,其他基址应该避免,否则会产生hole。其中CS0~CS6是可配置的,而CS7不是外部引脚(not pin out),配置了也没用。我研究了Linux3.2.0中NAND驱动的源码,发现NAND使用的是CS0,用户可从CS1~CS6中选择一个来设计自己的驱动。
二、FIFO芯片IDT7205L20简介
IDT7205L20是Integrated Device Technology,Inc.公司的一款8K*9bit的FIFO芯片,
异步读写,没有时钟引脚,没有地址引脚。通过WE和OE来控制读写,以读为例,OE变为低电平(enable电平)时,数据送到输出端口,等待CPU取走,CPU取走数据后,OE变为高电平(disable 电平),这样就完成了一个读周期。FIFO内部有一个读指针和写指针,复位后读写指针指向同一个位置。每往FIFO写一个数据时,写指针加一,每从FIFO读一个数据时,读指针加一,当读写指针相等时,FIFO空标志EF变为低电平,此时数据输出线呈高阻态,不能再读数据。当往FIFO写满8K个数据后,满标志FF变为低电平,数据输入线呈高阻态,不能再写数据。
在GPMC_CONFIG1_i中应当配置成异步读写,并且是非地址/数据复用模式,采用NOR接口协议。此外还需配置GPMC_CONFIG4_i的 WEONTIME和WEOFFTIME,OEONTIME和OEOFFTIM,根据你的FIFO datasheet来配置时间,一般是几十个纳秒级的,然后配置GPMC_CONFIG5_i和GPMC_CONFIG6_i的total read time和total write time。这些工作做完之后,FIFO与GPMC应该可以通信了。
三、 配置GPMC引脚
在以下文件Linux-3.2.0-psp04.06.00.08.sdk/arch/arm/mach-omap2/board-am335xevm.c
中配置:
1、添加如下引脚配置代码
/*pin mux for FIFO writing*/
static struct pinmux_config fifo_pin_mux[] = {
{"gpmc_ad0.gpmc_ad0", OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT},
{"gpmc_ad1.gpmc_ad1", OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT},
{"gpmc_ad2.gpmc_ad2", OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT},
{"gpmc_ad3.gpmc_ad3", OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT},
{"gpmc_ad4.gpmc_ad4", OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT},
{"gpmc_ad5.gpmc_ad5", OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT},
{"gpmc_ad6.gpmc_ad6", OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT},
{"gpmc_ad7.gpmc_ad7", OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT},
{"gpmc_ad8.gpmc_ad8", OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT},
{"gpmc_ad9.gpmc_ad9", OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT},
{"gpmc_ad10.gpmc_ad10", OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT},
{"gpmc_ad11.gpmc_ad11", OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT},
{"gpmc_ad12.gpmc_ad12", OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT},
{"gpmc_ad13.gpmc_ad13", OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT},
{"gpmc_ad14.gpmc_ad14", OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT},
{"gpmc_ad15.gpmc_ad15", OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT},
{"gpmc_csn1.gpio1_30", OMAP_MUX_MODE7 | AM33XX_PIN_OUTPUT},
{"gpmc_csn2.gpio1_31", OMAP_MUX_MODE7 | AM33XX_PIN_INPUT},
{"gpmc_csn3.gpio2_0", OMAP_MUX_MODE7 | AM33XX_PIN_INPUT},
{"gpmc_clk.gpio2_1", OMAP_MUX_MODE7 | AM33XX_PIN_OUTPUT},
{"lcd_vsync.gpio2_22", OMAP_MUX_MODE7 | AM33XX_PIN_OUTPUT},
{"lcd_pclk.gpio2_24", OMAP_MUX_MODE7 | AM33XX_PIN_OUTPUT},
{"gpmc_wen.gpmc_wen", OMAP_MUX_MODE0 | AM33XX_PULL_DISA},
{"gpmc_ben1.gpmc_dir", OMAP_MUX_MODE4 | AM33XX_PULL_DISA},
{NULL, 0},
};
2、定义GPMC device,这个gpmc_device的内容可根据驱动的实际情况而定,其目的是作为调用GPMC初始化函数omap_init_gpmc()的参数。
struct omap_fifo_platform_data {
int cs;
struct gpmc_timings *gpmc_t;
int gpmc_irq;
unsigned long phys_base;
int devsize;
bool elm_used;
};
static struct gpmc_timings fifo_timings = {
.sync_clk = 0,
.cs_on = 0, /* Assertion time */
.cs_rd_off = 82,/* Read deassertion time */
.cs_wr_off = 82,/* Write deassertion time */
.we_on = 40, /* WE assertion time */
.we_off = 40, /* WE deassertion time */
.oe_on = 40, /* OE assertion time */
.oe_off = 40, /* OE deassertion time */
.access = 64, /* Start-cycle to first data valid delay */
.rd_cycle = 82, /* Total read cycle time */
.wr_cycle = 82, /* Total write cycle time */
};
static struct omap_fifo_platform_data fifo_data = {
.cs = 1,
.gpmc_t = &fifo_timings,
.devsize = 16,
.elm_used = true,
};
static struct gpmc_devices_info gpmc_device[2] = {
{ .pdata = &fifo_data, .flag = GPMC_DEVICE_NOR },
{ NULL, 0 }, /* reserved */
};
3、添加初始化函数fifo_init(),调用setup_pin_mux()使引脚配置生效,调用omap_init_gpmc()与omap_init_elm()初始化GPMC,这一步是必须的,否则GPMC相关的函数不能使用。
static void fifo_init(int evm_id, int profile)
{
setup_pin_mux(fifo_pin_mux);
omap_init_gpmc(gpmc_device, sizeof(gpmc_device));
omap_init_elm();
return;
}
3、在结构体evm_dev_cfg sbc8600_dev_cfg[]中添加
{fifo_init, DEV_ON_BASEBOARD, PROFILE_ALL},
系统初始化的时候会调用下面的函数:
_configure_device(EVM_SK, sbc8600_dev_cfg, PROFILE_NONE);
4、注意事项
引脚配置不应有冲突,即不应有两个或以上的驱动配置相同的引脚,同一个引脚不应被配置两次或多次不同的数值。
5、编译内核,生成uImage文件,下载到开发板上。
四、驱动设计
1、除一般驱动所要的头文件外,还需包含的头文件
#include <plat/gpmc.h>
#include <plat/dma.h>
#include <asm/gpio.h>
#include <asm/io.h>
#include <linux/platform_device.h>
2、定义片选号、GPIO引脚等
/* GPMC chip select */
#define GPMC_CS 4
#define NONE_MUXADDATA ~(0x03 << 8)
/* GPIO pin */
#define GPIO_TO_PIN(bank, gpio) ((32 * (bank)) + (gpio))
#define GPMC_CSN1_RESET GPIO_TO_PIN(1, 30) /*FIFO reset*/
#define GPMC_DEVICE_NAME "mygpmc"
#define GPMC_DRIVER_NAME "gpmc_write"
#define FIFO_SIZE SZ_8K
#define BUF_SIZE 1024
3、 配置时序信息
static struct gpmc_timings fifo_timings = {/*GPMC timing configurations*/
.sync_clk = 0,
.cs_on = 0, /* Assertion time */
.cs_rd_off = 82, /* Read deassertion time */
.cs_wr_off = 82, /* Write deassertion time */
.we_on = 40, /* WE assertion time */
.we_off = 40, /* WE deassertion time */
.oe_on = 40, /* OE assertion time */
.oe_off = 40, /* OE deassertion time */
.access = 50, /* Start-cycle to first data valid delay */
.rd_cycle = 82, /* Total read cycle time */
.wr_cycle = 82, /* Total write cycle time */
};
4、 配置GPMC寄存器
/*Chip-select configuration (base and mask address or any protocol and timing settings) must be performed while the associated chip-select is disabled through the GPMC_CONFIG7_i[6] CSVALID bit. In addition, a chip-select configuration can only be disabled if there is no ongoing access to that chip-select.*/
val = gpmc_cs_read_reg(GPMC_CS, GPMC_CS_CONFIG7);
val &= ~GPMC_CONFIG7_CSVALID; /*disable chip-select*/
gpmc_cs_write_reg(GPMC_CS, GPMC_CS_CONFIG7, val);
gpmc_cs_configure(GPMC_CS, GPMC_SET_IRQ_STATUS, 0); /*set irq status*/
gpmc_cs_configure(GPMC_CS, GPMC_ENABLE_IRQ, 0); /*disable irqs*/
gpmc_cs_write_reg(GPMC_CS,GPMC_CS_CONFIG1, GPMC_CONFIG1_READTYPE_ASYNC | /*set read type Asynchronous*/
GPMC_CONFIG1_WRITETYPE_ASYNC | /*set write type Asynchronous*/
GPMC_CONFIG1_DEVICESIZE_16 | /*set device size 16bit*/
GPMC_CONFIG1_DEVICETYPE_NOR | /*set device type NOR*/
GPMC_CONFIG1_FCLK_DIV3); /*GPMC_CLK frequency=GPMC_FCLK frequency/3*/
val = gpmc_cs_read_reg(GPMC_CS, GPMC_CS_CONFIG1);
val &= NONE_MUXADDATA; /*Non-multiplexed attached device*/
gpmc_cs_write_reg(GPMC_CS, GPMC_CS_CONFIG1, val);
ret = gpmc_cs_set_timings(GPMC_CS, &fifo_timings); /*set timing parameters*/
这段代码编译时可能会提示gpmc_cs_write_reg、 gpmc_cs_read_reg、 gpmc_cs_set_timings这几个函数undefined,这时需要在mach-omap2/gpmc.c文件中找到这些函数,并在函数定义的末尾添加EXPORT_SYMBOL(gpmc_cs_write_reg);
EXPORT_SYMBOL(gpmc_cs_read_reg); EXPORT_SYMBOL(gpmc_cs_set_timings);
重新编译内核,下载到开发板上。
5、 申请gpmc片选空间并用ioremap函数映射为内核虚拟地址
/***********为FIFO申请GPMC chip-select空间**********/
if (gpmc_cs_request(GPMC_CS, FIFO_SIZE, &mem_base)<0)
{
printk(KERN_ERR "gpmc_cs_request failed.\n");
return -1;
}
/***********为FIFO申请虚拟内存空间**********/
if (!request_mem_region(mem_base, FIFO_SIZE, GPMC_DRIVER_NAME))
{
printk(KERN_ERR "request_mem_region failed.\n");
return -1;
}
/******物理地址映射为虚拟地址*****/
fifo_base = ioremap (mem_base, FIFO_SIZE);
6、 完成以上步骤后,就可以对fifo进行读写了
writew(gpmc_dev.user_buff[i], fifo_base);
gpmc_dev.user_buff[i] = readw(fifo_base);
FIFO是一个none ramdom access的器件,每次的读写操作都基于同一个地址。
7、 中断
GPMC有中断但是内部中断,只有wait0与wait1引脚是外部中断,而wait引脚通常连接外设的ready引脚,所以采用GPIO引脚来产生中断:
GPIO引脚号转换为中断号: irq_num = gpio_to_irq(gpio_pin);
设置中断出发方式为下降沿触发
irq_set_irq_type(irq_num, IRQF_TRIGGER_FALLING);
申请中断:
request_irq(irq_num, irq_proc, IRQF_DISABLED, GPIO_DEVICE_NAME, NULL);
中断处理函数:
static irqreturn_t irq_proc(int irq, void *dev_id)
{
// your code
return IRQ_HANDLED;
}
中断类型IRQF_DISABLED表示此中断处理函数调用时,屏蔽所有中断,更多信息可在头文件<linux/interrupt.h> 与<linux/irq.h>中找到。
8、 释放GPMC、 GPIO资源
release_mem_region(mem_base, FIFO_SIZE);
gpmc_cs_free(GPMC_CS);
iounmap(fifo_base);
gpio_free(gpio_pin);
9、释放中断资源
free_irq(irq_num, NULL);
10、驱动源程序
见附件
五、Makefile
ARCH=arm
CROSS_COMPILE=/opt/arm-2009q1/bin/arm-none-linux-gnueabi-
KERNELDIR := /home/linfan/linux-3.2.0-for-write
obj-m := gpmc_write.o
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules
app:
arm-none-linux-gnueabi-gcc test_write.c -o write
clean:
rm -rf *.o *.ko *.mod.c *.symvers *.order
六、用户空间应用程序
见附件
七、总结
AM3359的驱动设计要在board-am335xevm.c中定义设备和配置引脚,然后再编译内核,生成映像文件,颇有不便之处。我在编写这个驱动时,大部分时间花在修改内核源码及编译内核之上。
GPMC驱动设计的关键是要正确配置相关的寄存器,否则得不到想要的运行结果。对于NOR类型的器件,只需配置GPMC_CONFIG1~ GPMC_CONFIG7这几个寄存器就可以了;对于NAND类型的器件,还需配置prefetch、ECC engine等的内容。我在写这个驱动时,由于在GPMC_CONFIG1中配置了同步读写,以及一些时序上的不正确配置,一直不能往/从FIFO读写数据,后来通过FIFO datasheet的研究,才发现并解决这个问题的。
Linux3.2.0内核源码中有gpmc.h与 gpmc.c文件,里面的函数对GPMC的支持还是比较全面的,但要使用这些函数,还要先调用omap_init_gpmc()与omap_init_elm()初始化GPMC device,否则无法调用insmod加载驱动模块。有些函数明明在gpmc.c文件中有定义,但编译时却提示undefined,对于这个问题,我的解决办法是,对于非static函数,调用EXPORT_SYMBOL()来导出这些函数。编译内核时,会在module.symvers文件中生成相应的symbol。当编译驱动模块时,编译器自动进入你指定的Linux内核源码目录,而你的驱动中的系统调用则由编译器在module.symvers文件中查找,如果找不到,则会提示undefined。
我曾试图自己编写配置GPMC寄存器的函数,方法是根据technical reference manual里的memory map找到GPMC寄存器的地址,通过ioremap函数转换为内核虚拟地址,然后调用writel(),readl()之类的函数将配置data写进寄存器中。但编写起来所遇到的问题比解决的问题还多,要考虑的方面很多,是一个浩大的工程。对于编写一个小程序测试一下还是容易的,当你的工程比较大的时候,这个工作就显得困难了,而且代码的可读性与可移植性都比较差。所以还是使用系统自带的函数来操作好,毕竟是经过原厂编译并测试通过的,可靠性稳定性都比较高。
http://blog.chinaunix.net/uid-28818752-id-3655729.html