查看: 1297|回复: 0

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

[复制链接]
  • TA的每日心情
    开心
    2014-7-10 11:21
  • 签到天数: 2 天

    连续签到: 1 天

    [LV.1]初来乍到

    发表于 2016-2-23 09:09:42 | 显示全部楼层 |阅读模式
    分享到:
    大家好,今天我们来学习一下 linux 中断处理驱动的编写,本节我们实现的功能是通过开发板上的按键来控制 led 发光二极管,在之前的章节我们学习了 led 驱动的编写,在掌握了 led 驱动的编写以后,如果要实现按键控制 led 的功能,大家可能会想到可以在 led 的驱动里面使用轮询的方式一直查询按键的状态,如果有按键按下就设置 led 的状态。通过这种方式可以实现按键控制 led 的功能, 但是通过这样的方式有一个缺点就是led 驱动会占用 cpu,这样 cpu 的利用率就大大降低了,所以我们可以通过中断的方式来实现。这样 cpu 就可以去做其他的事情了,当有按键中断触发的时候才会去设置 led。
    ARM 架构 linux 内核中,有 5 种常见的异常,其中中断异常是其一,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, "LED");
    if (ret) {
    printk("%s: request GPIO %d for LED failed, ret = %d\n", DRIVER_NAME,
    led_gpios, ret);
    return ret;
    }
    s3c_gpio_cfgpin(led_gpios, S3C_GPIO_OUTPUT);
    gpio_set_value(led_gpios, 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, "LED");
    if (ret) {
    printk("%s: request GPIO %d for LED failed, ret = %d\n", DRIVER_NAME,
    led_gpios, ret);
    return ret;
    }
    s3c_gpio_cfgpin(led_gpios, S3C_GPIO_OUTPUT);
    gpio_set_value(led_gpios, 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 目录下面,如下图所示:

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

    file:///C:UsersxunweiAppDataLocalTempksohtmlwps3F50.tmp.jpg
    然后打开 arch/arm/mach-exynos/mack-itop4412.c 文件,找到“struct platform_device
    s3c_device_buzzer_ctl” ,如下图所示:

    file:///C:UsersxunweiAppDataLocalTempksohtmlwps3F60.tmp.jpg
    然后在它的下面添加下面的信息:
    struct platform_device s3c_device_irq_test = {
    .name = "irq_test",
    .id = -1,
    };
    如下图所示:

    file:///C:UsersxunweiAppDataLocalTempksohtmlwps3F61.tmp.jpg
    然后找到“&s3c_device_buzzer_ctl,”这一行,如下图所示:

    file:///C:UsersxunweiAppDataLocalTempksohtmlwps3F72.tmp.jpg
    在这一行的下面添加“&s3c_device_irq_test,” ,如下图所示:

    file:///C:UsersxunweiAppDataLocalTempksohtmlwps3F83.tmp.jpg
    然后保存并退出。因为本章实验使用到了 led 和按键,所以我们要把内核里面的 led 驱动和按键的驱动去掉,
    在内核目录下使用 make menuconfig 命令打开内核配置界面,如下图所示:

    file:///C:UsersxunweiAppDataLocalTempksohtmlwps3F84.tmp.jpg
    进入到“Device Drivers” ,如下图所示:

    file:///C:UsersxunweiAppDataLocalTempksohtmlwps3F94.tmp.jpg
    然后进入到“Input device support”界面,如下图所示:

    file:///C:UsersxunweiAppDataLocalTempksohtmlwps3FA5.tmp.jpg
    然后进入到“Keyboards”界面,如下图所示:

    file:///C:UsersxunweiAppDataLocalTempksohtmlwps3FA6.tmp.jpg
    然后取消掉“GPIO Buttons” ,如下图所示:

    file:///C:UsersxunweiAppDataLocalTempksohtmlwps3FB6.tmp.jpg
    然后返回到“Device Drivers”界面,如下图所示:

    file:///C:UsersxunweiAppDataLocalTempksohtmlwps3FC7.tmp.jpg
    然后选择“Character devices” ,进入 Character devices 界面,如下图所示:

    file:///C:UsersxunweiAppDataLocalTempksohtmlwps3FC8.tmp.jpg
    然后取消“Enable LEDS config” ,如下图所示:

    file:///C:UsersxunweiAppDataLocalTempksohtmlwps3FD9.tmp.jpg
    然后保存并退出内核配置界面,使用 make 命令编译内核,如下图所示:

    file:///C:UsersxunweiAppDataLocalTempksohtmlwps3FE9.tmp.jpg
    编译完成后,如下图所示:

    file:///C:UsersxunweiAppDataLocalTempksohtmlwps3FEA.tmp.jpg
    然后把编译生成的 zImage 烧写到 iTOP-4412 开发板上,烧写完成后启动开发板,系统起来以后,我们可以
    按开发板上的 BACK 或 HOME 按键,来看下运行结果。
    当我们按下 BACK 按键时,可以看到 led 会点亮,在次按下,led 就会熄灭,同事串口会打印信息,如下图所
    示:
    file:///C:UsersxunweiAppDataLocalTempksohtmlwps3FFB.tmp.jpg

    我们按下 HOME 键,可以看到另外一个 led 会点亮,再次按下,led 就会熄灭,同事串口会打印信息,如下
    图所示:
    file:///C:UsersxunweiAppDataLocalTempksohtmlwps3FFC.tmp.jpg

    至此,linux 下中断驱动我们就已经完成了。
    热门推荐:
    4412开发板轻轻松松学上手
    回复

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /5 下一条

    手机版|小黑屋|与非网

    GMT+8, 2024-12-24 05:26 , Processed in 0.122676 second(s), 17 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001037号

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.