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

2023-11-16

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子系统

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 = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
508 <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
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 <linux/types.h>
2 #include <linux/kernel.h>
3 #include <linux/delay.h>
4 #include <linux/ide.h>
5 #include <linux/init.h>
6 #include <linux/module.h>
7 #include <linux/errno.h>
8 #include <linux/gpio.h>
9 #include <linux/cdev.h>
10 #include <linux/device.h>
11 #include <linux/of.h>
12 #include <linux/of_address.h>
13 #include <linux/of_gpio.h>
14 #include <asm/mach/map.h>
15 #include <asm/uaccess.h>
16 #include <asm/io.h>
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子系统的介绍、实验程序及其测试。重点内容是驱动程序的框架。


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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux学习第17天:pinctrl和gpio子系统开发:由0到1 的相关文章

随机推荐

  • css3颜色渐变:css3如何实现背景颜色渐变?

    为了开发网页的美观 css3背景颜色渐变是经常会用到的 那么 css3背景颜色渐变如何设置呢 本篇文章我们就来介绍关于css颜色渐变背景的设置方法 我们要知道的是css3渐变有两种类型 css3线性渐变和css3径向渐变 下面我们就来看一下
  • C语言基础知识梳理<1>

    1 前言 各位小伙伴现在还在为学习C语言而苦恼吗 接下来我将更新一系列与C语言相关的文章与干货 希望各位小伙伴可以留下自己宝贵的 并持续关注我 真诚点赞 手留余香 2 网站推荐 软件推荐 用gitee 码云 保存自己的代码 自己写完的代码总
  • Visual Studio 2005 安装经验谈

    看到这题目 我都有点不自信 你用了Visual Studio 2005才几天啊 的确 关于编程我还是新手 不过这几天重复进行的Visual Studio 2005安装 到安装失败 再到解决问题 再安装 往返3次 下载Visual Studi
  • JDK 21探秘:引领Java开发新潮流的强大功能

    JDK 21是Java开发工具包的最新版本 它引入了许多令人振奋的新特性 旨在提高开发人员的生产力和代码质量 在本文中 我们将介绍一些JDK 21的新特性 并提供使用示例 以帮助您更好地理解和应用这些功能 一 字符串模板 String Te
  • 当SSC遇见RPA:最大化发挥共享服务模式效用

    作为一种创新的管理模式或战略 共享服务中心 SSC 如今正越来越受到企业的青睐 很多中大型集团 跨国公司都有某种类型的SSC存在 SSC可为企业带来哪些好处 RPA在这其中又是如何发挥作用 共享服务中心 Shared Service Cen
  • MAC版本Mysql数据库忘记密码解决教程

    MAC初始化安装获取密码 删除Data文件夹内容 并重新生成 rm rf usr local mysql data 初始化再次生成Data文件夹中的内容 初始化完成之后再代码最后生成随机密码需要保存 mysqld initialize us
  • 解决win 10操作系统中图片查看方式没有window7中的照片查看器问题

    1 在桌面新建一个记事本文件 保存为 reg 不能保存为 reg 代表任意 如下图 为我自建的 1 reg 2 保存后 双击打开该文件 并将如下代码拷入 并保存 如下图 2 1 右键该文件用记事本或者notepad 打开 我这里是用note
  • 【C++】C/C++内存管理(new和delete详解)

    目录 1 C C 内存分布 2 C语言中动态内存管理方式 3 C 内存管理方式 3 1 new delete操作内置类型 3 2 new delete操作自定义类型 4 operator new与operator delete函数 4 1
  • 解决width: 100%;再设置margin问题

    最近在做前端 遇到了这么个小问题 就是当我们把一个 div 的 width 设置为 100 之后 再设置 margin 的时候 这个div 莫名其妙的超出了屏幕 情景如下图 这就很难受了啊 不过办法总比困难多 下面 我将讲两种解决方法 方法
  • Python调用文心一言的API

    最近申请了文心一言的key 然后尝试调用了一下文心一言 这里使用一个简单的方式来调用文心一言 pip install paddle pipelines from pipelines nodes import ErnieBot api key
  • 应聘时最漂亮的回答,看后不分享都难啊

    1 请你自我介绍一下自己好吗 回答提示 一般人回答这个问题过于平常 只说姓名 年龄 爱好 工作经验 这些在简历上都有 其实 企业最希望知道的是求职者能否胜任工作 包括 最强的技能 最深入研究的知识领域 个性中最积极的部分 做过的最成功的事
  • python基础总结:1.8、输入输出

    python基础总结 1 8 输入输出 文章目录 python基础总结 1 8 输入输出 1 更漂亮的输出格式 1 1 格式化字符串文字 1 2 字符串的format 方法 1 3 手动格式化字符串 1 4 旧的字符串格式化方法 2 读写文
  • Android下截屏 及 格式转换

    http wiseideal iteye com blog 1250175 Android下截屏 及 格式转换 2011 02 17 11 41 43 转载 标签 it 根据王研科先生的探索 Android G1手机的色深是16bit 即R
  • linux shell 实现 四则运算(整数及浮点) 简单方法

    在刚刚学习写shell 批处理时候 进行逻辑运算中 少不了需要进行基础的 四则运算 这里说说在linux shell 里面简单的实现方法 1 简单方法 chengmo centos5 b 5 5 5 3 2 chengmo centos5
  • gps纠偏及大陆地图偏移原因

    大陆地图偏移原因 国家安全与地图保密插件 国家保密插件 也叫做加密插件或者加偏或者SM模组 其实就是对真实坐标系统进行人为的加偏处理 按照几行代码的算法 将真实的坐标加密成虚假的坐标 而这个加偏并不是线性的加偏 所以各地的偏移情况都会有所不
  • mysql为空转换为0_MySQL数据库 null转为0,及一些case when用法

    1 如果为空返回0 select ifnull null 0 应用情景 如果在进行右连接或者左连接时 有一些为空的字段 可以进行这样的处理 select ifnull B submission time A submission time
  • arm的多级流水线技术和和存储管理单元mmu

    流水线概念 流水线的概念与原理 处理器按照一系列步骤来执行每一条指令 典型的步骤如下 1 从存储器读取指令 fetch 2 译码以鉴别它属于哪一条指令 decode 3 从指令中提取指令的操作数 这些操作数往往存在于寄存器reg中 4 将操
  • java for循环时间复杂度_关于for循环的时间复杂度

    今天在看算法时 遇见了一些问题 想了很久 现总结如下 关于for循环的时间复杂度 我们知道当一重for循环时 packageSuanfa public classFortest public static voidmain String a
  • 垂直同步到底要不要开?老司机教你G-Sync显示器的正确打开姿势

    一直以来我们都认为PC的画面效果取决于显卡 认为游戏的FPS值越高代表游戏越流畅 但实际上 显示器也是决定游戏帧数的重要一环 显卡将画面渲染并输出到显示器中 显示器接收GPU的信号并输出 然而 因为显卡性能和运行程序的差异 显卡一般无法以恒
  • Linux学习第17天:pinctrl和gpio子系统开发:由0到1

    Linux版本号4 1 15 芯片I MX6ULL 大叔学Linux 品人间百味 思文短情长 本篇笔记的题目为 pinctrl和gpio子系统开发 由0到1 做嵌入式系统开发 肯定经历过单片机 ARM Linux这么一个过程 这是一个8位单