Linux下触摸屏驱动程序分析
?????本文以Linux3.5--Exynos4412平臺,分析觸摸屏驅動核心內容。Linux下觸摸屏驅動(以ft5x06_ts為例)需要了解如下知識:
1.? I2C協議
2.? Exynos4412處理器的I2C接口
3.? I2C接口觸摸屏基本知識
4.? bus-dev-drv模型
5.? Linux下I2C總線驅動框架
6.? Linux下輸入子系統
7.? Linux下中斷處理
8.? Linux下工作隊列機制
9.? Linux下驅動程序設計基本知識
????? 由此可見,Linux下觸摸屏驅動涉及的知識點是非常多的,基本上每一個知識點都是Linux下的一個子系統,綜合性很強。
一、背景
???? 觸摸屏偶爾會卡死,不能操作。通過分析原因,初步認定為I2C死鎖問題,關于I2C死鎖可以了解文章《I2C死鎖原因及解決方法》。為了解決這個問題,決定在發生I2C死鎖時,斷掉I2C觸摸屏的電源,然后給它上電,使處理器和觸摸屏之間的I2C通信恢復正常。
??? 這樣做的前提是I2C觸摸屏內部單片機自帶flash,并且一上電,它會自動運行flash里面的程序初始化觸摸屏本身,不用外界主控制器通過I2C的SDA信號初始化觸摸屏單片機。這樣,I2C觸摸屏上電后,它自動運行嵌入在內部flash里面的程序,用戶有操作時,拉低跟主控制器相連的中斷引腳,告訴主控制器有手指按下,這個時候主控制器會讀I2C觸摸屏,然后觸摸屏通過SDA線把觸摸數據傳輸給主控制器。
??? 上述解決思路是參考我們日常用手機時,也會偶而出現手機不能觸摸的問題。但是按一下手機關機按鈕,然后再打開觸摸屏就能用了(并沒有重啟手機)。這樣的操作過程就是把手機觸摸屏給斷了個電,然后再重新上了下電。
???? 使用這種方法還需找到觸摸屏卡死的地方,換句話說得知道在哪種情況下發生后給觸摸屏上電和斷電。因此,得分析Linux下觸摸屏驅動程序,每個細節都不放過。
二、驅動程序的總體框架
1.? bus-dev-drv模型
? ?? I2C觸摸屏屬于I2C設備,它是掛接在I2C總線上的,所以它在Linux下的驅動肯定會涉及到bus-dev-drv模型。在 bus-dev-drv模型中,肯定要注冊dev,及把dev結構掛接到bus下的dev鏈表中;另外還要注冊drv,把drv結構掛接到bus結構下的drv鏈表中。
???? 它們注冊的先后順序任意。但是不管誰注冊,(除開要dev或drv掛接到bus相應鏈表中外)都會觸發bus結構的match函數比較"name"。如果匹配,將調用drv的probe函數。在probe函數中可以做任何事情,由用戶決定(驅動開發者),一般是注冊設備驅動。
2.? 設置和注冊i2c_client結構
????? 根據文章《Linux3.5下I2C設備驅動程序》,可以知道注冊此結構體有四種方法,比較常用的是第二種方法。然后通過分析源碼發現本系統中使用了第一種方法。代碼如下:
i2c_register_board_info(1, smdk4x12_i2c_devs1,ARRAY_SIZE(smdk4x12_i2c_devs1));
????? 上述代碼將被smdk4x12_machine_init函數調用(/arch/arm/mach-exynos/Mach-Tiny4412.c),關于smdk4x12_machine_init函數,它在MACHINE_START-MACHINE_END中被賦給了init_machine,代碼如下:
MACHINE_START(TINY4412, "TINY4412")/* Maintainer: FriendlyARM (www.arm9.net) *//* Maintainer: Kukjin Kim <kgene.kim@samsung.com> *//* Maintainer: Changhwan Youn <chaos.youn@samsung.com> */.atag_offset = 0x100,.init_irq = exynos4_init_irq,.map_io = smdk4x12_map_io,.handle_irq = gic_handle_irq,.init_machine = smdk4x12_machine_init,.init_late = exynos_init_late,.timer = &exynos4_timer,.restart = exynos4_restart,.reserve = &smdk4x12_reserve, MACHINE_END????? ?關于MACHINE_START(TINY4412, "TINY4412")和MACHINE_END,都是在arch.h文件中定義的宏,具體定義代碼如下:
/*
?* Set of macros to define architecture features.? This is built into
?* a table by the linker.
?*/
#define MACHINE_START(_type,_name)?? ??? ??? ?\
static const struct machine_desc __mach_desc_##_type?? ?\
?__used?? ??? ??? ??? ??? ??? ??? ?\
?__attribute__((__section__(".arch.info.init"))) = {?? ?\
?? ?.nr?? ??? ?= MACH_TYPE_##_type,?? ??? ?\
?? ?.name?? ??? ?= _name,
#define MACHINE_END?? ??? ??? ??? ?\
};
????? 這里需要展開介紹下,我們在移植linux內核時,有個結構體變量需要被定義和初始化,這個變量就是__mach_desc_##_type,類型為machine_desc結構體類型。這個類型的變量放在內核代碼段.arch.info.init中,在內核運行初期,被函數lookup_machine_type(匯編)取出,讀取流程為:
Start_kernel() -> setup_arch() -> setup_processor() -> lookup_machine_type()
????? __mach_desc_##_type結構體變量的init_machine(函數指針)指向上述smdk4x12_machine_init函數,在smdk4x12_machine_init函數中,主要是些資源注冊的初始化代碼,這些代碼告訴linux內核一些開發板相關的硬件設備信息,即注冊開發板所用到的所有設備的相關硬件信息。另外,?__mach_desc_##_type結構體變量其他成員也需要我們實現,如果要定制Linux內核,這里就不展開了。?????? 在上述i2c_register_board_info函數中會把i2c_board_info結構放入__i2c_board_lis鏈表,然后在i2c_scan_static_board_info函數中使用到__i2c_board_list鏈表,即調用i2c_new_device函數把鏈表中的每個成員構造成一個i2c_client,并放入bus-dev-drv模型中總線中設備鏈表中去。這樣,就完成了i2c_client結構體的設置和注冊。
???? 另外,在上述smdk4x12_i2c_devs1結構數組中的每項元素的platform_data成員(即i2c_board_info類型結構的platform_data成員)被初始化為了ft5x0x_pdata,如下:
static struct ft5x0x_i2c_platform_data ft5x0x_pdata = http://www.ithao123.cn/{
?? ?.gpio_irq?? ??? ?= EXYNOS4_GPX1(6),
?? ?.irq_cfg?? ??? ?= S3C_GPIO_SFN(0xf),
?? ?.screen_max_x?? ?= 800,
?? ?.screen_max_y?? ?= 1280,
?? ?.pressure_max?? ?= 255,
};
????? 成功調用probe函數后,probe函數的第一個參數struct i2c_client *client即client就可以通過client->dev.platform_data的方式訪問到上述構建的ft5x0x_pdata。
????? 因此,在設置和注冊i2c_client結構時,需要填充一個i2c_board_info結構,此結構的platform_data成員被初始化的值是保存在了 ? i2c_client.dev.platform_data中的。
3.? 設置和注冊i2c_driver結構
????? 在driver/input/touchscreen中的ft5x05_ts.c中,入口函數ft5x0x_ts_init將調用i2c_add_driver注冊i2c_driver。i2c_add_driver函數的參數指向如下的結構體:
static struct i2c_driver ft5x0x_ts_driver = {.probe = ft5x0x_ts_probe,.remove = __devexit_p(ft5x0x_ts_remove),.id_table = ft5x0x_ts_id,.driver = {.name = FT5X0X_NAME,.owner = THIS_MODULE,}, };
???????注冊過程就是向bus總線的drv鏈表中增加一個i2c_driver結構,并調用bus的match函數匹配上述i2c_board_info結構中的name(用i2c_driver結構的id_table成員去匹配)。如果匹配,將調用探測函數probe,即上述ft5x0x_ts_probe。
????????4.? 關于設備驅動層、核心層、適配器層
???? 當探測函數probe被調用后,設備驅動層即在里面實現(跟上層應用相關)。在這里,ft5x05_ts.c中的ft5x0x_ts_probe函數注冊中斷(設備驅動相關內容),在中斷處理函數中將調用I2C核心層相關函數,核心層相關函數最終是調用適配器層相關函數實現I2C總線上的具體的底層硬件操作(這里是操作4412處理器的I2C控制器的寄存器即可實現I2C操作)。也就是說,我們一般不用實現核心層、適配器層,這些都是內核中做好了的,只是在實現設備驅動層時要調用核心層、適配器層的某些現成的函數。
5. 關于平臺設備的注冊(I2C控制器)
(1)構建platform_device型設備tiny4412_i2c1_data
???? ?在smdk4x12_machine_init函數中,調用s3c_i2c1_set_platdata(&tiny4412_i2c1_data)構建platform_device型設備tiny4412_i2c1_data,相關代碼如下:
→s3c_i2c1_set_platdata(&tiny4412_i2c1_data);
??????????→s3c_set_platdata(&tiny4412_i2c1_data, sizeof(struct s3c2410_platform_i2c),&s3c_device_i2c1);
??????????????????????????→struct platform_device s3c_device_i2c1 = {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ???? .name?? ??? ?= "s3c2410-i2c",
?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??? .id?? ??? ?= 1,
?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??? .num_resources?? ?= ARRAY_SIZE(s3c_i2c1_resource),
?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??? .resource?? ?= s3c_i2c1_resource,
??????????????????????????????????????????????????????????????????? ? ? };
?????????????????????????????????????????????????????????? →static struct resource s3c_i2c1_resource[] = {
?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [0] = DEFINE_RES_MEM(S3C_PA_IIC1, SZ_4K),
?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [1] = DEFINE_RES_IRQ(IRQ_IIC1),
?????????????????????????????????????????????????????????????????????????????????????????????????????????????????? };
?????
???其中,tiny4412_i2c1_data結構如下:
static struct s3c2410_platform_i2c tiny4412_i2c1_data __initdata = http://www.ithao123.cn/{?? ?.flags?? ??? ??? ?= 0,
?? ?.bus_num?? ??? ?= 1,
?? ?.slave_addr?? ??? ?= 0x10,
?? ?.frequency?? ??? ?= 200*1000,
?? ?.sda_delay?? ??? ?= 100,
};
(2)I2C總線注冊到平臺總線
????? 我們都知道,Linux中所有總線上的設備通過bus-dev-drv模型實現驅動,然后所有的總線也會掛接在平臺總線上,只是平臺總線是Linux內核中虛擬出來的一條總線而已。
???? 首先將之前創建的platform_device型設備tiny4412_i2c1_data添加到platform_bus總線,一般調用platform_add_device函數實現,此函數最終調用platform_device_register函數,這就就實現了平臺設備的注冊。
三、設備驅動層實現之中斷
???? 我們都知道,一般的I2C觸摸屏有中斷引腳。當用戶觸摸屏幕后,觸摸屏里的MCU將把中斷引腳拉低告知主控制器有觸摸動作。所以,主控制器必須要初始化中斷,在這里就體現為Linux下中斷的注冊(關于Linux下中斷體系結構、中斷的初始化、中斷的注冊、中斷實現、卸載詳見其他資料,比如《嵌入式Linux應用開發完全手冊》中講得很透徹)。
??? 上述ft5x0x_ts_probe函數中,注冊中斷的代碼如下:
err = request_irq(client->irq, ft5x0x_ts_interrupt, IRQ_TYPE_EDGE_FALLING /*IRQF_TRIGGER_FALLING*/, "ft5x0x_ts", ts);
1.? 第一個參數client->irq
??????在介紹client指向的結構體的irq成員之前,先分析client指向的內容的初始化,初始化函數為i2c_set_clientdata(client, ts)。由此可見,client指向的內容被ts指向的內容初始化。在ft5x0x_ts_probe前面部分中可以看到ts指向的內容被pdata指向的內容初始化。而pdata指向client->dev.platform_dat,即代碼pdata = http://www.ithao123.cn/client->dev.platform_data,
????? request_irq函數的第一個參數為注冊中斷的中斷號,此中斷號的值為client->irq,client->irq的設置過程為client->irq = gpio_to_irq(ts->gpio_irq)。由此可見此中斷號跟ts->gpio_irq有關,看代碼可知ts->gpio_irq = pdata->gpio_irq,因此最終中斷號跟pdata->gpio_irq的值有關。pdata指向ft5x0x_i2c_platform_data類型的結構體,在Mach-tiny4412.c中,有如下部分代碼:
#ifdef CONFIG_TOUCHSCREEN_FT5X0X #include <plat/ft5x0x_touch.h> static struct ft5x0x_i2c_platform_data ft5x0x_pdata = http://www.ithao123.cn/{.gpio_irq = EXYNOS4_GPX1(6),//對應原理圖中EINT14.irq_cfg = S3C_GPIO_SFN(0xf),.screen_max_x = 800,.screen_max_y = 1280,.pressure_max = 255, }; #endifstatic struct i2c_board_info smdk4x12_i2c_devs1[] __initdata = http://www.ithao123.cn/{ #ifdef CONFIG_TOUCHSCREEN_FT5X0X{I2C_BOARD_INFO("ft5x0x_ts", (0x70 >> 1)),.platform_data = &ft5x0x_pdata,}, #endif ??????? 從個代碼中可以知道g pio_irq的值被設置為 E XYNOS4_GPX1(6) ,即原理圖上的EINT14,也就是觸摸屏的中斷引腳。
2.? 第二個參數ft5x0x_ts_interrupt
???? 此參數就是中斷處理函數,也就是中斷發生后將自動調用的函數。此函數代碼如下:
static irqreturn_t ft5x0x_ts_interrupt(int irq, void *dev_id) {
?? ?struct ft5x0x_ts_data *ts = dev_id;
?? ?disable_irq_nosync(this_client->irq);?? //禁用中斷
?? ?if (!work_pending(&ts->work)) {
?? ??? ?queue_work(ts->queue, &ts->work);
?? ?}
?? ?return IRQ_HANDLED;
}
?? ???ft5x0x_ts_interrupt函數中剩下部分代碼為linux下workqueue相關,workqueue相關知識查看文章《linux? workqueue工作原理》。這里意思是比較耗時的操作不能放在中斷處理函數中,應該使用workqueue的知識在內核中另外創建一個線程,專門用來做比較耗時的操作,對應函數為ft5x0x_ts_pen_irq_work。
????? 注意,INIT_WORK(&ts->work, ft5x0x_ts_pen_irq_work);和ts->queue = create_singlethread_workqueue(dev_name(&client->dev));都是為使用workqueue機制做準備。
????? 總之,中斷處理函數被調用后,接下來就會調用ft5x0x_ts_pen_irq_work函數,此函數代碼如下:
static void ft5x0x_ts_pen_irq_work(struct work_struct *work) {
?? ?struct ft5x0x_ts_data *ts = container_of(work, struct ft5x0x_ts_data, work);
?? ?if (!ft5x0x_read_data(ts)) {
?? ??? ?ft5x0x_ts_report(ts);
?? ?}
?? ?enable_irq(this_client->irq);
}
????? 如果讀到數據,將調用ft5x0x_ts_report(ts)函數上報數據,這是輸入子系統相關內容,下面會介紹到。
??? ? 最后,中斷處理完成,可以打開中斷了,即調用enable_irq(this_client->irq);使能中斷。
3.? 剩下的參數
????? 第三個參數是中斷觸發方式,即上升延觸發還是電平觸發等;第4個參數是名字;第5個參數是中斷發生的次數的記錄。
????? 最后,再說下ft5x0x_ts_probe函數中,ft5x0x_i2c_platform_data
????? 因此,lInux內核源碼也不是那么神秘。另外吐槽一下國產手機,為什么屏幕不能觸摸了后非要黑屏一下,然后在上電就能用了,那樣非要用戶按一下,如果能在程序中自動做多好,那樣給用戶的感覺就是觸摸屏不會出現不能使用的情況,ip手機貌似觸摸屏就沒死過,里面肯定做了恢復處理的。
4.? 屏卡死問題解決思路
????? 不管觸摸屏與主控制器的I2C連接是否死鎖,當有手指按下后,觸摸屏MCU里面的程序還是會響應一個中斷,即會把中斷引腳拉低,這里對應的中斷引腳為EINT14。主控制器端是注冊過中斷的,當此中斷引腳拉低就會觸發主控制器那邊注冊的中斷處理函數。
????? 所以,在觸摸屏斷電、上電過程肯定在probe函數中的中斷處理函數中進行。而且是發生中斷,但是讀寫I2C觸摸屏數據失敗的情況下關閉觸摸屏電源,中斷返回時打開觸摸屏電源。
四、設備驅動層實現之輸入子系統
????? 關于Linux下輸入子系統查看文章《Linux輸入子系統(input? subsystem)》。
???? ?在probe函數中,也實現了輸入子系統設備驅動程序,部分代碼如下:
input_dev = input_allocate_device();if (!input_dev) {err = -ENOMEM;dev_err(&client->dev, "failed to allocate input device\n");goto exit_input_dev_alloc_failed;}ts->input_dev = input_dev;set_bit(EV_SYN, input_dev->evbit);set_bit(EV_ABS, input_dev->evbit);set_bit(EV_KEY, input_dev->evbit);#ifdef CONFIG_FT5X0X_MULTITOUCHset_bit(ABS_MT_TRACKING_ID, input_dev->absbit);set_bit(ABS_MT_TOUCH_MAJOR, input_dev->absbit);set_bit(ABS_MT_WIDTH_MAJOR, input_dev->absbit);set_bit(ABS_MT_POSITION_X, input_dev->absbit);set_bit(ABS_MT_POSITION_Y, input_dev->absbit);input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, ts->screen_max_x, 0, 0);input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, ts->screen_max_y, 0, 0);input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, ts->pressure_max, 0, 0);input_set_abs_params(input_dev, ABS_MT_WIDTH_MAJOR, 0, 200, 0, 0);input_set_abs_params(input_dev, ABS_MT_TRACKING_ID, 0, FT5X0X_PT_MAX, 0, 0); #elseset_bit(ABS_X, input_dev->absbit);set_bit(ABS_Y, input_dev->absbit);set_bit(ABS_PRESSURE, input_dev->absbit);set_bit(BTN_TOUCH, input_dev->keybit);input_set_abs_params(input_dev, ABS_X, 0, ts->screen_max_x, 0, 0);input_set_abs_params(input_dev, ABS_Y, 0, ts->screen_max_y, 0, 0);input_set_abs_params(input_dev, ABS_PRESSURE, 0, ts->pressure_max, 0 , 0); #endifinput_dev->name = FT5X0X_NAME;input_dev->phys = "input(mt)";input_dev->id.bustype = BUS_I2C;input_dev->id.vendor = 0x12FA;input_dev->id.product = 0x2143;input_dev->id.version = 0x0100;err = input_register_device(input_dev);if (err) {input_free_device(input_dev);dev_err(&client->dev, "failed to register input device %s, %d\n",dev_name(&client->dev), err);goto exit_input_dev_alloc_failed;}msleep(3);??????ft5x0x_ts_report函數即上報事件,在ft5x0x_ts_pen_irq_work函數中被調用。
五、probe函數里面所有源碼
六、關于校準
????? 可以參見資料:http://blog.csdn.net/liukun321/article/details/24102307
總結
以上是生活随笔為你收集整理的Linux下触摸屏驱动程序分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 敦泰FT6X06单层自容调屏
- 下一篇: 项目管理概要记录