迅为iTOP-4412嵌入式开发板实现中断驱动例程

topeet 发布于 2016/02/23 14:06
阅读 681
收藏 0

本文转自迅为:www.topeetboard.com

大家好,今天我们来学习一下 linux 中断处理驱动的编写,本节我们实现的功能是通过开发板上的按键来控制 led 发光二极管,在之前的章节我们学习了 led 驱动的编写,在掌握了 led 驱动的编写以后,如果要实现按键控制 led 的功能,大家可能会想到可以在 led 的驱动里面使用轮询的方式一直查询按键的状态,如果有按键按下就设置 led 的状态。通过这种方式可以实现按键控制 led 的功能, 但是通过这样的方式有一个缺点就是led 驱动会占用 cpu,这样 cpu 的利用率就大大降低了,所以我们可以通过中断的方式来实现。这样 cpu 就可以去做其他的事情了,当有按键中断触发的时候才会去设置 led

ARM 架构 linux 内核中,有 种常见的异常,其中中断异常是其一,Linux 内核将所有中断统一编号,使用一个 irq_desc 结构体来描述这些中断,里面记录了中断名称、中断状态、中断标记、并提供了中断的底层硬件访问函数(如:清除、屏蔽、使能中断) ,提供了这个中断的处理函数入口,通过它还可以调用用户注册的的中断处理函数。linux 内核的中断体系已经很完善了,驱动工程师需要做的就是调用 request_irq 函数向内核注册中断处理函数,下面我们来看看 request_irq 函数的定义:

static inline int __must_check

request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,

const char *name, void *dev)

第一个参数 irq:中断号,与平台架构相关;

第二个参数 handler:用户中断处理函数;

第三个参数 flags:中断标记

第四个参数 devname:中断名字,可以通过 cat /proc/interrupts 查看;

第五个参数 dev_id:在 free_irq 中有用,也用做区分中断处理函数;

 

有注册就得对应着有注销,驱动的注销函数是 free_irq,其定义如下:

void free_irq(unsigned int irq, void *dev_id)

第一个参数 irq:中断号,与 request_irq 中的 irq 一致,用于定位 action 链表;

第二个参数 dev_id:用于在 action 链表中找到要卸载的表项;同一个中断的不同中断处理函数必须使用不通

的 dev_id 来区分,这就要求在注册中断共享时参数 dev_id 必须唯一。

下面我们来看一下中断按键的驱动,代码如下:

#include <linux/init.h>

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/fs.h>

#include <linux/interrupt.h>

#include <linux/irq.h>

#include <mach/gpio.h>

#include <plat/gpio-cfg.h>

#include <linux/miscdevice.h>

#include <linux/platform_device.h>

#include <mach/regs-gpio.h>

#include <asm/io.h>

#include <linux/regulator/consumer.h>

#include <linux/delay.h>

#define IRQ_DEBUG

#ifdef IRQ_DEBUG

#define DPRINTK(x...) printk("IRQ_CTL DEBUG:" x)

#else

#define DPRINTK(x...)

#endif

#define DRIVER_NAME "irq_test"

static int led_gpios[] = {

EXYNOS4_GPL2(0),

EXYNOS4_GPK1(1),

};

#define LED_NUM ARRAY_SIZE(led_gpios)

#if 1

static irqreturn_t eint9_interrupt(int irq, void *dev_id) {

 

printk("%s(%d)\n", __FUNCTION__, __LINE__);

if(gpio_get_value(led_gpios[0]))

gpio_set_value(led_gpios[0], 0);

else

gpio_set_value(led_gpios[0], 1);

return IRQ_HANDLED;

}

static irqreturn_t eint10_interrupt(int irq, void *dev_id) {

printk("%s(%d)\n", __FUNCTION__, __LINE__);

if(gpio_get_value(led_gpios[1]))

gpio_set_value(led_gpios[1], 0);

else

gpio_set_value(led_gpios[1], 1);

return IRQ_HANDLED;

 

}

#endif

static int irq_probe(struct platform_device *pdev)

{

int ret, i;

char *banner = "irq_test Initialize\n";

printk(banner);

for(i=0; i<LED_NUM; i++)

{

ret = gpio_request(led_gpios[i], "LED");

if (ret) {

printk("%s: request GPIO %d for LED failed, ret = %d\n", DRIVER_NAME,

led_gpios[i], ret);

return ret;

}

s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT);

gpio_set_value(led_gpios[i], 0);

}

 

ret = gpio_request(EXYNOS4_GPX1(1), "EINT9");

if (ret) {

printk("%s: request GPIO %d for EINT9 failed, ret = %d\n", DRIVER_NAME,

EXYNOS4_GPX1(1), ret);

return ret;

}

