Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作

2024-03-07 17:48

本文主要是介绍Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 全系列传送门
  • 什么是pinctrl和gpio子系统
  • pinctel子系统
    • pinctel子系统功能
    • 查看属性表达
    • 查看pinctrl
  • gpio子系统
    • gpio子系统功能
    • 常用gpio子系统提供的api函数
      • gpio_request函数
      • gpio_free函数
      • gpio_direction_input函数
      • gpio_dierction_output函数
      • gpio_get_value函数
      • gpio_set_value函数
  • 设备树节点
    • 添加节点信息
    • pinfunc.h文件查找宏定义
  • 测试程序源码
    • app.c
    • driver.c
  • 结果验证
    • 查看设备树节点
    • 安装ko模块生成驱动
    • 执行app

全系列传送门

Linux嵌入式驱动开发01——第一个驱动Hello World(附源码)

Linux嵌入式驱动开发02——驱动编译到内核

Linux嵌入式驱动开发03——杂项设备驱动(附源码)

Linux嵌入式驱动开发04——应用层和内核层数据传输

Linux嵌入式驱动开发05——物理地址到虚拟地址映射

Linux嵌入式驱动开发06——第一个相对完整的驱动实践编写

Linux嵌入式驱动开发07——GPIO驱动过程记录(飞凌开发板)

Linux嵌入式驱动开发08——字符设备(步步为营)

Linux嵌入式驱动开发09——平台总线详解及实战

Linux嵌入式驱动开发10——设备树开发详解

Linux嵌入式驱动开发11——平台总线模型修改为设备树实例

Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作

Linux嵌入式驱动开发13——ioctl接口(gpio控制使用)

Linux嵌入式驱动开发14——中断的原理以及按键中断的实现(tasklet中断下文)

Linux嵌入式驱动开发15——等待队列和工作队列

Linux嵌入式驱动开发16——按键消抖实验(内核定时器)

Linux嵌入式驱动开发17——输入子系统

Linux嵌入式驱动开发18——I2C通信

什么是pinctrl和gpio子系统

在学习单片机(比如51单片机和STM32)的时候,我们可以直接对单片机的寄存器进行操作,进而达到控制pin脚的目的。

而Linux系统相较于一个单片机系统,要庞大而复杂得多,因此在Linux系统中我们不能直接对pin脚进行操作。

Linux系统讲究驱动分层,pinctrl子系统和GPIO子系统就是驱动分层的产物。如果我们要操作pin脚,就必须要借助pinctrl子系统和GPIO子系统。

pinctrl子系统的作用是pin config(引脚配置)和pin mux(引脚复用),而如果pin脚被复用为了GPIO(注意:GPIO功能只是pin脚功能的一种),就需要再借助GPIO子系统对pin脚进行控制了,GPIO子系统提供了一系列关于GPIO的API函数,供我们调用。

pinctel子系统

pinctel子系统功能

  1. 管理系统中所有的可以控制的pin,在系统初始化的时候,枚举所有可以控制的pin,并标识这些pin
  2. 管理这些pin的复用,对于SOC而言,其引脚除了配置成普通的GPIO之外,若干个引脚还可以组成一个pin group,形成特定的功能
  3. 配置这些pin的特性,例如使能关闭引脚上的pull-up、pull-down电阻,配置引脚的driver strength

不同SOC厂家的pin controller的节点

这些节点里面都是把某些引脚复用成某些功能
在这里插入图片描述

查看属性表达

不同的SOC厂家,他们的pin controller的节点里面的属性表达的含义都是不同的,所以,要想查看这些属性的含义,去到Linux源码目录

/Documentation/devicetree/bindings

在这里插入图片描述

/Documentation/devicetree/bindings/pinctrl

进入到pinctrl文件中,可以看到txt文件,我们的型号是im6q,找到对应的文件就可以打开查看信息
在这里插入图片描述
在这里插入图片描述

查看pinctrl

打开我们板子对应的dts文件,这里的我是imx6q-c-sabresd.dts
在这里插入图片描述
打开之后发现没有我们想要看到的pinctrl相关的代码,都是一些引用添加节点信息
在这里插入图片描述
根据头文件,我们继续打开imx6qdl-sabresd.dtsi文件
在这里插入图片描述
在imx6qdl-sabresd.dtsi文件里就可以看到详细的pinctrl的信息了
在这里插入图片描述

gpio子系统

gpio子系统功能

