驱动开发 —— 输入子系统(输入子系统的作用与框架及编程方式)

目录:

  输入子系统的作用与框架

  输入子系统的编程方式

一、输入子系统的作用与框架

   1、输入设备

    按键、鼠标、触摸屏:gt811,ft56xx 

 有多个输入设备需要驱动的时候,假如不考虑输入子系统

a, gt811
    注册设备号,创建设备文件,硬件初始化,实现fop,阻塞
b, ft56xx
    注册设备号,创建设备文件,硬件初始化,实现fop,阻塞               

  多个输入设备有共同点:
   获取到数据(操作硬件),上报给用户(xxx_read, copy_to_user, 阻塞)
              差异化                                          通用

   通用的部分内核会完成,差异化的代码由开发人员编写

   由此对于不同的、分散的输入设备进行统一的驱动,将其设计成输入子系统

   2、输入子系统的作用

     1)兼容所有输入设备

  统一了物理形态各异的相似的输入设备的处理功能。例如,各种鼠标,不论PS/2、USB、还是蓝牙,都被同样处理。

      2)统一的应用操作接口

   提供了用于分发输入报告给用户应用程序的简单的事件(event)接口。你的驱动不必创建、管理/dev节点以及相关的访问方法。因此它能够很方便的调用输入API以发送鼠标移动、键盘按键,或触摸事件给用户空间。

  3)统一的编程驱动方法

  抽取出了输入驱动的通用部分,简化了驱动,并提供了一致性。例如,输入子系统提供了一个底层驱动(成为serio)的集合,支持对串口和键盘控制器等硬件输入的访问

  输入子系统使得应用编程人员和驱动编程人员编程的时候变得简单统一。

  3、输入子系统框架

linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:

应用层
--------------------------------------------------------------------------------------------------------------------------
事件处理层:数据处理者
  完成fop:实现xxx_read(), xxx_open
  将数据交给用户:数据从input device层
  不知道具体数据是什么,只知道把数据给用户
-----------------------------------------------------------------------------------------------------------------------------
核心层:承上启下

  为驱动层提供输入设备注册与操作接口,如:input_register_device;通知事件处理层对事件 进行处理;在/Proc下产生相应的设备信息。设备驱动层只要关心如何驱动硬件并获得硬件数据,然后调用核心层提供的接口,核心层会自动把数据提交给事件处理层。

------------------------------------------------------------------------------------------------------------------------------
设备驱动层数据采集者
  抽象出一个对象,描述输入设备信息
  初始化输入设备硬件,获取到数据
  知道具体的数据是什么,但是不知道数据如何给用户
------------------------------------------------------------------------------------------------------------------------------
硬件层:mouse,ts, keybaord,joystick

(编程主要在设备驱动层)

 二、输入子系统编程方式

 1、最简单的输入设备驱动程序

  实现最简单的输入设备注册

 1 #include <linux/module.h>
 2 #include <linux/init.h>
 3 #include <linux/input.h>
 4 
 5 struct input_dev *inputdev;
 6 
 7 static int __init simple_input_init(void)
 8 {
 9     int ret;
10     
11     //编写输入子系统代码
12     /*
13      * 1、分配一个input device对象
14      * 2、初始化input device对象
15      * 3、注册input device 对象
16      */
17 
18     inputdev = input_allocate_device();
19     if(inputdev == NULL)
20     {
21         printk(KERN_ERR"input_allocate_device error\n");
22         return -ENOMEM;
23     }
24 
25     //使当前设备能够产生按键数据
26     __set_bit(EV_KEY, inputdev->evbit);
27     //表示当前设备能产生power按键
28     __set_bit(KEY_POWER, inputdev->keybit);
29 
30     ret = input_register_device(inputdev);
31     if(ret != 0)
32     {
33         printk(KERN_ERR"input_register_device error\n");
34         goto err_0;
35         return ret;
36     }
37     
38     return 0;
39 
40 err_0:
41     input_free_device(inputdev);
42     return ret;
43 }
44 
45 static void __exit simple_input_exit(void)
46 {
47     input_unregister_device(inputdev);
48     input_free_device(inputdev);
49 }
50 
51 module_init(simple_input_init);
52 module_exit(simple_input_exit);
53 MODULE_LICENSE("GPL");
simple_input_drv.c

