1、linux输入子系统简述
在Linux中,输入子系统是由输入子系统设备驱动层、输入子系统核心层(InputCore)和输入子系统事件处理层(Event Handler)组成。其中设备驱动层提供对硬件各寄存器的读写访问和将底层硬件对用户输入访问的响应转换为标准的输入事件,再通过核心层提交给事件处理层;而核心层对下提供了设备驱动层的编程接口,对上又提供了事件处理层的编程接口;而事件处理层就为我们用户空间的应用程序提供了统一访问设备的接口和驱动层提交来的事件处理。所以这使得我们输入设备的驱动部分不在用关心对设备文件的操作,而是要关心对各硬件寄存器的操作和提交的输入事件。下面用图形来描述一下这三者的关系!
输入子系统与驱动关系
2、输入子系统设备驱动层实现原理
在Linux中,Input设备用input_dev结构体描述,定义在input.h中。设备的驱动只需按照如下步骤就可实现了。
(1)、在驱动模块加载函数中设置Input设备支持input子系统的数据;
(2)、将Input设备注册到input子系统中;
(3)、在Input设备发生输入操作时(如:键盘被按下/抬起、触摸屏被触摸/抬起/移动、鼠标被移动/单击/抬起时等),提交所发生的事件及对应的键值/坐标等状态。
Linux中输入设备的事件类型有(这里只列出了常用的一些,更多请看linux/input.h中):
EV_SYN 0x00 同步事件
EV_KEY 0x01 按键事件
EV_REL 0x02 相对坐标(如:鼠标移动,报告的是相对最后一次位置的偏移)
EV_ABS 0x03 绝对坐标(如:触摸屏和操作杆,报告的是绝对的坐标位置)
EV_MSC 0x04 其它
EV_LED 0x11 LED
EV_SND 0x12 声音
EV_REP 0x14 Repeat
EV_FF 0x15 力反馈
用于提交较常用的事件类型给输入子系统的函数有:
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); //提交绝对坐标事件的函数
注意,在提交输入设备的事件后必须用下列方法使事件同步,让它告知input系统,设备驱动已经发出了一个完整的报告:
void input_sync(struct input_dev *dev)
3、以下以gt9xx.c触摸屏驱动来说明调用input的关系。
驱动模块的加载函数入口和退出函数为
late_initcall(goodix_ts_init);
module_exit(goodix_ts_exit);
goodix_ts_init即为入口查看函数,所有的分析从该函数开始
static int __devinit goodix_ts_init(void)
{
gtp_io_init(1); // 初始化
goodix_ts_driver.detect = ctp_detect;
ret = i2c_add_driver(&goodix_ts_driver);
return ret;
}
注册一个IIC设备
```
static const struct i2c_device_id goodix_ts_id[] = {
{ CTP_NAME, 0 },
{ }
};
static struct i2c_driver goodix_ts_driver = {
.class = I2C_CLASS_HWMON,
.probe = goodix_ts_probe,
.remove = goodix_ts_remove,
#ifndef CONFIG_HAS_EARLYSUSPEND
#ifdef CONFIG_PM .suspend = goodix_ts_suspend,
.resume = goodix_ts_resume,
#endif
#endif
.id_table = goodix_ts_id,
.driver = {
.name = CTP_NAME,
.owner = THIS_MODULE,
},
.address_list = normal_i2c,
};
IIC设备中用到了所有需要的函数,其中goodix_ts_probe函数 是ctp操作相关函数的集合
ctp_detect函数完成IIC设备的一些工作,具体后续再分析。
/**
* ctp_detect - Device detection callback for automatic device creation
* return value:
* = 0; success;
* < 0; err
*/
static int ctp_detect(struct i2c_client *client, struct i2c_board_info *info)
{
struct i2c_adapter *adapter = client->adapter;
int ret = -1;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)){
printk("======return=====\n");
return -ENODEV;
}
if(twi_id == adapter->nr){
dprintk(DEBUG_INIT,"%s: addr = %x\n", __func__, client->addr);
ret = i2c_test(client);
if(!ret){
printk("%s:I2C connection might be something wrong \n", __func__);
return -ENODEV;
}else{
strlcpy(info->type, CTP_NAME, I2C_NAME_SIZE);
return 0;
}
}else{
return -ENODEV;
}
}
goodix_ts_probe中调用input设备注册
ret = gtp_request_input_dev(ts);
if (ret < 0) {
printk("GTP request input dev failed");
goto exit_device_detect;
}
/*******************************************************
Function:
Request input device Function.
Input:
ts:private data.
Output:
Executive outcomes.0--success,non-0--fail.
*******************************************************/
static s8 gtp_request_input_dev(struct goodix_ts_data *ts)
{
s8 ret = -1;
ts->input_dev = input_allocate_device(); //分配内核空间
if (ts->input_dev == NULL) {
GTP_ERROR("Failed to allocate input device.");
return -ENOMEM;
}
ts->input_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) ;
ts->input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X, 0, SCREEN_MAX_X, 0, 0);
input_set_abs_params(ts->input_dev, ABS_MT_POSITION_Y, 0, SCREEN_MAX_Y, 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);
set_bit(INPUT_PROP_DIRECT, ts->input_dev->propbit);
//sprintf(phys, "input/ts");
ts->input_dev->name = goodix_ts_name;
//ts->input_dev->phys = phys;
ts->input_dev->phys = "input/goodix-ts";
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; //设置设备的参数
ret = input_register_device(ts->input_dev); //注册input设备
if (ret) {
printk("Register %s input device failed", ts->input_dev->name);
return -ENODEV;
}
return 0;
}
退出时释放掉input设备
static int goodix_ts_remove(struct i2c_client *client)
{
struct goodix_ts_data *ts = i2c_get_clientdata(client);
dprintk(DEBUG_INIT,"%s start!\n", __func__);
sw_gpio_irq_free(int_handle);
flush_workqueue(goodix_wq);
//cancel_work_sync(&goodix_init_work);
cancel_work_sync(&goodix_resume_work);
destroy_workqueue(goodix_wq);
//destroy_workqueue(goodix_init_wq);
destroy_workqueue(goodix_resume_wq);
i2c_set_clientdata(ts->client, NULL);
input_unregister_device(ts->input_dev); //释放掉注册的设备
input_free_device(ts->input_dev); // 清空设备input_dev structure的内存
kfree(ts);
return 0;
}
static void __exit goodix_ts_exit(void)
{
printk("GTP driver exited.");
i2c_del_driver(&goodix_ts_driver);
input_free_platform_resource(&(config_info.input_type)); // free platform related resource 释放相关资源,全志平台自己所写函数
}
对CTP进行操作时,需要对input系统提交自己的事件,例如下述按下触摸屏时,需要提交绝对坐标input_report_abs
static void gtp_touch_down(struct goodix_ts_data* ts,s32 id,s32 x,s32 y,s32 w)
{
dprintk(DEBUG_X_Y_INFO, "source data:ID:%d, X:%d, Y:%d, W:%d\n", id, x, y, w);
if(1 == exchange_x_y_flag){
swap(x, y);
}
if(1 == revert_x_flag){
x = screen_max_x - x;
//pr_err("x = %d , max = %d \n", x , screen_max_x);
}
if(1 == revert_y_flag){
y = screen_max_y - y ;
//pr_err("y = %d , max = %d \n", y , screen_max_y);
}
dprintk(DEBUG_X_Y_INFO,"report data:ID:%d, X:%d, Y:%d, W:%d\n", id, x, y, w);
// pr_err("report data:ID:%d, X:%d, Y:%d, W:%d\n", id, x, y, w);
input_report_abs(ts->input_dev, ABS_MT_POSITION_X, x);
input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, y);
input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, w);
input_report_abs(ts->input_dev, ABS_MT_WIDTH_MAJOR, w);
input_report_abs(ts->input_dev, ABS_MT_TRACKING_ID, id);
input_mt_sync(ts->input_dev);
}
if (touch_num ) {
for (i = 0; i < touch_num; i++) {
coor_data = &point_data[i * 8 + 3];
id = coor_data[0] & 0x0F;
input_x = coor_data[1] | coor_data[2] << 8;
input_y = coor_data[3] | coor_data[4] << 8;
input_w = coor_data[5] | coor_data[6] << 8;
gtp_touch_down(ts, id, input_x, input_y, input_w);
}
}else if(pre_touch){
dprintk(DEBUG_X_Y_INFO, "Touch Release!");
gtp_touch_up(ts, 0);
}
pre_touch = touch_num;
//本例中最大触点,为获取的值,非固定。
input_sync(ts->input_dev);
//在提交输入设备的事件后必须用下列方法使事件同步,让它告知input系统,设备驱动已经发出了一个完整的报告。
由于多点触摸技术需要采集到多个点,然后再一起处理这些点,所以在软件实现中需要保证每一波点的准确性和完整性。因此,Linux内核提供了input_mt_sync(struct input_dev * input)函数。在每波的每个点上报后需要紧跟一句input_mt_sync(), 当这波所有点上报后再使用input_sync()进行同步。例如一波要上报3个点:
……………..
input_mt_sync(input);
……………..
input_mt_sync(input);
……………..
input_mt_sync(input);
input_sync(input);
注:即使是仅上报一个点的单点事件,也需要一次input_my_sync。
至此input子系统可以从以上三方面的函数调用来分析。