驱动程序实例(四):按键驱动程序(platform + input子系统 + 外部中断方式)

结合之前对Linux内核的platform总线与input子系统的分析 ,本文将编写基于platform总线和input子系统的Button设备的实例代码并对其进行分析。

platform总线的分析,详见Linux platform驱动模型

input子系统的分析,详见Linux字符设备驱动框架(四):Linux内核的input子系统

硬件接口:

  CPU:s5pv210;

  Button的GPIO:GPIO_H0_2,EINT2;

  LED的工作方式:按键弹起,低电平;按键按下,高电平。

1. device

在/kernel/arch/arm/mach-s5pv210/include/mach目录下,建立一个buttons_gpio.h文件,并填充如下内容。

#ifndef __ASM_ARCH_BUTTONSGPIO_H
#define __ASM_ARCH_BUTTONSGPIO_H "buttons-gpio.h"

//定义一个Button设备的数据结构
struct s5pv210_button_platdata 
{
    char            *name;
    unsigned int    gpio;
    unsigned int    irqnum;
    unsigned int    flags;
};

#endif

在/kernel/arch/arm/mach-s5pv210/mach-x210.c下,添加如下内容,并添加对buttons_gpio.h的包含。

/*Buttons*/

static struct s5pv210_button_platdata s5pv210_button_pdata = 
{ .name
= "button1", .gpio = S5PV210_GPH0(2), .irqnum = IRQ_EINT2,//中断号 .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,//上升沿触发+下降沿触发 }; static struct platform_device s5pv210_button = { .name = "s5pv210_button", .id = 1, .dev = { .platform_data = &s5pv210_button_pdata, }, };

将LED设备信息集成至smdkc110_devices,内核初始化时smdkc110_devices中的设备将被注册进内核。

static struct platform_device *smdkc110_devices[] __initdata = 
{
    ......
    &s5pv210_button,
};

2. driver

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/slab.h>
#include <linux/input.h> 

#include <mach/buttons_gpio.h>
#include <mach/hardware.h>
#include <mach/regs-gpio.h>
#include <mach/irqs.h>
#include <linux/interrupt.h>

static struct s5pv210_button_platdata *pdata;
static struct input_dev *button_dev = NULL;

static irqreturn_t button_interrupt(int irq, void *dummy) 
{ 
    int flag;

    s3c_gpio_cfgpin(pdata->gpio, S3C_GPIO_SFN(0x0)); //设置GPIO为input模式
    flag = gpio_get_value(pdata->gpio);              //读取GPIO的值
    s3c_gpio_cfgpin(pdata->gpio, S3C_GPIO_SFN(0x0f));//设置GPIO为eint2模式

    input_report_key(button_dev, KEY_LEFT, !flag);   //上报事件
    input_sync(button_dev);                          //同步事件
    return IRQ_HANDLED; 
}


static int s5pv210_button_remove(struct platform_device *dev)
{
    input_free_device(button_dev);            //释放button_dev内存
    free_irq(pdata->irqnum, button_interrupt);//释放中断资源
    gpio_free(pdata->gpio);                   //释放GPIO
    
    return 0;
}

static int s5pv210_button_probe(struct platform_device *dev)
{
    int ret;
    
    pdata = dev->dev.platform_data;
    
    /*****************************申请资源******************************/
    //申请GPIO
    ret = gpio_request(pdata->gpio, pdata->name);
    if (ret) 
    {
        printk(KERN_ERR "gpio_request failed, ret = %d.\n", ret);
        return -EBUSY;
    } 
    
    //申请IRQ
    if (request_irq(pdata->irqnum, button_interrupt, pdata->flags, pdata->name, NULL)) 
    { 
        printk(KERN_ERR "key-s5pv210.c: Can't allocate irq %d\n", pdata->irqnum);
        ret = -EBUSY;
        goto ERR_STER0;
    }
    
    
    /************************初始化GPIO资源*************************/
    s3c_gpio_setpull(pdata->gpio, S3C_GPIO_PULL_UP); //设置GPIO为上拉模式
    s3c_gpio_cfgpin(pdata->gpio, S3C_GPIO_SFN(0x0f));//设置GPIO为eint模式
    
    
    /*******************************创建接口*******************************/
    
    //申请button_dev内存空间
    button_dev = input_allocate_device();
    if(!button_dev)
    {
        ret = -ENOMEM;
        goto ERR_STER1;
    }
    
    //初始化button_dev
    set_bit(EV_KEY, button_dev->evbit);    //支持EV_KEY事件
    set_bit(KEY_LEFT, button_dev->keybit); //支持KEY_LEFT子事件
    
    //注册button_dev
    if(input_register_device(button_dev) != 0)
    {
        printk("s5pv210-button input register device fail!!\n");

        ret = -ENODEV;
        goto ERR_STER2;
    }

    return 0;
    
    /****************************倒映式错误处理****************************/
ERR_STER2:
    input_free_device(button_dev);

ERR_STER1:
    free_irq(pdata->irqnum, button_interrupt);
    
ERR_STER0:
    gpio_free(pdata->gpio);
    
    return ret;
}

//定义并初始化驱动信息
static struct platform_driver s5pv210_button_driver = 
{
    .probe        = s5pv210_button_probe,
    .remove        = s5pv210_button_remove,
    .driver        = 
    {
        .name    = "s5pv210_button",
        .owner    = THIS_MODULE,
    },
};

//注册驱动
static int __init s5pv210_button_init(void)
{
    return platform_driver_register(&s5pv210_button_driver);
}

//注销驱动
static void __exit s5pv210_button_exit(void)
{
    platform_driver_unregister(&s5pv210_button_driver);
}

module_init(s5pv210_button_init);
module_exit(s5pv210_button_exit);

MODULE_AUTHOR("Lin");
MODULE_DESCRIPTION("S5PV210 BUTTON driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:s5pv210_button");

3. 测试

编写一个简易的应用程序,来测试上述驱动程序。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/input.h>
#include <string.h>

#define X210_KEY            "/dev/input/event1"

int main(void)
{
    int fd = -1, ret = -1;
    struct input_event ev;
    
    //打开设备文件
    fd = open(X210_KEY, O_RDONLY);
    if (fd < 0)
    {
        perror("open");
        return -1;
    }
    
    while (1)
    {
        //读取一个event事件包
        memset(&ev, 0, sizeof(struct input_event));
        ret = read(fd, &ev, sizeof(struct input_event));
        if (ret != sizeof(struct input_event))
        {
            perror("read");
            close(fd);
            return -1;
        }
        
        //解析event包
        printf("-------------------------\n");
        printf("type: %hd\n", ev.type);
        printf("code: %hd\n", ev.code);
        printf("value: %d\n", ev.value);
        printf("\n");
    }
    
    //关闭设备
    close(fd);
    
    return 0;
}

装载驱动模块后,在Linux终端运行该应用程序。在一次按键按下并弹起的过程中,终端中将打印出如下的信息,表明驱动程序工作正常。

-------------------------//按键按下
type: 1
code: 105
value: 1

-------------------------//同步事件
type: 0
code: 0
value: 0

-------------------------//按键弹起
type: 1
code: 105
value: 0

-------------------------//同步事件
type: 0
code: 0
value: 0

猜你喜欢

转载自www.cnblogs.com/linfeng-learning/p/9468018.html