s3c_gpio_cfgpin(EXYNOS4_GPX1(1), S3C_GPIO_SFN(0xF));

s3c_gpio_setpull(EXYNOS4_GPX1(1), S3C_GPIO_PULL_UP);

gpio_free(EXYNOS4_GPX1(1));

ret = gpio_request(EXYNOS4_GPX1(2), "EINT10");

if (ret) {

printk("%s: request GPIO %d for EINT10 failed, ret = %d\n", DRIVER_NAME,

EXYNOS4_GPX1(2), ret);

return ret;

}

s3c_gpio_cfgpin(EXYNOS4_GPX1(2), S3C_GPIO_SFN(0xF));

s3c_gpio_setpull(EXYNOS4_GPX1(2), S3C_GPIO_PULL_UP);

 

gpio_free(EXYNOS4_GPX1(2));

#if 1

ret = request_irq(IRQ_EINT(9), eint9_interrupt,

IRQ_TYPE_EDGE_FALLING /*IRQF_TRIGGER_FALLING*/, "eint9", pdev);

if (ret < 0) {

printk("Request IRQ %d failed, %d\n", IRQ_EINT(9), ret);

goto exit;

}

ret = request_irq(IRQ_EINT(10), eint10_interrupt,

IRQ_TYPE_EDGE_FALLING /*IRQF_TRIGGER_FALLING*/, "eint10", pdev);

if (ret < 0) {

printk("Request IRQ %d failed, %d\n", IRQ_EINT(10), ret);

goto exit;

}

#endif

return 0;

exit:

return ret;

}

 

static int irq_remove (struct platform_device *pdev)

{

return 0;

}

static int irq_suspend (struct platform_device *pdev, pm_message_t state)

{

DPRINTK("irq suspend:power off!\n");

return 0;

}

static int irq_resume (struct platform_device *pdev)

{

DPRINTK("irq resume:power on!\n");

return 0;

}

static struct platform_driver irq_driver = {

.probe = irq_probe,

.remove = irq_remove,

 

.suspend = irq_suspend,

.resume = irq_resume,

.driver = {

.name = DRIVER_NAME,

.owner = THIS_MODULE,

},

};

static void __exit irq_test_exit(void)

{

platform_driver_unregister(&irq_driver);

}

static int __init irq_test_init(void)

{

return platform_driver_register(&irq_driver);

}

module_init(irq_test_init);

module_exit(irq_test_exit);

 

MODULE_LICENSE("Dual BSD/GPL");

首先我们来看下这个驱动的入口函数 irq_test_init,定义如下:

static int __init irq_test_init(void)

{

return platform_driver_register(&irq_driver);

}

该函数想内核注册一个 irq_driver 类型的设备,irq_driver 定义如下:

static struct platform_driver irq_driver = {

.probe = irq_probe,

.remove = irq_remove,

.suspend = irq_suspend,

.resume = irq_resume,

.driver = {

.name = DRIVER_NAME,

.owner = THIS_MODULE,

},

};

从这个结构里我们可以看到驱动的探测函数是 irq_probe,注销函数是 irq_remove,休眠调用的函数是

irq_suspend, 唤醒调用的函数是 irq_resume, 驱动的名字是 DRIVER_NAME, DRIVER_NAME 是个宏定义,

如下:

#define DRIVER_NAME "irq_test"

 

接下来我们看看驱动的探测函数 irq_probe,这个函数也是这个驱动里面最主要的函数,这个函数一开始掉欧

勇 printk 打印一条信息,接着是一个 for 循环,初始化 led 的 GPIO,如下:

for(i=0; i<LED_NUM; i++)

{

ret = gpio_request(led_gpios[i], "LED");

if (ret) {

printk("%s: request GPIO %d for LED failed, ret = %d\n", DRIVER_NAME,

led_gpios[i], ret);

return ret;

}

s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT);

gpio_set_value(led_gpios[i], 0);

}

然后是初始化两个按键的 gpio,设置为中断模式,如下所示:

ret = gpio_request(EXYNOS4_GPX1(1), "EINT9");

if (ret) {

printk("%s: request GPIO %d for EINT9 failed, ret = %d\n", DRIVER_NAME,

EXYNOS4_GPX1(1), ret);

return ret;

}

s3c_gpio_cfgpin(EXYNOS4_GPX1(1), S3C_GPIO_SFN(0xF));

s3c_gpio_setpull(EXYNOS4_GPX1(1), S3C_GPIO_PULL_UP);

gpio_free(EXYNOS4_GPX1(1));

ret = gpio_request(EXYNOS4_GPX1(2), "EINT10");

