一、 Linux 下電容觸摸屏驅(qū)動(dòng)框架簡(jiǎn)介
1、多點(diǎn)觸摸(MT)協(xié)議詳解
電容觸摸屏驅(qū)動(dòng)其實(shí)就是以下幾種 linux 驅(qū)動(dòng)框架的組合:
①、IIC 設(shè)備驅(qū)動(dòng),因?yàn)殡娙萦|摸 IC 基本都是 IIC 接口的,因此大框架就是 IIC 設(shè)備驅(qū)動(dòng)。
②、通過(guò)中斷引腳(INT)向 linux 內(nèi)核上報(bào)觸摸信息,因此需要用到 linux 中斷驅(qū)動(dòng)框架。坐標(biāo)的上報(bào)在中斷服務(wù)函數(shù)中完成。
③、觸摸屏的坐標(biāo)信息、屏幕按下和抬起信息都屬于 linux 的 input 子系統(tǒng),因此向 linux 內(nèi)核上報(bào)觸摸屏坐標(biāo)信息就得使用 input 子系統(tǒng)。只是,我們得按照 linux 內(nèi)核規(guī)定的規(guī)則來(lái)上報(bào)坐標(biāo)信息。
MT(Multi-touch,簡(jiǎn)稱 MT) 協(xié)議被分為兩種類型,TypeA 和 TypeB,這兩種類型的區(qū)別如下:
TypeA:適用于觸摸點(diǎn)不能被區(qū)分或者追蹤,此類型的設(shè)備上報(bào)原始數(shù)據(jù)(此類型在實(shí)際使用中非常少!)。
Type B:適用于有硬件追蹤并能區(qū)分觸摸點(diǎn)的觸摸設(shè)備,此類型設(shè)備通過(guò) slot 更新某一個(gè)觸摸點(diǎn)的信息,F(xiàn)T5426 就屬于此類型,一般的多點(diǎn)電容觸摸屏 IC 都有此能力。
ABS_MT 事件定義在文件 include/uapi/linux/input.h 中,相關(guān)事件如下所示:
#define ABS_MT_SLOT 0x2f /* MT slot being modified */
#define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */
#define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */
#define ABS_MT_WIDTH_MAJOR 0x32 /* Major axis of approaching ellipse */
#define ABS_MT_WIDTH_MINOR 0x33 /* Minor axis (omit if circular) */
#define ABS_MT_ORIENTATION 0x34 /* Ellipse orientation */
#define ABS_MT_POSITION_X 0x35 /* Center X touch position */
#define ABS_MT_POSITION_Y 0x36 /* Center Y touch position */
#define ABS_MT_TOOL_TYPE 0x37 /* Type of touching device */
#define ABS_MT_BLOB_ID 0x38 /* Group a set of packets as a blob */
#define ABS_MT_TRACKING_ID 0x39 /* Unique ID of initiated contact */
#define ABS_MT_PRESSURE 0x3a /* Pressure on contact area */
#define ABS_MT_DISTANCE 0x3b /* Contact hover distance */
#define ABS_MT_TOOL_X 0x3c /* Center X tool position */
#define ABS_MT_TOOL_Y 0x3d /* Center Y tool position */
最 常 用 的 就 是 ABS_MT_SLOT 、ABS_MT_POSITION_X 、 ABS_MT_POSITION_Y 和 ABS_MT_TRACKING_ID 。其中ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y 用 來(lái) 上報(bào) 觸 摸點(diǎn) 的 (X,Y) 坐 標(biāo) 信息 ,ABS_MT_SLOT 用 來(lái) 上 報(bào) 觸 摸 點(diǎn) ID ,對(duì)于 Type B 類 型 的 設(shè) 備 , 需 要 用 到ABS_MT_TRACKING_ID 事件來(lái)區(qū)分觸摸點(diǎn)。
對(duì)于 TypeA 類型的設(shè)備,通過(guò) input_mt_sync()
函數(shù)來(lái)隔離不同的觸摸點(diǎn)數(shù)據(jù)信息,此函數(shù)原型如下所示:
void input_mt_sync(struct input_dev *dev)
此函數(shù)只要一個(gè)參數(shù),類型為 input_dev,用于指定具體的 input_dev 設(shè)備。input_mt_sync()
函數(shù)會(huì)觸發(fā) SYN_MT_REPORT 事件,此事件會(huì)通知接收者獲取當(dāng)前觸摸數(shù)據(jù),并且準(zhǔn)備接收下一個(gè)觸摸點(diǎn)數(shù)據(jù)。
對(duì)于 Type B 類型的設(shè)備,上報(bào)觸摸點(diǎn)信息的時(shí)候需要通過(guò) input_mt_slot()
函數(shù)區(qū)分是哪一個(gè)觸摸點(diǎn),input_mt_slot()
函數(shù)原型如下所示:
void input_mt_slot(struct input_dev *dev, int slot)
此函數(shù)有兩個(gè)參數(shù),第一個(gè)參數(shù)是 input_dev 設(shè)備,第二個(gè)參數(shù) slot 用于指定當(dāng)前上報(bào)的是哪個(gè)觸摸點(diǎn)信息。input_mt_slot()
函數(shù)會(huì)觸發(fā) ABS_MT_SLOT 事件,此事件會(huì)告訴接收者當(dāng)前正在更新的是哪個(gè)觸摸點(diǎn)(slot)的數(shù)據(jù)。
TypeA 設(shè)備,內(nèi)核驅(qū)動(dòng)需要一次性將觸摸屏上所有的觸摸點(diǎn)信息全部上報(bào),每個(gè)觸摸點(diǎn)的信息在本次上報(bào)事件流中的順序不重要,因?yàn)槭录倪^(guò)濾和手指(觸摸點(diǎn))跟蹤是在內(nèi)核空間處理的。
Type B 設(shè)備驅(qū)動(dòng)需要給每個(gè)識(shí)別出來(lái)的觸摸點(diǎn)分配一個(gè) slot,后面使用這個(gè) slot 來(lái)上報(bào)觸摸點(diǎn)信息。可以通過(guò) slot 的 ABS_MT_TRACKING_ID 來(lái)新增、替換或刪除觸摸點(diǎn)。一個(gè)非負(fù)數(shù)的 ID 表示一個(gè)有效的觸摸點(diǎn),-1 這個(gè) ID 表示未使用 slot。一個(gè)以前不存在的 ID 表示這是一個(gè)新加的觸摸點(diǎn),一個(gè) ID 如果再也不存在了就表示刪除了。
Type B 和 Type A 相比最大的區(qū)別就是 Type B 可以區(qū)分出觸摸點(diǎn), 因此可以減少發(fā)送到用戶空間的數(shù)據(jù)。
2、Type A 觸摸點(diǎn)信息上報(bào)時(shí)序
對(duì)于 Type A 類型的設(shè)備,發(fā)送觸摸點(diǎn)信息的時(shí)序如下所示,這里以 2 個(gè)觸摸點(diǎn)為例:
ABS_MT_POSITION_X x[0] //上報(bào)第一個(gè)觸摸點(diǎn)的 X 坐標(biāo)數(shù)據(jù),通過(guò)input_report_abs 函數(shù)實(shí)現(xiàn)
ABS_MT_POSITION_Y y[0] //上報(bào)第一個(gè)觸摸點(diǎn)的 Y 坐標(biāo)數(shù)據(jù)
SYN_MT_REPORT //上報(bào) SYN_MT_REPORT 事件,通過(guò)調(diào)用 input_mt_sync 函數(shù)來(lái)實(shí)現(xiàn)
ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]
SYN_MT_REPORT
SYN_REPORT
具體實(shí)現(xiàn)代碼如下:
static irqreturn_t st1232_ts_irq_handler(int irq, void *dev_id)
{
......
ret = st1232_ts_read_data(ts);//獲取所有觸摸點(diǎn)信息
if (ret < 0)
goto end;
/* multi touch protocol */
for (i = 0; i < MAX_FINGERS; i++)
{
if (!finger[i].is_valid)
continue;
input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, finger[i].t);
input_report_abs(input_dev, ABS_MT_POSITION_X, finger[i].x);
input_report_abs(input_dev, ABS_MT_POSITION_Y, finger[i].y);
input_mt_sync(input_dev);
count++;
}
......
/* SYN_REPORT */
input_sync(input_dev); //發(fā)送一個(gè)SYN_REPORT 事件
end:
return IRQ_HANDLED;
}
3、Type B 觸摸點(diǎn)信息上報(bào)時(shí)序
對(duì)于 Type B 類型的設(shè)備,發(fā)送觸摸點(diǎn)信息的時(shí)序如下所示,這里以 2 個(gè)觸摸點(diǎn)為例:
ABS_MT_SLOT 0
ABS_MT_TRACKING_ID 45
ABS_MT_POSITION_X x[0]
ABS_MT_POSITION_Y y[0]
ABS_MT_SLOT 1
ABS_MT_TRACKING_ID 46
ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]
SYN_REPORT
第 1 行,上報(bào) ABS_MT_SLOT 事件,也就是觸摸點(diǎn)對(duì)應(yīng)的 SLOT。每次上報(bào)一個(gè)觸摸點(diǎn)坐標(biāo)之前要先使用input_mt_slot函數(shù)上報(bào)當(dāng)前觸摸點(diǎn)SLOT,觸摸點(diǎn)的SLOT其實(shí)就是觸摸點(diǎn)ID,需要由觸摸 IC 提供。
第 2 行,根據(jù) Type B 的要求,每個(gè) SLOT 必須關(guān)聯(lián)一個(gè) ABS_MT_TRACKING_ID,通過(guò)修改 SLOT 關(guān)聯(lián)的 ABS_MT_TRACKING_ID 來(lái)完成對(duì)觸摸點(diǎn)的添加、替換或刪除。具體用到的函數(shù)就是 input_mt_report_slot_state,如果是添加一個(gè)新的觸摸點(diǎn),那么此函數(shù)的第三個(gè)參數(shù)active 要設(shè)置為 true,linux 內(nèi)核會(huì)自動(dòng)分配一個(gè) ABS_MT_TRACKING_ID 值,不需要用戶去指定具體的 ABS_MT_TRACKING_ID 值。
第 3 行,上報(bào)觸摸點(diǎn) 0 的 X 軸坐標(biāo),使用函數(shù) input_report_abs 來(lái)完成。
第 4 行,上報(bào)觸摸點(diǎn) 0 的 Y 軸坐標(biāo),使用函數(shù) input_report_abs 來(lái)完成。
第 5 - 8行,和第 1~4 行類似,只是換成了上報(bào)觸摸點(diǎn) 0 的(X,Y)坐標(biāo)信息
第 9 行,當(dāng)所有的觸摸點(diǎn)坐標(biāo)都上傳完畢以后就得發(fā)送 SYN_REPORT 事件,使用 input_sync函數(shù)來(lái)完成。
當(dāng)一個(gè)觸摸點(diǎn)移除以后,同樣需要通過(guò) SLOT 關(guān)聯(lián)的 ABS_MT_TRACKING_ID 來(lái)處理,時(shí)序如下所示:
ABS_MT_TRACKING_ID -1
SYN_REPORT
第 1 行,當(dāng)一個(gè)觸摸點(diǎn)(SLOT)移除以后,需要通過(guò) ABS_MT_TRACKING_ID 事件發(fā)送一個(gè)-1 給內(nèi)核。方法很簡(jiǎn)單,同樣使用 input_mt_report_slot_state 函數(shù)來(lái)完成,只需要將此函數(shù)的第三個(gè)參數(shù) active 設(shè)置為 false 即可,不需要用戶手動(dòng)去設(shè)置-1。
第 2 行,當(dāng)所有的觸摸點(diǎn)坐標(biāo)都上傳完畢以后就得發(fā)送 SYN_REPORT 事件。
用于上報(bào) ili210x觸摸坐標(biāo)信息的,函數(shù)內(nèi)容如下所示:
static void ili210x_report_events(struct input_dev *input,const struct touchdata *touchdata)
{
int i;
bool touch;
unsigned int x, y;
const struct finger *finger;
for (i = 0; i < MAX_TOUCHES; i++) //實(shí)現(xiàn)所有觸摸點(diǎn)上報(bào)
{
input_mt_slot(input, i); //上 報(bào) ABS_MT_SLOT 事件
finger = &touchdata->finger[i];
touch = touchdata->status & (1 << i);
input_mt_report_slot_state(input, MT_TOOL_FINGER, touch);//上報(bào)ABS_MT_TRACKING_ID 事件,也就是給 SLOT 關(guān)聯(lián)一個(gè) ABS_MT_TRACKING_ID
if (touch)
{
x = finger->x_low | (finger->x_high << 8);
y = finger->y_low | (finger->y_high << 8);
input_report_abs(input, ABS_MT_POSITION_X, x);
input_report_abs(input, ABS_MT_POSITION_Y, y);
}
}
input_mt_report_pointer_emulation(input, false);
input_sync(input); //上報(bào) SYN_REPORT 事件
}
4、多點(diǎn)觸摸所使用到的 API 函數(shù)
4.1、input_mt_init_slots 函數(shù)
input_mt_init_slots 函數(shù)用于初始化 MT 的輸入 slots,編寫 MT 驅(qū)動(dòng)的時(shí)候必須先調(diào)用此函數(shù)初始化 slots,此函數(shù)定義在文件 drivers/input/input-mt.c 中,函數(shù)原型如下所示:
int input_mt_init_slots( struct input_dev *dev, unsigned int num_slots,unsigned int flags)
函數(shù)參數(shù)和返回值含義如下:
dev: MT 設(shè)備對(duì)應(yīng)的 input_dev,因?yàn)?MT 設(shè)備隸屬于 input_dev。
num_slots:設(shè)備要使用的 SLOT 數(shù)量,也就是觸摸點(diǎn)的數(shù)量。
flags:其他一些 flags 信息,可設(shè)置的 flags 如下所示:
#define INPUT_MT_POINTER 0x0001 /* pointer device, e.g. trackpad */
#define INPUT_MT_DIRECT 0x0002 /* direct device, e.g. touchscreen */
#define INPUT_MT_DROP_UNUSED 0x0004 /* drop contacts not seen in frame */
#define INPUT_MT_TRACK 0x0008 /* use in-kernel tracking */
#define INPUT_MT_SEMI_MT 0x0010 /* semi-mt device, finger count handled manually */
可以采用‘|’運(yùn)算來(lái)同時(shí)設(shè)置多個(gè) flags 標(biāo)識(shí)。
返回值:0,成功;負(fù)值,失敗。
4.2、input_mt_slot 函數(shù)
此函數(shù)用于 Type B 類型,此函數(shù)用于產(chǎn)生 ABS_MT_SLOT 事件,告訴內(nèi)核當(dāng)前上報(bào)的是哪個(gè)觸摸點(diǎn)的坐標(biāo)數(shù)據(jù),此函數(shù)定義在文件 include/linux/input/mt.h 中,函數(shù)原型如下所示:
void input_mt_slot(struct input_dev *dev, int slot)
函數(shù)參數(shù)和返回值含義如下:
dev: MT 設(shè)備對(duì)應(yīng)的 input_dev。
slot:當(dāng)前發(fā)送的是哪個(gè) slot 的坐標(biāo)信息,也就是哪個(gè)觸摸點(diǎn)。
返回值:無(wú)。
4.3、input_mt_report_slot_state 函數(shù)
此函數(shù)用于 Type B 類型,用于產(chǎn)生 ABS_MT_TRACKING_ID 和 ABS_MT_TOOL_TYPE事件, ABS_MT_TRACKING_ID 事 件 給 slot 關(guān)聯(lián)一個(gè) ABS_MT_TRACKING_ID ,ABS_MT_TOOL_TYPE 事 件 指 定 觸 摸 類 型 ( 是 筆 還 是 手 指 等 )。 此 函 數(shù) 定 義 在 文 件
drivers/input/input-mt.c 中,此函數(shù)原型如下所示:
void input_mt_report_slot_state( struct input_dev *dev,unsigned int tool_type, bool active)
函數(shù)參數(shù)和返回值含義如下:
dev: MT 設(shè)備對(duì)應(yīng)的 input_dev。
tool_type:觸摸類型,可以選擇 MT_TOOL_FINGER(手指)、MT_TOOL_PEN(筆)或MT_TOOL_PALM(手掌),對(duì)于多點(diǎn)電容觸摸屏來(lái)說(shuō)一般都是手指。
active:true,連續(xù)觸摸,input 子系統(tǒng)內(nèi)核會(huì)自動(dòng)分配一個(gè) ABS_MT_TRACKING_ID 給 slot。
false,觸摸點(diǎn)抬起,表示某個(gè)觸摸點(diǎn)無(wú)效了,input 子系統(tǒng)內(nèi)核會(huì)分配一個(gè)-1 給 slot,表示觸摸點(diǎn)溢出。
返回值:無(wú)。
4.4、input_report_abs 函數(shù)
TypeA 和 Type B 類型都使用此函數(shù)上報(bào)觸摸點(diǎn)坐標(biāo)信息,通過(guò) ABS_MT_POSITION_X 和ABS_MT_POSITION_Y 事 件 實(shí) 現(xiàn) X 和 Y 軸 坐 標(biāo) 信 息 上 報(bào) 。 此 函 數(shù) 定 義 在 文 件include/linux/input.h 中,函數(shù)原型如下所示:
void input_report_abs( struct input_dev *dev, unsigned int code, int value)
函數(shù)參數(shù)和返回值含義如下:
dev: MT 設(shè)備對(duì)應(yīng)的 input_dev。
code:要上報(bào)的是什么數(shù)據(jù),可以設(shè)置為 ABS_MT_POSITION_X 或 ABS_MT_POSITION_Y,
也就是 X 軸或者 Y 軸坐標(biāo)數(shù)據(jù)。
value:具體的 X 軸或 Y 軸坐標(biāo)數(shù)據(jù)值。
返回值:無(wú)。
4.5、input_mt_report_pointer_emulation 函數(shù)
如果追蹤到的觸摸點(diǎn)數(shù)量多于當(dāng)前上報(bào)的數(shù)量,驅(qū)動(dòng)程序使用 BTN_TOOL_TAP 事件來(lái)通知用戶空間當(dāng)前追蹤到的觸摸點(diǎn)總數(shù)量,然后調(diào)用 input_mt_report_pointer_emulation 函數(shù)將use_count 參數(shù)設(shè)置為 false。否則的話將 use_count 參數(shù)設(shè)置為 true,表示當(dāng)前的觸摸點(diǎn)數(shù)量(此函數(shù)會(huì)獲取到具體的觸摸點(diǎn)數(shù)量,不需要用戶給出),此函數(shù)定義在文件 drivers/input/input-mt.c中,函數(shù)原型如下:
void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)
函數(shù)參數(shù)和返回值含義如下:
dev: MT 設(shè)備對(duì)應(yīng)的 input_dev。
use_count:true,有效的觸摸點(diǎn)數(shù)量;false,追蹤到的觸摸點(diǎn)數(shù)量多于當(dāng)前上報(bào)的數(shù)量。
返回值:無(wú)。
5、多點(diǎn)電容觸摸驅(qū)動(dòng)框架
我們?cè)诰帉戲?qū)動(dòng)的時(shí)候需要注意一下幾點(diǎn):
①、多點(diǎn)電容觸摸芯片的接口,一般都為 I2C 接口,因此驅(qū)動(dòng)主框架肯定是 I2C。
②、linux 里面一般都是通過(guò)中斷來(lái)上報(bào)觸摸點(diǎn)坐標(biāo)信息,因此需要用到中斷框架。
③、多點(diǎn)電容觸摸屬于 input 子系統(tǒng),因此還要用到 input 子系統(tǒng)框架。
④、在中斷處理程序中按照 linux 的 MT 協(xié)議上報(bào)坐標(biāo)信息。
根據(jù)上面的分析,多點(diǎn)電容觸摸驅(qū)動(dòng)編寫框架以及步驟如下:
5.1、I2C 驅(qū)動(dòng)框架
/* 設(shè)備匹配表 */
static const struct i2c_device_id xxx_ts_id[] = {
{ "xxx", 0, },
{ /* sentinel */ }
};
/* 設(shè)備樹匹配表 */
static const struct of_device_id xxx_of_match[] = {
{ .compatible = "xxx", },
{ /* sentinel */ }
};
/* i2c 驅(qū)動(dòng)結(jié)構(gòu)體 */
static struct i2c_driver ft5x06_ts_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "edt_ft5x06",
.of_match_table = of_match_ptr(xxx_of_match),
},
.id_table = xxx_ts_id,
.probe = xxx_ts_probe,
.remove = xxx_ts_remove,
};
/*
* @description : 驅(qū)動(dòng)入口函數(shù)
* @param : 無(wú)
* @return : 無(wú)
*/
static int __init xxx_init(void)
{
int ret = 0;
ret = i2c_add_driver(&xxx_ts_driver);
return ret;
}
/*
* @description : 驅(qū)動(dòng)出口函數(shù)
* @param : 無(wú)
* @return : 無(wú)
*/
static void __exit xxx_exit(void)
{
i2c_del_driver(&ft5x06_ts_driver);
}
module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("hsd");
5.2、初始化觸摸 IC、中斷和 input 子系統(tǒng)
static int xxx_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct input_dev *input;
/* 1、初始化 I2C */
......
/* 2,申請(qǐng)中斷, */
devm_request_threaded_irq(&client->dev, client->irq, NULL,xxx_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,client->name, &xxx); //中斷線程化,防止程序一直進(jìn)入中斷占用CPU,這里單獨(dú)給他個(gè)線程,使用“devm_”前綴的函數(shù)申請(qǐng)到的資源可以由系統(tǒng)自動(dòng)釋放,不需要我們手動(dòng)處理。
......
/* 3,input 設(shè)備申請(qǐng)與初始化 */
input = devm_input_allocate_device(&client->dev); //申請(qǐng) input_dev,因?yàn)槎帱c(diǎn)電容觸摸屬于 input 子系統(tǒng)。
input->name = client->name;
input->id.bustype = BUS_I2C;
input->dev.parent = &client->dev;
......
/* 4,初始化 input 和 MT */
__set_bit(EV_ABS, input->evbit); //設(shè)置 input_dev 需要上報(bào)的事件為 EV_ABS 和 BTN_TOUCH
__set_bit(BTN_TOUCH, input->keybit);
input_set_abs_params(input, ABS_X, 0, width, 0, 0);
input_set_abs_params(input, ABS_Y, 0, height, 0, 0);
input_set_abs_params(input, ABS_MT_POSITION_X,0, width, 0, 0);
input_set_abs_params(input, ABS_MT_POSITION_Y,0, height, 0, 0);
input_mt_init_slots(input, MAX_SUPPORT_POINTS, 0); //初始化多點(diǎn)電容觸摸的 slots
......
/* 5,注冊(cè) input_dev */
input_register_device(input); //數(shù)系統(tǒng)注冊(cè)前面申請(qǐng)到的 input_dev
......
}
5.3、上報(bào)坐標(biāo)信息
static irqreturn_t xxx_handler(int irq, void *dev_id)
{
int num; /* 觸摸點(diǎn)數(shù)量 */
int x[n], y[n]; /* 保存坐標(biāo)值 */
/* 1、從觸摸芯片獲取各個(gè)觸摸點(diǎn)坐標(biāo)值 */
......
/* 2、上報(bào)每一個(gè)觸摸點(diǎn)坐標(biāo) */
for (i = 0; i < num; i++) {
input_mt_slot(input, id);
input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
input_report_abs(input, ABS_MT_POSITION_X, x[i]);
input_report_abs(input, ABS_MT_POSITION_Y, y[i]);
}
......
input_sync(input);
......
return IRQ_HANDLED;
}
二、試驗(yàn)程序編寫
1、修改設(shè)備樹
1.1、添加 FT5426 所使用的 IO
FT5426 觸摸芯片用到了 4 個(gè) IO,一個(gè)復(fù)位 IO、一個(gè)中斷 IO、I2C2 的 SCL 和 SDA,所以我們需要先在設(shè)備樹中添加 IO 相關(guān)的信息。
觸摸屏的中斷引腳信息,修改以后的“pinctrl_tsc”節(jié)點(diǎn)內(nèi)容如下所示:
pinctrl_tsc: tscgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0xF080 /* TSC_INT */
>;
};
復(fù)位引腳配置信息即可,如下所示:
pinctrl_tsc_reset: tsc_reset {
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x10B0
>;
};
找到“pinctrl_i2c2”節(jié)點(diǎn),此節(jié)點(diǎn)就是用于描述 I2C2 的 IO 信息,節(jié)點(diǎn)內(nèi)容如下所示:
pinctrl_i2c2: i2c2grp {
fsl,pins = <
MX6UL_PAD_UART5_TX_DATA__I2C2_SCL 0x4001b8b0
MX6UL_PAD_UART5_RX_DATA__I2C2_SDA 0x4001b8b0
>;
};
確保觸摸屏所使用的 IO 沒(méi)有被其他的外設(shè)使用,如果有的話就需要將其屏蔽掉,保證只有觸摸屏用到了這四個(gè) IO。
2、添加 FT5426 節(jié)點(diǎn)
&i2c2 {
clock_frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c2>;
status = "okay";
/****************************/
/* 省略掉其他的設(shè)備節(jié)點(diǎn) */
/****************************/
/* zuozhongkai FT5406/FT5426 */
ft5426: ft5426@38 { //觸摸屏所使用的 FT5426 芯片節(jié)點(diǎn),掛載 I2C2 節(jié)點(diǎn)下,F(xiàn)T5426 的器件地址為0X38。
compatible = "edt,edt-ft5426";
reg = <0x38>; //reg 屬性描述 FT5426 的器件地址為 0x38。
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_tsc
&pinctrl_tsc_reset >;
interrupt-parent = <&gpio1>; //中斷 IO 對(duì)應(yīng)的 GPIO 組為 GPIO1
interrupts = <9 0>; //述中斷 IO 對(duì)應(yīng)的是 GPIO1 組的 IOI09
reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>; //復(fù)位 IO 對(duì)應(yīng)的 GPIO 為 GPIO5_IO09
interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>; //中斷 IO 對(duì)應(yīng)的 GPIO 為 GPIO1_IO09
};
};
3、編寫多點(diǎn)電容觸摸驅(qū)動(dòng)
#include <linux/module.h>
#include <linux/ratelimit.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/debugfs.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/input/mt.h>
#include <linux/input/touchscreen.h>
#include <linux/input/edt-ft5x06.h>
#include <linux/i2c.h>
#define MAX_SUPPORT_POINTS 5 /* 5點(diǎn)觸摸 */
#define TOUCH_EVENT_DOWN 0x00 /* 按下 */
#define TOUCH_EVENT_UP 0x01 /* 抬起 */
#define TOUCH_EVENT_ON 0x02 /* 接觸 */
#define TOUCH_EVENT_RESERVED 0x03 /* 保留 */
/* FT5X06寄存器相關(guān)宏定義 */
#define FT5X06_TD_STATUS_REG 0X02 /* 狀態(tài)寄存器地址 */
#define FT5x06_DEVICE_MODE_REG 0X00 /* 模式寄存器 */
#define FT5426_IDG_MODE_REG 0XA4 /* 中斷模式 */
#define FT5X06_READLEN 29 /* 要讀取的寄存器個(gè)數(shù) */
struct ft5x06_dev {
struct device_node *nd; /* 設(shè)備節(jié)點(diǎn) */
int irq_pin,reset_pin; /* 中斷和復(fù)位IO */
int irqnum; /* 中斷號(hào) */
void *private_data; /* 私有數(shù)據(jù) */
struct input_dev *input; /* input結(jié)構(gòu)體 */
struct i2c_client *client; /* I2C客戶端 */
};
static struct ft5x06_dev ft5x06;
/*
* @description : 復(fù)位FT5X06
* @param - client : 要操作的i2c
* @param - multidev: 自定義的multitouch設(shè)備
* @return : 0,成功;其他負(fù)值,失敗
*/
static int ft5x06_ts_reset(struct i2c_client *client, struct ft5x06_dev *dev)
{
int ret = 0;
if (gpio_is_valid(dev->reset_pin)) { /* 檢查IO是否有效 */
/* 申請(qǐng)復(fù)位IO,并且默認(rèn)輸出低電平 */
ret = devm_gpio_request_one(&client->dev,
dev->reset_pin, GPIOF_OUT_INIT_LOW,
"edt-ft5x06 reset");
if (ret) {
return ret;
}
msleep(5);
gpio_set_value(dev->reset_pin, 1); /* 輸出高電平,停止復(fù)位 */
msleep(300);
}
return 0;
}
/*
* @description : 從FT5X06讀取多個(gè)寄存器數(shù)據(jù)
* @param - dev: ft5x06設(shè)備
* @param - reg: 要讀取的寄存器首地址
* @param - val: 讀取到的數(shù)據(jù)
* @param - len: 要讀取的數(shù)據(jù)長(zhǎng)度
* @return : 操作結(jié)果
*/
static int ft5x06_read_regs(struct ft5x06_dev *dev, u8 reg, void *val, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *)dev->client;
/* msg[0]為發(fā)送要讀取的首地址 */
msg[0].addr = client->addr; /* ft5x06地址 */
msg[0].flags = 0; /* 標(biāo)記為發(fā)送數(shù)據(jù) */
msg[0].buf = ® /* 讀取的首地址 */
msg[0].len = 1; /* reg長(zhǎng)度*/
/* msg[1]讀取數(shù)據(jù) */
msg[1].addr = client->addr; /* ft5x06地址 */
msg[1].flags = I2C_M_RD; /* 標(biāo)記為讀取數(shù)據(jù)*/
msg[1].buf = val; /* 讀取數(shù)據(jù)緩沖區(qū) */
msg[1].len = len; /* 要讀取的數(shù)據(jù)長(zhǎng)度*/
ret = i2c_transfer(client->adapter, msg, 2);
if(ret == 2) {
ret = 0;
} else {
ret = -EREMOTEIO;
}
return ret;
}
/*
* @description : 向ft5x06多個(gè)寄存器寫入數(shù)據(jù)
* @param - dev: ft5x06設(shè)備
* @param - reg: 要寫入的寄存器首地址
* @param - val: 要寫入的數(shù)據(jù)緩沖區(qū)
* @param - len: 要寫入的數(shù)據(jù)長(zhǎng)度
* @return : 操作結(jié)果
*/
static s32 ft5x06_write_regs(struct ft5x06_dev *dev, u8 reg, u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->client;
b[0] = reg; /* 寄存器首地址 */
memcpy(&b[1],buf,len); /* 將要寫入的數(shù)據(jù)拷貝到數(shù)組b里面 */
msg.addr = client->addr; /* ft5x06地址 */
msg.flags = 0; /* 標(biāo)記為寫數(shù)據(jù) */
msg.buf = b; /* 要寫入的數(shù)據(jù)緩沖區(qū) */
msg.len = len + 1; /* 要寫入的數(shù)據(jù)長(zhǎng)度 */
return i2c_transfer(client->adapter, &msg, 1);
}
/*
* @description : 向ft5x06指定寄存器寫入指定的值,寫一個(gè)寄存器
* @param - dev: ft5x06設(shè)備
* @param - reg: 要寫的寄存器
* @param - data: 要寫入的值
* @return : 無(wú)
*/
static void ft5x06_write_reg(struct ft5x06_dev *dev, u8 reg, u8 data)
{
u8 buf = 0;
buf = data;
ft5x06_write_regs(dev, reg, &buf, 1);
}
/*
* @description : FT5X06中斷服務(wù)函數(shù)
* @param - irq : 中斷號(hào)
* @param - dev_id : 設(shè)備結(jié)構(gòu)。
* @return : 中斷執(zhí)行結(jié)果
*/
static irqreturn_t ft5x06_handler(int irq, void *dev_id)
{
struct ft5x06_dev *multidata = dev_id;
u8 rdbuf[29];
int i, type, x, y, id;
int offset, tplen;
int ret;
bool down;
offset = 1; /* 偏移1,也就是0X02+1=0x03,從0X03開始是觸摸值 */
tplen = 6; /* 一個(gè)觸摸點(diǎn)有6個(gè)寄存器來(lái)保存觸摸值 */
memset(rdbuf, 0, sizeof(rdbuf)); /* 清除 */
/* 讀取FT5X06觸摸點(diǎn)坐標(biāo)從0X02寄存器開始,連續(xù)讀取29個(gè)寄存器 */
ret = ft5x06_read_regs(multidata, FT5X06_TD_STATUS_REG, rdbuf, FT5X06_READLEN);
if (ret) {
goto fail;
}
/* 上報(bào)每一個(gè)觸摸點(diǎn)坐標(biāo) */
for (i = 0; i < MAX_SUPPORT_POINTS; i++) {
u8 *buf = &rdbuf[i * tplen + offset];
/* 以第一個(gè)觸摸點(diǎn)為例,寄存器TOUCH1_XH(地址0X03),各位描述如下:
* bit7:6 Event flag 0:按下 1:釋放 2:接觸 3:沒(méi)有事件
* bit5:4 保留
* bit3:0 X軸觸摸點(diǎn)的11~8位。
*/
type = buf[0] >> 6; /* 獲取觸摸類型 */
if (type == TOUCH_EVENT_RESERVED)
continue;
/* 我們所使用的觸摸屏和FT5X06是反過(guò)來(lái)的 */
x = ((buf[2] << 8) | buf[3]) & 0x0fff;
y = ((buf[0] << 8) | buf[1]) & 0x0fff;
/* 以第一個(gè)觸摸點(diǎn)為例,寄存器TOUCH1_YH(地址0X05),各位描述如下:
* bit7:4 Touch ID 觸摸ID,表示是哪個(gè)觸摸點(diǎn)
* bit3:0 Y軸觸摸點(diǎn)的11~8位。
*/
id = (buf[2] >> 4) & 0x0f;
down = type != TOUCH_EVENT_UP;
input_mt_slot(multidata->input, id);
input_mt_report_slot_state(multidata->input, MT_TOOL_FINGER, down);
if (!down)
continue;
input_report_abs(multidata->input, ABS_MT_POSITION_X, x);
input_report_abs(multidata->input, ABS_MT_POSITION_Y, y);
}
input_mt_report_pointer_emulation(multidata->input, true);
input_sync(multidata->input);
fail:
return IRQ_HANDLED;
}
/*
* @description : FT5x06中斷初始化
* @param - client : 要操作的i2c
* @param - multidev: 自定義的multitouch設(shè)備
* @return : 0,成功;其他負(fù)值,失敗
*/
static int ft5x06_ts_irq(struct i2c_client *client, struct ft5x06_dev *dev)
{
int ret = 0;
/* 1,申請(qǐng)中斷GPIO */
if (gpio_is_valid(dev->irq_pin)) {
ret = devm_gpio_request_one(&client->dev, dev->irq_pin,
GPIOF_IN, "edt-ft5x06 irq");
if (ret) {
dev_err(&client->dev,
"Failed to request GPIO %d, error %d\n",
dev->irq_pin, ret);
return ret;
}
}
/* 2,申請(qǐng)中斷,client->irq就是IO中斷, */
ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
ft5x06_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
client->name, &ft5x06);
if (ret) {
dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
return ret;
}
return 0;
}
/*
* @description : i2c驅(qū)動(dòng)的probe函數(shù),當(dāng)驅(qū)動(dòng)與
* 設(shè)備匹配以后此函數(shù)就會(huì)執(zhí)行
* @param - client : i2c設(shè)備
* @param - id : i2c設(shè)備ID
* @return : 0,成功;其他負(fù)值,失敗
*/
static int ft5x06_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = 0;
ft5x06.client = client;
/* 1,獲取設(shè)備樹中的中斷和復(fù)位引腳 */
ft5x06.irq_pin = of_get_named_gpio(client->dev.of_node, "interrupt-gpios", 0);
ft5x06.reset_pin = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0);
/* 2,復(fù)位FT5x06 */
ret = ft5x06_ts_reset(client, &ft5x06);
if(ret < 0) {
goto fail;
}
/* 3,初始化中斷 */
ret = ft5x06_ts_irq(client, &ft5x06);
if(ret < 0) {
goto fail;
}
/* 4,初始化FT5X06 */
ft5x06_write_reg(&ft5x06, FT5x06_DEVICE_MODE_REG, 0); /* 進(jìn)入正常模式 */
ft5x06_write_reg(&ft5x06, FT5426_IDG_MODE_REG, 1); /* FT5426中斷模式 */
/* 5,input設(shè)備注冊(cè) */
ft5x06.input = devm_input_allocate_device(&client->dev);
if (!ft5x06.input) {
ret = -ENOMEM;
goto fail;
}
ft5x06.input->name = client->name;
ft5x06.input->id.bustype = BUS_I2C;
ft5x06.input->dev.parent = &client->dev;
__set_bit(EV_KEY, ft5x06.input->evbit);
__set_bit(EV_ABS, ft5x06.input->evbit);
__set_bit(BTN_TOUCH, ft5x06.input->keybit);
input_set_abs_params(ft5x06.input, ABS_X, 0, 1024, 0, 0);
input_set_abs_params(ft5x06.input, ABS_Y, 0, 600, 0, 0);
input_set_abs_params(ft5x06.input, ABS_MT_POSITION_X,0, 1024, 0, 0);
input_set_abs_params(ft5x06.input, ABS_MT_POSITION_Y,0, 600, 0, 0);
ret = input_mt_init_slots(ft5x06.input, MAX_SUPPORT_POINTS, 0);
if (ret) {
goto fail;
}
ret = input_register_device(ft5x06.input);
if (ret)
goto fail;
return 0;
fail:
return ret;
}
/*
* @description : i2c驅(qū)動(dòng)的remove函數(shù),移除i2c驅(qū)動(dòng)的時(shí)候此函數(shù)會(huì)執(zhí)行
* @param - client : i2c設(shè)備
* @return : 0,成功;其他負(fù)值,失敗
*/
static int ft5x06_ts_remove(struct i2c_client *client)
{
/* 釋放input_dev */
input_unregister_device(ft5x06.input);
return 0;
}
/*
* 傳統(tǒng)驅(qū)動(dòng)匹配表
*/
static const struct i2c_device_id ft5x06_ts_id[] = {
{ "edt-ft5206", 0, },
{ "edt-ft5426", 0, },
{ /* sentinel */ }
};
/*
* 設(shè)備樹匹配表
*/
static const struct of_device_id ft5x06_of_match[] = {
{ .compatible = "edt,edt-ft5206", },
{ .compatible = "edt,edt-ft5426", },
{ /* sentinel */ }
};
/* i2c驅(qū)動(dòng)結(jié)構(gòu)體 */
static struct i2c_driver ft5x06_ts_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "edt_ft5x06",
.of_match_table = of_match_ptr(ft5x06_of_match),
},
.id_table = ft5x06_ts_id,
.probe = ft5x06_ts_probe,
.remove = ft5x06_ts_remove,
};
/*
* @description : 驅(qū)動(dòng)入口函數(shù)
* @param : 無(wú)
* @return : 無(wú)
*/
static int __init ft5x06_init(void)
{
int ret = 0;
ret = i2c_add_driver(&ft5x06_ts_driver);
return ret;
}
/*
* @description : 驅(qū)動(dòng)出口函數(shù)
* @param : 無(wú)
* @return : 無(wú)
*/
static void __exit ft5x06_exit(void)
{
i2c_del_driver(&ft5x06_ts_driver);
}
module_init(ft5x06_init);
module_exit(ft5x06_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("hsd");
運(yùn)行測(cè)試
depmod //第一次加載驅(qū)動(dòng)的時(shí)候需要運(yùn)行此命令
modprobe ft5x06.ko //加載驅(qū)動(dòng)模塊
驅(qū)動(dòng)加載成功以后就會(huì)生成/dev/input/eventX(X=1,2,3…),比如本實(shí)驗(yàn)的多點(diǎn)電容觸摸驅(qū)動(dòng)就會(huì)在我所使用的 ALPHA 開發(fā)板平臺(tái)下就會(huì)生成/dev/input/event2 這個(gè)文件
上報(bào)數(shù)據(jù)的格式
多點(diǎn)電容觸摸信息含義
/* 編號(hào) */ /* tv_sec */ /* tv_usec */ /* type */ /* code */ /* value */
0000000 02bb 0000 9459 0007 0003 002f 0000 0000
0000010 02bb 0000 9459 0007 0003 0039 0005 0000
0000020 02bb 0000 9459 0007 0003 0035 03ec 0000
0000030 02bb 0000 9459 0007 0003 0036 0017 0000
0000040 02bb 0000 9459 0007 0001 014a 0001 0000
0000050 02bb 0000 9459 0007 0003 0000 03ec 0000
0000060 02bb 0000 9459 0007 0003 0001 0017 0000
0000070 02bb 0000 9459 0007 0000 0000 0000 0000
0000080 02bb 0000 e5f8 0008 0003 0039 ffff ffff
0000090 02bb 0000 e5f8 0008 0001 014a 0000 0000
00000a0 02bb 0000 e5f8 0008 0000 0000 0000 0000
第 1 行,type 為 0x3,說(shuō)明是一個(gè) EV_ABS 事件,code 為 0x2f,為 ABS_MT_SLOT,因此這一行就是 input_mt_slot 函數(shù)上報(bào)的 ABS_MT_SLOT 事件。value=0,說(shuō)明接下來(lái)上報(bào)的是第一個(gè)觸摸點(diǎn)坐標(biāo)。
第 2 行 , type 為 0x3 , 說(shuō) 明 是 一 個(gè) EV_ABS 事 件 , code 為 0x39 , 也 就 是ABS_MT_TRACKING_ID ,這一行就是 input_mt_report_slot_state 函 數(shù) 上 報(bào)ABS_MT_TRACKING_ID 事件。value=5 說(shuō)明給 SLOT0 分配的 ID 為 5。
第 3 行,type 為 0x3,是一個(gè) EV_ABS 事件,code 為 0x35,為 ABS_MT_POSITION_X,這一行就是 input_report_abs 函數(shù)上報(bào)的 ABS_MT_POSITION_X 事件,也就是觸摸點(diǎn)的 X 軸坐標(biāo)。value=0x03ec=1004,說(shuō)明觸摸點(diǎn) X 軸坐標(biāo)為 1004,屬于屏幕右上角區(qū)域。
第 4 行,type 為 0x3,是一個(gè) EV_ABS 事件,code 為 0x36,為 ABS_MT_POSITION_Y,這一行就是 input_mt_report_slot_state 函數(shù)上報(bào)的 ABS_MT_POSITION_Y 事件,也就是觸摸點(diǎn)的 Y 軸坐標(biāo)。value=0x17=23,說(shuō)明 Y 軸坐標(biāo)為 23,由此可以看出本次觸摸的坐標(biāo)為(1004,23),處于屏幕右上角區(qū)域。
第 5 行,type 為 0x1,是一個(gè) EV_KEY 事件,code=0x14a,為 BTN_TOUCH,value=0x1 表示觸摸屏被按下。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-458041.html
三、tslib 移植與使用(測(cè)試觸摸屏的軟件)
略文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-458041.html
到了這里,關(guān)于Linux 多點(diǎn)電容觸摸屏實(shí)驗(yàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!