使用Linux内核自带的GPIO 按键驱动
CopyRight: 武汉凌云嵌入式大学生嵌入式学习辅导Author:郭文学 <guowenxue@gmail.com QQ:281143292>
在Linux内核里,已经提供了GPIO的LED驱动和按键驱动,这里我们介绍一下如何使用Linux内核里自带的按键驱动。我的开发板环境为:
开发板:AT91SAM9G20
Linux: Linux-3.4
1,添加Button设备
我们先在linux-3.4/arch/arm/mach-at91/board-sam9g20ek.c文件中添加我们的button设备。这样Linux内核在启动的时候,会自动添加该设备:
#if defined(CONFIG_KEYBOARD_GPIO)||defined(CONFIG_KEYBOARD_GPIO_MODULE)
static struct gpio_keys_button ek_buttons[] = {
{
//我们的按键驱动使用PB20这个管脚
.gpio = AT91_PIN_PB20,
//这里把这个按键值定义为BTN_1,注意后面的测试程序会用该值。
.code = BTN_1,
//我们把这个关键定义为系统restore的按键
.desc = "restore",
.active_low = 1,
.wakeup = 1,
},
};
static struct gpio_keys_platform_data ek_button_data = {
.buttons = ek_buttons,
.nbuttons = ARRAY_SIZE(ek_buttons),
};
static struct platform_device ek_button_device = {
.name = "gpio-keys",
.id = -1,
.num_resources= 0,
.dev = {
.platform_data= &ek_button_data,
}
};
static void __init ek_add_device_buttons(void)
{
at91_set_gpio_input(AT91_PIN_PB20, 1);/* restore button*/
at91_set_deglitch(AT91_PIN_PB20, 1);
platform_device_register(&ek_button_device);
}
#else
static void __init ek_add_device_buttons(void) {}
#endif
修改ek_board_init()函数,在里面添加button设备:
static void __init ek_board_init(void)
{
/* Serial */
at91_add_device_serial();
/* USB Host */
at91_add_device_usbh(&ek_usbh_data);
/* USB Device */
at91_add_device_udc(&ek_udc_data);
/* SPI */
at91_add_device_spi(ek_spi_devices, ARRAY_SIZE(ek_spi_devices));
/* NAND */
ek_add_device_nand();
/* Ethernet */
ek_add_device_macb();
ek_add_device_dm9000(); /* Add dm9000 driver by guowenxue, 2012.08.28 */
/* Regulators */
ek_add_regulators();
/* MMC */
ek_add_device_mmc();
/* I2C */
at91_add_device_i2c(ek_i2c_devices, ARRAY_SIZE(ek_i2c_devices));
ek_add_device_buttons();
#if 0 /*Comment by guowenxue, 2012.08.28*/
/* LEDs */
ek_add_device_gpio_leds();
/* Push Buttons */
/* PCK0 provides MCLK to the WM8731 */
at91_set_B_periph(AT91_PIN_PC1, 0);
/* SSC (for WM8731) */
at91_add_device_ssc(AT91SAM9260_ID_SSC, ATMEL_SSC_TX);
#endif
}
2, 在内核的make menuconfig时,添加GPIO Button驱动
在上面添加设备的过程中我们可以看到,只有内核make menuconfig时选择了CONFIG_KEYBOARD_GPIO或CONFIG_KEYBOARD_GPIO_MODULE才会定义ek_add_device_buttons(void) 函数,否则该函数为空函数。这样,我们就需要在make menuconfig时选择该选项。在make menuconfig时,会有那么多选项,我们怎么找到这个选项呢?
如果熟悉Linux内核编译过程的同学可能会知道,make menuconfig是根据Linux内核源码中的Kconfig文件和一些脚本/程序来生成界面供我们选择,在我们make menuconfig时,如果前面的选项为
的时候,表示这个模块直接编译进内核image中去,如果前面的选项为的时候,这时该模块将会编译成一个内核模块.ko文件,在需要的时候,我们再通过insmod/rmmod来添加/删除该模块。如果前面的选项为[]的时候,就不会编译该选项。
Make menuconfig命令执行完成之后,我们配置了Linux内核需要编译哪些模块/驱动,哪些不需要编译。这些信息将会保存在make menuconfig生成的掩藏文件.config中。而当我们敲make命令开始执行Linux内核源码的编译过程的时候,子目录的Makefile将会根据.config文件中指定的信息来选择哪些C文件需要编译,哪些C文件不需要编译。
了解这些知识后,我们就可以“倒推”CONFIG_KEYBOARD_GPIO究竟怎么来配置。首先,我们在所有的Kconfig文件中查找KEYBOARD_GPIO关键字:
$ find -iname Kconfig | xargs grep KEYBOARD_GPIO
./arch/arm/mach-davinci/Kconfig:config KEYBOARD_GPIO_POLLED
./drivers/input/keyboard/Kconfig:config KEYBOARD_GPIO
./drivers/input/keyboard/Kconfig:config KEYBOARD_GPIO_POLLED
这时我们可以看到,应该是在drivers/input/keyboard/Kconfig文件中定义了KEYBOARD_GPIO关键字。这时我们看看这个文件,并找到相关信息:
$ vim ./drivers/input/keyboard/Kconfig
****
config KEYBOARD_GPIO
tristate "GPIO Buttons"
depends on GENERIC_GPIO
help
This driver implements support for buttons connected
to GPIO pins of various CPUs (and some other chips).
Say Y here if your device has buttons connected
directly to such GPIO pins.Your board-specific
setup logic must also provide a platform device,
with configuration data saying which GPIOs are used.
To compile this driver as a module, choose M here: the
module will be called gpio_keys.
***
从上面的信息,我们可以看出,在make menuconfig时,我们就知道怎么找到配置这个选项了:
$ make menuconfig
Device Drivers--->
Input device support--->
Keyboards--->
<*> GPIO Buttons
<*> Polled GPIO buttons
现在,将光标放到GPIO Buttons上,结合这里的make menuconfig的信息,我们对着前面Kconfig的文件内容,来理解该文件里的每个字段的意思:
config KEYBOARD_GPIO
如果这里我们选择编译进内核,即<*> GPIO Buttons,那么最终生成的.config文件中将会出现CONFIG_KEYBOARD_GPIO=y。而如果我们选择编译成模块形式,即<M> GPIO Buttons,则在生产的.config文件中将会出现CONFIG_KEYBOARD_GPIO=m
tristate "GPIO Buttons"
这一行就是我们上面看到的make menuconfig提示符 <> GPIO Buttons
depends on GENERIC_GPIO
这一行说明,KEYBOARD_GPIO这个选项依赖GENERIC_GPIO这个选项。所以如果在make menuconfig时没有出现这个选项,那么你就需要检查GENERIC_GPIO是否选上了。
Help
最后的help选项就是,如果我们的光标落在<*> GPIO Buttons上时,如果按H键,这时就能看到该选项的详细帮助信息了。
在这里我们同时选上了<*> Polled GPIO buttons选项。
现在我们可以找找究竟是那个C文件提供了该设备的驱动:
$ find -iname Makefile | xargs grep CONFIG_KEYBOARD_GPIO
./drivers/input/keyboard/Makefilebj-$(CONFIG_KEYBOARD_GPIO) += gpio_keys.o
./drivers/input/keyboard/Makefilebj-$(CONFIG_KEYBOARD_GPIO_POLLED) += gpio_keys_polled.o
从上面的信息,我们可以看到:在./drivers/input/keyboard/Makefile文件中,如果定义了CONFIG_KEYBOARD_GPIO选项,那么就将编译gpio_keys.c文件成gpio_keys.o文件。这时,我们就明白了:
Linux内核里自带的GPIO Button设备是在arch/arm/mach-at91/board-sam9g20ek.c源文件中添加的,而该设备的驱动是在drivers/input/keyboard/gpio_keys.c文件中提供的。而他们之间是通过platform这个虚拟总线来连接起来的。这就需要大家深入学习Linux的设备驱动开发,才能搞明白他们究竟是怎么工作的了。
3, 编译Linux内核
在添加button设备和make menuconfig选上它的驱动支持后,我们需要重现编译Linux内核并烧录到开发板上去。
$ make
scripts/kconfig/conf --silentoldconfig Kconfig
CHK include/linux/version.h
CHK include/generated/utsrelease.h
make: `include/generated/mach-types.h' is up to date.
CALL scripts/checksyscalls.sh
CHK include/generated/compile.h
Kernel: arch/arm/boot/Image is ready
LD arch/arm/boot/compressed/vmlinux
OBJCOPY arch/arm/boot/zImage
Kernel: arch/arm/boot/zImage is ready
Building modules, stage 2.
MODPOST 2 modules
cp arch/arm/boot/zImage . -f
../../../bin/mkimage -A arm -O linux -n AT91SAM9260 -C NONE -a 0x20008000 -e 0x20008000 -d zImage uImage-sam9g20.gz
Image Name: AT91SAM9260
Created: Wed Sep5 19:13:42 2012
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 2090072 Bytes = 2041.09 kB = 1.99 MB
Load Address: 20008000
Entry Point:20008000
rm -f zImage
$ ls -l uImage-sam9g20.gz
-rw-r--r-- 1 guowenxue root 2090136 Sep5 19:13 uImage-sam9g20.gz 回复 1# dglwx
4, 应用程序按键驱动测试
测试驱动源代码:
$ cat event_button.c
/******************************************************************************
* Copyright:(C) 2012 Guo Wenxue<Email:guowenxue@gmail.com QQ:281143292>
* All rights reserved.
*
* Filename:event_button.c
* Description:This file used to test GPIO button driver builtin Linux kernel on ARM board
*
* Version:1.0.0(07/13/2012~)
* Author:Guo Wenxue <guowenxue@gmail.com>
* ChangeLog:1, Release initial version on "07/13/2012 02:46:18 PM"
*
******************************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <libgen.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <linux/input.h>
#include <linux/kd.h>
#include <linux/keyboard.h>
#if 0 /* Just for comment here, Reference to linux-3.3/include/linux/input.h */
struct input_event
{
struct timeval time;
__u16 type;/* 0x00:EV_SYN 0x01:EV_KEY 0x04:EV_MSC 0x11:EV_LED*/
__u16 code;/* key value, which key */
__s32 value; /* 1: Pressed0:Not pressed2:Always Pressed */
};
#endif
#define TRUE 1
#define FALSE 0
#define EV_RELEASED 0
#define EV_PRESSED 1
#define EV_REPEAT 2
#define BUTTON_CNT 1
#define MODE_POLL 0x01
#define MODE_NORMAL 0x02
void usage(char *name);
void display_button_event(struct input_event *ev, int cnt);
int main(int argc, char **argv)
{
char *kbd_dev = NULL;
char kbd_name = "Unknown";
int kbd_fd = -1;
int rv, opt;
int mode = MODE_NORMAL;
int size = sizeof (struct input_event);
struct input_event ev;
struct option long_options[] = {
{"device", required_argument, NULL, 'd'},
{"poll", no_argument, NULL, 'p'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
while ((opt = getopt_long(argc, argv, "d:ph", long_options, NULL)) != -1)
{
switch (opt)
{
case 'd':
kbd_dev = optarg;
break;
case 'p':
mode = MODE_POLL;
break;
case 'h':
usage(argv);
return 0;
default:
break;
}
}
if(NULL == kbd_dev)
{
usage(argv);
return -1;
}
if ((getuid ()) != 0)
printf ("You are not root! This may not work...\n");
if ((kbd_fd = open(kbd_dev, O_RDONLY)) < 0)
{
printf("Open %s failure: %s", kbd_dev, strerror(errno));
return -1;
}
ioctl (kbd_fd, EVIOCGNAME (sizeof (kbd_name)), kbd_name);
printf ("Monitor input device %s (%s) event with %s mode:\n", kbd_dev, kbd_name, MODE_POLL==mode?"poll":"infilit loop");
#if 0 /* Not implement in the Linux GPIO button driver */
unsigned char key_b;
memset(key_b, 0, sizeof(key_b));
if(ioctl(kbd_fd, EVIOCGKEY(sizeof(key_b)), key_b) < 0)
{
printf("EVIOCGKEY ioctl get error: %s\n", strerror(errno));
return -1;
}
#endif
#if 0 /* Not implement in the Linux GPIO button driver */
/* rep表示在按键重复出现之前 delay的时间,rep表示按键重复出现的时间间隔。 */
int rep ={2500, 1000} ;
if(ioctl(kbd_fd, EVIOCSREP, rep) < 0)
{
printf("EVIOCSREP ioctl get error: %s\n", strerror(errno));
return -1;
}
if(ioctl(kbd_fd, EVIOCGREP, rep) < 0)
{
printf("EVIOCGKEY ioctl get error: %s\n", strerror(errno));
return -1;
}
else
{
printf("repeate speed: = %d, = %d/n", rep, rep);
}
#endif
while (1)
{
if(MODE_POLL==mode)
{
fd_set rds;
FD_ZERO(&rds);
FD_SET(kbd_fd, &rds);
rv = select(kbd_fd + 1, &rds, NULL, NULL, NULL);
if (rv < 0)
{
printf("Select() system call failure: %s\n", strerror(errno));
goto CleanUp;
}
else if (FD_ISSET(kbd_fd, &rds))
{
if ((rv = read (kbd_fd, ev, size*BUTTON_CNT )) < size)
{
printf("Reading data from kbd_fd failure: %s\n", strerror(errno));
break;
}
else
{
display_button_event(ev, rv/size);
}
}
}
else
{
if ((rv = read (kbd_fd, ev, size*BUTTON_CNT )) < size)
{
printf("Reading data from kbd_fd failure: %s\n", strerror(errno));
break;
}
else
{
display_button_event(ev, rv/size);
}
}
}
CleanUp:
close(kbd_fd);
return 0;
}
void usage(char *name)
{
char *progname = NULL;
char *ptr = NULL;
ptr = strdup(name);
progname = basename(ptr);
printf("Usage: %s [-p] -d <device>\n", progname);
printf(" -d button device name\n");
printf(" -p Use poll mode, or default use infinit loop.\n");
printf(" -h Display this help information\n");
free(ptr);
return;
}
void display_button_event(struct input_event *ev, int cnt)
{
int i;
struct timeval pressed_time, duration_time;
for(i=0; i<cnt; i++)
{
//printf("type:%d code:%d value:%d\n", ev.type, ev.code, ev.value);
if(EV_KEY==ev.type && EV_PRESSED==ev.value)
{
if(BTN_1 == ev.code)
{
pressed_time = ev.time;
//pressed_time.tv_sec = ev.time.tv_sec;
//pressed_time.tv_usec = ev.time.tv_usec;
printf("Restore button keypressed time: %ld.%ld\n", pressed_time.tv_sec, pressed_time.tv_usec);
}
}
if(EV_KEY==ev.type && EV_RELEASED==ev.value)
{
if(BTN_1 == ev.code)
{
timersub(&ev.time, &pressed_time, &duration_time);
printf("Restore button key released time: %ld.%ld\n", ev.time.tv_sec, ev.time.tv_usec);
printf("Restore button key duration time: %ld.%ld\n", duration_time.tv_sec, duration_time.tv_usec);
}
}
} /*for(i=0; i<cnt; i++) */
}
编译,测试结果:
X86上编译:
$ /opt/buildroot-2011.11/arm926t/usr/bin/arm-linux-gcc -o event_button event_button.c -Wall -Werror --static
$ file event_button
event_button: ELF 32-bit LSB executable, ARM, version 1 (SYSV), statically linked, not stripped
$ cp event_button /tftp/
ARM开发板通过tftp下载并做测试:
/tmp >: tftp -gr event_button 192.168.1.78
event_button 100% |*******************************| 123k0:00:00 ETA
/tmp >: chmod 777 event_button
/tmp >: ./event_button -h
Usage: event_button [-p] -d <device>
-d button device name
-p Use poll mode, or default use infinit loop.
-h Display this help information
/tmp >: ./event_button -p -d /dev/event0
Monitor input device /dev/event0 (gpio-keys) event with poll mode:
Restore button keypressed time: 1346917034.192994
Restore button key released time: 1346917034.271743
Restore button key duration time: 0.78749
/tmp >:
页:
[1]