驅動程序實例(四):按鍵驅動程序(platform + input子系統 + 外部中斷方式)

結合以前對Linux內核的platform總線與input子系統的分析 ,本文將編寫基於platform總線和input子系統的Button設備的實例代碼並對其進行分析。html

platform總線的分析,詳見Linux platform驅動模型linux

input子系統的分析,詳見Linux字符設備驅動框架(四):Linux內核的input子系統數據結構

硬件接口:框架

  CPU:s5pv210;測試

  Button的GPIO:GPIO_H0_2,EINT2;spa

  LED的工做方式:按鍵彈起,低電平;按鍵按下,高電平。code

1. device

在/kernel/arch/arm/mach-s5pv210/include/mach目錄下,創建一個buttons_gpio.h文件,並填充以下內容。orm

#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的包含。htm

/*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中的設備將被註冊進內核。blog

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
相關文章
相關標籤/搜索