[Linux_IMX6ULL驱动开发]-GPIO子系统和Pinctrl子系统
目录
Pinctrl子系统的概念
GPIO子系统的概念
定义自己的GPIO节点
GPIO子系统的函数
引脚号的确定
基于GPIO子系统的驱动程序
驱动程序
设备树修改
之前我们进行驱动开发的时候,对于硬件的操作是依赖于ioremap对寄存器的物理地址进行映射,以此来达到对操控硬件的目的,但是在实际的开发中,如果对引脚一个个的进行物理地址的映射并不现实,我们在这里使用Pinctrl子系统来完成对引脚的配置以及复用等。pinctrl子系统由BSP工程师来完成,驱动工程师使用这个子系统
Pinctrl子系统的概念
pinctrl中引入两个概念,一个是client device 一个是 controller,client device表示客户,也就是子系统的使用者,在这里主要指定状态,也就是引脚在不同的状态下使用的不同配置,而controller则主要是对应状态的具体详细配置
假如此时引脚的状态为“default”,那么就是对应pinctrl-0这个节点,那么引脚的功能(function)设置为uart0,groups设置为“u0rxtx”,“u0rtscts”。
client device的格式一般是一样的,但是controller这里的格式,不同的开发板有不同的格式,格式虽然不同,但是概念是一样的
GPIO子系统的概念
当我们使用pinctrl子系统设置了引脚为GPIO模式的时候,我们就可以使用GPIO子系统来设置引脚方向(输入还是输出)、读值──获得电平状态,写 值──输出高低电平。
在之前,我们一般是使用寄存器操控来控制引脚,而在这里我们使用GPIO子系统,完全是使用API来控制引脚,而不是设计寄存器操作,这样,不管是使用什么板都是兼容的
我们可以在设备树中指定GPIO引脚,然后在驱动程序中,直接使用对应的函数来获得 GPIO、设置 GPIO 方向、读 取/设置 GPIO 值。
几乎所有芯片的GPIO都是分组的,我们需要在自己所需要操控的GPIO分组中,找到自己所需要的。如下就是一个GPIO Controller,也就是一个GPIO分组,为GPIO分组1.一般来说,GPIO Controller是由厂家来实现的,我们只负责使用
我们暂时关注这两个属性 。“gpio-controller”表示这个节点是一个 GPIO Controller,它下面有很多引脚。 “#gpio-cells = ”表示这个控制器下每一个引脚要用 2 个 32 位的数 (cell)来描述。至于为什么需要使用两个cell来表示,一般第一个cell(第一个32位数)表示哪个引脚,第二个表示有效电平。
GPIO_ACTIVE_HIGH : 高电平有效
GPIO_ACTIVE_LOW : 低电平有效
定义自己的GPIO节点
定义 GPIO Controller 是芯片厂家的事,那么我们该如何定义自己的节点呢。
如下图所示,我在的这个节点中,通过gpios = 多少来设置我在这个节点当中使用的是哪个GPIO节点,一般的使用格式为:GPIO组 引脚 什么电平激活 , 下图中的表示为,此节点使用的是GPIO5组当中的3引脚,当电平为低电平的时候激活
(如下的设置为设备树当中的例子,只需要看 gpios = 这个属性即可)
一般来说,如果我们要定义自己的节点,那么格式如下:
compatible属性,作用是与驱动代码中的of_match_table进行匹配,当匹配成功之后,驱动代码才会调用probe,获取到引脚硬件信息。
pinctrl-name和pinctrl-0都是pinctrl的信息,用于设置引脚在不同状态下(pinctrl-name决定)的引脚复用和状态设置(开漏、上拉等)(pinctrl-0.....对应的状态决定)
led-gpio用来决定引脚信息,这个属性的节点为name-gpio,如下代码中可知,name为led,则在驱动代码中可以通过name来获得引脚信息
mynode{compatible = ;pinctrl-name = ;pinctrl-0 = ;led-gpio = ;};
GPIO子系统的函数
当我们通过pinctrl子系统定义完了引脚信息后,我们应该如何在驱动代码中获取设备树中对应的引脚信息呢,我们使用如下这些GPIO 子系统的接口函数。
GPIO 子系统有两套接口:基于描述符的(descriptor-based)、老的(legacy)。通常情况下,我们使用前者,也就是基于描述符的。前者使用起来更为简单,后者还要基于引脚号。前者的函数都有前缀“gpiod_”,后者的函数都有前缀“gpio_”。
使用这些函数需要包含如下头文件
#include <linux/gpio/consumer.h> // descriptor-based
#include <linux/gpio.h> // legacy
常用函数如下
descriptor-based | legacy |
获得 GPIO | |
gpiod_get | gpio_request |
gpiod_get_index | |
gpiod_get_array | gpio_request_array |
devm_gpiod_get | |
devm_gpiod_get_index | |
devm_gpiod_get_array | |
设置方向 | |
gpiod_direction_input | gpio_direction_input |
gpiod_direction_output | gpio_direction_output |
读值、写值 | |
gpiod_get_value | gpio_get_value |
gpiod_set_value | gpio_set_value |
释放 GPIO | |
gpio_free | gpio_free |
gpiod_put | gpio_free_array |
gpiod_put_array | |
devm_gpiod_put | |
devm_gpiod_put_array |
我们假设在设备树中存在如下节点
foo_device {
compatible = "acme,foo";
...
led-gpios = <&gpio 15 GPIO_ACTIVE_HIGH>, /* red */<&gpio 16 GPIO_ACTIVE_HIGH>, /* green */<&gpio 17 GPIO_ACTIVE_HIGH>; /* blue */power-gpios = <&gpio 1 GPIO_ACTIVE_LOW>; };
那我们我们该如何在驱动程序中获得如下的节点信息呢,使用如下方法
我们看red,第二个参数“led”也就是name,led-gpios,格式为<name>-gpio,那么led就是name,0表示获得第一个name为led的参数信息,GPIOD_OUT_HIGH表示设置为激活状态,但是并不一定是高电平,它的意思是逻辑输出,是一个逻辑值。如果引脚低电平点亮,则为0,反之则为1.
struct gpio_desc *red, *green, *blue, *power;red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_HIGH);
green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_HIGH);
blue = gpiod_get_index(dev, "led", 2, GPIOD_OUT_HIGH);
power = gpiod_get(dev, "power", GPIOD_OUT_HIGH);
引脚号的确定
如果我们需要使用旧的GPIO函数才操控引脚,那么我们就需要知道引脚号为多少,才能够操控对应的引脚,那么引脚号一般是怎么计算的呢。
一般来说,假如GPIO1的基引脚号为base,那么GPIO0的第n号引脚号为base+n
进入开发板目录:/sys/class/gpio ,可以看到如下GPIO分组
它表示GPIO的分组,我们进入gpioip128,得到如下
查看label属性
通过如上的地址“20ac000”这个地址通过查阅芯片手册或者设备树即可知道引脚号为128的是哪个GPIO组的基引脚号
如下为imx6ull.dtsi中的信息,可知20ac000这个地址表示为gpio5组的基引脚号,那么gpioip128就表示gpio5组了,那么可知,如果我们需要使用GPIO5的引脚3,那么此引脚的引脚号则为 128+3 = 131
在这里我们可以做一个测试,已知按键的电路图如下
可知它使用的是GPIO4的引脚14,我们可推算GPIO4组的基引脚号为96,那么引脚14就是96+14=110,引脚号110表示这个引脚,键入如下命令
echo 110 > /sys/class/gpio/export
echo in > /sys/class/gpio/gpio110/direction
cat /sys/class/gpio/gpio110/value
echo 110 > /sys/class/gpio/unexport
echo 110 > /sys/class/gpio/export 这个命令将编号为110的GPIO引脚导出为用户空间的设备,使其可以在用户空间进行操作。
echo in > /sys/class/gpio/gpio110/direction 这个命令设置编号为110的GPIO引脚的方向为输入("in")。
cat /sys/class/gpio/gpio110/value 这个命令读取编号为110的GPIO引脚的当前值。如果引脚连接的是一个按钮或传感器,它会输出0或1,表示引脚的状态是低电平或高电平。
echo 110 > /sys/class/gpio/unexport 这个命令将编号为110的GPIO引脚取消导出,使其不再在用户空间可用。
通过如上命令可以在用户空间读取按键值,但是如果此引脚被使用,则会出现如下提醒
基于GPIO子系统的驱动程序
驱动程序
如下函数为probe函数,只有当设备树与驱动成功配对后,才会调用此函数,主要的目的是在驱动设备中获取设备树当中的引脚等信息
如上两个函数主要为驱动和设备树节点对应成功后以及卸载时所需要设置的步骤,往下实现file_operation结构体中的相关提供给应用层的函数(这里实现open和write举例)
gpiod_set_value第二个参数传入的是逻辑值,只要传入的是1,那么它一定是激活引脚的电平,这个在dts的属性中可以设置引脚是低电平有效还是高电平有效,子系统会达到控制效果。
#include <linux/module.h>
#include <linux/platform_device.h>#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>/* 1. 确定主设备号 */
static int major = 0;
static struct class *led_class;
static struct gpio_desc *led_gpio;/* 3. 实现对应的open/read/write等函数,填入file_operations结构�? */
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}/* write(fd, &val, 1); */
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{int err;char status;//struct inode *inode = file_inode(file);//int minor = iminor(inode);printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);err = copy_from_user(&status, buf, 1);/* 根据次设备号和status控制LED */gpiod_set_value(led_gpio, status);return 1;
}static int led_drv_open (struct inode *node, struct file *file)
{//int minor = iminor(node);printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);/* 根据次设备号初始化LED */gpiod_direction_output(led_gpio, 0);return 0;
}static int led_drv_close (struct inode *node, struct file *file)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}/* 定义自己的file_operations结构�? */
static struct file_operations led_drv = {.owner = THIS_MODULE,.open = led_drv_open,.read = led_drv_read,.write = led_drv_write,.release = led_drv_close,
};/* 4. 从platform_device获得GPIO* 把file_operations结构体告诉内核:注册驱动程序*/
static int chip_demo_gpio_probe(struct platform_device *pdev)
{//int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);/* 4.1 设备树中定义�? led-gpios=<...>; */led_gpio = gpiod_get(&pdev->dev, "led", 0);if (IS_ERR(led_gpio)) {dev_err(&pdev->dev, "Failed to get GPIO for led\n");return PTR_ERR(led_gpio);}/* 4.2 注册file_operations */major = register_chrdev(0, "100ask_led", &led_drv); led_class = class_create(THIS_MODULE, "100ask_led_class");if (IS_ERR(led_class)) {printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);unregister_chrdev(major, "led");gpiod_put(led_gpio);return PTR_ERR(led_class);}device_create(led_class, NULL, MKDEV(major, 0), NULL, "100ask_led%d", 0); /* /dev/100ask_led0 */return 0;}static int chip_demo_gpio_remove(struct platform_device *pdev)
{device_destroy(led_class, MKDEV(major, 0));class_destroy(led_class);unregister_chrdev(major, "100ask_led");gpiod_put(led_gpio);return 0;
}static const struct of_device_id ask100_leds[] = {{ .compatible = "100ask,leddrv" },{ },
};/* 1. 定义platform_driver */
static struct platform_driver chip_demo_gpio_driver = {.probe = chip_demo_gpio_probe,.remove = chip_demo_gpio_remove,.driver = {.name = "100ask_led",.of_match_table = ask100_leds,},
};/* 2. 在入口函数注册platform_driver */
static int __init led_init(void)
{int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);err = platform_driver_register(&chip_demo_gpio_driver); return err;
}/* 3. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函�? * 卸载platform_driver*/
static void __exit led_exit(void)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);platform_driver_unregister(&chip_demo_gpio_driver);
}/* 7. 其他完善:提供设备信息,自动创建设备节点 */module_init(led_init);
module_exit(led_exit);MODULE_LICENSE("GPL");
设备树修改
imx6ull芯片,有对应的设备树生成的工具,使用 “Pins_Tool_for_i.MX_Processors_v6_x64.exe”可以生成对应的设备树文件。
把自动生成的信息放在设备树 arch/arm/boot/dts/100ask_imx6ull-14x14.dts 下
然后,在此设备树文件中定义自己的新节点,添加在根节点下
由于GPIO5 3 已经被用来做CPU指示灯,需要再设备树中关闭它
由于都是使用pinctrl来控制,我们在dts中搜索 MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03
在搜索框中的节点名,既可以反推找到使用该节点的节点
设置完后,在内核目录下使用 make dtbs 编译,设备树文件拷贝到网络文件系统中,更新设备树,安装编译后的驱动程序
相关文章:

