为了实现EVB板上Linux系统中SD卡插拔自动检测,最近进行了GPIO驱动的开发,基于GPIOLIB。
做个记录,以备以后参考。。。
参考已有的驱动文件:arch/arm/plat-mxc/gpio.c, 基本只需要将该文件复制过来,针对自己的
平台进行修改即可。这个文件中有一个gpio初始化函数,其原型是:
int __init mxc_gpio_init(struct mxc_gpio_port *port, int cnt);
对mxc_gpio_init()的调用,参考arch/arm/mach-mx1/devices.c文件, 如下所示:
/* GPIO port description */
static struct mxc_gpio_port imx_gpio_ports[] = {
{
.chip.label = "gpio-0",
.base = (void __iomem *)IO_ADDRESS(GPIO_BASE_ADDR),
.irq = GPIO_INT_PORTA,
.virtual_irq_start = MXC_GPIO_IRQ_START,
}, {
.chip.label = "gpio-1",
.base = (void __iomem *)IO_ADDRESS(GPIO_BASE_ADDR + 0x100),
.irq = GPIO_INT_PORTB,
.virtual_irq_start = MXC_GPIO_IRQ_START + 32,
}, {
.chip.label = "gpio-2",
.base = (void __iomem *)IO_ADDRESS(GPIO_BASE_ADDR + 0x200),
.irq = GPIO_INT_PORTC,
.virtual_irq_start = MXC_GPIO_IRQ_START + 64,
}, {
.chip.label = "gpio-3",
.base = (void __iomem *)IO_ADDRESS(GPIO_BASE_ADDR + 0x300),
.irq = GPIO_INT_PORTD,
.virtual_irq_start = MXC_GPIO_IRQ_START + 96,
}
};
int __init mxc_register_gpios(void)
{
return mxc_gpio_init(imx_gpio_ports, ARRAY_SIZE(imx_gpio_ports));
}
对于一款SOC应用处理芯片,必然会有一套中断处理系统,对应不同模块有一定数量
中断入口及中断号,例如UART,I2C,LCD,I2S等,当然也包括一个或多个GPIO中断,如上
面的GPIO_INT_PORTA,GPIO_INT_PORTB,GPIO_INT_PORTC,GPIO_INT_PORTD就是四个GPIO
端口的1级中断号。每个GPIO端口通常都对应一个下一级的中断控制器,因为每个端口都对应
1-32个GPIO,每个GPIO引脚通常都可以进行中断配置,每个GPIO引脚都对应一个2级中断号。
GPIO扩展的2级IRQ编号就是从virtual_irq_start开始。Linux内核中有一个NR_IRQS的宏,
代表系统中所有的IRQ数,在实现GPIO驱动时,NR_IRQS也要相应扩充,增加由GPIO扩充了的
2级IRQ中断数量。
申请一个GPIO的IRQ时,只需要调用 request_irq(gpio_to_irq(gpio),handler2,...);即可,
不需要申请gpio对应的1级GPIO中断。request_irq()函数中很重要的一步就是enable_irq,
而在mxc_gpio_init()函数中,已经使能所有1级GPIO中断号,通过以下调用:
set_irq_chained_handler(port[i].irq, handler1);
当GPIO中断来临,首先跳到handler1, 在mx3_gpio_irq_handler中,会根据中断号找到对应的
1级GPIO端口,从1级GPIO中断控制寄存器找读出中断状态,从而找到对应的引起中断的GPIO,
最后跳到request_irq时传入的handler2中处理。
/* handle 32 interrupts in one status register */
static void mxc_gpio_irq_handler(struct mxc_gpio_port *port, u32 irq_stat)
{
u32 gpio_irq_no_base = port->virtual_irq_start;
while (irq_stat != 0) {
int irqoffset = fls(irq_stat) - 1;
if (port->both_edges & (1 << irqoffset))
mxc_flip_edge(port, irqoffset);
generic_handle_irq(gpio_irq_no_base + irqoffset);
irq_stat &= ~(1 << irqoffset);
}
}
/* MX1 and MX3 has one interrupt *per* gpio port */
static void mx3_gpio_irq_handler(u32 irq, struct irq_desc *desc)
{
u32 irq_stat;
struct mxc_gpio_port *port = (struct mxc_gpio_port *)get_irq_data(irq);
irq_stat = __raw_readl(port->base + GPIO_ISR) &
__raw_readl(port->base + GPIO_IMR);
mxc_gpio_irq_handler(port, irq_stat);
}