linux3.4.0 按键驱动程序分析(pandaboard omap4460)
在内核中,按键的驱动程序已经设计好了,要使自己板上的按键工作起来,需要做的是在相应的文件中添加硬件信息,然后对内核进行正确的配置。
以下先使按键工作起来,再对驱动进行分析。
按键属于输入设备,所以在分析按键驱动之前要先学习input子系统的相关知识
1、内核配置:
Device Drivers ->input device support ->[*] keyboards ->[*] GPIO buttons ->
2、添加硬件信息:
修改文件 /arch/arm/mach-omap2/board-omap4panda.c
第一处:
#include <linux/gpio_keys.h>
第二处:
static struct gpio_keys_button pandaboard_exp_gpio_keys[] = {{.code = KEY_HOME, //按键码,需要预先定义.gpio = 36, //使用的GPIO口.active_low = 1,.desc = "pandaboard_exp_power",.type = EV_KEY, //类型.wakeup = 1,.debounce_interval = 10, //去抖},{.code = KEY_PROG1,.gpio = 32,.active_low = 1,.desc = "pandaboard_exp_menu",.type = EV_KEY,.wakeup = 1,.debounce_interval = 10,},{.code = KEY_PROG2,.gpio = 38,.active_low = 1,.desc = "pandaboard_exp_back",.type = EV_KEY,.wakeup = 1,.debounce_interval = 10,},{.code = KEY_PROG3,.gpio = 33,.active_low = 1,.desc = "pandaboard_exp_volup",.type = EV_KEY,.wakeup = 1,.debounce_interval = 10,},{.code = KEY_PROG4,.gpio = 37,.active_low = 1,.desc = "pandaboard_exp_voldw",.type = EV_KEY,.wakeup = 1,.debounce_interval = 10,},
};
static struct gpio_keys_platform_data pandaboard_exp_gpio_keys_data = {.buttons = pandaboard_exp_gpio_keys,.nbuttons = ARRAY_SIZE(pandaboard_exp_gpio_keys),
};static struct platform_device pandaboard_exp_gpio_keys_device = {.name = "gpio-keys", //平台设备的名称.id = -1,.dev = {.platform_data = &pandaboard_exp_gpio_keys_data,},
};
第三处:
static struct platform_device *panda_devices[] __initdata = {&pandaboard_exp_gpio_keys_device, //加入到系统平台设备数组中&wl1271_device,&btwilink_device,
};
3、测试
cat /dev/input/event1 ,按下或者抬起按键会在终端输出乱码。
测试应用程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <linux/input.h>int main(void)
{int key_fd;int key_value,i=0,count;struct input_event ev_key;key_fd = open("/dev/event1", O_RDWR);if (key_fd < 0) {perror("open device keys");exit(1);}while (1) {count = read(key_fd,&ev_key,sizeof(struct input_event));for(i=0; i<(int)count/sizeof(struct input_event); i++){if(EV_KEY==ev_key.type)printf("type:%d, code:%d, value:%d", ev_key.type, ev_key.code, ev_key.value);}}close(key_fd);return 0;
}
驱动程序分析:
代码位置:/drivers/input/keyboard/gpio_keys.c
//模块加载函数
static int __init gpio_keys_init(void)
{return platform_driver_register(&gpio_keys_device_driver); //注册平台驱动
}
//平台驱动结构体的定义
static struct platform_driver gpio_keys_device_driver = {.probe = gpio_keys_probe, //探测函数,在驱动找到对应的设备时执行.remove = __devexit_p(gpio_keys_remove),.driver = {.name = "gpio-keys", //驱动名称 ,可以在 /sys/ 下找到.owner = THIS_MODULE,.pm = &gpio_keys_pm_ops, //操作函数集.of_match_table = gpio_keys_of_match,}
};
//探测函数
static int __devinit gpio_keys_probe(struct platform_device *pdev)
{struct gpio_keys_platform_data *pdata = pdev->dev.platform_data; //获取平台设备的相关数据struct gpio_keys_drvdata *ddata;struct device *dev = &pdev->dev;struct gpio_keys_platform_data alt_pdata;struct input_dev *input;int i, error;int wakeup = 0;if (!pdata) {error = gpio_keys_get_devtree_pdata(dev, &alt_pdata);if (error)return error;pdata = &alt_pdata;}ddata = kzalloc(sizeof(struct gpio_keys_drvdata) + pdata->nbuttons * sizeof(struct gpio_button_data),GFP_KERNEL);input = input_allocate_device(); //分配一个输入设备结构体if (!ddata || !input) {dev_err(dev, "failed to allocate state\n");error = -ENOMEM;goto fail1;}
//对驱动结构体赋值ddata->input = input;ddata->n_buttons = pdata->nbuttons;ddata->enable = pdata->enable;ddata->disable = pdata->disable;mutex_init(&ddata->disable_lock);platform_set_drvdata(pdev, ddata);input_set_drvdata(input, ddata);
//对输入设备机构体进行赋值,这些值都可以在系统启动后在 /sys/ 下找到对应项input->name = pdata->name ? : pdev->name;input->phys = "gpio-keys/input0";input->dev.parent = &pdev->dev;input->open = gpio_keys_open;input->close = gpio_keys_close;input->id.bustype = BUS_HOST;input->id.vendor = 0x0001;input->id.product = 0x0001;input->id.version = 0x0100;
/* Enable auto repeat feature of Linux input subsystem */if (pdata->rep)__set_bit(EV_REP, input->evbit);
// 依次对每个按键进行设置for (i = 0; i < pdata->nbuttons; i++) {struct gpio_keys_button *button = &pdata->buttons[i];struct gpio_button_data *bdata = &ddata->data[i];unsigned int type = button->type ?: EV_KEY;
bdata->input = input;bdata->button = button;
//设置按键,相当于初始化,主要工作是申请gpio、设置为输入、设置防抖动时间、中断申请error = gpio_keys_setup_key(pdev, bdata, button);if (error)goto fail2;if (button->wakeup)wakeup = 1;input_set_capability(input, type, button->code); //对输入设备进行类型和码的设置,}error = sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group); //设置属性并创建接口if (error) {dev_err(dev, "Unable to export keys/switches, error: %d\n",error);goto fail2;}error = input_register_device(input); //注册输入设备if (error) {dev_err(dev, "Unable to register input device, error: %d\n",error);goto fail3;}/* get current state of buttons */for (i = 0; i < pdata->nbuttons; i++) //报告事件gpio_keys_report_event(&ddata->data[i]);input_sync(input);device_init_wakeup(&pdev->dev, wakeup);return 0;fail3:sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);
fail2:while (--i >= 0) {free_irq(gpio_to_irq(pdata->buttons[i].gpio), &ddata->data[i]);if (ddata->data[i].timer_debounce)del_timer_sync(&ddata->data[i].timer);cancel_work_sync(&ddata->data[i].work);gpio_free(pdata->buttons[i].gpio);}platform_set_drvdata(pdev, NULL);
fail1:input_free_device(input);kfree(ddata);
/* If we have no platform_data, we allocated buttons dynamically. */if (!pdev->dev.platform_data)kfree(pdata->buttons);return error;
}
中断处理函数:
static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{struct gpio_button_data *bdata = dev_id;struct gpio_keys_button *button = bdata->button;BUG_ON(irq != gpio_to_irq(button->gpio));if (bdata->timer_debounce)mod_timer(&bdata->timer,jiffies + msecs_to_jiffies(bdata->timer_debounce));elseschedule_work(&bdata->work);return IRQ_HANDLED;
}
工作流程:
按键触发->gpio_keys_isr->schedule_work(&bdata->work)->gpio_keys_work_func()->gpio_keys_report_event()报告事件
其中work的初始化在gpio_keys_setup_key()函数中:INIT_WORK(&bdata->work, gpio_keys_work_func);