if (ret) {

printk("%s: request GPIO %d for EINT10 failed, ret = %d\n", DRIVER_NAME,

EXYNOS4_GPX1(2), ret);

return ret;

}

s3c_gpio_cfgpin(EXYNOS4_GPX1(2), S3C_GPIO_SFN(0xF));

s3c_gpio_setpull(EXYNOS4_GPX1(2), S3C_GPIO_PULL_UP);

gpio_free(EXYNOS4_GPX1(2));

接着调用中断注册函数 request_irq 向内核注册中断处理函数,如下:

ret = request_irq(IRQ_EINT(9), eint9_interrupt,

IRQ_TYPE_EDGE_FALLING /*IRQF_TRIGGER_FALLING*/, "eint9", pdev);

if (ret < 0) {

printk("Request IRQ %d failed, %d\n", IRQ_EINT(9), ret);

goto exit;

 

}

ret = request_irq(IRQ_EINT(10), eint10_interrupt,

IRQ_TYPE_EDGE_FALLING /*IRQF_TRIGGER_FALLING*/, "eint10", pdev);

if (ret < 0) {

printk("Request IRQ %d failed, %d\n", IRQ_EINT(10), ret);

goto exit;

}

上面的中断注册函数分别注册了两个中断处理函数 eint9_interrupt 和 eint10_interrupt

eint9_interrupt 的定义如下:

static irqreturn_t eint9_interrupt(int irq, void *dev_id) {

printk("%s(%d)\n", __FUNCTION__, __LINE__);

if(gpio_get_value(led_gpios[0]))

gpio_set_value(led_gpios[0], 0);

else

gpio_set_value(led_gpios[0], 1);

return IRQ_HANDLED;

}

这个函数首先会打印一句信息,然后是获取 led 的状态,把状态取反。

eint10_interrupt 的定义如下:

static irqreturn_t eint10_interrupt(int irq, void *dev_id) {

printk("%s(%d)\n", __FUNCTION__, __LINE__);

if(gpio_get_value(led_gpios[1]))

gpio_set_value(led_gpios[1], 0);

else

gpio_set_value(led_gpios[1], 1);

return IRQ_HANDLED;

}

这个函数和 eint9_interrupt 的功能类似。

其他的函数就和前面讲的 led 驱动里面的函数基本一样了, 唯一的区别是我们在注销函数 irq_remove 里面使

用了 free_irq 函数来注销之前初测的中断处理函数。

把这个驱动放到内核的 driver/char 目录下面,如下图所示:

 

 

 

然后打开 driver/char 目录下面的 Makefile,添加“obj-y += itop4412_irq.o” ,如下图所示:

 

 

然后打开 arch/arm/mach-exynos/mack-itop4412.c 文件,找到“struct platform_device

s3c_device_buzzer_ctl” ,如下图所示:

 

 

然后在它的下面添加下面的信息:

struct platform_device s3c_device_irq_test = {

.name = "irq_test",

.id = -1,

};

如下图所示:

 

 

然后找到“&s3c_device_buzzer_ctl,”这一行,如下图所示:

 

 

 

在这一行的下面添加“&s3c_device_irq_test,” ,如下图所示:

 

 

 

然后保存并退出。因为本章实验使用到了 led 和按键,所以我们要把内核里面的 led 驱动和按键的驱动去掉,

在内核目录下使用 make menuconfig 命令打开内核配置界面,如下图所示:

 

 

进入到“Device Drivers” ,如下图所示:

 

 

 

然后进入到“Input device support”界面,如下图所示:

 

 

 

然后进入到“Keyboards”界面,如下图所示:

 

 

然后取消掉“GPIO Buttons” ,如下图所示:

 

 

然后返回到“Device Drivers”界面,如下图所示:

 

然后选择“Character devices” ,进入 Character devices 界面,如下图所示:

 

 

然后取消“Enable LEDS config” ,如下图所示:

 

 

 

 

然后保存并退出内核配置界面,使用 make 命令编译内核,如下图所示:

 

 

编译完成后,如下图所示:

 

 

然后把编译生成的 zImage 烧写到 iTOP-4412 开发板上,烧写完成后启动开发板,系统起来以后,我们可以

按开发板上的 BACK 或 HOME 按键,来看下运行结果。

当我们按下 BACK 按键时,可以看到 led 会点亮,在次按下,led 就会熄灭,同事串口会打印信息,如下图所

示:

 

 

 

我们按下 HOME 键,可以看到另外一个 led 会点亮,再次按下,led 就会熄灭,同事串口会打印信息,如下

图所示:

 

 

至此,linux 下中断驱动我们就已经完成了。

加载中
返回顶部
顶部