[Linux_IMX6ULL驱动开发]-GPIO子系统和Pinctrl子系统
目录 Pinctrl子系统的概念 GPIO子系统的概念 定义自己的GPIO节点 GPIO子系统的函数 引脚号的确定 基于GPIO子系统的驱动程序 驱动程序 设备树修改 之前我们进行驱动开发的时候,对于硬件的操作是依赖于ioremap对寄存器的物理地址进行映射,以此来达…...

Elasticsearch:理解人工智能相似性搜索
理解相似性搜索(也称为语义搜索)的指南,这是人工智能最新阶段的关键发现之一。 最新阶段人工智能的关键发现之一是根据相似性搜索和查找文档的能力。相似性搜索是一种比较信息的方法,其基于含义而非关键字。 相似性搜索也被称为语…...

Mac YOLO V9推理测试(基于ultralytics)
环境: Mac M1 (MacOS Sonoma 14.3.1) Python 3.11PyTorch 2.1.2 一、准备工作 使用YOLO一般都会接触ultralytics这个框架,今天来试试用该框架进行YOLO V9模型的推理。 YOLOv9目前提供了四种模型下载:yolov9-c.pt、yolov9-e.pt、gelan-c.p…...
OuterClass.this cannot be referenced from a static context
目标,定义了一个内部类,然后把这个内部类设置为单例 一 使用非静态内部类 public class OuterClass {public class InnerClass {} } 直接定义单例: .OuterClass.this cannot be referenced from a static context public class OuterClass …...