在加载驱动前:

 加载驱动后:

 可以看到在input目录下,注册了一个设备event1,查看详细信息,可以看到主设备号为13、次设备号65,设备文件为/dev/input/event1

 2、硬件初始化

 在上一个程序中,我们并没由去申请设备号,创建设备文件,但是只要按照以上流程,系统会自动创建设备号与设备文件

   这些工作都是由核心层和事件处理层完成的,当然前提是内核中有input的核心层和事件处理层,相应文件在内核源码的:

 /linux-3.14/drivers/input 下,比如input.c、input-core.c 、evdev.c等。

 若做输入子系统开发时,要确定在内核编译时,配置input

1      make menuconfig
2         Device Drivers  --->
3              Input device support  ---> 
4                     -*- Generic input layer (needed for keyboard, mouse, ...)  // input.c
5                      <*>   Event interface   //input handler层--evdev.c

3、上报数据

1 void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
2 //参数
3 //1:当前的input device上报数据
4 //2 :上报的是那种数据类型 EV_KEY,EV_ABS
5 //3:具体数据是什么:KEY_POWER
6 //4:值是是什么

4、用户空间读到的数据:统一的数据包

1 struct input_event {
2     struct timeval time; //时间戳
3     __u16 type;           //数据类型
4     __u16 code;          //具体数据是什么
5     __s32 value;         //值是是什么
6 };

 

5、初始化input device

 1 struct input_dev {//表示的是一个具体的输入设备,描述设备能够产生什么数据
 2     const char *name; // sysfs中给用户看的信息
 3     const char *phys;
 4     const char *uniq;
 5     struct input_id id;
 6     //evbit结构是一个位表,描述输入设备能够产生什么类型数据
 7     unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; // EV_KEY,EV_ABS, EV_REL
 8     //表示能够产生哪种按键
 9     unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];//KEY_POWER.. 能够表示768bit,直接用24个long来表示
10                              // KEY_CNT == 768   BITS_TO_LONGS== nr/32 = 768/32==24
11     //表示能够产生哪种相对坐标数据
12     unsigned long relbit[BITS_TO_LONGS(REL_CNT)];// REL_X
13     //表示能够产生哪种绝对坐标数据
14     unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; //ABS_X
15     unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
16     unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
17     unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
18     unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
19     unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
20 
21     struct device dev; // 继承device对象
22 
23     struct list_head    h_list;
24     struct list_head    node; //表示节点
25 }

  添加设备信息

在linux系统中,将输入设备注册到输入子系统之后,可以在相关目录下看到:

 在input目录下有很多输入设备,event0,event1等等,对于应用程序员,它并不知道这些输入设备实际上对应哪些硬件,

无法区分。但是可以通过 /sys/class/input目录下的设备相关文件来查看:

 在device设备目录下,有device、id、name、uniq、phys等信息,对应的在input_dev结构体中,也有相应信息

1 struct input_dev {
2     const char *name;
3     const char *phys;
4     const char *uniq;
5     struct input_id id;
6         ......
7 }

由此可知,只要在input_dev结构体中初始化相应的信息,用户就能在目录下直观的看到设备的信息。

那如何为设备添加相应的信息呢?

直接初始化inputdev 的成员就可以了,之后在相应目录下就可以看到添加的信息

6、设备树中定义按键信息的子节点

实现驱动多个按键
一个按键有多个与其相关的元素:
  a, 中断号码
  b, 按键的状态--gpio的数据寄存器获取到
  c, 按键的值--code

在设备树文件中设置这几个元素:

 1       key_int_node{
 2                 compatible = "test_key";
 3                 #address-cells = <1>;
 4                 #size-cells = <1>;
 5 
 6                 key_int@0 {
 7                         key_name = "key2_power_eint";
 8                         key_code = <116>;
 9                         gpio = <&gpx1 1 0>;
10                         reg = <0x11000C20 0x18>;
11                         interrupt-parent = <&gpx1>;
12                         interrupts = <1 0>;
13                 };
14 
15                 key_int@1 {
16                         key_name = "key3_vup_eint";
17                         key_code = <115>;
18                         gpio = <&gpx1 2 0>;
19                         reg = <0x11000C20 0x18>;
20                         interrupt-parent = <&gpx1>;
21                         interrupts = <2 0>;
22                 };
23 
24                 key_int@2 {
25                         key_name = "key4_vdown_eint";
26                         key_code = <114>;
27                         gpio = <&gpx3 2 0>;
28                         reg = <0x11000C60 0x18>;
29                         interrupt-parent = <&gpx3>;
30                         interrupts = <2 0>;
31                 };
32         };    

  重新编译设备树,在板子上运行

   那如何获取到设备树上的信息

 在代码中也会设计这几个元素

  在代码中获取节点:

 1 of_get_next_child(const struct device_node * node, struct device_node * prev)
 2         参数1:表示节点
 3         参数2:之前的节点,如果是第一个节点,设置成NULL
 4 
 5                     // 通过节点去获取到中断号码
 6                  irqno = irq_of_parse_and_map(cnp, 0);
 7                 
 8                 //获取key name
 9                 of_property_read_string(cnp, "key_name",  &key_name);
