强调:下面的设备指触摸屏
ABS:绝对值
1. input子系统简介
Linux输入设备总类繁杂,常见的包括有按键、键盘、触摸屏、鼠标、摇杆等等,他们本身就是字符设备,而linux内核将这些设备的共同性抽象出来,简化驱动开发建立了一个input子系统。子系统共分为三层,如图1所示。
图1
驱动层和硬件相关,直接捕捉和获取硬件设备的数据信息等(包括触摸屏被按下、按下位置、鼠标移动、键盘按下等等),然后将数据信息报告到核心层。核心层负责连接驱动层和事件处理层,设备驱动(device driver)和处理程序(handler)的注册需要通过核心层来完成,核心层接收来自驱动层的数据信息,并将数据信息选择对应的handler去处理,最终handler将数据复制到用户空间。
所有的input device在注册后会加入一个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设备发生输入操作时(如:键盘被按下/抬起、触摸屏被触摸/抬起/移动、鼠标被移动/单击/抬起时等),提交所发生的事件及对应的键值/坐标等状态。
参考:
ARM Linux内核Input输入子系统浅解
http://www.51hei.com/bbs/dpj-27652-1.html
2.1 input device的注册
Input device的注册实际上仅仅只有几行代码,因为在input.c中已经将大量的代码封装好了,主需要调用几个关键的函数就能完成对input device的注册。
在xxx_ts.c中预先定义全局变量structinput_dev tsdev;然后进入到初始化函数
一个完整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; };