参考资源:
我的内容
开发板:正点原子的 rv1126
内核版本:emm
0 input子系统介绍
在linux系统中,输入设备(如按键,键盘,触摸屏,鼠标,蜂鸣器等)是典型的字符设备
其一般的工作机制是:
- 用户在按键、触摸等动作发生时产生一个中断
- 然后cpu读取键值、坐标等数据,再放一个缓冲区
- 字符设备驱动管理该缓冲区,而驱动的read()接口让用户可以读取键值、坐标等数据。
如下图,在Linux中,输入子系统是由:
- 输入子系统设备驱动层
- 输入子系统核心层(Input Core)
- 输入子系统事件处理层(Event Handler)组成
- 设备驱动层提供对硬件各寄存器的读写访问和将底层硬件对用户输入访问的响应转换为标准的输入事件,再通过核心层提交给事件处理层;
- 核心层对下提供了设备驱动层的编程接口,对上又提供了事件处理层的编程接口;
- 事件处理层就为我们用户空间的应用程序提供了统一访问设备的接口和驱动层提交来的事件处理。
- 这使得我们输入设备的驱动部分不在用关心对设备文件的操作,而是要关心对各硬件寄存器的操作和提交的输入事件。
- 在 Linux中,输入子系统作为一个模块存在,向上,为用户层提供接口函数,向下,为驱动层程序提供统一的接口函数。其构建非常灵活,只需要调用一些简单的函数,就可以将一个输入设备的功能呈现给应用程序。这样,就能够使输入设备的事件通过输入子系统发送给用户层应用程序,用户层应用程序也可以通过输入子系统通知驱动程序完成某项功能。
1 input子系统分析
1.1 设备驱动层、事件处理驱动层是如何通过核心层(input.c)联系起来的?
- 打开input.c文件
在input_init中只简单创建了 虚拟文件系统,没有进行具体的操作联系
- 需要使用 核心层 提供的input_register_device(设备驱动层中例如普通的按键驱动程序)和 input_register_handler(evdev.c, joystick.c)中将 设备 input_dev 和设备对应的input_handler添加到链表中
- 内核会查询设备链表和事件链表,将设备和具体的事件对应起来
- 事件驱动层(例如evdev.c) 内核已经帮我们写好了,所以只需要在 设备驱动层(比如 按键驱动程序)中,分配,设置,注册设备,并上报相关事件即可
1.2 编写简单的设备驱动层的程序(以按键驱动为例)
1.2.1 基本步骤
- 分配、注册、注销input设备
struct input_dev *input_allocate_device(void)
int input_register_device(struct input_dev *dev)
void input_unregister_device(struct input_dev *dev)
设置input设备支持的事件类型、事件码、事件值的范围、input_id等信息
一个设备可以支持一个或多个事件类型。每个事件类型下面还需要设置具体的触发事件码。比如:EV_KEY事件,需要定义其支持哪些按键事件码。
如果需要,设置input设备的打开、关闭、写入数据时的处理方法
在发生输入事件时,向子系统报告事件
注册输入设备的函数为:
int input_register_device(struct input_dev *dev)
注销输入设备的函数为:
void input_unregister_device(struct input_dev *dev)
设备驱动通过set_bit()告诉input子系统它支持哪些事件,
如下所示:
set_bit(EV_KEY, button_dev.evbit)
//struct iput_dev 中有两个成员,一个是evbit;一个是keybit
-
上报事件(include/linux/input.h)上报的事件内容如下
type : 事件类型(详见rk_input.h文件,这个文件可能是从设备树 中生成的,未进行考证~)
/*
* Event types
*/
#define EV_SYN 0x00 // 同步事件
#define EV_KEY 0x01 // 按键
#define EV_REL 0x02 // 触摸屏相关 相对坐标
#define EV_ABS 0x03 // 触摸屏相关 绝对坐标
#define EV_MSC 0x04 // 其它杂项
#define EV_SW 0x05
#define EV_LED 0x11
#define EV_SND 0x12 // 声音
#define EV_REP 0x14 // 重复
#define EV_FF 0x15 // 力反馈
#define EV_PWR 0x16
#define EV_FF_STATUS 0x17
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)
用于报告EV_KEY、EV_REL和EV_ABS事件的函数分别为:
void input_report_key(struct input_dev *dev,unsigned int code, int value)
void input_report_rel(struct input_dev *dev,unsigned int code, int value)
void input_report_abs(struct input_dev *dev,unsigned int code, int value)
其中 code:
如果事件的类型是EV_KEY,该代码code为具体是哪个按键按下了
//.....
#define KEY_1 2
#define KEY_2 3
#define KEY_3 4
#define KEY_4 5
#define KEY_5 6
#define KEY_6 7
#define KEY_7 8
#define KEY_8 9
#define KEY_9 10
//....
value:
事件的值。如果事件的类型是EV_KEY,当按键按下时值为1,松开时值为0。
-
input_sync()
用于事件同步,它告知事件的接收者:驱动已经发出了一个完整的报告。例如,在触摸屏设备驱动中,一次坐标及按下状态的整个报告过程如下:
input_report_abs(input_dev, ABS_X, x); //X坐标
input_report_abs(input_dev, ABS_Y, y); //Y坐标
input_report_abs(input_dev, ABS_PRESSURE, pres); //压力
input_sync(input_dev); //同步
设备描述
-
Linux内核中,input设备用input_dev结构体描述,使用input子系统实现输入设备驱动的时候,驱动的核心工作是向系统报告按键、触摸屏、键盘、鼠标等输入事件(event,通过input_event结构体描述),不再需要关心文件操作接口,因为input子系统已经完成了文件操作接口。驱动报告的事件经过InputCore和Eventhandler最终到达用户空间。
/*事件的代码。以电脑上的linux为例:
如果事件的类型是EV_KEY,该代码code为设备键盘代码。code值0~127为键盘上的按键代码,0x110~0x116 为鼠标上按键代码,其中0x110(BTN_LEFT)为鼠标左键,0x111(BTN_RIGHT)为鼠标右键,0x112(BTN_ MIDDLE)为鼠标中键。其它代码含义请参看include/linux/input.h文件
*/
evbit 事件类型
#define EV_SYN 0x00 /*表示设备支持所有的事件*/
#define EV_KEY 0x01 /*键盘或者按键,表示一个键码*/
#define EV_REL 0x02 /*鼠标设备,表示一个相对的光标位置结果*/
#define EV_ABS 0x03 /*手写板产生的值,其是一个绝对整数值*/
#define EV_MSC 0x04 /*其他类型*/
#define EV_LED 0x11 /*LED灯设备*/
#define EV_SND 0x12 /*蜂鸣器,输入声音*/
#define EV_REP 0x14 /*允许重复按键类型*/
#define EV_PWR 0x16 /*电源管理事件*/
- 示例程序 参考朱有鹏的嵌入式linux 1期 的 驱动相关课程
#include <linux/input.h>
#include <linux/module.h>
#include <linux/init.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <mach/irqs.h> // arch/arm/mach-s5pv210/include/mach/irqs.h
#include <linux/interrupt.h>
#include <linux/gpio.h>
/*
* X210:
*
* POWER -> EINT1 -> GPH0_1
* LEFT -> EINT2 -> GPH0_2
* DOWN -> EINT3 -> GPH0_3
* UP -> KP_COL0 -> GPH2_0
* RIGHT -> KP_COL1 -> GPH2_1
* MENU -> KP_COL3 -> GPH2_3 (KEY_A)
* BACK -> KP_COL2 -> GPH2_2 (KEY_B)
*/
#define BUTTON_IRQ IRQ_EINT2
static struct input_dev *button_dev;
static irqreturn_t button_interrupt(int irq, void *dummy)
{
int flag;
s3c_gpio_cfgpin(S5PV210_GPH0(2), S3C_GPIO_SFN(0x0)); // input模式
flag = gpio_get_value(S5PV210_GPH0(2));
s3c_gpio_cfgpin(S5PV210_GPH0(2), S3C_GPIO_SFN(0x0f)); // eint2模式
// 4. 在中断中上报相应的事件
input_report_key(button_dev, KEY_LEFT, !flag);
// 5. 每上报一次事件,就要进行一次同步
input_sync(button_dev);
return IRQ_HANDLED;
}
static int __init button_init(void)
{
int error;
error = gpio_request(S5PV210_GPH0(2), "GPH0_2");
if(error)
printk("key-s5pv210: request gpio GPH0(2) fail");
s3c_gpio_cfgpin(S5PV210_GPH0(2), S3C_GPIO_SFN(0x0f)); // eint2模式
if (request_irq(BUTTON_IRQ, button_interrupt, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "button-x210", NULL))
{
printk(KERN_ERR "key-s5pv210.c: Can't allocate irq %d\n", BUTTON_IRQ);
return -EBUSY;
}
// 1.分配input设备
button_dev = input_allocate_device();
if (!button_dev)
{
printk(KERN_ERR "key-s5pv210.c: Not enough memory\n");
error = -ENOMEM;
goto err_free_irq;
}
// 2. 设置上报哪类事件
button_dev->evbit[0] = BIT_MASK(EV_KEY);
button_dev->keybit[BIT_WORD(KEY_LEFT)] = BIT_MASK(KEY_LEFT);
// 3. 注册input设备
error = input_register_device(button_dev);
if (error)
{
printk(KERN_ERR "key-s5pv210.c: Failed to register device\n");
goto err_free_dev;
}
return 0;
err_free_dev:
input_free_device(button_dev);
err_free_irq:
free_irq(BUTTON_IRQ, button_interrupt);
return error;
}
static void __exit button_exit(void)
{
input_unregister_device(button_dev);
free_irq(BUTTON_IRQ, button_interrupt);
}
module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("aston <1264671872@qq.com>");
MODULE_DESCRIPTION("key driver for x210 button.");
2 i2c与input子系统的结合(触摸屏驱动gt9xx.c分析)
-
触摸屏与主机需要采用i2c通信,因此也属于i2c子系统
-
触摸屏属于i2c_client 设备,所以 当i2c总线匹配完成后,会执行i2c_driver中的probe函数,在这个函数中进行input设备的分配设置和注册
-
具体的input子系统的注册过程如下:
// gt9xx.c中
static int goodix_ts_init(void){ ... ret = i2c_add_driver(&goodix_ts_driver); ... }
static struct i2c_driver goodix_ts_driver = { .probe = goodix_ts_probe, ... };
static int goodix_ts_probe(struct i2c_client *client, const struct i2c_device_id *id){ // 在这里就注册了input_dev设备 ... ret = gtp_request_input_dev(client, ts); ... }
static s8 gtp_request_input_dev(struct i2c_client *client, struct goodix_ts_data *ts) { s8 ret = -1; s8 phys[32]; #if GTP_HAVE_TOUCH_KEY u8 index = 0; #endif GTP_DEBUG_FUNC(); /*1. 分配input_dev*/ ts->input_dev = devm_input_allocate_device(&client->dev); if (ts->input_dev == NULL) { GTP_ERROR("Failed to allocate input device."); return -ENOMEM; } // 2. 设置 上报的 事件类型 // EV_SYN 事件同步;EV_KEY 按键; EV_ABS 触摸屏 绝对坐标; ts->input_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) ; #if GTP_ICS_SLOT_REPORT input_mt_init_slots(ts->input_dev, 16, INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); // in case of "out of memory" #else ts->input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); #endif __set_bit(INPUT_PROP_DIRECT, ts->input_dev->propbit); #if GTP_HAVE_TOUCH_KEY for (index = 0; index < GTP_MAX_KEY_NUM; index++) { // 3. 设置可以上报哪些事件 input_set_capability(ts->input_dev, EV_KEY, touch_key_array[index]); } #endif #if GTP_GESTURE_WAKEUP input_set_capability(ts->input_dev, EV_KEY, KEY_POWER); #endif if (gtp_change_x2y) GTP_SWAP(ts->abs_x_max, ts->abs_y_max); #if defined(CONFIG_CHROME_PLATFORMS) input_set_abs_params(ts->input_dev, ABS_X, 0, ts->abs_x_max, 0, 0); input_set_abs_params(ts->input_dev, ABS_Y, 0, ts->abs_y_max, 0, 0); #endif input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X, 0, ts->abs_x_max, 0, 0); input_set_abs_params(ts->input_dev, ABS_MT_POSITION_Y, 0, ts->abs_y_max, 0, 0); input_set_abs_params(ts->input_dev, ABS_MT_WIDTH_MAJOR, 0, 255, 0, 0); input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); input_set_abs_params(ts->input_dev, ABS_MT_TRACKING_ID, 0, 255, 0, 0); sprintf(phys, "input/ts"); ts->input_dev->name = goodix_ts_name; ts->input_dev->phys = phys; ts->input_dev->id.bustype = BUS_I2C; ts->input_dev->id.vendor = 0xDEAD; ts->input_dev->id.product = 0xBEEF; ts->input_dev->id.version = 10427; //4.注册input_dev ret = input_register_device(ts->input_dev); if (ret) { GTP_ERROR("Register %s input device failed", ts->input_dev->name); return -ENODEV; } ts->tp.tp_resume = goodix_ts_early_resume; ts->tp.tp_suspend = goodix_ts_early_suspend; tp_register_fb(&ts->tp); #if GTP_WITH_PEN gtp_pen_init(ts); #endif return 0; }