Linux学习第17天:pinctrl和gpio子系统开发:由0到1

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子系统的介绍、实验程序及其测试。重点内容无疑是驱动程序的框架。

        本篇笔记的思维导图如下:

Linux学习第17天:pinctrl和gpio子系统开发:由0到1_第1张图片

一、pinctrl子系统

1.pinctrl子系统简介

        pinctrl 子系统主要工作内容如下:
①、获取设备树中 pin 信息。
②、根据获取到的 pin 信息来设置 pin 的复用功能
③、根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。

2.I.MX6ULL的pinctrl子系统驱动

1)、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.


2)、PIN驱动程序详解

        因为里面涉及到驱动分层和分离、平台设备驱动等未学习内容,该部分内容暂时不进行分析。

3.设备树中添加pinctrl节点模板

1)、创建对应节点

        节点前缀一定要是pinctrl_,如下:

1 pinctrl_test: testgrp {
2 /* 具体的 PIN 信息 */
3 };

2)、添加“fsl,pins”属性

        属性的名字一定要是“fsl,pins”,如下:

1 pinctrl_test: testgrp {
2 fsl,pins = <
3 /* 设备所使用的 PIN 配置信息 */
4 >;
5 };

3)、在“fsl,pins”属性中添加 PIN 配置信息
 

1 pinctrl_test: testgrp {
2 fsl,pins = <
3 MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 config /*config 是具体设置值*/
4 >;
5 };

二、gpio子系统

1.gpio子系统简介

        gpio 子系统顾名思义,就是用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO
为输入输出,读取 GPIO 的值等。

2.I.MX6ULL的gpio子系统驱动

1)、设备树中的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极性。

2)、GPIO驱动程序详解

        因为里面涉及到驱动分层和分离、平台设备驱动等未学习内容,为减少干扰,该部分内容暂时不进行分析。


3.gpio子系统API函数

        对于驱动开发人员,设置好设备树以后就可以使用 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 的值

4.设备树中添加gpio节点模板

1)、创建设备节点

        例如在根节点“/”下创建 test 设备子节点,如下所示:

1 test {
2 /* 节点内容 */
3 };

2)、添加 pinctrl 信息
 

1 test {
2 pinctrl-names = "default";
3 pinctrl-0 = <&pinctrl_test>;
4 /* 其他节点内容 */
5 };

        添加 pinctrl-0 节点,此节点引用 之前创建的 pinctrl_test 节点,表示 tset 设备的所使用的 PIN 信息保存在 pinctrl_test 节点中。

3)、添加 GPIO 属性信息

        在 test 节点中添加 GPIO 属性信息,表明 test 所使用的 GPIO 是哪个引脚,如下所示:

test {
2 pinctrl-names = "default";
3 pinctrl-0 = <&pinctrl_test>;
4 gpio = <&gpio1 0 GPIO_ACTIVE_LOW>;
5 };

5.与gpio相关的OF函数

        总结如下表所示:

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 编
号,此函数在驱动中使用很频繁!

三、驱动程序编写

1.修改设备树文件

1)、添加pinctrl节点

        在 iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_led”的子节点,节点
内容如下所示:

1 pinctrl_led: ledgrp {
2 fsl,pins = <
3 MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */
4 >;
5 };

2)、添加LED设备节点

        在根节点“/”下创建 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 }

3)、检查PIN是否被其他外设使用

        找到,并注释掉。

2.LED灯驱动程序编写

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 内核驱动里面非常常见。


3.编写测试APP

        和以前的一样。

四、运行测试

1.编译驱动程序和测试APP

1)、编译驱动程度

        将 obj-m 变量的值改为 gpioled.o,

        输入如下命令编译出驱动模块文件gpioled.ko
 

make -j32

2)、编译测试APP

        输入如下命令编译测试 ledApp.c 这个测试程序:
 

arm-linux-gnueabihf-gcc ledApp.c -o ledApp

2.运行测试

输入如下命令加载 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子系统的介绍、实验程序及其测试。重点内容是驱动程序的框架。


本文为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,不得用于商业用途。

你可能感兴趣的:(Linux学习,linux,学习,嵌入式硬件,arm开发)