内核提供了一个通用的CPIO驱动,定义在drivers/input/keyboard/gpio-keys.c中
GPIO按键驱动是基于平台设备的,这里分析s3c6410的GPIO按键驱动
一、GPIO按键使用的资源定义
-
//定义在arch/arm/mach-s3c64xx/mach-smdk6410.c中
-
//按键定义
-
static struct gpio_keys_button gpio_buttons[] = {
-
{
-
.gpio = S3C64XX_GPN(0),//使用的GPIO口
-
.code = KEY_UP,//按键产生的事件编码
-
.desc = "BUTTON1",//描述
-
.active_low = 1,//低电平触发
-
.wakeup = 0,
-
},
-
{
-
.gpio = S3C64XX_GPN(1),
-
.code = KEY_DOWN,
-
.desc = "BUTTON2",
-
.active_low = 1,
-
.wakeup = 0,
-
},
-
{
-
.gpio = S3C64XX_GPN(2),
-
.code = KEY_LEFT,
-
.desc = "BUTTON3",
-
.active_low = 1,
-
.wakeup = 0,
-
},
-
{
-
.gpio = S3C64XX_GPN(3),
-
.code = KEY_RIGHT,
-
.desc = "BUTTON4",
-
.active_low = 1,
-
.wakeup = 0,
-
},
-
{
-
.gpio = S3C64XX_GPN(4),
-
.code = KEY_ENTER,
-
.desc = "BUTTON5",
-
.active_low = 1,
-
.wakeup = 0,
-
},
-
{
-
.gpio = S3C64XX_GPN(5),
-
.code = KEY_ESC,
-
.desc = "BUTTON6",
-
.active_low = 1,
-
.wakeup = 0,
-
}
-
};
-
//私有数据结构
-
static struct gpio_keys_platform_data gpio_button_data = {
-
.buttons = gpio_buttons,//按键列表
-
.nbuttons = ARRAY_SIZE(gpio_buttons),//按键数
-
};
-
//定义GPIO按键的平台设备结构
-
static struct platform_device gpio_button_device = {
-
.name = "gpio-keys",//设备名
-
.id = -1,//设备ID
-
.num_resources = 0,//使用的资源大小
-
.dev = {
-
.platform_data = &gpio_button_data,//私有数据
-
}
-
};
二、基于平台设备驱动的结构
-
static struct platform_driver gpio_keys_device_driver = {
-
.probe = gpio_keys_probe, //探测方法
-
.remove = __devexit_p(gpio_keys_remove),//移除方法
-
.driver = {
-
.name = "gpio-keys",//驱动名,这里的name要和platform_device结构中的name相同
-
.owner = THIS_MODULE,//驱动所属的模块
-
#ifdef CONFIG_PM
-
.pm = &gpio_keys_pm_ops,//电源管理函数集合
-
#endif
-
}
-
};
三、平台设备驱动的注册和卸载方法
-
//驱动加载方法
-
static int __init gpio_keys_init(void)
-
{
-
//注册平台设备驱动
-
return platform_driver_register(&gpio_keys_device_driver);
-
}
-
//驱动卸载方法
-
static void __exit gpio_keys_exit(void)
-
{
-
//注销平台设备驱动
-
platform_driver_unregister(&gpio_keys_device_driver);
-
}
四、探测方法
-
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;//gpio键私有数据
-
struct device *dev = &pdev->dev;//得到平台设备
-
struct input_dev *input;
-
int i, error;
-
int wakeup = 0;
-
//分配空间,gpio_keys_drvdata结构中有struct gpio_button_data data[0];成员,
-
//这里分配pdata->nbuttons个gpio_button_data大小的空间
-
ddata = kzalloc(sizeof(struct gpio_keys_drvdata) +
-
pdata->nbuttons * sizeof(struct gpio_button_data),
-
GFP_KERNEL);
-
//分配input_dev输入设备结构
-
input = input_allocate_device();
-
if (!ddata || !input) {
-
dev_err(dev, "failed to allocate state\n");
-
error = -ENOMEM;
-
goto fail1;
-
}
-
//为struct gpio_keys_drvdata *ddata;赋值
-
ddata->input = input;//input_dev输入设备结构
-
ddata->n_buttons = pdata->nbuttons;//按键数
-
ddata->enable = pdata->enable;//使用系统默认的方法
-
ddata->disable = pdata->disable;
-
mutex_init(&ddata->disable_lock);//初始化互斥体
-
//将ddata作为平台设备的私有数据
-
platform_set_drvdata(pdev, ddata);
-
//将ddata作为input设备的私有数据
-
input_set_drvdata(input, ddata);
-
//为input_dev结构体赋值
-
input->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;
-
-
/* 启用自动重复特性的Linux输入子系统*/
-
if (pdata->rep)
-
__set_bit(EV_REP, input->evbit);
-
//依次设置各gpio_keys_button
-
for (i = 0; i < pdata->nbuttons; i++) {
-
struct gpio_keys_button *button = &pdata->buttons[i];//得到gpio_keys_button
-
struct gpio_button_data *bdata = &ddata->data[i];//得到gpio_button_data
-
unsigned int type = button->type ?: EV_KEY;
-
-
bdata->input = input;
-
bdata->button = button;
-
/*
-
该函数完成的功能为
-
1、设置定时器处理方法
-
2、设置工作队列处理方法
-
3、申请并设置GPIO口
-
4、申请中断
-
*/
-
//配置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);
-
}
-
//在sys下创建组
-
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;
-
}
-
//注册input设备
-
error = input_register_device(input);
-
if (error) {
-
dev_err(dev, "Unable to register input device, error: %d\n",
-
error);
-
goto fail3;
-
}
-
-
/* 得到当前按钮的状态*/
-
for (i = 0; i < pdata->nbuttons; i++)
-
gpio_keys_report_event(&ddata->data[i]);
-
input_sync(input);//同步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);
-
-
return error;
-
}
设置GPIO口方法:用于申请GPIO资源,配置GPIO口功能,申请中断
-
static int __devinit gpio_keys_setup_key(struct platform_device *pdev,
-
struct gpio_button_data *bdata,
-
struct gpio_keys_button *button)
-
{
-
char *desc = button->desc ? button->desc : "gpio_keys";
-
struct device *dev = &pdev->dev;
-
unsigned long irqflags;
-
int irq, error;
-
//设置定时器处理方法
-
setup_timer(&bdata->timer, gpio_keys_timer, (unsigned long)bdata);
-
//设置工作队列处理方法
-
INIT_WORK(&bdata->work, gpio_keys_work_func);
-
//申请GPIO口
-
error = gpio_request(button->gpio, desc);
-
if (error < 0) {
-
dev_err(dev, "failed to request GPIO %d, error %d\n",
-
button->gpio, error);
-
goto fail2;
-
}
-
//设置GPIO口为输入
-
error = gpio_direction_input(button->gpio);
-
if (error < 0) {
-
dev_err(dev, "failed to configure"
-
" direction for GPIO %d, error %d\n",
-
button->gpio, error);
-
goto fail3;
-
}
-
//如果gpio_keys_button->debounce_interval设置了放抖动的时间,则设置GPIO口的去抖动时间
-
if (button->debounce_interval) {
-
error = gpio_set_debounce(button->gpio,
-
button->debounce_interval * 1000);
-
/* use timer if gpiolib doesn't provide debounce */
-
if (error < 0)
-
bdata->timer_debounce = button->debounce_interval;
-
}
-
//根据GPIO得到中断号
-
irq = gpio_to_irq(button->gpio);
-
if (irq < 0) {
-
error = irq;
-
dev_err(dev, "Unable to get irq number for GPIO %d, error %d\n",
-
button->gpio, error);
-
goto fail3;
-
}
-
//中断的触发方向设置为上升沿和下降沿触发
-
irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
-
/*
-
* If platform has specified that the button can be disabled,
-
* we don't want it to share the interrupt line.
-
*/
-
//若按钮是可用的,设置中断为共享中断
-
if (!button->can_disable)
-
irqflags |= IRQF_SHARED;
-
//申请中断
-
error = request_irq(irq, gpio_keys_isr, irqflags, desc, bdata);
-
if (error) {
-
dev_err(dev, "Unable to claim irq %d; error %d\n",
-
irq, error);
-
goto fail3;
-
}
-
-
return 0;
-
-
fail3:
-
gpio_free(button->gpio);
-
fail2:
-
return error;
-
}
定时器处理方法
-
static void gpio_keys_timer(unsigned long _data)
-
{
-
struct gpio_button_data *data = (struct gpio_button_data *)_data;
-
//调度其它任务
-
schedule_work(&data->work);
-
}
处理任务的方法:用于处理任务队列中的任务
-
static void gpio_keys_work_func(struct work_struct *work)
-
{
-
struct gpio_button_data *bdata =
-
container_of(work, struct gpio_button_data, work);
-
//调用gpio_keys_report_event传递事件
-
gpio_keys_report_event(bdata);
-
}
传递事件方法:获得事件的类型、编码、值,调用input_event函数将事件传递给输入子系统核心
-
static void gpio_keys_report_event(struct gpio_button_data *bdata)
-
{
-
struct gpio_keys_button *button = bdata->button;
-
struct input_dev *input = bdata->input;
-
unsigned int type = button->type ?: EV_KEY;
-
int state = (gpio_get_value(button->gpio) ? 1 : 0) ^ button->active_low;//得到按键状态
-
input_event(input, type, button->code, !!state);//向input核心传递事件
-
input_sync(input);//同步input
-
}
中断处理方法:当按键被按下会产生中断
-
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));
-
else
-
schedule_work(&bdata->work);
-
-
return IRQ_HANDLED;
-
}
五、移除方法
-
static int __devexit gpio_keys_remove(struct platform_device *pdev)
-
{
-
struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
-
struct gpio_keys_drvdata *ddata = platform_get_drvdata(pdev);
-
struct input_dev *input = ddata->input;
-
int i;
-
//删除sys下的组
-
sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);
-
-
device_init_wakeup(&pdev->dev, 0);
-
//释放中断,释放GPIO资源
-
for (i = 0; i < pdata->nbuttons; i++) {
-
int irq = gpio_to_irq(pdata->buttons[i].gpio);
-
free_irq(irq, &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);
-
}
-
//注销输入设备
-
input_unregister_device(input);
-
-
return 0;
-
}
六、总结
基于GPIO的按键驱动编写过程
1、定义GPIO按键平台设备使用的系统资源
2、注册GPIO按键平台设备
3、在探测方法中分配input_dev结构,读取前面定义GPIO按键平台设备使用的系统资源,初始化input_dev结构,
申请GPIO资源,配置GPIO口功能,通过GPIO口得到中断号,设置中断触发方式,申请中断。
4、向内核注册输入设备驱动;
5、当有中断产生时,获得事件类型、编码、值,调用input_event函数向输入子系统传递事件;
6、移除方法释放GPIO资源,释放中断号,注销输入设备驱动
阅读(3867) | 评论(0) | 转发(0) |