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子系統可以從以上三方面的函數調用來分析。