结合之前对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. ", 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 ", 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!! "); 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("------------------------- "); printf("type: %hd ", ev.type); printf("code: %hd ", ev.code); printf("value: %d ", ev.value); printf(" "); } //关闭设备 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