嵌入式爱好者

查看: 17303|回复: 1

使用Linux内核自带的GPIO 按键驱动

[复制链接]

13

主题

75

帖子

368

积分

扫一扫,手机访问本帖
发表于 2012-9-6 16:01:07 | 显示全部楼层 |阅读模式
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中去,如果前面的选项为[M]的时候,这时该模块将会编译成一个内核模块.ko文件,在需要的时候,我们再通过insmod/rmmod来添加/删除该模块。如果前面的选项为[]的时候,就不会编译该选项。
  Make menuconfig命令执行完成之后,我们配置了Linux内核需要编译哪些模块/驱动,哪些不需要编译。这些信息将会保存在make menuconfig生成的掩藏文件.config中。而当我们敲make命令开始执行Linux内核源码的编译过程的时候,子目录的Makefile将会根据.config文件中指定的信息来选择哪些C文件需要编译,哪些C文件不需要编译。
  了解这些知识后,我们就可以“倒推”CONFIG_KEYBOARD_GPIO究竟怎么来配置。首先,我们在所有的Kconfig文件中查找KEYBOARD_GPIO关键字:
[guowenxue@centos6 linux-3.4]$ 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关键字。这时我们看看这个文件,并找到相关信息:
[guowenxue@centos6 linux-3.4]$ 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时,我们就知道怎么找到配置这个选项了:
[guowenxue@centos6 linux-3.4]$ 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文件提供了该设备的驱动:
[guowenxue@centos6 linux-3.4]$ 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内核并烧录到开发板上去。
[guowenxue@centos6 linux-3.4]$ make
scripts/kconfig/conf --silentoldconfig Kconfig
  CHK     include/linux/version.h
  CHK     include/generated/utsrelease.h
make[1]: `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 Sep  5 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
[guowenxue@centos6 linux-3.4]$ ls -l uImage-sam9g20.gz
-rw-r--r-- 1 guowenxue root 2090136 Sep  5 19:13 uImage-sam9g20.gz
该会员没有填写今日想说内容.
回复

使用道具 举报

13

主题

75

帖子

368

积分

 楼主| 发表于 2012-9-6 16:01:41 | 显示全部楼层
回复 1# dglwx


    4, 应用程序按键驱动测试
测试驱动源代码:
[guowenxue@centos6 test]$ 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: Pressed  0:Not pressed  2: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[256] = "Unknown";
    int                   kbd_fd = -1;
    int                   rv, opt;
    int                   mode = MODE_NORMAL;
    int                   size = sizeof (struct input_event);
    struct input_event    ev[BUTTON_CNT];
    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[0]);
                return 0;
            default:
                break;
        }
    }
    if(NULL == kbd_dev)
    {
        usage(argv[0]);
        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[BUTTON_CNT/8 + 1];
    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[0]表示在按键重复出现之前 delay的时间,rep[1]表示按键重复出现的时间间隔。 */
    int rep[2] ={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: [0]= %d, [1] = %d/n", rep[0], rep[1]);
    }
#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[device  ] button device name\n");
    printf(" -p[poll    ] Use poll mode, or default use infinit loop.\n");
    printf(" -h[help    ] 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 key  pressed 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上编译:
[guowenxue@centos6 test]$ /opt/buildroot-2011.11/arm926t/usr/bin/arm-linux-gcc -o event_button event_button.c -Wall -Werror --static
[guowenxue@centos6 test]$ file event_button
event_button: ELF 32-bit LSB executable, ARM, version 1 (SYSV), statically linked, not stripped
[guowenxue@centos6 test]$ cp event_button /tftp/
ARM开发板通过tftp下载并做测试:
/tmp >: tftp -gr event_button 192.168.1.78
event_button         100% |*******************************|   123k  0:00:00 ETA
/tmp >: chmod 777 event_button
/tmp >: ./event_button -h
Usage: event_button [-p] -d <device>
-d[device  ] button device name
-p[poll    ] Use poll mode, or default use infinit loop.
-h[help    ] Display this help information
/tmp >: ./event_button -p -d /dev/event0
Monitor input device /dev/event0 (gpio-keys) event with poll mode:
Restore button key  pressed time: 1346917034.192994
Restore button key released time: 1346917034.271743
Restore button key duration time: 0.78749
/tmp >:
该会员没有填写今日想说内容.
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋| 飞凌嵌入式 ( 冀ICP备12004394号-1 )

GMT+8, 2024-12-29 10:13

Powered by Discuz! X3.4

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表