前段時間負責項目的觸摸屏和光電滑鼠,都是輸入裝置,看了會這方面的資料,結合項目代碼,做點總結,基本上來自個人了解和網際網路
在linux2.6以後,linux對輸入裝置進行了抽象,抽象出了輸入子系統,該系統(Input子系統)是所有I/O裝置驅動的中間層,為上層提供了一個統一的界面,将事件的上報和處理分離開,采用了分層模式,在我們的driver中,我們隻需要關注事件的上報,其他的都由linux自己處理。在上層系統中,它不需要知道底層有多少鍵盤,滑鼠,軌迹球,觸摸屏等裝置,隻需要把上報上來的input事件做相應的處理就行了。
一.Input輸入子系統的架構
Input子系統将整個将輸入驅動分成三塊: driver,input core和Event handler. 一個輸入事件,如滑鼠移動,鍵盤按鍵按下,joystick的移動,觸摸屏的點選滑動等等通過 Driver -> InputCore -> Eventhandler -> userspace 的順序到達使用者空間傳給應用程式
而在我們的平時的驅動開發中,往往需要做的隻是input driver這一層,其他的基本上都由linux做好了,不需要任何改動,除非需要加入新的功能支援之類的(如想把多點觸摸的支援添加進來等)
二.一個簡單使用input子系統的例子
在核心自帶的文檔Documentation/input/input-programming.txt中。有一個使用input子系統的例子,并附帶相應的說明。初學者可以以此例學習
view plain
- #include <linux/input.h>
- #include <linux/module.h>
- #include <linux/init.h>
- #include <asm/irq.h>
- #include <asm/io.h>
- static struct input_dev *button_dev;
- static irqreturn_t button_interrupt(int irq, void *dummy)
- {
- input_report_key(button_dev, BTN_0, inb(BUTTON_PORT) & 1);
- input_sync(button_dev);
- return IRQ_HANDLED;
- }
- static int __init button_init(void)
- {
- int error;
- if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL)) {
- printk(KERN_ERR "button.c: Can't allocate irq %d/n", button_irq);
- return -EBUSY;
- }
- button_dev = input_allocate_device();
- if (!button_dev) {
- printk(KERN_ERR "button.c: Not enough memory/n");
- error = -ENOMEM;
- goto err_free_irq;
- }
- button_dev->evbit[0] = BIT_MASK(EV_KEY);
- button_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);
- error = input_register_device(button_dev);
- if (error) {
- printk(KERN_ERR "button.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);
這個例子比較簡單,沒有具體的硬體裝置,但程式裡包含了最基本的input子系統驅動的注冊流程。
1)在初始化函數中申請了一個input device
button_dev = input_allocate_device();
2)注冊
input_register_device(button_dev);
3)還需要申請一個中斷處理例程,在中斷處理例程中将收到的按鍵資訊上報給input子系統
request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL)
4)還有一個需要注意的就是上報資料的參數設定,告知input子系統它支援上報的事件
button_dev->evbit[0] = BIT_MASK(EV_KEY);
button_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);
分别用來設定裝置所産生的事件以及上報的按鍵值。Struct iput_dev中有兩個成員,一個是evbit.一個是keybit.分别用表示裝置所支援的動作和按鍵類型。
5)input_report_key()
用于給上層上報一個按鍵動作
input_sync()
用來告訴上層,本次的上報事件已經完成了.
三.input core和Event handler
另外在input子系統中還有很多關于input core和Event handler等方面的介紹,這些主要是linux核心的工作,在我們driver開發中用的不多,也不是我們所關注的重點,我在這裡不做多的描述,如果感興趣的話可以去網上去找,很很多這方面的資源
四.G15項目中的觸摸屏驅動
驅動程式流程和上述流程基本一緻,但由于涉及到了具體的硬體裝置,在一些細節處理方面會不一樣,也會複雜一些
Host端通過IIC總線,從晶片讀出需要的資料,一般為X,Y的絕對坐标,還有資料的标志位(單點,多點,未點選)等,這些就夠了。有些晶片還有手勢之類的寄存器值,但在linux/android系統中一般都沒有使用,在上層處理都是用坐标值的變化來算出手勢的。
初始化函數如下:
view plain
- static int micco_ts_probe(struct platform_device *pdev)
- {
- int ret;
- struct proc_dir_entry *micco_ts_proc_entry;
- micco_td = kzalloc(sizeof(struct micco_ts_data), GFP_KERNEL);
- if (!micco_td) {
- ret = -ENOMEM;
- goto micco_ts_out;
- }
- //micco_td->pen_state = TSI_PEN_UP;
- pen_state = TSI_PEN_UP;
- micco_td->suspended = 0;
- micco_td->use_count = 0;
- mutex_init(&mutex);
- micco_ts_input_dev = input_allocate_device(); //申請一個input裝置
- if (micco_ts_input_dev == NULL) {
- printk(KERN_ERR "%s: failed to allocate input dev/n",__FUNCTION__);
- return -ENOMEM;
- }
- //填充input 結構體,這些是對input裝置的一些屬性描述,名稱,總線等
- micco_ts_input_dev->name = "micco-ts";
- micco_ts_input_dev->phys = "micco-ts/input0";
- micco_ts_input_dev->dev.parent = &pdev->dev;
- micco_ts_input_dev->open = new_ts_open;
- micco_ts_input_dev->close = new_ts_close;
- micco_ts_input_dev->id.bustype = BUS_I2C;
- //所支援的事件
- set_bit(EV_SYN, micco_ts_input_dev->evbit);
- set_bit(EV_KEY, micco_ts_input_dev->evbit);
- set_bit(BTN_TOUCH, micco_ts_input_dev->keybit);
- set_bit(BTN_TOUCH2, micco_ts_input_dev->keybit);
- set_bit(EV_ABS, micco_ts_input_dev->evbit);
- input_set_abs_params(micco_ts_input_dev, ABS_MT_POSITION_X, 0, 0x31f, 0, 0);
- input_set_abs_params(micco_ts_input_dev, ABS_MT_POSITION_Y, 0, 0x1dF, 0, 0);
- input_set_abs_params(micco_ts_input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
- input_set_abs_params(micco_ts_input_dev, ABS_MT_WIDTH_MAJOR, 0, 15, 0, 0);
- //初始化一個工作隊列,用于排程中斷處理例程的底半部
- INIT_WORK(&work, new_ts_work);
- //向系統注冊input裝置
- ret = input_register_device(micco_ts_input_dev);
- if (ret) {
- printk(KERN_ERR
- "%s: unabled to register input device, ret = %d/n",
- __FUNCTION__, ret);
- return ret;
- }
- init_ts(); //晶片的一些初始化工作
- if (gpio_request(mfp_to_gpio(MFP_CFG_PIN(TP_INT)), "TP INT")) {
- gpio_free(mfp_to_gpio(MFP_CFG_PIN(TP_INT)));
- printk(KERN_ERR "Request GPIO failed,"
- "gpio: %d/n", TP_INT);
- return 0;
- }
- gpio_direction_input(mfp_to_gpio(MFP_CFG_PIN(TP_INT)));
- //用TP_INT腳申請一個中斷,當手指觸摸到屏,将産生中斷,進入中斷處理程式,讀取資料并上報
- if (request_irq(TP_IRQ_NO, new_ts_isr,IRQF_TRIGGER_FALLING, "micco-ts", NULL)) {
- printk(KERN_ERR "micco_touch.c: Can't allocate irq %d/n", TP_IRQ_NO);
- return -EBUSY;
- }
- //屏蔽中斷,在open時再打開
- disable_irq_nosync(TP_IRQ_NO);
- //printk("disable_irq_nosync(TP_IRQ_NO)mark2/n");
- enable_irq_flag = 0;
- if (ret < 0)
- goto pmic_cb_out;
- micco_ts_proc_entry = create_proc_entry("driver/micc_ts", 0, NULL);
- if (micco_ts_proc_entry) {
- micco_ts_proc_entry->write_proc = micco_ts_proc_write;
- }
- return 0;
- pmic_cb_out:
- input_unregister_device(micco_ts_input_dev);
- micco_ts_out:
- kfree(micco_td);
- return ret;
- }
中斷處理頂半部
static irqreturn_t new_ts_isr(int irq, void *dev_data)
{
schedule_work(&work);
return IRQ_HANDLED;
}
隻是簡單的排程了工作隊列,以下才是真正的中斷處理(底半部)
view plain
- static void new_ts_work(struct work_struct *work)
- {
- u16 tem_x1, tem_y1;
- u16 tem_x2, tem_y2;
- u8 pen_down;
- int pen_up_already_reported = 0;
- mutex_lock(&mutex);
- pen_down=tsi2c_byte_read(SLAVE_ADDR,CAP_TP_MODE);
- //printk("new_ts_work pen_down is %x/n",pen_down);
- if(129==pen_down)
- {
- pen_state = TSI_PEN_DOWN;
- cap_tp_read(&tem_x1, &tem_y1,1);
- // TOUCHSCREEN_CONSOLE_PRINT_XY1;
- input_report_abs(micco_ts_input_dev, ABS_MT_TOUCH_MAJOR, 1);
- input_report_abs(micco_ts_input_dev, ABS_MT_WIDTH_MAJOR,1);
- input_report_abs(micco_ts_input_dev, ABS_MT_POSITION_X, tem_x1&0x3ff);
- input_report_abs(micco_ts_input_dev, ABS_MT_POSITION_Y, tem_y1&0x3ff);
- input_mt_sync(micco_ts_input_dev);
- input_sync(micco_ts_input_dev);
- pen_up_already_reported = 0;
- }
- else if(130==pen_down)
- {
- pen_state = TSI_PEN_DOWN;
- cap_tp_read(&tem_x1, &tem_y1,1);
- cap_tp_read(&tem_x2, &tem_y2,2);
- //TOUCHSCREEN_CONSOLE_PRINT_XY1;
- //TOUCHSCREEN_CONSOLE_PRINT_XY2;
- input_report_abs(micco_ts_input_dev, ABS_MT_TOUCH_MAJOR, 1);
- input_report_abs(micco_ts_input_dev, ABS_MT_WIDTH_MAJOR,1);
- input_report_abs(micco_ts_input_dev, ABS_MT_POSITION_X, tem_x1&0x3ff);
- input_report_abs(micco_ts_input_dev, ABS_MT_POSITION_Y, tem_y1&0x3ff);
- input_mt_sync(micco_ts_input_dev);
- input_report_abs(micco_ts_input_dev, ABS_MT_TOUCH_MAJOR, 100);
- input_report_abs(micco_ts_input_dev, ABS_MT_WIDTH_MAJOR,1);
- input_report_abs(micco_ts_input_dev, ABS_MT_POSITION_X, tem_x2&0x3ff);
- input_report_abs(micco_ts_input_dev, ABS_MT_POSITION_Y, tem_y2&0x3ff);
- input_mt_sync(micco_ts_input_dev);
- input_sync(micco_ts_input_dev);
- pen_up_already_reported = 0;
- }
- else
- {
- if((pen_state != TSI_PEN_UP) && !pen_up_already_reported) {
- input_report_abs(micco_ts_input_dev, ABS_MT_TOUCH_MAJOR, 0);
- input_report_abs(micco_ts_input_dev, ABS_MT_WIDTH_MAJOR, 0);
- input_mt_sync(micco_ts_input_dev);
- input_sync(micco_ts_input_dev);
- pen_up_already_reported = 1;
- }
- pen_state = TSI_PEN_UP;
- }
- mutex_unlock(&mutex);
- }
五.單點觸摸&多點觸摸
如上面的例子所示,已經包含了多點觸摸的功能,現在簡單介紹一下單點觸摸和多點觸摸在驅動實作時的差別
要實作多點觸摸,必須要有核心的支援,也就是說input子系統中,input core和Event handler層要支援多點,在linux官方2.6.30中才加入了多點支援,但現在很多2.6.29的BSP中也給多點觸摸打了更新檔,也是可以用的,G15項目中的就是這樣
首先,在probe函數的裝置初始化階段
input_set_abs_params()函數,設定方式不同
單點:
input_set_abs_params(micco_ts_input_dev, ABS_X, 0, 0x31f, 0, 0);
input_set_abs_params(micco_ts_input_dev, ABS_Y, 0, 0x1df, 0, 0);
input_set_abs_params(micco_ts_input_dev, ABS_PRESSURE, 0, 255, 0, 0);
input_set_abs_params(micco_ts_input_dev, ABS_TOOL_WIDTH, 0, 15, 0, 0);
多點:
input_set_abs_params(micco_ts_input_dev, ABS_MT_POSITION_X, 0, 0x31f, 0, 0);
input_set_abs_params(micco_ts_input_dev, ABS_MT_POSITION_Y, 0, 0x1dF, 0, 0);
input_set_abs_params(micco_ts_input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
//相當于單點屏的 ABX_PRESSURE
input_set_abs_params(micco_ts_input_dev, ABS_MT_WIDTH_MAJOR, 0, 15, 0, 0);
//相當于單點屏的 ABS_TOOL_WIDTH
其次,就是上報資料的方式
單點上報:
input_report_abs(micco_ts_input_dev,ABS_X,tem_x&0x3ff);
input_report_abs(micco_ts_input_dev,ABS_Y,tem_y&0x3ff);
input_report_key(micco_ts_input_dev, BTN_TOUCH, 1);
input_sync(micco_ts_input_dev);
即
ABS_X
ABS_Y
SYN_REPORT
多點上報:
input_report_abs(micco_ts_input_dev, ABS_MT_TOUCH_MAJOR, 100);
input_report_abs(micco_ts_input_dev, ABS_MT_WIDTH_MAJOR,1);
input_report_abs(micco_ts_input_dev, ABS_MT_POSITION_X, tem_x1&0x3ff);
input_report_abs(micco_ts_input_dev, ABS_MT_POSITION_Y, tem_y1&0x3ff);
input_mt_sync(micco_ts_input_dev);
input_report_abs(micco_ts_input_dev, ABS_MT_TOUCH_MAJOR, 100);
input_report_abs(micco_ts_input_dev, ABS_MT_WIDTH_MAJOR,1);
input_report_abs(micco_ts_input_dev, ABS_MT_POSITION_X, tem_x2&0x3ff);
input_report_abs(micco_ts_input_dev, ABS_MT_POSITION_Y, tem_y2&0x3ff);
input_mt_sync(micco_ts_input_dev);
input_sync(micco_ts_input_dev);
即
for(int i;i<n;i++)
{
ABS_MT_POSITION_X
ABS_MT_POSITION_Y
SYN_MT_REPORT
}
SYN_REPORT
以上隻是單點和多點的在驅動開發時的主要差別,當然還有其他很多方面的差異,如硬體支援等,如果對多點觸摸感興趣的話可以去仔細閱讀一下linux的核心文檔Documentation / input / multi-touch-protocol.txt