CAP与BASE分布式理论
一、分布式理论 1.CAP理论 CAP理论是说对于分布式数据存储,最多只能同时满足一致性(C,Consistency)、可用性(A, Availability)、分区容忍性(P,Partition Tolerance&…...
JavaScript性能优化策略
JavaScript性能优化策略可以分为以下几个方面: 减少内存使用:避免创建不必要的对象和数组,使用对象池或数组缓存来重复利用已有的对象和数组。此外,及时释放不再需要的对象和数组,避免内存泄漏。 减少重绘和回流&…...
curl访问流式非流式大模型openai api接口
参考:https://platform.openai.com/docs/api-reference/making-requests 命令行访问: 直接是vllm的openai api接口 curl http://192.168.***:10860/v1/chat/completions -H "Content-Type: application/json" -H "Authorization: EMPTY" -d {"mod…...
Go 使用 MongoDB
MongoDB 安装(Docker)安装 MongoDB Go 驱动使用 Go Driver 连接到 MongoDB在 Go 里面使用 BSON 对象CRUD 操作 插入文档更新文档查询文档删除文档 下一步 MongoDB 安装(Docker) 先装个 mongo,为了省事就用 docker 了。 docker 的 daemon.json 加一个国内的源地址…...
什么是g++-arm-linux-gnueabihf
2024年5月3日,周五晚上 g-arm-linux-gnueabihf 是针对 ARM 架构(ARMv7 和 ARMv8)的 Linux 系统开发的 GNU C 编译器套件,可以在 x86 或 x86_64 架构的主机上使用,用于交叉编译 ARM Linux 应用程序和库。 与 gcc-arm-l…...
Unity延时触发的几种常规方法
目录 1、使用协程Coroutine2、使用Invoke、InvokeRepeating函数3、使用Time.time4、使用Time.deltaTime5、使用DOTween。6、使用Vision Timer。 1、使用协程Coroutine public class Test : MonoBehaviour {// Start is called before the first frame updatevoid Start(){ …...

CSS文字描边,文字间隔,div自定义形状切割
clip-path: polygon( 0 0, 68% 0, 100% 32%, 100% 100%, 0 100% );//这里切割出来是少一角的正方形 letter-spacing: 1vw; //文字间隔 -webkit-text-stroke: 1px #fff; //文字描边1px uniapp微信小程序顶部导航栏设置透明,下拉改变透明度 onP…...

XWiki 服务没有正确部署在tomcat中,如何尝试手动重新部署?
1. 停止 Tomcat 服务 首先,您需要停止正在运行的 Tomcat 服务器,以确保在操作文件时不会发生冲突或数据损坏: sudo systemctl stop tomcat2. 清空 webapps 下的 xwiki 目录和 work 目录中相关的缓存 删除 webapps 下的 xwiki 目录和 work …...
【退役之重学Java】关于 Redis
一、Redis 都有哪些数据类型 String 最基本的类型,普通的set和get,做简单的kv缓存hash 这是一个类似map 的一种结构,这个一般可以将结构化的数据,比如一个对象(前提是这个对象没有嵌套其他的对象)给缓存在…...
DateKit
目录 1、 DateKit 1.1、 DaysBetween 1.2、 compareDate 1.3、 dateFormat 1.4、 birthdayFormat 1.5、 getYesterday...
百度智能云数据仓库 Palo 实战课程
通过本课程,您将学习如何使用 Palo 构建高性能、低延迟的分布式数仓服务,掌握数据建模、数据导入、查询优化和系统调优等技能,掌握如何管理和运维 Palo 集群,提高数据处理和分析的效率。同时,我们将进一步向您介绍 Pal…...
服务端JavaScript(Node.js)与去IO编程:Node.js的事件驱动和非阻塞IO模型,它是如何使JavaScript走向后端的
在Node.js中,JavaScript代码运行在V8引擎上。由于JavaScript是单线程语言,一次只能处理一个事件。为了解决这个问题,Node.js引入了事件驱动模型。每个进行IO操作的函数都是异步的,当这个函数被调用的时候,它不会立即执…...

一键局域网共享工具
一键局域网共享工具:实现文件快速共享的新选择 在数字化时代,文件共享已成为我们日常工作和生活中的重要需求。无论是在家庭还是在办公环境中,我们经常需要在不同的设备之间传输文件。为了满足这一需求,一键局域网共享工具应运而…...
python实现把doc文件批量转化为docx
python实现把doc文件批量转化为docx import os from win32com import client as wcdef doSaveAas(doc_path,docx_path):#该函数参考https://blog.csdn.net/m0_38074612/article/details/128985384word wc.Dispatch(Word.Application)doc word.Documents.Open(doc_path) # 目…...
WEB基础---反射
什么是反射 相对官方解释 反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力; 在运行时期,动态地去获取类中的信息(类的信息,方法信息,构造器信息,字段等信息); 在运行的时候获取到的类信息 封装一个字节码对象…...
impdp恢复表后发现比原表多了100多行
因客户删除数据,恢复表时发现恢复表后发现比原表多了100多行,啥原因暂不清楚,继续学习 [oraclehydb ~]$ more expdp_orcl_20240406_2100.log |grep "USR_HY"."T_COPIES". . exported "USR_HY"."T_COPIES…...

接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...

python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...
Linux系统部署KES
1、安装准备 1.版本说明V008R006C009B0014 V008:是version产品的大版本。 R006:是release产品特性版本。 C009:是通用版 B0014:是build开发过程中的构建版本2.硬件要求 #安全版和企业版 内存:1GB 以上 硬盘…...

MyBatis中关于缓存的理解
MyBatis缓存 MyBatis系统当中默认定义两级缓存:一级缓存、二级缓存 默认情况下,只有一级缓存开启(sqlSession级别的缓存)二级缓存需要手动开启配置,需要局域namespace级别的缓存 一级缓存(本地缓存&#…...

协议转换利器,profinet转ethercat网关的两大派系,各有千秋
随着工业以太网的发展,其高效、便捷、协议开放、易于冗余等诸多优点,被越来越多的工业现场所采用。西门子SIMATIC S7-1200/1500系列PLC集成有Profinet接口,具有实时性、开放性,使用TCP/IP和IT标准,符合基于工业以太网的…...

数据结构第5章:树和二叉树完全指南(自整理详细图文笔记)
名人说:莫道桑榆晚,为霞尚满天。——刘禹锡(刘梦得,诗豪) 原创笔记:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 上一篇:《数据结构第4章 数组和广义表》…...

【Java多线程从青铜到王者】单例设计模式(八)
wait和sleep的区别 我们的wait也是提供了一个还有超时时间的版本,sleep也是可以指定时间的,也就是说时间一到就会解除阻塞,继续执行 wait和sleep都能被提前唤醒(虽然时间还没有到也可以提前唤醒),wait能被notify提前唤醒…...