Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长
本篇笔记的题目为 pinctrl和gpio子系统开发:由0到1。做嵌入式系统开发,肯定经历过单片机-ARM-Linux这么一个过程。这是一个8位单片机到32位单片机再到嵌入式系统(WinCE或Linux)由浅到深、由简到难的过程。在每个相关单元的学习和实践中,总是从第一个项目出发,就如软件开发中的第一行代码,第一个HELLO WORLD!功能的实现,这也是一个容易被初学者接收的过程。本节学习的引脚的控制也是相关单元中最基础、最浅显易懂的“0”,也是进入嵌入式Linux驱动开发的第一步。其实前面所学习的LED灯的亮灭控制也可以归于引脚控制的相关内容。有句话是这么说的,问题的关键不是做与不做,而是开始去做,不管是否能够成功,你已经成功了一半。
本篇笔记的主要内容为pinctrl和gpio子系统开发。主要内容包括pinctrl子系统及gpio子系统的介绍、实验程序及其测试。重点内容无疑是驱动程序的框架。
本篇笔记的思维导图如下:
pinctrl 子系统主要工作内容如下:
①、获取设备树中 pin 信息。
②、根据获取到的 pin 信息来设置 pin 的复用功能
③、根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。
pinctrl 子系统要根据你提供的信息来配置 PIN 功能,一般会在设备树里面创建一个节点来描述 PIN 的配置信息。打开 imx6ull.dtsi 文件,找到一个叫做 iomuxc 的节点,如下所示:
756 iomuxc: iomuxc@020e0000 {
757 compatible = "fsl,imx6ul-iomuxc";
758 reg = <0x020e0000 0x4000>;
759 };
如果在iomuxc中添加我们自定义外设的PIN,那么需要新建一个子节点,然后将这个自定义外设的所有PIN配置信息都放到这个子节点中。
如何添加PIN的配置信息?对于一个 PIN 的配置主要包括两方面,一个是设置这个 PIN 的复用功能,另一个就是设置这个 PIN 的电气特性,分为对应MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 和 0x17059.
因为里面涉及到驱动分层和分离、平台设备驱动等未学习内容,该部分内容暂时不进行分析。
节点前缀一定要是pinctrl_,如下:
1 pinctrl_test: testgrp {
2 /* 具体的 PIN 信息 */
3 };
属性的名字一定要是“fsl,pins”,如下:
1 pinctrl_test: testgrp {
2 fsl,pins = <
3 /* 设备所使用的 PIN 配置信息 */
4 >;
5 };
1 pinctrl_test: testgrp {
2 fsl,pins = <
3 MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 config /*config 是具体设置值*/
4 >;
5 };
gpio 子系统顾名思义,就是用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO
为输入输出,读取 GPIO 的值等。
pinctrl 配置好以后就是设置 gpio 了。例如在设备树中 SD 卡节点下添加一个属性来描述 SD 卡的 CD 引脚就行了, SD卡驱动直接读取这个属性值就知道 SD 卡的 CD 引脚使用的是哪个 GPIO 了。
打开 imx6ull.dtsi,在里面可以找到如下所示内容:
504 gpio1: gpio@0209c000 {
505 compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
506 reg = <0x0209c000 0x4000>;
507 interrupts = ,
508 ;
509 gpio-controller;
510 #gpio-cells = <2>;
511 interrupt-controller;
512 #interrupt-cells = <2>;
513 };
gpio1 节点信息描述了 GPIO1 控制器的所有信息,重点就是 GPIO1 外设寄存器基地址以及
兼 容 属 性 。
#gpio-cells = <2>;一个cell为GPIO编号,一个cell为GPIO极性。
因为里面涉及到驱动分层和分离、平台设备驱动等未学习内容,为减少干扰,该部分内容暂时不进行分析。
对于驱动开发人员,设置好设备树以后就可以使用 gpio 子系统提供的 API 函数来操作指定的 GPIO。
常用的API函数如下表:
1 | gpio_request | 申请一个 GPIO 管脚 |
2 | gpio_free | 释放GPIO管脚 |
3 | gpio_direction_input | 设置GPIO管脚为输入 |
4 | gpio_direction_output | 设置GPIO管脚为输出 |
5 | gpio_get_value | 获取某个 GPIO 的值(0 或 1) |
6 | gpio_set_value | 设置某个 GPIO 的值 |
例如在根节点“/”下创建 test 设备子节点,如下所示:
1 test {
2 /* 节点内容 */
3 };
1 test {
2 pinctrl-names = "default";
3 pinctrl-0 = <&pinctrl_test>;
4 /* 其他节点内容 */
5 };
添加 pinctrl-0 节点,此节点引用 之前创建的 pinctrl_test 节点,表示 tset 设备的所使用的 PIN 信息保存在 pinctrl_test 节点中。
在 test 节点中添加 GPIO 属性信息,表明 test 所使用的 GPIO 是哪个引脚,如下所示:
test {
2 pinctrl-names = "default";
3 pinctrl-0 = <&pinctrl_test>;
4 gpio = <&gpio1 0 GPIO_ACTIVE_LOW>;
5 };
总结如下表所示:
1 | of_gpio_named_count | 用于获取设备树某个属性里面定义了几个 GPIO 信息 |
2 | of_gpio_count | 此函数统计的是“gpios”这个属 性的 GPIO 数量,而 of_gpio_named_count 函数可以统计任意属性的 GPIO 信息 |
3 | of_get_named_gpio | 此函数会将设备树中类似<&gpio5 7 GPIO_ACTIVE_LOW>的属性信息转换为对应的 GPIO 编 号,此函数在驱动中使用很频繁! |
在 iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_led”的子节点,节点
内容如下所示:
1 pinctrl_led: ledgrp {
2 fsl,pins = <
3 MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */
4 >;
5 };
在根节点“/”下创建 LED 灯节点,节点名为“gpioled”,节点内容如下:
1 gpioled {
2 #address-cells = <1>;
3 #size-cells = <1>;
4 compatible = "atkalpha-gpioled";
5 pinctrl-names = "default";
6 pinctrl-0 = <&pinctrl_led>;
7 led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
8 status = "okay";
9 }
找到,并注释掉。
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
9 #include
10 #include
11 #include
12 #include
13 #include
14 #include
15 #include
16 #include
17 /***************************************************************
18 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
19 文件名 : gpioled.c
20 作者 : 左忠凯
21 版本 : V1.0
22 描述 : 采用 pinctrl 和 gpio 子系统驱动 LED 灯。
23 其他 : 无
24 论坛 : www.openedv.com
25 日志 : 初版 V1.0 2019/7/13 左忠凯创建
26 ***************************************************************/
27 #define GPIOLED_CNT 1 /* 设备号个数 */
28 #define GPIOLED_NAME "gpioled" /* 名字 */
29 #define LEDOFF 0 /* 关灯 */
30 #define LEDON 1 /* 开灯 */
31
32 /* gpioled 设备结构体 */
33 struct gpioled_dev{
34 dev_t devid; /* 设备号 */
35 struct cdev cdev; /* cdev */
36 struct class *class; /* 类 */
37 struct device *device; /* 设备 */
38 int major; /* 主设备号 */
39 int minor; /* 次设备号 */
40 struct device_node *nd; /* 设备节点 */
41 int led_gpio; /* led 所使用的 GPIO 编号 */
42 };
43
44 struct gpioled_dev gpioled; /* led 设备 */
45
46 /*
47 * @description : 打开设备
48 * @param – inode : 传递给驱动的 inode
49 * @param – filp : 设备文件, file 结构体有个叫做 private_data 的成员变量
50 * 一般在 open 的时候将 private_data 指向设备结构体。
51 * @return : 0 成功;其他 失败
52 */
53 static int led_open(struct inode *inode, struct file *filp)
54 {
55 filp->private_data = &gpioled; /* 设置私有数据 */
56 return 0;
57 }
58
59 /*
60 * @description : 从设备读取数据
61 * @param – filp : 要打开的设备文件(文件描述符)
62 * @param - buf : 返回给用户空间的数据缓冲区
63 * @param - cnt : 要读取的数据长度
64 * @param – offt : 相对于文件首地址的偏移
65 * @return : 读取的字节数,如果为负值,表示读取失败
66 */
67 static ssize_t led_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
68 {
69 return 0;
70 }
71
72 /*
73 * @description : 向设备写数据
74 * @param - filp : 设备文件,表示打开的文件描述符
75 * @param - buf : 要写给设备写入的数据
76 * @param - cnt : 要写入的数据长度
77 * @param – offt : 相对于文件首地址的偏移
78 * @return : 写入的字节数,如果为负值,表示写入失败
79 */
80 static ssize_t led_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
81 {
82 int retvalue;
83 unsigned char databuf[1];
84 unsigned char ledstat;
85 struct gpioled_dev *dev = filp->private_data;
86
87 retvalue = copy_from_user(databuf, buf, cnt);
88 if(retvalue < 0) {
89 printk("kernel write failed!\r\n");
90 return -EFAULT;
91 }
92
93 ledstat = databuf[0]; /* 获取状态值 */
94
95 if(ledstat == LEDON) {
96 gpio_set_value(dev->led_gpio, 0); /* 打开 LED 灯 */
97 } else if(ledstat == LEDOFF) {
98 gpio_set_value(dev->led_gpio, 1); /* 关闭 LED 灯 */
99 }
100 return 0;
101 }
102
103 /*
104 * @description : 关闭/释放设备
105 * @param – filp : 要关闭的设备文件(文件描述符)
106 * @return : 0 成功;其他 失败
107 */
108 static int led_release(struct inode *inode, struct file *filp)
109 {
110 return 0;
111 }
112
113 /* 设备操作函数 */
114 static struct file_operations gpioled_fops = {
115 .owner = THIS_MODULE,
116 .open = led_open,
117 .read = led_read,
118 .write = led_write,
119 .release = led_release,
120 };
121
122 /*
123 * @description : 驱动入口函数
124 * @param : 无
125 * @return : 无
126 */
127 static int __init led_init(void)
128 {
129 int ret = 0;
130
131 /* 设置 LED 所使用的 GPIO */
132 /* 1、获取设备节点: gpioled */
133 gpioled.nd = of_find_node_by_path("/gpioled");
134 if(gpioled.nd == NULL) {
135 printk("gpioled node cant not found!\r\n");
136 return -EINVAL;
137 } else {
138 printk("gpioled node has been found!\r\n");
139 }
140
141 /* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
142 gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
143 if(gpioled.led_gpio < 0) {
144 printk("can't get led-gpio");
145 return -EINVAL;
146 }
147 printk("led-gpio num = %d\r\n", gpioled.led_gpio);
148
149 /* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯 */
150 ret = gpio_direction_output(gpioled.led_gpio, 1);
151 if(ret < 0) {
152 printk("can't set gpio!\r\n");
153 }
154
155 /* 注册字符设备驱动 */
156 /* 1、创建设备号 */
157 if (gpioled.major) { /* 定义了设备号 */
158 gpioled.devid = MKDEV(gpioled.major, 0);
159 register_chrdev_region(gpioled.devid, GPIOLED_CNT,
GPIOLED_NAME);
160 } else { /* 没有定义设备号 */
161 alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT,
GPIOLED_NAME); /* 申请设备号 */
162 gpioled.major = MAJOR(gpioled.devid); /* 获取分配号的主设备号 */
163 gpioled.minor = MINOR(gpioled.devid); /* 获取分配号的次设备号 */
164 }
165 printk("gpioled major=%d,minor=%d\r\n",gpioled.major,
gpioled.minor);
166
167 /* 2、初始化 cdev */
168 gpioled.cdev.owner = THIS_MODULE;
169 cdev_init(&gpioled.cdev, &gpioled_fops);
170
171 /* 3、添加一个 cdev */
172 cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
173
174 /* 4、创建类 */
175 gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
176 if (IS_ERR(gpioled.class)) {
177 return PTR_ERR(gpioled.class);
178 }
179
180 /* 5、创建设备 */
181 gpioled.device = device_create(gpioled.class, NULL,
gpioled.devid, NULL, GPIOLED_NAME);
182 if (IS_ERR(gpioled.device)) {
183 return PTR_ERR(gpioled.device);
184 }
185 return 0;
186 }
187
188 /*
189 * @description : 驱动出口函数
190 * @param : 无
191 * @return : 无
192 */
193 static void __exit led_exit(void)
194 {
195 /* 注销字符设备驱动 */
196 cdev_del(&gpioled.cdev); /* 删除 cdev */
197 unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销 */
198
199 device_destroy(gpioled.class, gpioled.devid);
200 class_destroy(gpioled.class);
201 }
202
203 module_init(led_init);
204 module_exit(led_exit);
205 MODULE_LICENSE("GPL");
206 MODULE_AUTHOR("zuozhongkai");
将设备结构体设置为 filp 私有数据的方法在 Linux 内核驱动里面非常常见。
和以前的一样。
将 obj-m 变量的值改为 gpioled.o,
输入如下命令编译出驱动模块文件gpioled.ko
make -j32
输入如下命令编译测试 ledApp.c 这个测试程序:
arm-linux-gnueabihf-gcc ledApp.c -o ledApp
输入如下命令加载 gpioled.ko 驱动模块:
depmod //第一次加载驱动的时候需要运行此命令
modprobe gpioled.ko //加载驱动
输入如下命令打开LED:
./ledApp /dev/gpioled 1 //打开 LED 灯
输入如下命令关闭LED:
./ledApp /dev/gpioled 0 //关闭 LED 灯
输入如下命令卸载驱动:
rmmod gpioled.ko
本篇笔记的主要内容为pinctrl和gpio子系统开发。主要内容包括pinctrl子系统及gpio子系统的介绍、实验程序及其测试。重点内容是驱动程序的框架。
本文为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,不得用于商业用途。