在这里插入图片描述
也就是,我们的pinctrl子系统配置完成后,我们接下来就是要交给gpio子系统来进行控制

在设备树文件中代码示例如下
在这里插入图片描述

	gpio_user: gpios{pinctrl-names = "default";pinctrl-0 = <&pinctrl_user>;		// default的状态就是对应pinctrl_user的节点compatible = "gpio-user";gpio0{label = "D01";gpios = <&gpio3 15 1>;default-direction = "out";};gpio1{label = "D02";gpios = <&gpio2 21 1>;default-direction = "out";};};

针对gpios = <&gpio3 15 1>;代表的含义就是,gpio3组中的第15个IO口,给配置为1

那么这里使用了pinctrl和gpio子系统之后,就可以替代之前的这种写法

在这里插入图片描述
在之前,我们控制引脚需要对寄存器进行控制,是reg = <0x20ac000 0x0000004>;
这样的形式进行描述,而现在使用了gpio子系统之后,就可以更方便规范的使用。

常用gpio子系统提供的api函数

gpio_request函数

在这里插入图片描述
在这里我们看到了一个of函数来获取gpio的编号,就是of_get_named_gpio函数,这个函数的详情如下
在这里插入图片描述

gpio_free函数

在这里插入图片描述

gpio_direction_input函数

在这里插入图片描述

gpio_dierction_output函数

在这里插入图片描述

gpio_get_value函数

在这里插入图片描述

gpio_set_value函数

在这里插入图片描述

设备树节点

添加节点信息

我们从上次的代码,设备树的驱动代码进行修改,本次使用了pinctrl和gpio子系统,本质上是替代了之前的寄存器操作,使用函数来进行电平的控制。

现在首先还是在设备树文件中添加我们的节点信息。
在这里插入图片描述

pinctrl_user: usergrp {fsl,pins = <MX6QDL_PAD_EIM_DA15__GPIO3_IO15 0x1b0b0MX6QDL_PAD_EIM_A17__GPIO2_IO21	0x1b0b0>;};

我们要控制的gpio引脚是EIM_A17
在这里插入图片描述
那么,我们是如何找到EIM_A17对应的宏定义MX6QDL_PAD_EIM_A17__GPIO2_IO21呢?

pinfunc.h文件查找宏定义

在我们的dts文件夹下,有一个对应imx6q-pinfunc.h文件,里面定义着所需要的宏定义
在这里插入图片描述
这样,我们只需要按照需要查找就可以了
在这里插入图片描述
通过搜索EIM_A17.这样就找到了对应的宏定义,通过后面的后缀名称,可以看到有很多的功能
在这里插入图片描述
我们也可以通过查找芯片手册,看到EIM_A17对应的引脚的说明IOMUXC_SW_MUX_CTL_PAD_EIM_ADDR17
在这里插入图片描述
回到我们刚才说的,根据名称,我们需要一个gpio的功能,所以选择的宏定义是

#define MX6QDL_PAD_EIM_A17__GPIO2_IO21              0x0f0 0x404 0x000 0x5 0x0

MX6QDL_PAD_EIM_A17__GPIO2_IO21所代表的数 0x0f0 0x404 0x000 0x5 0x0 实际上就是电器属性,都已经配置好了,这样就不用我们一个一个的去手动的查找芯片手册然后进行配置。

0x0f0 0x404 0x000 0x5 0x0 这 5个值的含义如下所示:

<mux_reg conf_reg input_reg mux_mode input_val>

0x0f0 :mux_reg寄存器偏移地址,设备树中的 iomuxc节点就是 IOMUXC外设对应的节点,根据其 reg属性可知 IOMUXC外设寄存器起始地址为 0x020e0000。因此20E_0000h base + F0h offset = 20E_00F0h, IOMUXC_SW_MUX_CTL_PAD_EIM_ADDR17寄存器地址正好是 20E_00F0h,如图所示,所以这里的mux_reg = 0x0f0
在这里插入图片描述
0x404 conf_reg寄存器偏移地址,和 mux_reg一样, 0x020e0000+0x404=0x020e0404这个就 是寄存器IOMUXC_SW_MUX_CTL_PAD_EIM_ADDR17的地址。

0x000 input_reg寄存器偏移地址,有些外设有 input_reg寄存器,有 input_reg寄存器的外设需要配置 input_reg寄存器。没有的话就不需要设置

0x5 mux_reg寄存器值,在这里就相当于设置IOMUXC_SW_MUX_CTL_PAD_EIM_ADDR17寄存器为 0x5,也即是设置这个 PIN复用为 GPIO2_IO21。

0x0 input_reg寄存器值,在这里无效。

这就是宏 IOMUXC_SW_MUX_CTL_PAD_EIM_ADDR17的含义,看的比较仔细的同学应该会发现并没有 conf_reg寄存器的值, config_reg寄存器是设置一个 PIN的电气特性的,这么重要的寄存器怎么没有值呢?

MX6QDL_PAD_EIM_A17__GPIO2_IO21	0x1b0b0

MX6QDL_PAD_EIM_A17__GPIO2_IO21 我们上面已经分析了,就剩下了一个 0x0b0b1反应快的同学应该已经猜出来了, 0x0b0b1就是 conf_reg寄存器值!

此值由用户自行设置,通过此值来设置一个 IO的上 /下拉、驱动能力和速度等。在这里就相当于设置寄存器IOMUXC_SW_MUX_CTL_PAD_EIM_ADDR17的值为 0x0b0b1。

如果不知道要设置成多少,这里我是当作普通gpio来进行操作,所以只需要参考led灯和按键的配置信息就可以了。

这里还有一个点需要注意,就是一个io引脚只能定义一个功能,就例如我们的EIM_A17,有这么多

#define MX6QDL_PAD_EIM_A17__EIM_ADDR17              0x0f0 0x404 0x000 0x0 0x0
#define MX6QDL_PAD_EIM_A17__IPU1_DISP1_DATA12       0x0f0 0x404 0x000 0x1 0x0
#define MX6QDL_PAD_EIM_A17__IPU2_CSI1_DATA12        0x0f0 0x404 0x8b8 0x2 0x1
#define MX6QDL_PAD_EIM_A17__GPIO2_IO21              0x0f0 0x404 0x000 0x5 0x0
#define MX6QDL_PAD_EIM_A17__SRC_BOOT_CFG17          0x0f0 0x404 0x000 0x7 0x0

所以我们就要在设备树文件中,搜索MX6QDL_PAD_EIM_A17_,来确定io口没有被使用过,不然就注释掉。

在这里插入图片描述
然后对这些信息继续进行配置,在我们的test节点中,添加如下代码,test-gpios = <&gpio2 21 1>;对应的就是MX6QDL_PAD_EIM_A17__GPIO2_IO21,gpio2组中的第21个io口,默认置高。
在这里插入图片描述
下面就该是driver驱动程序的代码了,依然是在probe函数中进行添加修改

测试程序源码

app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main(int argc, char *argv[])
{int fd;char buff[64] = {0};fd = open("/dev/hello_misc", O_RDWR);       // 打开节点if(fd < 0){perror("open error\n");                // perror在应用中打印return fd;}buff[0] = atoi(argv[1]);                    //字符串转化成整形                            // read(fd, buff, sizeof(buff));write(fd, buff,sizeof(buff));               //在write中就传数据到底层,这样可以调用驱动的mis_write的操作了,直接操作 ./app 1或者./app 0// printf("buf is:%s\n", buff);close(fd);return 0;
}

driver.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>#include <linux/gpio.h>
#include <linux/of_gpio.h>struct device_node  *test_device_node;
u32 out_values[2] = {0};
int beep_gpio = 0;                                                      // 返回到的gpio编号struct resource *beep_mem;
struct resource *beep_mem_tmp;int misc_open (struct inode *inode, struct file *file){printk("hello misc_open!!!\n");return 0;
}int misc_release(struct inode *inode, struct file *file){printk("bye bye misc_release!!!\n");return 0;}ssize_t misc_read(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){char kbuf[64] = "copy to user!!!\n";if( copy_to_user(ubuf, kbuf, size) != 0 ){printk("copy_to_user error!!!\n");return -1;}printk("hello misc_read!!!\n");return 0;
}ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){char kbuf[64] = {0};printk("hello misc_write!!!\n");if( copy_from_user(kbuf, ubuf, size) != 0 ){printk("copy_from_user error!!!\n");return -1;}printk("buf is:%s\n", kbuf);/*********实现IO的逻辑功能*********************************************************************/if(kbuf[0] == 1){                           //对蜂鸣器的控制,如果是1,控制gpio口// *vir_gpio5_dr |= (1 << 1);           //因为是gpio5的01,所以左移一位就可以,给一个高电平,使蜂鸣器工作gpio_set_value(beep_gpio, 1);           //Linux体现出来了通用性,不需要寄存器地址了printk("gpio_set_value is high!!!\n");}else if(kbuf[0] == 0){// *vir_gpio5_dr &= ~(1 << 1); gpio_set_value(beep_gpio, 0);   printk("gpio_set_value is low!!!\n");             }return 0;
}struct file_operations misc_fops = {.owner = THIS_MODULE,.open = misc_open,.release = misc_release,.read = misc_read,.write = misc_write,
};struct miscdevice misc_dev = {.minor = MISC_DYNAMIC_MINOR,                // 次设备号.name = "hello_misc",                       // 生成一个名字叫做hello_misc的设备节点.fops = &misc_fops                          // 完善file_operations
};int beep_probe(struct platform_device *pdev){int ret;printk("beep_probe ok!!!\n");/*直接获取设备节点信息*/// printk("node name is %s\n", pdev->dev.of_node->name);            //pdev->dev.of_node获取节点,和下面的test_device_node功能相同/*间接获取设备节点信息*//********查找指定路径的节点***********/test_device_node = of_find_node_by_path("/test");if(test_device_node == NULL) {printk("of_find_node_by_path error!!!\n");return -1;}else {printk("of_find_node_by_path ok!!!\n");printk("test_device_node name is %s\n", test_device_node->name);}beep_gpio = of_get_named_gpio(test_device_node, "test-gpios", 0);       // test-gpios是设备树文件中包含我们定义的gpio信息属性名test-gpios = <&gpio2 21 1>;if(beep_gpio < 0) {printk("of_get_named_gpio error!!!\n");}else{printk("of_get_named_gpio is %d\n", beep_gpio);}ret = gpio_request(beep_gpio, "beep");                                  // beep是我们现在起的名字if(ret != 0) {printk("gpio_request error!!!\n");}else{printk("gpio_request ok!!!\n");}/*gpio子系统,控制输出为高电平*/gpio_direction_output(beep_gpio, 1);                                           // 使用gpio_direction直接对编号beep_gpio对应的io口拉高/*注册杂项设备*/ret = misc_register(&misc_dev);if(ret < 0){printk("misc_register failed!!!\n");return -1;}else{printk("misc_register succeed!!!\n"); }return 0;
}int beep_remove(struct platform_device *pdev){printk("beep_remove ok!!!\n");return 0;
}const struct platform_device_id beep_id_table = {.name = "beep_device_test",
};const struct of_device_id of_match_table_test[] = {{.compatible = "test12345"},{}                                              // 不写会提示警告
};struct platform_driver beep_device = {.probe = beep_probe,                            //  这个probe函数其实和  device_driver中的是一样的功能,但是一般是使用device_driver中的那个.remove = beep_remove,                          //  卸载平台设备驱动的时候会调用这个函数,但是device_driver下面也有,具体调用的是谁这个就得分析了.driver = {.owner = THIS_MODULE,      .name  = "beep_device_test",                //  有了id_table,这个driver->name可有可无,优先级低.of_match_table = of_match_table_test,      // 最优先匹配of_match_table其次是id_table最后是name},                                              //   内置的device_driver 结构体.id_table = &beep_id_table,                     //  该设备驱动支持的设备的列表  他是通过这个指针去指向  platform_device_id 类型的数组
};static int beep_driver_init(void)
{int ret = 0;printk("beep_driver_init ok!!!\n");             // 在内核中无法使用c语言库,所以不用printfret = platform_driver_register(&beep_device);if(ret < 0){printk("platform_driver_register error!!!\n");return ret;}else{printk("platform_driver_register ok!!!\n");}return 0;
}static void beep_driver_exit(void)
{printk("beep_driver_exit bye!!!\n");gpio_free(beep_gpio);                               // 与gpio_request对应起来,把获取到的gpio编号释放掉misc_deregister(&misc_dev);platform_driver_unregister(&beep_device);
}module_init(beep_driver_init);
module_exit(beep_driver_exit);MODULE_LICENSE("GPL");                                  //声明模块拥有开源许可

结果验证

查看设备树节点

看看申请的gpio是不是在设备树中

在这里插入图片描述

安装ko模块生成驱动

在这里插入图片描述
在dev文件中查看驱动生成的节点
在这里插入图片描述

执行app

我们可以看到对应的信息打印了出来,说明程序都得到了顺利的执行,接下来就是测试EIM_A17引脚实际的高低电平,通过万用表的测量,与打印信息一致。
在这里插入图片描述

这篇关于Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/784329

相关文章

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal