前言
一、 修改设备树文件
1、 在.dts文件中的iomuxc节点的imx6ul-evk子节点下创建“pinctrl_key”的子节点:
1. /* djw key 2022.4.29 */
2. pinctrl_key: keygrp {
3. fsl,pins = <
4. MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080
5. >;
6. };
7. /* 该引脚是复用为GPIO使用 */
2、 在.dts文件中的根节点创建“key”节点。
1. /* DJW 2022.4.29 */
2. key {
3. #address-cells = <1>;
4. #size-cells = <1>;
5. compatible = "atkalpha-key";
6. pinctrl-names = "default";
7. pinctrl-0 = <&pinctrl_key>;
8. key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* key0 */
9. status = "okay";
10. };
3、 检查该PIN脚有没有被其他外设使用,否则驱动程序在申请GPIO的时候会失败。
① 检查pinctrl设置,是否还有别的外设复用了。
② 检查该PIN脚有没有别的外设配置成了GPIO。
4、编译设备树“make dtbs”,启动Linux系统,进入“/proc/device-tree”目录中查看“key”节点是否存在,如果存在则说明设备树修改成功。
二、驱动程序架构的编写
1. /*
2. * 根据linux内核的程序查找所使用函数的对应头文件。
3. */
4. #include <linux/module.h> // MODULE_LICENSE,MODULE_AUTHOR
5. #include <linux/init.h> // module_init,module_exit
6. #include <linux/kernel.h> // printk
7. #include <linux/fs.h> // struct file_operations
8. #include <linux/slab.h> //kmalloc, kfree
9. #include <linux/uaccess.h> // copy_to_user,copy_from_user
10. #include <linux/io.h> //ioremap,iounmap
11. #include <linux/cdev.h> //struct cdev,cdev_init,cdev_add,cdev_del
12. #include <linux/device.h> //class
13. #include <linux/of.h> //of_find_node_by_path
14. #include <linux/of_gpio.h> //of_get_named_gpio
15. #include <linux/gpio.h> //gpio_request,gpio_direction_output,gpio_set_value
16. #include <linux/atomic.h> //atomic_t
17.
18. /* 1.5 key设备结构体 */
19. struct key_dev {
20. dev_t devid; /* 设备号 */
21. int major; /* 主设备号 */
22. int minor; /* 次设备号 */
23. char *name; /* 设备名称 */
24. int dev_count; /* 设备个数 */
25. struct cdev cdev; /* 注册设备结构体 */
26. struct class *class; /* 类 */
27. struct device *device; /* 设备 */
28. struct device_node *nd; /* 设备节点 */
29. int key_gpio; /* key所使用的GPIO编号 */
30. };
31. struct key_dev key; /* 定义key设备结构体 */
32.
33. /*
34. * inode: 传递给驱动的inode
35. * filp : 要进行操作的设备文件(文件描述符)
36. * buf : 数据缓冲区
37. * cnt : 数据长度
38. * ppos : 相对于文件首地址的偏移
39. */
40. /* 2.1 打开字符设备文件 */
41. static int key_open(struct inode *inode, struct file *filp) {
42. int ret = 0;
43. /* 设置私有类数据 */
44. filp->private_data = &key;
45. return ret;
46. }
47.
48. /* 2.2 关闭字符设备文件 */
49. static int key_release(struct inode *inode, struct file *filp) {
50. int ret = 0;
51. return ret;
52. }
53.
54. /* 2.3 向字符设备文件读出数据 */
55. static ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) {
56. int ret = 0;
57. /* 提取私有属性 */
58. struct key_dev *dev = filp->private_data;
59. return ret;
60. }
61.
62. /* 2.4 向字符设备文件写入数据 */
63. static ssize_t key_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) {
64. int ret = 0;
65. return ret;
66. }
67.
68. /* 2.5 key设备操作集 */
69. static const struct file_operations key_fops = {
70. .owner = THIS_MODULE,
71. .open = key_open,
72. .release = key_release,
73. .read = key_read,
74. .write = key_write,
75. };
76.
77.
78. /* 1.1 驱动模块入口函数 */
79. static int __init key_init(void) {
80. int ret = 0;
81. printk("key_init!\r\n");
82. /***********************************************************************************************/
83. /* 3.1 配置key结构体参数 */
84. key.dev_count = 1; //设置设备号个数
85. key.name = "key"; //设置设备名字
86. key.major = 0; //0为系统分配设备号, 1为自定义设备号
87. if(key.major) {
88. key.devid = MKDEV(key.major,0); //自定义主设备号和次设备号,并整合为设备号结构体中
89. ret = register_chrdev_region(key.devid, key.dev_count, key.name); //注册设备号
90. } else {
91. alloc_chrdev_region(&key.devid, 0, key.dev_count, key.name); //注册设备号,系统自动分配
92. key.major = MAJOR(key.devid);
93. key.minor = MINOR(key.devid);
94. }
95. if(ret < 0) {
96. goto fail_devid; //注册设备号失败
97. }
98. printk("key major = %d, minor = %d\r\n",key.major,key.minor); //注册设备号成功了,就打印主次设备号
99.
100. /* 3.2 注册或者叫添加字符设备 */
101. cdev_init(&key.cdev, &key_fops); //初始化cdev结构体
102. ret = cdev_add(&key.cdev, key.devid, key.dev_count); //添加字符设备
103. if(ret < 0) {
104. goto fail_cdev; //注册或者叫添加设备失败
105. }
106.
107. /* 3.3 自动创建设备节点 */
108. key.class = class_create(THIS_MODULE, key.name); //创建类
109. if(IS_ERR(key.class)) {
110. ret = PTR_ERR(key.class);
111. goto fail_class;
112. }
113. key.device = device_create(key.class, NULL, key.devid, NULL, key.name); //创建设备
114. if(IS_ERR(key.device)) {
115. ret = PTR_ERR(key.device);
116. goto fail_device;
117. }
118. /***********************************************************************************************/
119.
120. return ret;
121.
122. fail_device:
123. class_destroy(key.class);
124. fail_class:
125. cdev_del(&key.cdev);
126. fail_cdev:
127. unregister_chrdev_region(key.devid, key.dev_count);
128. fail_devid:
129. return ret;
130. }
131.
132. /* 1.2 驱动模块出口函数 */
133. static void __exit key_exit(void) {
134. /* 最后一一添加 , 注销字符设备驱动 */
135. device_destroy(key.class, key.devid); //摧毁设备
136. class_destroy(key.class); //摧毁类
137. cdev_del(&key.cdev); //注销字符设备结构体
138. unregister_chrdev_region(key.devid, key.dev_count); //注销设备号
139. }
140.
141. /* 1.3 注册驱动模块 */
142. module_init(key_init);
143. module_exit(key_exit);
144.
145. /* 1.4 驱动许可和个人信息 */
146. MODULE_LICENSE("GPL");
147. MODULE_AUTHOR("djw");
三、基于驱动程序框架编写需要实现的驱动程序
在驱动程序中使用一个整形变量来表示按键值,应用程序通过 read 函数来读取按键值,判断按键有没有按下。在这里,这个保存按键值的变量就是个共享资源,驱动程序要向其写入按键值,应用程序要读取按键值。所以我们要对其进行保护,对于整形变量而言我们首选的就是原子操作,使用原子操作对变量进行赋值以及读取。
1. /*
2. * 根据linux内核的程序查找所使用函数的对应头文件。
3. */
4. #include <linux/module.h> // MODULE_LICENSE,MODULE_AUTHOR
5. #include <linux/init.h> // module_init,module_exit
6. #include <linux/kernel.h> // printk
7. #include <linux/fs.h> // struct file_operations
8. #include <linux/slab.h> //kmalloc, kfree
9. #include <linux/uaccess.h> // copy_to_user,copy_from_user
10. #include <linux/io.h> //ioremap,iounmap
11. #include <linux/cdev.h> //struct cdev,cdev_init,cdev_add,cdev_del
12. #include <linux/device.h> //class
13. #include <linux/of.h> //of_find_node_by_path
14. #include <linux/of_gpio.h> //of_get_named_gpio
15. #include <linux/gpio.h> //gpio_request,gpio_direction_output,gpio_set_value
16. #include <linux/atomic.h> //atomic_t
17.
18. #define KEYPRESS 0XF0 //按下
19. #define KEYRELEASE 0X00 //释放
20.
21. /* 1.5 key设备结构体 */
22. struct key_dev {
23. dev_t devid; /* 设备号 */
24. int major; /* 主设备号 */
25. int minor; /* 次设备号 */
26. char *name; /* 设备名称 */
27. int dev_count; /* 设备个数 */
28. struct cdev cdev; /* 注册设备结构体 */
29. struct class *class; /* 类 */
30. struct device *device; /* 设备 */
31. struct device_node *nd; /* 设备节点 */
32. int key_gpio; /* key所使用的GPIO编号 */
33.
34. atomic_t key_value; /* 按键值 */
35. };
36. struct key_dev key; /* 定义key设备结构体 */
37.
38. /*
39. * inode: 传递给驱动的inode
40. * filp : 要进行操作的设备文件(文件描述符)
41. * buf : 数据缓冲区
42. * cnt : 数据长度
43. * ppos : 相对于文件首地址的偏移
44. */
45. /* 2.1 打开字符设备文件 */
46. static int key_open(struct inode *inode, struct file *filp) {
47. int ret = 0;
48. /* 设置私有类数据 */
49. filp->private_data = &key;
50. return ret;
51. }
52.
53. /* 2.2 关闭字符设备文件 */
54. static int key_release(struct inode *inode, struct file *filp) {
55. int ret = 0;
56. return ret;
57. }
58.
59. /* 2.3 向字符设备文件读出数据 */
60. static ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) {
61. int ret = 0;
62. int value; //原子类型的变量不能直接使用,要转换为int类型
63. /* 提取私有属性 */
64. struct key_dev *dev = filp->private_data;
65. /* 按下为低电平 */
66. if(gpio_get_value(dev->key_gpio) == 0) {
67. while (!gpio_get_value(dev->key_gpio)); //等待松开
68. atomic_set(&dev->key_value, KEYPRESS); //设置为按下状态
69. } else {
70. atomic_set(&dev->key_value, KEYRELEASE); //设置为松开状态
71. }
72. value = atomic_read(&dev->key_value);
73. ret = copy_to_user(buf, &value, sizeof(value));
74.
75. return ret;
76. }
77.
78. /* 2.4 向字符设备文件写入数据 */
79. static ssize_t key_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) {
80. int ret = 0;
81. return ret;
82. }
83.
84. /* 2.5 key设备操作集 */
85. static const struct file_operations key_fops = {
86. .owner = THIS_MODULE,
87. .open = key_open,
88. .release = key_release,
89. .read = key_read,
90. .write = key_write,
91. };
92.
93. /* 4.1 key GPIO 的初始化 */
94. static int key_gpio_init(struct key_dev *dev) {
95. int ret = 0;
96. /* 4.1.1 根据设备树的设备节点路径获取设备节点 */
97. dev->nd = of_find_node_by_path("/key");
98. if(dev->nd == NULL) {
99. ret = -EINVAL;
100. goto fail_nd;
101. }
102. /* 4.1.2 获取到key对应的设备节点中的GPIO信息 */
103. dev->key_gpio = of_get_named_gpio(dev->nd, "key-gpio", 0);
104. if(dev->key_gpio < 0) {
105. ret = -EINVAL;
106. goto fail_gpio;
107. }
108. printk("key gpio num = %d\r\n",dev->key_gpio); //获取key的GPIO编号
109. /* 4.1.3 请求GPIO */
110. ret = gpio_request(dev->key_gpio, "key-gpio");
111. if(ret) {
112. ret = -EBUSY;
113. printk("IO %d can't request!\r\n",dev->key_gpio);
114. goto fail_request;
115. }
116. /* 4.1.4 使用IO,设置IO的输出方向 */
117. ret = gpio_direction_input(dev->key_gpio);
118. if(ret < 0) {
119. ret = -EINVAL;
120. goto fail_input;
121. }
122. return 0;
123.
124. fail_input:
125. gpio_free(dev->key_gpio); //释放GPIO
126. fail_request:
127. fail_gpio:
128. fail_nd:
129. return ret;
130. }
131.
132. /* 1.1 驱动模块入口函数 */
133. static int __init key_init(void) {
134. int ret = 0;
135. printk("key_init!\r\n");
136. /***********************************************************************************************/
137. /* 3.1 配置key结构体参数 */
138. key.dev_count = 1; //设置设备号个数
139. key.name = "key"; //设置设备名字
140. key.major = 0; //0为系统分配设备号, 1为自定义设备号
141. if(key.major) {
142. key.devid = MKDEV(key.major,0); //自定义主设备号和次设备号,并整合为设备号结构体中
143. ret = register_chrdev_region(key.devid, key.dev_count, key.name); //注册设备号
144. } else {
145. alloc_chrdev_region(&key.devid, 0, key.dev_count, key.name); //注册设备号,系统自动分配
146. key.major = MAJOR(key.devid);
147. key.minor = MINOR(key.devid);
148. }
149. if(ret < 0) {
150. goto fail_devid; //注册设备号失败
151. }
152. printk("key major = %d, minor = %d\r\n",key.major,key.minor); //注册设备号成功了,就打印主次设备号
153.
154. /* 3.2 注册或者叫添加字符设备 */
155. cdev_init(&key.cdev, &key_fops); //初始化cdev结构体
156. ret = cdev_add(&key.cdev, key.devid, key.dev_count); //添加字符设备
157. if(ret < 0) {
158. goto fail_cdev; //注册或者叫添加设备失败
159. }
160.
161. /* 3.3 自动创建设备节点 */
162. key.class = class_create(THIS_MODULE, key.name); //创建类
163. if(IS_ERR(key.class)) {
164. ret = PTR_ERR(key.class);
165. goto fail_class;
166. }
167. key.device = device_create(key.class, NULL, key.devid, NULL, key.name); //创建设备
168. if(IS_ERR(key.device)) {
169. ret = PTR_ERR(key.device);
170. goto fail_device;
171. }
172. /***********************************************************************************************/
173.
174. /***********************************************************************************************/
175. /* 5.1 初始化key的GPIO */
176. ret = key_gpio_init(&key);
177. if(ret < 0) {
178. printk("gpio init failed! ret = %d\r\n",ret);
179. goto fail_device;
180. }
181. /* */
182. atomic_set(&key.key_value, KEYRELEASE); //无效按键值,也就是松开
183. /***********************************************************************************************/
184. return ret;
185.
186. fail_device:
187. class_destroy(key.class);
188. fail_class:
189. cdev_del(&key.cdev);
190. fail_cdev:
191. unregister_chrdev_region(key.devid, key.dev_count);
192. fail_devid:
193. return ret;
194. }
195.
196. /* 1.2 驱动模块出口函数 */
197. static void __exit key_exit(void) {
198. /* 最后一一添加 , 注销字符设备驱动 */
199. gpio_free(key.key_gpio); //释放IO
200. device_destroy(key.class, key.devid); //摧毁设备
201. class_destroy(key.class); //摧毁类
202. cdev_del(&key.cdev); //注销字符设备结构体
203. unregister_chrdev_region(key.devid, key.dev_count); //注销设备号
204. }
205.
206. /* 1.3 注册驱动模块 */
207. module_init(key_init);
208. module_exit(key_exit);
209.
210. /* 1.4 驱动许可和个人信息 */
211. MODULE_LICENSE("GPL");
212. MODULE_AUTHOR("djw");
四、编写测试APP(keyAPP.c)
1. #include <sys/types.h>
2. #include <sys/stat.h>
3. #include <fcntl.h>
4. #include <stdio.h>
5. #include <unistd.h>
6. #include <stdlib.h>
7. #include <string.h>
8.
9. /* 宏定义按键状态标志 */
10. #define KEYPRESS 0XF0
11. #define KEYRELEASE 0X00
12.
13.
14. /*
15. * argc:应用程序argv数组元素个数
16. * argv[]:具体打参数内容,字符串形式
17. * ./keyAPP <filename>
18. * ./keyAPP /dev/key
19. */
20.
21. int main(int argc, char *argv[])
22. {
23. int fd, ret; //fd文件描述符变量,返回值临时变量
24. int value = 0; //按键值
25. char *filename;
26. //判断输入的参数是否为两个参数
27. if(argc != 2) {
28. printf("ERROR USAGE!\r\n");
29. return -1;
30. }
31.
32. filename = argv[1]; //第二个元素
33. fd = open(filename,O_RDWR); //打开key驱动文件
34. if(fd < 0) {
35. printf("file %s open failed!\r\n",filename);
36. return -1;
37. }
38.
39. /* 循环读取按键值 */
40. while(1) {
41. read(fd, &value, sizeof(value));
42. if(value == KEYPRESS) {
43. printf("key press, value = %d\r\n",value);
44. }
45. }
46.
47. close(fd);
48. if(ret < 0){
49. printf("file %s close failed!\r\n", argv[1]);
50. return -1;
51. }
52. return 0;
53. }
四、 运行测试
1、编译驱动程序:make
2、编译测试APP:arm-linux-gnueabihf-gcc keyApp.c -o keyApp
3、启动Linux,进入/lib/modueles/4.1.15目录中,使用“depmod”和“modprobe key.ko”命令加载驱动模块。最后使用“./keyAPP /dev/key”命令启动测试APP进行试验测试。