強調:下面的設備指觸摸屏
ABS:絕對值
1.input子系統簡介
Linux輸入設備總類繁雜,常見的包括有按鍵、鍵盤、觸摸屏、鼠標、搖桿等等,他們本身就是字符設備,而linux內核將這些設備的共同性抽象出來,簡化驅動開發建立了一個input子系統。子系統共分為三層,如圖1所示。
圖1
驅動層和硬件相關,直接捕捉和獲取硬件設備的數據信息等(包括觸摸屏被按下、按下位置、鼠標移動、鍵盤按下等等),然後將數據信息報告到核心層。核心層負責連接驅動層和事件處理層,設備驅動(device driver)和處理程序(handler)的注冊需要通過核心層來完成,核心層接收來自驅動層的數據信息,並將數據信息選擇對應的handler去處理,最終handler將數據復制到用戶空間。
所有的inputdevice在注冊後會加入一個input_dev_list(輸入設備鏈表),所有的eventhandler在注冊後會加入一個input_handler_list(輸入處理程序鏈表),這裡的list_head主要的作用是作為input_dev_list和input_handler_list的一個節點來保存地址。Input_dev_list和input_handler_list之間的對應關系由input_handle結構體橋接
input_handle是用來關聯input_dev和input_handler。為什麼用input_handle來關聯input_dev和input_handler而不將input_dev和input_handler直接對應呢?因為一個device可以對應多個handler,而一個handler也可處理多個device。就如一個觸摸屏設備可以對應event handler也可以對應tseve handler。
input_dev、input_handler、input_handle的關系如下圖2所示。
圖2
2.觸摸屏驅動簡介
流程
在Linux中,Input設備用input_dev結構體描述,定義在input.h中。設備的驅動只需按照如下步驟就可實現了。
1).在驅動模塊加載函數中設置Input設備支持input子系統的哪些事件;
2).將Input設備注冊到input子系統中;
3).在Input設備發生輸入操作時(如:鍵盤被按下/抬起、觸摸屏被觸摸/抬起/移動、鼠標被移動/單擊/抬起時等),提交所發生的事件及對應的鍵值/坐標等狀態。
2.1 input device的注冊
Inputdevice的注冊實際上僅僅只有幾行代碼,因為在input.c中已經將大量的代碼封裝好了,主需要調用幾個關鍵的函數就能完成對input device的注冊。
在xxx_ts.c中預先定義全局變量structinput_devtsdev;然後進入到初始化函數
一個完整input設備系統不僅要有設備,還需要有處理程序input_handler
2.2 input handler的注冊
Input_handler是要和用戶層打交道的,在evdev.c中直接定義了一個input_handler結構體並初始化了一些內部成員變量。
2.3 數據傳遞過程
從硬件設備(觸摸屏)中獲得的數據需要經過input.c選擇相應的handler進行處理,最後上報到用戶空間。如何從硬件設備(觸摸屏)中獲得數據,那就得看xxx_ts.c中的代碼了,在xxx_ts.c中的源代碼是直接和硬件設備相關的。在xxx_ts.c中我們一方面要完成觸摸屏設備相關的寄存器配置,另一方面要完成將獲得的數據上報。
至於設備初始化配置方面,根據每個人使用的ARM芯片的Datasheet去初始化配置寄存器,這裡也不需要多說了。不管是通過查詢法還是中斷法(我沒見過用查詢的),當觸摸屏按下時候我們會得到觸摸屏被按下的相關數據(主要是被按下的X和Y坐標值),然後需要將數據信息上報,在觸摸屏被按下的時候需要上報:
input_report_key(tsdev, BTN_TOUCH, 1); //報告按鍵被按下事件
input_report_abs(tsdev, ABS_X, x); //報告觸摸屏被按下的x坐標值
input_report_abs(tsdev, ABS_Y, y); //報告觸摸屏被按下的y坐標值
input_report_abs(tsdev, ABS_PRESSURE, 1);//報告觸摸屏被按下的壓力值(0或者1)
input_sync(tsdev); //報告同步事件,表示一次事件結束
當觸筆從觸摸屏上抬起時需要上報:
input_report_key(tsdev, BTN_TOUCH, 0); //報告按鍵被松開事件
input_report_abs(tsdev, ABS_PRESSURE, 0);//報告觸摸屏被按下的壓力值(0或者1)
input_sync(tsdev); //報告同步事件,表示一次事件結束
2.4 數據讀取過程
讀取就變得很簡單了,做過linux編程的都能知道,只要在應用中定義了個input_event結構體,通過open打開設備,然後進行read即可。
2.5
3.Msg2133A驅動代碼學習
3.1 touch_driver_probe()
所涉及的文件及一些主要函數關系如下:
圖3
(1)mstar_drv_platform_porting_layer.c:DrvPlatformLyrInputDeviceInitialize()
//為一個新的輸入設備分配內容,返回一個預先准備好的結構體input_dev,並讓//g_InputDevice指向它。 /* allocate an input device */ g_InputDevice = input_allocate_device(); if (g_InputDevice == NULL) { DBG("*** input device allocation failed ***\n"); return -ENOMEM; } //phys:系統層次結構中觸摸屏設備的物理路徑,這裡的觸摸屏設備是掛載在//I2C總線上的;id:設備的id,對應結構體input_id,設備采用的總線是I2C。 g_InputDevice->name= pClient->name; g_InputDevice->phys = "I2C"; g_InputDevice->dev.parent= &pClient->dev; g_InputDevice->id.bustype= BUS_I2C; //設置設備支持的事件類型,evbit表示設備支持的事件類型,這裡支持 EV_ABS:絕對坐標事件,用於搖桿 EV_SYN:同步事件 EV_KEY:鍵盤事件 Keybit表示設備支持的按鍵類型,BTN_TOUCH表示觸摸按鍵;propbit表示設備屬性和兼容(quirks),INPUT_PROP_DIRECT表示直接的輸入設備 /* set the supported event type for input device */ set_bit(EV_ABS, g_InputDevice->evbit); set_bit(EV_SYN, g_InputDevice->evbit); set_bit(EV_KEY, g_InputDevice->evbit); set_bit(BTN_TOUCH, g_InputDevice->keybit); set_bit(INPUT_PROP_DIRECT, g_InputDevice->propbit); // when touch panel support virtual key(EX.Menu, Home, Back, Search)定義此宏,input_set_capability()作用是標記設備有能力處理按鍵事件(EV_KEY),可處理的事件code由g_TpVirtualKey[i]指定,這裡支持menu、home、back和search。 #ifdef CONFIG_TP_HAVE_KEY // Method 1. { u32 i; for (i = 0; i < MAX_KEY_NUM; i ++) { input_set_capability(g_InputDevice, EV_KEY, g_TpVirtualKey[i]); } } #endif
// ABS_MT_TOUCH_MAJOR表示描述了主接觸面的長軸
ABS_MT_WIDTH_MAJOR表示了接觸工具的長軸,比如
ABS_MT_TOUCH_MAJOR/ABS_MT_WIDTH_MAJOR,永遠小於1,並且和壓力有關,壓力越大,越接近1。
ABS_MT_POSITION_X接觸位置的中心點X坐標
ABS_MT_POSITION_Y接觸位置的中心點Y坐標
input_set_abs_params(g_InputDevice,ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); input_set_abs_params(g_InputDevice,ABS_MT_WIDTH_MAJOR, 0, 15, 0, 0); //對於X軸范圍是TOUCH_SCREEN_X_MIN到TOUCH_SCREEN_X_MIN,數據誤差是-0到+0,中心平滑位置是0。 input_set_abs_params(g_InputDevice,ABS_MT_POSITION_X, TOUCH_SCREEN_X_MIN, TOUCH_SCREEN_X_MAX, 0, 0); input_set_abs_params(g_InputDevice,ABS_MT_POSITION_Y, TOUCH_SCREEN_Y_MIN, TOUCH_SCREEN_Y_MAX, 0, 0); //向input子系統注冊輸入設備 /* register the input device to input sub-system */ nRetVal = input_register_device(g_InputDevice); if (nRetVal < 0) { DBG("*** Unable to register touch input device ***\n"); }
(2)mstar_drv_platform_porting_layer.c:DrvPlatformLyrTouchDeviceRequestGPIO()
此函數就是申請復位和中斷引腳的GPIO。
#define MS_TS_MSG_IC_GPIO_RST 12 #define MS_TS_MSG_IC_GPIO_INT 13 #define MSM_TS_MSG_IC_GPIO_RST (MS_TS_MSG_IC_GPIO_RST+911) #define MSM_TS_MSG_IC_GPIO_INT (MS_TS_MSG_IC_GPIO_INT+911) //向申請MSM_TS_MSG_IC_GPIO_RST這個GPIO,其名字為為C_TP_RST nRetVal =gpio_request(MSM_TS_MSG_IC_GPIO_RST, "C_TP_RST"); if (nRetVal < 0) { DBG("*** Failed to request GPIO %d, error %d ***\n",MSM_TS_MSG_IC_GPIO_RST, nRetVal); } nRetVal = gpio_request(MSM_TS_MSG_IC_GPIO_INT,"C_TP_INT"); if (nRetVal < 0) { DBG("*** Failed to request GPIO %d, error %d ***\n",MSM_TS_MSG_IC_GPIO_INT, nRetVal); }
(3)mstar_drv_platform_porting_layer.c:DrvPlatformLyrTouchDevicePowerOn()
// 復位觸摸屏IC,gpio_direction_output()在某個GPIO口寫上某個值之後,還會把這個端口設置為輸出模式。
gpio_direction_output(MSM_TS_MSG_IC_GPIO_RST,1); udelay(100); gpio_set_value(MSM_TS_MSG_IC_GPIO_RST, 0); udelay(100); gpio_set_value(MSM_TS_MSG_IC_GPIO_RST, 1); mdelay(25);
(4)mstar_drv_main.c :DrvMainTouchDeviceInitialize()
主要是創建procfs文件系統目錄入口和創建/kernel/kset_example/kobject_example目錄
圖4
(5)mstar_drv_ic_fw_porting_layer.c:DrvIcFwLyrGetChipType()
通過調用mstar_drv_self_fw_control.c:DrvFwCtrlGetChipType()獲取觸摸屏IC類型,msg2133A對應的為2
(6)mstar_drv_self_fw_control.c:DrvFwCtrlGetChipType()
獲取觸摸屏IC芯片類型,比如#defineCHIP_TYPE_MSG21XXA (0x02)
(7)mstar_drv_platform_porting_layer.c:DrvPlatformLyrTouchDeviceResetHw()
通過控制復位引腳復位hw,和DrvPlatformLyrTouchDevicePowerOn()實現一樣。
(8)mstar_drv_platform_porting_layer.c:DrvPlatformLyrTouchDeviceRegisterFingerTouchInterruptHandler()
//初始化手指觸摸工作隊列,並將此工作隊列和處理函數_DrvPlatformLyrFingerTouchDoWork()綁定。 /* initialize the finger touch work queue*/ INIT_WORK(&_gFingerTouchWork,_DrvPlatformLyrFingerTouchDoWork); //返回中斷標號給_gIrq。 _gIrq =gpio_to_irq(MSM_TS_MSG_IC_GPIO_INT); //申請一個IRQ和注冊對應的ISR,當IRQ發生的時候,會調用ISR(這裡為_DrvPlatformLyrFingerTouchInterruptHandler()) /*request an irq and register the isr */ nRetVal =request_threaded_irq(_gIrq/*MS_TS_MSG_IC_GPIO_INT*/, NULL,_DrvPlatformLyrFingerTouchInterruptHandler,IRQF_TRIGGER_RISING | IRQF_ONESHOT/*| IRQF_NO_SUSPEND *//* IRQF_TRIGGER_FALLING */, "msg2xxx",NULL); _gInterruptFlag = 1;
(9)mstar_drv_platform_porting_layer.c:DrvPlatformLyrTouchDeviceRegisterEarlySuspend()
//注冊通知器,什麼時候調用呢?
_gFbNotifier.notifier_call = MsDrvInterfaceTouchDeviceFbNotifierCallback; fb_register_client(&_gFbNotifier);
(10)
3.2 手指觸摸觸摸屏的處理過程
從上面可知當觸摸TP時,對應的中斷會產生,然後調用_DrvPlatformLyrFingerTouchInterruptHandler()。
圖5
(1)_DrvPlatformLyrFingerTouchInterruptHandler()
spin_lock_irqsave(&_gIrqLock,nIrqFlag); if(_gInterruptFlag == 1) { //disable_irq_nosync()關閉中斷,不等待中斷處理完成 disable_irq_nosync(_gIrq); _gInterruptFlag = 0; schedule_work(&_gFingerTouchWork); } spin_unlock_irqrestore(&_gIrqLock,nIrqFlag);
初始化後_gInterruptFlag就是1,schedule_work(&_gFingerTouchWork);這裡會調用_DrvPlatformLyrFingerTouchDoWork()
(2)_DrvPlatformLyrFingerTouchDoWork()
主要通過調用DrvIcFwLyrHandleFingerTouch來處理觸摸動作。
DrvIcFwLyrHandleFingerTouch(NULL, 0); spin_lock_irqsave(&_gIrqLock,nIrqFlag); if (_gInterruptFlag == 0) { //使能一個IRQ的處理,便於響應下個觸摸動作。 enable_irq(_gIrq); _gInterruptFlag = 1; } spin_unlock_irqrestore(&_gIrqLock,nIrqFlag); nReportPacketLength =DEMO_MODE_PACKET_LENGTH; pPacket = g_DemoModePacket; rc = IicReadData(SLAVE_I2C_ID_DWI2C,&pPacket[0], nReportPacketLength); if (rc < 0) { DBG("I2C read packet data failed, rc = %d\n", rc); goto TouchHandleEnd; }
(3)DrvIcFwLyrHandleFingerTouch()
調用DrvFwCtrlHandleFingerTouch()來處理
(4)DrvFwCtrlHandleFingerTouch()
DrvFwCtrlHandleFingerTouch
通過調用_DrvFwCtrlParsePacket()來解析數據,然後上報數據。
(5)_DrvFwCtrlParsePacket()
通過I2C讀取到msg2133a發送回來的8個字節數據包,這8個字節數據的意義
/*
pPacket[0]:id, pPacket[1]~pPacket[3]:thefirst point abs, pPacket[4]~pPacket[6]:the relative distance between the firstpoint abs and the second point abs
when pPacket[1]~pPacket[4], pPacket[6] is0xFF, keyevent, pPacket[5] to judge which key press.
pPacket[1]~pPacket[6] all are 0xFF, releasetouch
*/
對應msg2133A來說,pPacket[0]=0x52,pPacket[1]~pPacket[3]是ADC采樣值,需要轉換為x和y坐標值,轉換的公式為x=(x對應的AD采樣值*480)/2048,y的類似。
(6)DrvPlatformLyrFingerTouchPressed()
上報事件
input_report_key(g_InputDevice, BTN_TOUCH,1); input_report_abs(g_InputDevice, ABS_MT_TOUCH_MAJOR,1); input_report_abs(g_InputDevice,ABS_MT_WIDTH_MAJOR, 1); input_report_abs(g_InputDevice,ABS_MT_POSITION_X, nX); input_report_abs(g_InputDevice,ABS_MT_POSITION_Y, nY); input_mt_sync(g_InputDevice);
(7)
3.3 虛擬按鍵實現
when pPacket[1]~pPacket[4], pPacket[6] is0xFF, keyevent, pPacket[5] to judge which key press.根據我們觸摸屏絲印按鍵有menu、home、back和search,按下這幾個位置pPacket[5]的值分別為4、8、1、2,然後在DrvFwCtrlHandleFingerTouch函數中做對應的處理即可:
const int g_TpVirtualKey[] = {TOUCH_KEY_MENU,TOUCH_KEY_HOME, TOUCH_KEY_BACK, TOUCH_KEY_SEARCH}; if (tInfo.nTouchKeyCode != 0) { #ifdef CONFIG_TP_HAVE_KEY if (tInfo.nTouchKeyCode == 4)// TOUCH_KEY_MENU { nTouchKeyCode=g_TpVirtualKey[0]; } else if (tInfo.nTouchKeyCode ==1) // TOUCH_KEY_BACK { nTouchKeyCode =g_TpVirtualKey[2]; } else if (tInfo.nTouchKeyCode ==2) //TOUCH_KEY_SEARCH { nTouchKeyCode =g_TpVirtualKey[3]; } else if (tInfo.nTouchKeyCode ==8) //TOUCH_KEY_HOME { nTouchKeyCode =g_TpVirtualKey[1]; } if (nLastKeyCode !=nTouchKeyCode) { DBG("key touchpressed\n"); DBG("nTouchKeyCode =%d, nLastKeyCode = %d\n", nTouchKeyCode, nLastKeyCode); nLastKeyCode =nTouchKeyCode; input_report_key(g_InputDevice,BTN_TOUCH, 1); input_report_key(g_InputDevice, nTouchKeyCode, 1); input_sync(g_InputDevice); } #endif //CONFIG_TP_HAVE_KEY
3.4
4.調試方法
4.1 調試串口
4.2 Adb
(1)getevent,按鍵觸摸屏
圖6
(2)busybox hexdump /dev/input/event2
圖7
對應下面的代碼
struct timeval { __kernel_time_t tv_sec; /*seconds */ __kernel_suseconds_t tv_usec; /*microseconds */ }; /* *The event structure itself */ struct input_event { structtimeval time; __u16type; __u16code; __s32value; };