本文将以imx6q的板子和相应BSP代码来详细描述在linux下, 使用GPIO当做按键的实现原理及使用方法。
Linux 内核下的 drivers/input/keyboard/gpio_keys.c实现了一个体系结构无关的GPIO按键驱动,使用此按键驱动,只需在相应的板级支持包中(imx6q的是board-mx6q-sabresd.c)定义相关的数据即可。驱动的实现非常简单,但是较适合于实现独立式按键驱动。
gpio-keys是基于input架构实现的一个通用GPIO按键驱动。该驱动基于platform_driver架构,实现了驱动和设备分离,符合Linux设备驱动模型的思想。工程中的按键驱动我们一般都会基于gpio-keys来写,所以我们有必要对gpio_keys进行分析。
一. GPIO-KEY的实现原理
1. 定义GPIO按键:
首先定义一个gpio_keys_button的数组, 该类型定义了一个具体的GPIO按键信息
arch\arm\mach-mx6\board-mx6q-sabresd.c:
static struct gpio_keys_buttonsabresd_buttons[] = {
GPIO_BUTTON(SABRESD_VOLUME_UP,KEY_VOLUMEUP, 1, "volume-up", 0, 1),
GPIO_BUTTON(SABRESD_VOLUME_DN,KEY_POWER, 1, "volume-down", 1, 1),
/*addby aaron 2015.6.29*/
#ifdef FEATURE_YUTONG_ICARD2
GPIO_BUTTON(SABRESD_KEY_MENU,248, 1, "menu-key", 0, 3),
GPIO_BUTTON(SABRESD_KEY_UP,249, 1, "up-key", 0, 3),
GPIO_BUTTON(SABRESD_KEY_DOWN,250, 1, "down-key", 0, 3),
GPIO_BUTTON(SABRESD_KEY_CONFIRM,251, 1, "confirm-key", 0, 3),
GPIO_BUTTON(SABRESD_KEY_ICCARD_DET,247, 1, "iccard-det", 0, 3),
#endif
/*endby aaron*/
};
struct gpio_keys_button类型如下, 这是对按键的描述:
include\linux\gpio_keys.h:
struct gpio_keys_button {
/*Configuration parameters */
unsignedint code; /* input event code (KEY_*,SW_*) */
intgpio;
intactive_low;
constchar *desc;
unsignedint type; /* input event type (EV_KEY,EV_SW, EV_ABS) */
int wakeup; /* configure the button as awake-up source */
intdebounce_interval; /* debounce ticksinterval in msecs */
boolcan_disable;
intvalue; /* axis value for EV_ABS*/
};
宏 GPIO_BUTTON()就是初始化每个gpio_key的描述:
arch\arm\mach-mx6\board-mx6q-sabresd.c:
#define GPIO_BUTTON(gpio_num, ev_code,act_low, descr, wake, debounce) \
{ \
.gpio = gpio_num, \
.type = EV_KEY, \
.code = ev_code, \
.active_low = act_low, \
.desc = "btn " descr, \
.wakeup = wake, \
.debounce_interval= debounce, \
}
接着定义一个gpio_keys_platform_data变量:
arch\arm\mach-mx6\board-mx6q-sabresd.c:
staticstruct gpio_keys_platform_data new_sabresd_button_data = {
.buttons = new_sabresd_buttons,
.nbuttons = ARRAY_SIZE(new_sabresd_buttons),
};
struct gpio_keys_platform_data的定义也在gpio_keys.h中:
include\linux\gpio_keys.h:
structgpio_keys_platform_data {
struct gpio_keys_button *buttons;
int nbuttons;
unsigned int poll_interval; /* polling interval in msecs -
for polling driver only */
unsigned int rep:1; /* enable input subsystem auto repeat*/
int (*enable)(struct device *dev);
void (*disable)(struct device *dev);
const char *name; /* input device name */
};
2. 把 1 中定义的new_sabresd_button_data 注册到系统中去:
我们把gpio_key当成一个platform_device设备, 所以要先定义一个platform_device设备:
arch\arm\mach-mx6\board-mx6q-sabresd.c:
staticstruct platform_device sabresd_button_device = {
.name ="gpio-keys", /*名字非常关键, 找驱动就靠它来匹配了*/
.id =-1,
.num_resources = 0,
};
然后把我们定义的gpio_key 跟这个platform_device设备绑定在一起:
arch\arm\mach-mx6\board-mx6q-sabresd.c:
platform_device_add_data(&sabresd_button_device,
&new_sabresd_button_data,
sizeof(new_sabresd_button_data));
最后注册到系统中去:
arch\arm\mach-mx6\board-mx6q-sabresd.c:
platform_device_register(&sabresd_button_device);
其中, platform_device_add_data()函数如下:
drivers/base/platform.c:
intplatform_device_add_data(struct platform_device *pdev, const void *data,
size_t size)
{
void *d = NULL;
if (data) {
d = kmemdup(data, size,GFP_KERNEL); /*分配memory,并把data的内容拷贝进去*/
if (!d)
return -ENOMEM;
}
kfree(pdev->dev.platform_data);
pdev->dev.platform_data = d; /*把gpio_key绑定到这个platform_device上去*/
return 0;
}
3. 匹配驱动:
当用platform_device_register()把 gpio_key的platform_device的添加到系统中去后, 系统会去匹配是否有合适的驱动, 这里对应的驱动就是:gpio_keys.c, 很显然这是一个platform_driver的驱动:
drivers/input/keyboard/gpio_keys.c:
static struct platform_drivergpio_keys_device_driver = {
.probe = gpio_keys_probe,
.remove = __devexit_p(gpio_keys_remove),
.driver = {
.name = "gpio-keys", /*发现没有, 名字跟设备的名字一模一样*/
.owner = THIS_MODULE,
#ifdef CONFIG_PM
.pm = &gpio_keys_pm_ops,
#endif
}
};
系统能找到这个驱动, 主要就是因为他们的名字都是: gpio-keys, 这个很关键。 接下来我们就来分析一下这个驱动, 首先找到设备后, 会调用probe函数, 这里就是gpio_keys_probe();
在gpio_keys_probe()函数中, 会注册一个input设备, 并创建相应的设备文件。
二. GPIO_KEY使用
使用方式比较简单,和普通的文件操作一样, 先打开设备文件, 再读文件获取键值即可:
1. 打开设备文件,
我的设备上gpio_key对应的设备文件是/dev/input/event0, 不同的平台设备文件可能会有差异, 如果不清楚对应的设备文件, 可以用下面的命令来查看:
打开设备代码如下:
/*1.key device*/
fd_key= open(KEY_DEVICE_FILE, O_RDONLY);
if(fd_key< 0) {
LOGE("can'topen key device file");
returnfd_key;
}
2. 获取按键值及按键类型:
struct input_event key_evt;
monitor_key:
ret = read(fd_key, (unsigned char*)&key_evt, sizeof(struct input_event)); /*阻塞型读函数*/
if(ret < 0) {
LOGE("read key eventfailed :%d", ret);
}
/*filter unknown key*/
else if(key_evt.code != 248&& /*KEY_MENU*/
key_evt.code != 249 && /*KEY_UP*/
key_evt.code != 250 && /*KEY_DOWN*/
key_evt.code != 251 && /*KEY_CONFIRM*/
key_evt.code != 247){ /*ICCARD detect pin*/
LOGE("unknown key code:%d", key_evt.code);
goto monitor_key;
}
else { /*valid key*/
/*
* key_val[0..7] = key code
* key_val[8] = key value: 0 - released, 1 - pressed.
*/
key_val = ((int8_t)key_evt.value<< 8) | ((uint8_t)key_evt.code);
//LOGE("get key eventcode:%d, value:%d, type:%d, %d", key_evt.code, key_evt.value,key_evt.type, key_val);
}
return key_val;
实际上就是调用一个阻塞型的读函数, 所以这个函数尽量放在单独的一个线程中处理, 键值就是在前面板级支持包中定义并注册的值。