[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 编译,设备树文件拷贝到网络文件系统中,更新设备树,安装编译后的驱动程序

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/624591.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++笔试强训day21

目录 1.爱丽丝的人偶 2.集合 3.最长回文子序列 1.爱丽丝的人偶 链接 简单叙述就是每个数的左右两边不能一个比他大&#xff0c;一个比他小。 反之&#xff0c;就是要让每个数的左右两边数都大于或者都小于他。 方法一&#xff1a;一开始我想复杂了&#xff0c;其实用试错法…

Pycharm使用Anaconda虚拟环境

一、前置 安装 Pychram安装 Anaconda&#xff0c;并配置虚拟环境 参考&#xff1a; Anaconda虚拟环境 anaconda虚拟环境pytorch安装 二、在Pycharm中使用Anaconda的虚拟环境 打开 Pycharm的命令行可以看到 Anaconda 的虚拟环境已经启动。 三、问题集合 &#xff08;1&…

IDEA报错:java 找不到符号

IDEA报错:java 找不到符号,代码没问题,IDEA缓存也清理了也重新构建了就是不行 最后使用终极大法 -Djps.track.ap.dependenciesfalse

海外仓混合订单拣货策略:人工与海外仓系统的最佳搭配模式

根据订单高效拣货是任何海外仓都要面对的问题。只有当订单可以被高效&#xff0c;准确的拣货之后&#xff0c;才能继续走下面的物流流程&#xff0c;所以尽可能的缩短拣货时间&#xff0c;提升拣货精准度&#xff0c;才是提升订单交付率的最佳方法。 海外仓企业都在不断寻找&am…

直播预告-如何快乐学习亚马逊云科技AWS,玩游戏备考亚马逊云科技云从业者认证?

一边玩一边学习亚马逊云科技云技能&#xff0c;这么好的事尊的假的&#xff1f;本周六&#xff08;5约11日&#xff09;晚20点&#xff0c;亚马逊云科技UG云端夜话Night Talk活动精彩回归&#xff5e; 本次亚马逊云科技UG云端夜话直播是什么&#xff1f; 小李哥这次将在多平台…

Redis-集群方案

文章目录 Redis集群方案是用来做什么的&#xff1f;Redis集群方案有哪些&#xff1f;主从复制集群哨兵&#xff08;Sentinel&#xff09;集群Cluster分片集群第三方集群方案 更多相关内容可查看 Redis集群方案是用来做什么的&#xff1f; Redis集群方案是用来解决单节点Redis的…

知识图谱开发日志

应用于应用环境的配置.测试.发布 假如你写了一个web,并且测试调试都没有问题 并且,你想发给你的朋友,导师,或者部署到远程云服务器上 那么,你需要配置相同的软件,比如数据库,web服务器,必要的插件,库,etc…但这并不一定能保证软件的正常运行,因为别人可能使用完全不同的操作系统…

什么是CCRC?做什么用的?

CCRC&#xff08;中国网络安全审查认证和市场监管大数据中心&#xff09;原名为中国网络安全审查技术与认证中心&#xff0c;也被称为中国信息安全认证中心&#xff08;ISCCC&#xff09;。 该中心是经中央机构编制委员会办公室批准成立的&#xff0c;其主要职责是依据国家法律…

springboot通过 EasyExcel.read()方法解析csv(excel)文件中的数据用list接收

springboot通过 EasyExcel.read&#xff08;&#xff09;方法解析csv&#xff08;excel&#xff09;文件中的数据用list接收 文章目录 前言一、EasyExcel是什么&#xff1f;二、使用步骤1.引入库2.接收数据的实体类3.处理字典值ExcelDictConverter4.把文件中的数据解析出来放入…

Vue3+TS实现将html或富文本编辑器转为Word并下载

说明&#xff1a;我用的富文本编辑器是wangEditor&#xff1a; wangEditor官网 安装 yarn add wangeditor/editor # 或者 npm install wangeditor/editor --save yarn add wangeditor/editor-for-vuenext # 或者 npm install wangeditor/editor-for-vuenext --save yarn add …

CRM系统业务学习

与HR系统的联动 在CRM销售域里的账号都要和HR系统关联&#xff0c;如果离职了&#xff0c;CRM这里也会不允许登陆。 CRM工作台 通知公告、待办消息、线索跟进、日历任务。 CRM套件 线索与线索池、客户与公海池、跟进计划与跟进记录、联系人、商机等。 CRM标准业务流程 CR…

把视图放进对话框

大家好&#xff0c;才是真的好。 要是我没记错的&#xff0c;我们很久没有说到开发的话题了&#xff0c;尤其是关于lotusscript代码功能的问题。 今天我们讲一个非常实用的小功能。即在对话框当中显示一个视图&#xff0c;然后从该视图选择单个或多个文档&#xff0c;并把选中…

企业OA办公系统开发笔记:2、MyBatis-Plus

文章目录 企业办公系统&#xff1a;2、MyBatis-Plus一、MyBatis-Plus1、简介2、主要特点3、依赖 二、MyBatis-Plus入门1、配置文件2、启动类3、实体类4、添加Mapper类5、测试Mapper接口6、CRUD测试6.1、insert添加6.1.1、示例6.1.2、主键策略 6.2、更新6.3、删除6.3.1、根据id删…

eNSP PRO安装完整版(超详细)

目录 eNSP PRO包&#xff1a; 安装步骤&#xff1a; eNSP PRO包&#xff1a; 链接: https://pan.baidu.com/s/12oBJ708OHMZlhj8nS21HSw?pwdr64q 提取码: r64q 安装步骤&#xff1a; 将ensp pro的包下载并且解压出来 在Oracle VM VirtualBox&#xff0c;将我们解压好的en…

WMS仓储管理系统如何让仓库管理有过程

在当今竞争激烈的商业环境中&#xff0c;WMS仓储管理系统的智能化与过程化管理显得尤为重要。一个具有过程管理的WMS仓储管理系统不仅能够帮助企业实时监控、分析和调度仓库作业&#xff0c;还能显著提升作业效率和成本控制能力。下面&#xff0c;我们就来深入探讨一下这种“有…

学校为何更热衷于使用SOLIDWORKS教育版教学

在当今的教育环境中&#xff0c;SOLIDWORKS教育版因其独特的优势&#xff0c;越来越受到学校的青睐。为什么学校更热衷于使用SolidWorks教育版进行教学呢&#xff1f;本文将从以下几个方面进行阐述。 首先&#xff0c;SOLIDWORKS教育版为学生们提供了一个与实际工程应用紧密结…

记录MySQL数据库查询不等于xxx时的坑

目录 一、背景 二、需求 三、方法 四、示例 一、背景 在使用MySQL数据库查询数据时&#xff0c;需要查询字段name不等于xxx的记录&#xff0c;通过where name ! xxx查询出来的记录不符合预期&#xff0c;通过检查发现少了name字段为null的记录&#xff0c;后经查询得知在My…

Java面试八股之集合类为什么没实现cloneable和serilizable接口

Java集合类为什么没实现cloneable和serilizable接口 设计原则与职责分离&#xff1a; 集合类的主要职责是管理和组织对象的存储结构&#xff0c;提供增删查改等操作。而Cloneable接口用于实现对象的复制&#xff08;克隆&#xff09;&#xff0c;Serializable接口用于实现对象…

C# 在Excel中添加筛选器并执行筛选 (日期筛选、文本筛选、数字筛选)

自动筛选器是 Excel 中的一个基本但极其有用的功能&#xff0c;它可以让你根据特定的条件来自动隐藏和显示你的数据。当有大量的数据需要处理时&#xff0c;这个功能可以帮你快速找到你需要的信息&#xff0c;从未更加有效地分析和处理相关数据。 下面将介绍如何使用免费.NET …

LabVIEW二维码生成与识别

LabVIEW二维码生成与识别 随着数字化时代的快速发展&#xff0c;QR二维码作为一种高效的信息传递和识别手段&#xff0c;已广泛应用于各行各业。利用LabVIEW软件及其NI视觉开发模块(VDM)来实现一个高效的QR二维码生成与识别系统。该系统不仅能够快速生成带有自定义信息的二维码…