[OK210開發板體驗]功能篇(3)Linux input子系統之Key按鍵驅動
[OK210開發板體驗]入門篇(1):開箱驗板
[OK210開發板體驗]入門篇(2):板載資源
[OK210開發板體驗]入門篇(3):開發環境(軟件安裝,開發環境,燒寫系統)
[OK210開發板體驗]入門篇(4):編程入門(NFS登錄,驅動入門)
[OK210開發板體驗]功能篇(1):字符驅動之Led
[OK210開發板體驗]功能篇(2):字符驅動之Key按鍵
前一篇介紹了字符驅動之Key按鍵的控制,即用字符設備驅動的方式實現了按鍵驅動,但是,這個驅動程序只能供自己編寫的應用程序使用,其他應用程序都無法接收這個驅動的鍵值輸入,為了讓所有應用程序都可以接收到按鍵驅動解析的鍵值,Linux內核定義了“輸入子系統”的概念,也就是說,只要我們按照這個模型進行驅動開發,并為其提供必須的接口函數,那么,Linux內核就可以正常來獲取我們的鍵盤值。輸入子系統的原理分析強烈推薦觀看韋東山老師的視頻講座,講的非常清楚。
今天是功能篇的第三篇:input子系統之Key按鍵,由于本節的硬件和上節的一節,可以參見上一節的分析,本節主要分2部分:軟件基礎,驅動編程。
一、軟件基礎
1.1.input子系統概述
輸入設備(如按鍵,鍵盤,觸摸屏,鼠標等)是典型的字符設備,其一般的工作機制是低層在按鍵,觸摸等動作發生時產生一個中斷(或驅動通過timer定時查詢),然后cpu通過SPI,I2C或者外部存儲器總線讀取鍵值,坐標等數據,放一個緩沖區,字符設備驅動管理該緩沖區,而驅動的read()接口讓用戶可以讀取鍵值,坐標等數據。
Linux中,輸入子系統是由輸入子系統設備驅動層、輸入子系統核心層(Input Core)和輸入子系統事件處理層(Event Handler)組成。其中設備驅動層提供對硬件各寄存器的讀寫訪問和將底層硬件對用戶輸入訪問的響應轉換為標準的輸入事件,再通過核心層提交給事件處理層;而核心層對下提供了設備驅動層的編程接口,對上又提供了事件處理層的編程接口;而事件處理層就為我們用戶空間的應用程序提供了統一訪問設備的接口和驅動層提交來的事件處理。所以這使得我們輸入設備的驅動部分不在用關心對設備文件的操作,而是要關心對各硬件寄存器的操作和提交的輸入事件。
1.2. input子系統結構圖
1.3.linux中輸入設備驅動的分層
1.4. 輸入子系統設備驅動層實現原理
在Linux中,Input設備用input_dev結構體描述,定義在input.h中。設備的驅動只需按照如下步驟就可實現了。
1).在驅動模塊加載函數中設置Input設備支持input子系統的哪些事件;
2).將Input設備注冊到input子系統中;
3).在Input設備發生輸入操作時(如:鍵盤被按下/抬起、觸摸屏被觸摸/抬起/移動、鼠標被移動/單擊/抬起時等),提交所發生的事件及對應的鍵值/坐標等狀態。
1.5.軟件設計流程
1.6.與軟件設計有關的API函數
1.6.1.分配一個輸入設備
struct input_dev *input_allocate_device*(void);
1.6.2.注冊一個輸入設備
int input_register_device(struct input_dev *dev);
1.6.3.驅動實現-事件支持
set_bit告訴inout子系統它支持哪些事件
set_bit(EV_KEY,button_dev.evbit)
struct input_dev中有兩個成員,一個是evbit;一個是keybit.分別用來表示設備所支持的事件類型和按鍵類型。
1.6.3.1事件類型
Linux中輸入設備的事件類型有(這里只列出了常用的一些,更多請看linux/input.h中):EV_SYN 0x00 同步事件
EV_KEY 0x01 按鍵事件
EV_REL 0x02 相對坐標
EV_ABS 0x03 絕對坐標
EV_MSC 0x04 其它
EV_LED 0x11 LED
EV_SND 0x12 聲音
EV_REP 0x14 Repeat
EV_FF 0x15 力反饋
1.6.4.驅動實現-報告事件
void input_event(struct input_dev *dev,unsigned int type,unsigned int code,int value);//報告指定type,code的輸入事件
void input_report_key(struct input_dev *dev,unsigned int code,int value);//報告鍵值
void input_report_rel(struct input_dev *dev,unsigned int code,int value);//報告相對坐標
void input_report_abs(struct input_dev *dev,unsigned int code,int value);//報告絕對坐標
void input_sync(struct input_dev *dev);//報告同步事件
在觸摸屏驅動設計中,一次坐標及按下狀態的整個報告過程如下
input_report_abs(input_dev,ABS_Y,y);//Y坐標
input_report_abs(input_dev,ABS_PRESSURE,pres);//壓力
input_sync(struct input_dev *dev);//同步
1.6.5釋放與注銷設備
void input_free_device(struct input_dev *dev);
void input_unregister_device(struct input_dev *);
二、驅動編程
在編寫驅動的時候,首先要設置正確的硬件,為此首先定義了一個struct _ok210_key結構體,用以表示單個按鍵的引腳號,中斷號以及按鍵值;然后為這組按鍵(一共5個)定義了一個結構體struct ok210_kbd,以統一管理按鍵,最后使用ok210_init_kbd_data函數,初始化各按鍵,這樣驅動的編寫已經成功了一半。
在ok210_keys_probe函數中,通過調用__set_bit函數,為各按鍵設置事件類型和按鍵類型,最后當有按鍵按下時,驅動會調用ok210_kbd_handler回調函數,并分別通過兩個函數(input_report_key和input_sync)將按鍵事件傳輸到應用層。
值得一提的是,當系統中input子系統多于一個時,由于注冊的先后順序不一樣,每次生成在/dev/input目錄下生成的eventx指定的驅動設備也不一樣,不過可以通過如下命令,來查看具體的eventx指代的設備。
cat /proc/bus/input/devices
1 驅動程序
1.
#include <linux/module.h>
2. #include <linux/init.h>
3. #include <linux/interrupt.h>
4. #include <linux/platform_device.h>
5. #include <linux/input.h>
6. #include <linux/slab.h>
7. #include <mach/regs-gpio.h>
8. #include <linux/gpio.h>
9. #include <linux/irq.h>
10.
11. #define OK210_KBD "ok210kbd"
12. #define OK210_KBD_NUM (5)
13. #define KBD_NONE (0xff)
14.
15. #define KBD_UP (0)
16. #define KBD_DOWN (1)
17.
18. typedef struct _ok210_key{
19. unsigned int gpio;/*對應gpio口*/
20. unsigned int irq;/*對應中斷*/
21. int n_key;/*鍵值*/
22. }ok210_key;
23.
24. struct ok210_kbd{
25. ok210_key keys[OK210_KBD_NUM];
26. struct timer_list key_timer; /*按鍵去抖定時器*/
27. unsigned int key_status; /*按鍵狀態*/
28. struct input_dev *input;
29. };
30.
31. static void ok210_init_kbd_data(struct ok210_kbd *p_kbd)
32. {
33. printk("ok210_init_kbd_data p_kbd=%x\n", (unsigned int)p_kbd);
34.
35. p_kbd->keys[0].gpio = S5PV210_GPH0(3);
36. p_kbd->keys[1].gpio = S5PV210_GPH0(4);
37. p_kbd->keys[2].gpio = S5PV210_GPH0(5);
38. p_kbd->keys[3].gpio = S5PV210_GPH0(6);
39. p_kbd->keys[4].gpio = S5PV210_GPH0(7);
40.
41. p_kbd->keys[0].irq = IRQ_EINT3;
42. p_kbd->keys[1].irq = IRQ_EINT4;
43. p_kbd->keys[2].irq = IRQ_EINT5;
44. p_kbd->keys[3].irq = IRQ_EINT6;
45. p_kbd->keys[4].irq = IRQ_EINT7;
46.
47. p_kbd->keys[0].n_key = KEY_0;
48. p_kbd->keys[1].n_key = KEY_1;
49. p_kbd->keys[2].n_key = KEY_2;
50. p_kbd->keys[3].n_key = KEY_3;
51. p_kbd->keys[4].n_key = KEY_4;
52.
53. }
54.
55. struct ok210_kbd *p_ok210_kbd;
56.
57. struct ok210_kbd *get_kbd(void)
58. {
59. return p_ok210_kbd;
60. }
61.
62. void set_kbd(struct ok210_kbd *p_kbd)
63. {
64. p_ok210_kbd = p_kbd;
65. }
66.
67. static irqreturn_t ok210_kbd_handler(int irq, void *p_date)
68. {
69. unsigned int n_key = 0;
70. struct ok210_kbd *p_kbd = p_date;
71. unsigned int key_state = 0;
72. int i;
73.
74. for(i = 0; i < OK210_KBD_NUM; i++)
75. {
76. if( irq == p_kbd->keys[i].irq )
77. {
78. key_state = gpio_get_value(p_kbd->keys[i].gpio);
79. n_key = p_kbd->keys[i].n_key;
80. break;
81. }
82. }
83.
84. printk("ok210_kbd_handler n_key=%d, key_state=%d\n", n_key, key_state);
85.
86. input_report_key(p_kbd->input, n_key, !key_state);/*1表示按下*/
87. input_sync(p_kbd->input);
88.
89. return IRQ_HANDLED;
90. }
91.
92.
93. static void kbd_free_irqs(void)
94. {
95. int i;
96. struct ok210_kbd *p_kbd = get_kbd();
97.
98. for(i = 0; i < OK210_KBD_NUM; i++)
99. free_irq(p_kbd->keys[i].irq, p_kbd);
100. , , }
101.
102. static int kbd_req_irqs(void)
103. {
104. int n_ret;
105. int i;
106. struct ok210_kbd *p_kbd = get_kbd();
107.
108. for(i = 0; i < OK210_KBD_NUM; i++)
109. {
110. n_ret = request_irq(p_kbd->keys[i].irq, ok210_kbd_handler, IRQ_TYPE_EDGE_BOTH, OK210_KBD, p_kbd);
111. if(n_ret)
112. {
113. printk("%d: could not register interrupt\n", p_kbd->keys[i].irq);
114. goto fail;
115. }
116. }
117.
118. return n_ret;
119.
120. fail:
121. /*因為上面申請失敗的那個沒有成功,所以也不要釋放*/
122. for(i--; i >= 0; i--)
123. {
124. disable_irq(p_kbd->keys[i].irq);
125. free_irq(p_kbd->keys[i].irq, p_kbd);
126. }
127.
128. return n_ret;
129. }
130.
131.
132. static int __devinit ok210_keys_probe(struct platform_device *pdev)
133. {
134. int err = -ENOMEM;
135. struct ok210_kbd *p_ok210_keys = NULL;
136. struct input_dev *input_dev = NULL;
137.
138. printk("ok210_keys_probe entry!\n");