msg2133觸控螢幕(TP原始碼學習)
強調:下面的裝置指觸控螢幕
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_RSTnRetVal =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;};