10 
11                 //获取key code
12                 of_property_read_u32(cnp, "key_code", &code);
13 
14                 gpionum = of_get_named_gpio(cnp, "gpio", 0);
15 
16                 printk("name = %s, code = %d, gpionum = %d,irqno = %d\n",
17                         key_name, code, gpionum,irqno);    
18 
19     //设计一个对象出来
20     struct key_desc{
21         char *name;
22         int irqno;
23         int key_code;
24         int gpionum;
25         void *reg_base;
26         struct device_node *cnp;// 可以随时去获取节点各个信息
27     };    

  

  7、初始化所有按键并驱动

  1 #include <linux/module.h>
  2 #include <linux/init.h>
  3 #include <linux/input.h>
  4 #include <linux/of.h>
  5 #include <linux/of_irq.h>
  6 #include <linux/interrupt.h>
  7 #include <linux/slab.h>
  8 #include <linux/fs.h>
  9 #include <linux/device.h>
 10 #include <linux/kdev_t.h>
 11 #include <linux/err.h>
 12 #include <linux/device.h>
 13 #include <asm/io.h>
 14 #include <asm/uaccess.h>
 15 #include <linux/of_gpio.h>
 16 
 17 
 18 #define KEY_NUMS   3
 19 
 20 //把需要操作的对象的信息放到一个对象里来
 21 struct key_desc{
 22     char *name;
 23     int irqno;
 24     int key_code;
 25     int gpionum;
 26     void *reg_base;
 27     struct device_node *cnp;  //可以随时去获取节点的各个信息
 28     
 29 };
 30 
 31 struct key_desc all_key[KEY_NUMS];
 32 
 33 /*  如果有多个按键要实现,则重复以下过程就会十分繁杂,利用面向对象的思想,将按键封装为一个对象
 34     
 35     //通过节点获取中断号
 36     int irqno = irq_of_parse_and_map(np, 0);
 37     printk("iqrno = %d",irqno);
 38     //获取key_name
 39     of_property_read_string(cnp, "key_name", &key_name);
 40 
 41     //获取key_code
 42     of_property_read_u32(cnp, "key_code", &code);
 43 
 44     //获取GPIO
 45     gpionum = of_get_named_gpio(cnp, "gpio", 0);
 46 
 47     printk("name = %s, code = %s, gpionum = %d, irqno = %d\n",
 48             key_name,  code,      gpionum,        irqno);
 49 */
 50 
 51 struct input_dev *inputdev;
 52 
 53 
 54 //获取设备树中获取子节点
 55 void get_all_child_from_node(void)
 56 {
 57     //获取设备树中的节点
 58     struct device_node *np = of_find_node_by_path("/key_int_node");
 59     if(np){
 60         printk("find node success\n");
 61     }else{
 62         printk("find node failed\n");
 63     }
 64 
 65     //定义一个子节点
 66     struct device_node *cnp;
 67     struct device_node *prev = NULL;
 68 
 69     int i = 0;
 70 
 71     do{
 72         //获取到第一个子节点
 73         cnp = of_get_next_child(np, prev);
 74         if(cnp != NULL)
 75         {
 76             all_key[i++].cnp = cnp;  //将所有节点遍历一次并记录下来
 77         }
 78         prev = cnp; //在进入下一个节点前,把当前节点设置为上一节点
 79     }while(of_get_next_child(np, prev) != NULL);
 80 
 81 }
 82 
 83 //中断处理函数
 84 irqreturn_t input_irq_handler(int irqno, void *devid)
 85 {
 86     printk("----------%s---------\n",__FUNCTION__);
 87 
 88     //区分不同的按键
 89         //devid 是request_irq传入的参数all_key[i]
 90     struct key_desc *pdesc = (struct key_desc *)devid;
 91 
 92     printk("----------%s---------\n",pdesc->name);
 93 
 94     //在设备树中获取gpio号
 95     int gpionum = of_get_named_gpio(pdesc->cnp, "gpio", 0);  //设备树中,eg:  gpio = <&gpx1 1 0>;
 96     //通过gpio号获取gpio引脚状态
 97     int value = gpio_get_value(gpionum);
 98     /*
 99     //通过读数据寄存器读取按键状态
100     value = readl(reg_base + 4) & (0x01<<2);
101     */
102     
103     if(value){
104         //上报数据,输入子系统默认抬起为0
105         input_report_key(inputdev, pdesc->key_code, 0);  //为什么是1?
106             //调用:input_event(dev, EV_KEY, code, !!value);
107         input_sync(inputdev);  //同步,上报数据结束
108     }else{
109         //上报数据,输入子系统默认按下为1
110         input_report_key(inputdev, pdesc->key_code, 1);
111         input_sync(inputdev);  //同步,上报数据结束
112     }
113 
114     //阻塞的相关操作,上层会去实现
115     return IRQ_HANDLED;
116 }
117 
119 
120 static int __init simple_input_init(void)
121 {
122     int ret;
123     
124     //编写输入子系统代码
125     /*
126      * 1、分配一个input device对象
127      * 2、初始化input device对象
128      * 3、注册input device 对象
129      */
130 
131     inputdev = input_allocate_device();  //分配对象
132     if(inputdev == NULL)
133     {
134         printk(KERN_ERR"input_allocate_device error\n");
135         return -ENOMEM;
136     }
137 
138     get_all_child_from_node();
139 
140     //添加设备信息
141     inputdev->name = "simple input key";
142     inputdev->phys = "key/input/0";
143     inputdev->uniq = "simple key0 for 4412";
144     inputdev->id.bustype = BUS_HOST; //以什么方式连接到SOC
145 
146     //使当前设备能够产生按键数据
147     __set_bit(EV_KEY, inputdev->evbit);
148     //表示当前设备能产生power按键
149     //__set_bit(KEY_POWER, inputdev->keybit);
150 
151     int i;
152     for(i = 0; i < KEY_NUMS; i++)
153     {
154         //设置bit。支持哪些按键
155         //从设备树获取按键值,并为每个按键设置
156         int code;
157         struct device_node *cnp = all_key[i].cnp;
158         
159         of_property_read_u32(cnp, "key_code", &code);
160         __set_bit(code, inputdev->keybit);
161         all_key[i].key_code = code; //记录code到全局对象,方便调用
162 
163         //从设备树节点中获取中断号
164         int irqno;
165         irqno = irq_of_parse_and_map(cnp, 0);
166         all_key[i].irqno = irqno;  //将中断号记录下来,在exit函数中,要逐个注销中断
167 
168         //从设备树节点获取key_name
169         char *key_name;
170         of_property_read_string(cnp, "key_name", &key_name);  //从cnp节点中获取key_name填充到key_name中
171         all_key[i].name = key_name;   //记录key_name
172 
173         //为每个按键(节点)申请中断    ----每个按键触发的是同一个中断处理函数
174         ret = request_irq(irqno, input_irq_handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, 
175         key_name, &all_key[i]);   //传递触发中断的按键信息给中断处理函数以便区分不同按键
176         printk("------------request_irq:%s--------------\n",key_name);
177         if(ret != 0)
178         {
179             printk("request_irq error\n");
180             goto err_1;
181         }
182     }
183 
184     ret = input_register_device(inputdev);
185     if(ret != 0)
186     {
187         printk(KERN_ERR"input_register_device error\n");
188         goto err_0;
189         return ret;
190     }
191 
192     
193     
194     return 0;
195 err_1:
196     input_unregister_device(inputdev);
197 
198 err_0:
199     input_free_device(inputdev);
200     return ret;
201 }
202 
203 static void __exit simple_input_exit(void)
204 {
205     int i;
206     for(i=0; i<KEY_NUMS; i++)
207     {
208         free_irq(all_key[i].irqno, &all_key[i]);
209     }
210     input_unregister_device(inputdev);
211     input_free_device(inputdev);
212 }
213 
214 module_init(simple_input_init);
215 module_exit(simple_input_exit);
216 MODULE_LICENSE("GPL");

  测试:

 

gg

猜你喜欢

转载自www.cnblogs.com/y4247464/p/12418237.html
今日推荐