/**********************************************************************************************************************/ 1.在i850_gpio.c中: 配置gpio: 两个:一个reset,一个中断 { MX51_PIN_EIM_A26, IOMUX_CONFIG_ALT5 | IOMUX_CONFIG_SION, (PAD_CTL_SRE_SLOW | PAD_CTL_DRV_MEDIUM | PAD_CTL_100K_PU | PAD_CTL_HYS_ENABLE | PAD_CTL_DRV_VOT_HIGH), MUX_IN_HSC_MIPI_MIX_IPP_IND_SENS2_DATA_EN_SELECT_INPUT, INPUT_CTL_PATH0, }, {/*egalax_i2c TouchScreen irq pin*/ MX51_PIN_EIM_OE, IOMUX_CONFIG_ALT1, }, 设置gpio: /*EGALAX_I2C_RESET PIN*/ gpio_request(IOMUX_TO_GPIO(MX51_PIN_EIM_A26), "eim_a26"); gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_EIM_A26), 0); gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_A26), 1); 对其提供gpio控制接口: void egalax_i2c_reset(void) { printk("/n/nEGALAX TS: %s line: %d /n/n", __FUNCTION__, __LINE__); gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_A26), 1); msleep(50); gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_A26), 0); msleep(50); gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_A26), 1); } EXPORT_SYMBOL(egalax_i2c_reset); /**********************************************************************************************************************/ 2.在mxc.h中: 定义平台的私有数据 struct egalax_i2c_platform_data { char *reg_vcam; void (*egalax_i2c_reset) (void); }; 其目的只是为了封装reset功能和供电功能,定义这个私有数据结构不是必须的,但如果这么做的话风格会比较统一。你会发现在这个文件中定义了诸多设备的私有数据结构,大部分中都包含关于供电的内容。或者是设备的gpio配置(如果需要动态配置的话就放在这里,放个函数指针,然后在别的地方实现一个函数,将来给这个域赋个值就好了),等等。总之,你可以在这个结构体内填充你想要要的任何东西,这个结构体对象将来会以空指针的形式传递给设备。 /**********************************************************************************************************************/ 3.在mx51_i850.c中: 首先实例化一个上述的平台私有数据结构的对象。 #ifdef CONFIG_TOUCHSCREEN_EGALAX void egalax_i2c_reset(void); static struct egalax_i2c_platform_data egalax_data = { .reg_vcam = "VCAM", .egalax_i2c_reset = egalax_i2c_reset, }; #endif 这里给reg_vcam域赋值时你要确保在mx51_i850_pmic_mc13892.c中确实有关于一路叫做"VCAM"的供电 在static struct i2c_board_info mxc_i2c0_board_info[] __initdata 数组中添加一项。在这里添加的一项数据便会被注册成i2c client设备,由于该TP是挂在i2c总线上的client设备,所以当该设备注册成功后便会调用我们的驱动代码中的i2c_probe函数,它怎么知道要调用的是这个probe呢,据说是根据名称匹配(我还没仔细跟踪过) #ifdef CONFIG_TOUCHSCREEN_EGALAX { .type = "egalax_i2c", .addr = 0x04,//这个用来区别挂在i2c总线上的每个client,因此具有唯一性,这个值由芯片制造上提供,发送到i2c总线上的数据包中有一项就是这个值,这样,如果设备发现这项值和自己的一样,它就知道这是发给自己的数据。当然,每个设备发送到i2c总线上的数据包也包含设备自己的addr,这样驱动在提取数据是才会有所选择;i2c client设备时会用该值作为从设备文件节点中的路径 .irq = IOMUX_TO_IRQ(MX51_PIN_EIM_OE),//定义中断引脚 .platform_data = (void*)&egalax_data,//关联私有数据 }, #endif BTW:顺便说下,如果你要将一个驱动注册到platform ,也就是你要写一个基于platform的驱动,那么你除了完成驱动程序本身的代码外(驱动中有probe函数),你还应该在这个文件中按照前面类似的方法在这里搞一个注册设备的函数,这样,当设备注册后便会调用对应的驱动的probe函数,在probe函数中你就可以搞你想要的动作了,也就相当于probe就是驱动的main函数!一般我们会在这个文件中注册设备,在驱动文件中注册driver,只有注册了设备后,驱动中的probe才会被调用到。下面是一个复制自该文件的一个例子: static struct mxc_audio_platform_data wm8993_data = {//实例化私有数据 .src_port = 2, .ext_port = 4, .hp_irq = IOMUX_TO_IRQ(MX51_PIN_CSPI1_SS0), .vddio_reg = "VSD", .vdda_reg = "VGEN3", .vddd_reg = "VVIDEO", .vddio = 2800000, .vdda = 1800000, .vddd = 2775000, .sysclk = 24000000, }; static struct platform_device mxc_wm8993_device = {//实例化platform设备 .name = "imx-3stack-wm8993",//设备名,用以匹配驱动 .dev = { .release = mxc_nop_release, .platform_data = &wm8993_data,//关联私有数据 }, }; static void mxc_init_wm8993(void)//初始化设备 { struct clk *mclk, *parent; mclk = clk_get(NULL, "cko2_clk"); if (IS_ERR(mclk)) return; parent = clk_get(NULL, "osc"); if (IS_ERR(parent)) return; clk_set_parent(mclk, parent); clk_set_rate(mclk, 24000000); clk_enable(mclk); platform_device_register(&mxc_wm8993_device);//这是最重要的!把设备注册到platform,成功后就会导致驱动的probe被调用 }; 这些代码完成后,接下来就是在合适的时机调用这个初始化函数即可!所有的设备的初始化调用其实还都在这个文件里,往下看看, /*! * Board specific initialization. */ static void __init mxc_board_init(void)/ 所有的调用都在这个函数里!!!通过这个例子,我们应该很容易就搞出一个注册到platform上的设备!!!!!不难!你甚至可以把一个dvice同时注册到platform中,也注册到i2c总线上,还是这个wm8993,你会发现在i2c中也注册了它,看看这个文件中的static struct i2c_board_info mxc_i2c0_board_info[] __initdata 数组,你会发现第一个元素就是wm8993的!我似乎感觉到我对这里玄机又明白了一些。。。。相当有快感!相当有高潮! /**********************************************************************************************************************/ 最后,因为这个电容TP要支持多点触摸,但是当前的input.h 和 input.c中缺少了一些支持的内容(因为当前的kernel版本比较旧了),因此在www.kernel.org下载了较新的kernel版本并对照着新的该两个文件在原文件中添加了一个支持。 下边是patch diff --git a/drivers/input/input.c b/drivers/input/input.c index c13ced3..9cd14c1 100644 --- a/drivers/input/input.c +++ b/drivers/input/input.c @@ -11,11 +11,13 @@ */ #include <linux/init.h> +#include <linux/types.h> #include <linux/input.h> #include <linux/module.h> #include <linux/random.h> #include <linux/major.h> #include <linux/proc_fs.h> +#include <linux/sched.h> #include <linux/seq_file.h> #include <linux/poll.h> #include <linux/device.h> @@ -29,6 +31,24 @@ MODULE_LICENSE("GPL"); #define INPUT_DEVICES 256 +/* + * EV_ABS events which should not be cached are listed here. + */ +static unsigned int input_abs_bypass_init_data[] __initdata = { + ABS_MT_TOUCH_MAJOR, + ABS_MT_TOUCH_MINOR, + ABS_MT_WIDTH_MAJOR, + ABS_MT_WIDTH_MINOR, + ABS_MT_ORIENTATION, + ABS_MT_POSITION_X, + ABS_MT_POSITION_Y, + ABS_MT_TOOL_TYPE, + ABS_MT_BLOB_ID, + ABS_MT_TRACKING_ID, + 0 +}; +static unsigned long input_abs_bypass[BITS_TO_LONGS(ABS_CNT)]; + static LIST_HEAD(input_dev_list); static LIST_HEAD(input_handler_list); @@ -132,6 +152,11 @@ static void input_start_autorepeat(struct input_dev *dev, int code) } } +static void input_stop_autorepeat(struct input_dev *dev) +{ + del_timer(&dev->timer); +} + #define INPUT_IGNORE_EVENT 0 #define INPUT_PASS_TO_HANDLERS 1 #define INPUT_PASS_TO_DEVICE 2 @@ -156,6 +181,10 @@ static void input_handle_event(struct input_dev *dev, disposition = INPUT_PASS_TO_HANDLERS; } break; + case SYN_MT_REPORT: + dev->sync = 0; + disposition = INPUT_PASS_TO_HANDLERS; + break; } break; @@ -185,6 +214,11 @@ static void input_handle_event(struct input_dev *dev, case EV_ABS: if (is_event_supported(code, dev->absbit, ABS_MAX)) { + if (test_bit(code, input_abs_bypass)) { + disposition = INPUT_PASS_TO_HANDLERS; + break; + } + value = input_defuzz_abs_event(value, dev->abs[code], dev->absfuzz[code]); @@ -1630,10 +1664,20 @@ static const struct file_operations input_fops = { .open = input_open_file, }; +static void __init input_init_abs_bypass(void) +{ + const unsigned int *p; + + for (p = input_abs_bypass_init_data; *p; p++) + input_abs_bypass[BIT_WORD(*p)] |= BIT_MASK(*p); +} + static int __init input_init(void) { int err; + input_init_abs_bypass(); + err = class_register(&input_class); if (err) { printk(KERN_ERR "input: unable to register input_dev class/n"); diff --git a/drivers/input/input.c b/drivers/input/input.c index c13ced3..9cd14c1 100644 --- a/drivers/input/input.c +++ b/drivers/input/input.c @@ -11,11 +11,13 @@ */ #include <linux/init.h> +#include <linux/types.h> #include <linux/input.h> #include <linux/module.h> #include <linux/random.h> #include <linux/major.h> #include <linux/proc_fs.h> +#include <linux/sched.h> #include <linux/seq_file.h> #include <linux/poll.h> #include <linux/device.h> @@ -29,6 +31,24 @@ MODULE_LICENSE("GPL"); #define INPUT_DEVICES 256 +/* + * EV_ABS events which should not be cached are listed here. + */ +static unsigned int input_abs_bypass_init_data[] __initdata = { + ABS_MT_TOUCH_MAJOR, + ABS_MT_TOUCH_MINOR, + ABS_MT_WIDTH_MAJOR, + ABS_MT_WIDTH_MINOR, + ABS_MT_ORIENTATION, + ABS_MT_POSITION_X, + ABS_MT_POSITION_Y, + ABS_MT_TOOL_TYPE, + ABS_MT_BLOB_ID, + ABS_MT_TRACKING_ID, + 0 +}; +static unsigned long input_abs_bypass[BITS_TO_LONGS(ABS_CNT)]; + static LIST_HEAD(input_dev_list); static LIST_HEAD(input_handler_list); @@ -132,6 +152,11 @@ static void input_start_autorepeat(struct input_dev *dev, int code) } } +static void input_stop_autorepeat(struct input_dev *dev) +{ + del_timer(&dev->timer); +} + #define INPUT_IGNORE_EVENT 0 #define INPUT_PASS_TO_HANDLERS 1 #define INPUT_PASS_TO_DEVICE 2 @@ -156,6 +181,10 @@ static void input_handle_event(struct input_dev *dev, disposition = INPUT_PASS_TO_HANDLERS; } break; + case SYN_MT_REPORT: + dev->sync = 0; + disposition = INPUT_PASS_TO_HANDLERS; + break; } break; @@ -185,6 +214,11 @@ static void input_handle_event(struct input_dev *dev, case EV_ABS: if (is_event_supported(code, dev->absbit, ABS_MAX)) { + if (test_bit(code, input_abs_bypass)) { + disposition = INPUT_PASS_TO_HANDLERS; + break; + } + value = input_defuzz_abs_event(value, dev->abs[code], dev->absfuzz[code]); @@ -1630,10 +1664,20 @@ static const struct file_operations input_fops = { .open = input_open_file, }; +static void __init input_init_abs_bypass(void) +{ + const unsigned int *p; + + for (p = input_abs_bypass_init_data; *p; p++) + input_abs_bypass[BIT_WORD(*p)] |= BIT_MASK(*p); +} + static int __init input_init(void) { int err; + input_init_abs_bypass(); + err = class_register(&input_class); if (err) { printk(KERN_ERR "input: unable to register input_dev class/n"); /**********************************************************************************************************************/ 关于代码的分析: 对中断的理解:设备的中断引脚和cpu的某个gpio相连。设备会主动产生中断,通过边缘触发,或者高电平、低电平触发来通知cpu中断的发生。当cpu得到这个中断信号后会调用中断处理函数,那么你应该在这个函数中进行你需要做的事情,如果你要处理的任务较耗时,那么就把你要做的工作放到底半部中去,然后只要在这个中断处理函数中调度一下底半部的工作就可以了(假设你采用工作队列的方式),这样你就可以让中断处理函数立刻返回,而具体工作则由工作队列在接下来的时间去完成。在当前这个TP驱动中,我们便采用工作队列的方式。简单描述如下: 没有触摸时irq引脚为高电平,当触摸后irq为高电平,同时触发中断,导致中断处理函数开始运行,该函数会将底半部推入工作队列,而后立刻退出(这个动作很快结束)。继而底半部开始运行。底半部会读取i2c总线上的数据,然后做出相应的处理,然后放弃cpu并等待下一次调度(这里会调用schedule()函数,该函数的作用是选择一个合适的进程在当前CPU上执行,即当前进程会被换出cpu(应该进入运行队列吧),这样当前进程就只能等待下次被调度到了),当下一次调度开始后它会先检查irq引脚的状态,如果为高电平则底半部动作结束(这也意味着本次对中断的处理完全结束了!),如果为低电平,则说明TP又有数据送到i2c总线,那么去i2c总线上读取数据并做相应处理,然后继续放弃cpu。。。。。如此反复 //关于通过input子系统上报动作的代码是比较拗口的! static void ProcessReport(unsigned char *buf, int buflen) { int i; short X=0, Y=0, ContactID=0, Status=0; DBG(); if(buflen!=MAX_I2C_LEN || buf[0]!=0x04) // check buffer len & header return; Status = buf[1]&0x01; ContactID = (buf[1]&0x7C)>>2; X = ((buf[3]<<8) + buf[2])>>4; Y = ((buf[5]<<8) + buf[4])>>4; PointBuf[ContactID].Status = Status; PointBuf[ContactID].X = X; PointBuf[ContactID].Y = Y; TS_DEBUG("Get Point[%d] Update: Status=%d X=%d Y=%d/n", ContactID, Status, X, Y); // Send point report to Android 只有当手指抬起的时候Status才会为0,因此 !Status 可以保证对于触碰结束的那条信息能够被上报, (ContactID <= LastUpdateID)可以保证每个单点信息均被上报。当多点信息到来时,能够在每次收集完所有的点后才上报(一次报完)。收集点的顺序为更具ID有大到小,即当收集到ID为0的那个点后才会一次性上报。 if( !Status || (ContactID <= LastUpdateID) )//@CPP:代码是对的,但是真的不好理解 { for(i=0; i<MAX_SUPPORT_POINT;i++) { if(PointBuf[i].Status >= 0)//任何一点的状态都只有两个0或1.如果为-1,则说明该点没有被按下或抬起,那么对于该点就什么也不上报。 { input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, PointBuf[i].Status); input_report_abs(input_dev, ABS_MT_WIDTH_MAJOR, 0); input_report_abs(input_dev, ABS_MT_POSITION_X, PointBuf[i].X); input_report_abs(input_dev, ABS_MT_POSITION_Y, PointBuf[i].Y); input_mt_sync(input_dev); if(PointBuf[i].Status == 0)//Status为0说明该点已经被释放(抬起手指了),那么此后将该点的Status置为-1可以保证不会再上报该点的信息, PointBuf[i].Status--; } } input_sync(input_dev); TS_DEBUG("Input sync point data done!/n"); } LastUpdateID = ContactID; } /**********************************************************************************************************************/ /**********************************************************************************************************************/ /**********************************************************************************************************************/ /**********************************************************************************************************************/