[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子系统结构图
input子系统结构图如下图1所示:
图1 输入子系统结构图
1.3.linux中输入设备驱动的分层
linux中输入设备驱动的分层如下图2所示:
图2 linux中输入设备的分层
1.4. 输入子系统设备驱动层实现原理
在Linux中,Input设备用input_dev结构体描述,定义在input.h中。设备的驱动只需按照如下步骤就可实现了。
1).在驱动模块加载函数中设置Input设备支持input子系统的哪些事件;
2).将Input设备注册到input子系统中;
3).在Input设备发生输入操作时(如:键盘被按下/抬起、触摸屏被触摸/抬起/移动、鼠标被移动/单击/抬起时等),提交所发生的事件及对应的键值/坐标等状态。
1.5.软件设计流程
软件设计流程如下图3所示
图 3 input子系统软件设计流程
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
2.#include
3.#include
4.#include
5.#include
6.#include
7.#include
8.#include
9.#include
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");