目录

1.Pinctrl

1.1 pin controller

1.2 client device

 1.3 补充说明

2.GPIO 子系统

2.1 利用 GPIO Controller 来指定使用哪个引脚:

2.2 怎么在设备节点引用某个引脚呢?

2.3 配置 GPIO

具体新接口和老接口的区别:

1. 新接口

2.老接口

引脚号怎么确定?

可以通过查看 sysfs 来确定 base number:

还可以基于 sysfs 操作引脚:

3.基于 GPIO 子系统的 LED 驱动程序

流程思路:

具体步骤(以led为例):

1.在设备树中添加 Pinctrl 信息

1.1 在设备树 iomuxc_snvs 节点中添加 pin controller

1.2 在 led 设备节点添加 client device 信息

2.在 led 设备节点中添加 GPIO 信息:

3.GPIO Controller 不需要改动,厂商在.dtsi文件中已经设置好了

完整的 led 设备节点(包含 Pinctrl 和 GPIO 子系统信息):

4.驱动的编写


配置一个 pin 引脚十分复杂,以配置 GPIO 为例:

  • 配置引脚复用功能(IOMUX)为 GPIO
  • 配置引脚参数(上拉、下拉...)
  • 配置 GPIO (方向、数据...)

难道每个引脚都通过一个一个配置寄存器吗?这不可能

因此需要使用 Pinctrl 子系统(用于配置引脚复用功能)

因为一般都是把引脚配置为 GPIO,因此这里把 GPIO 子系统也讲了(跟 Pinctrl 类似,用于配置 GPIO)

1.Pinctrl

Pinctrl 跟设备树是紧密相连的,一般涉及分为两个部分:

1.1 pin controller

一个软件上的概念,可实现 IOMUX 的功能作用:选择引脚复用功能配置引脚(比如上下拉电阻等)

例如可以把 pin 配置复用为 GPIO 功能和引脚参数,但 GPIO 的配置(方向、数据...)得使用后面讲的 GPIO 子系统

它是设备树中的一个节点,其中有多个子节点,每个子节点声明了

  • 该节点使用了哪些引脚
  • 把这些引脚复用为了什么功能
  • 把引脚参数配置为了多少


 

1.2 client device

“客户设备”,谁的客户?Pinctrl 系统的客户(理解为 pin controller 的客户更准确)

它是一个在设备树根目录下的设备节点,节点中各属性内容主要是:

  • 用 pinctrl-names 声明设备的状态(可以有多个)
  • 声明每个状态分别使用 pin controller 的哪个子节点来配置引脚(pinctrl-x  = < &pin conctrl >,x 代表第几个状态)


图示:

在图中,

当设备是 default 状态时,Pinctrl 子系统会自动配置好 pincontroller 中 state_0_node_a 节点的引脚和功能

当设备是 sleep 状态时,Pinctrl 子系统会自动配置好 pincontroller 中 state_1_node_a 节点的引脚和功能

 1.3 补充说明

pin controller 节点的格式没有统一的标准,但内容都是一样的:该子节点使用哪些引脚、复用成什么功能

示例:不同设备的 pin conctrller 和 client device 


2.GPIO 子系统

先通过 Pinctrl 把 pin 配置为 GPIO 功能,接着通过 GPIO 子系统配置 GPIO:引脚方向...等

一般也分为两部分 GPIO Controller 和 device client(跟Pinctrl是同一个,配置好引脚的复用功能后紧接着就是配置GPIO)

在使用 GPIO 子系统之前,就要先确定:它是哪组的?组里的哪一个?

2.1 利用 GPIO Controller 来指定使用哪个引脚:

(注意,只是指定使用哪个引脚,配置引脚要在后面使用函数配置)

在设备树中,GPIO按组来分,一个 “GPIO 组” 就是一个 GPIO Controller,它们一般都是厂家定 义好,在 xxx.dtsi 文件中

可以看到,在设备树中,GPIO 节点按组来分,需要重点关注 GPIO 组节点的两个属性

gpio-controller、#gpio-cells=<2>

  • gpio-controller:声明该组GPIO节点是一个 GPIO Controller ,
  • #gpio-cells = <2>”:表明在设备节点 device client 中每一个引脚要用 2 个 32 位的数(cell)来描述。
    (一般是一个数描述用哪个引脚,另一个数描述该引脚是高电平有效还是低电平有效)

 定义 GPIO Controller 是芯片厂家的事,我们只需要在设备树中引用该 .dtsi 文件就行

2.2 怎么在设备节点引用某个引脚呢?

在自己的设备节点中使用属性 “[<name>-]gpios” ,示例如下:

 可以使用 gpios 属性,也可以使用 <name>-gpios 属性

一个实现了 Pinctrl 和 GPIO 子系统的设备节点示例:

myled { 
     
     compatible = "100ask,leddrv";
     /*pinctrl部分*/ 
     pinctrl-names = "default";  //状态
     pinctrl-0 = <&myled_for_gpio_subsys>; //状态0对应的引脚复用 
     /*GPIO子系统部分*/
     //指定gpio组,并用两个参数描述引脚:3:pin3;GPIO_ACTIVE_LOW:低电平有效
     led-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>;  
 };

在这里虽然 Pinctrl 和 GPIO 子系统配置的引脚是同一个,但这是两个不同的操作,所以要引用 2 次

2.3 配置 GPIO

引用好gpio引脚后,就要开始对其进行配置了

GPIO 子系统有两套接口:

  • 一套是新的,基于描述符(descriptor-based)(不用管描述符是什么),函数前缀为 “gpiod_”
  • 一套是老的(legacy),函数前缀为 “gpio_”

PS:建议使用新的

在驱动程序中使用这些函数要包含头文件:

  • 新接口(descriptor-based):#include <linux/gpio/consumer.h>
  • 老接口(legacy):#include <linux/gpio.h>

常见函数如下图:

一般的操作流程:

  1. 通过 get 函数获取引脚
  2. 使用上示各种函数配置该引脚(在函数中传入获取的引脚)

具体新接口和老接口的区别:

1. 新接口

新接口用 gpio_desc 结构体表示一个引脚

新接口先要通过 get 函数获得一个引脚的 gpio_desc 结构体,其他的配置函数就是通过传入这个 gpio_desc 结构体实现具体引脚的配置

示例:

假设在设备树中有如下节点:

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>; 
 };

那么可以使用下面的函数获得引脚:

struct gpio_desc *red, *green, *blue, *power; 

/*解释这里的dev参数:
 *当plaform device和plaform driver配对成功后,
 *会自动把plaform device结构体传入到plaform driver的.probe函数中
 *即.probe函数中的*pdev参数,这个参数就是传入的plaform device结构体
 *而struct device *dev = &pdev->dev;
 *即这里的dev是pdev的dev成员
 *pdev->dev 是内嵌的 struct device 成员,代表设备的基类。
 *
 */
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);

解释这里的dev参数:
当 plaform device 和 plaform driver 配对成功后,会自动把 plaform device 结构体传入到 plaform driver 的 .probe函数 中,即 .probe 函数中的 *pdev 参数,这个参数就是传入的 plaform device 结构体,而 dev 是 pdev 的成员,pdev->dev 是内嵌的 struct device 成员,代表设备的基类。 

其最主要的作用:

                与设备树(DT)交互:通过 dev->of_node 访问设备树节点

在代码中,因为 led-gpios 中引用了多个引脚,因此使用 gpiod_get_index 获取 led-gpios 中的不同引脚的 gpio_desc

当 gpios 中只引用了一个引脚时候,可以直接使用 gpiod_get 获取该引脚的 gpio_desc

后续使用配置函数,就可以通过传入不同引脚的 gpio_desc,实现对不同引脚的配置
 

gpiod_get 函数可以顺便设置引脚的电平(上示第3个参数,第三个参数即可用作索引,也可以用作设置引脚输入输出模式及初始电平),这里设为0,表示用作索引,引用 led-gpios中的第一个引脚

也可以通过 gpiod_set_value 设置

注意:gpiod_set_value 设置的值是“逻辑值”,不一定等于物理值(高电平有效时逻辑1=物理1,低电平有效时逻辑1=物理0)

2.老接口

老接口用引脚编号代表一个接口

旧的 “gpio_” 函数没办法根据设备树信息获得引脚,它需要先知道引脚号

老接口就是通过向配置函数传入引脚编号实现具体引脚的配置

注意:需要先对引脚进行 gpio_request 申请(也是传入引脚号)之后,才能开始使用各种配置函数

引脚号怎么确定?

在 GPIO 子系统中,每注册一个 GPIO Controller 时会确定它的 “base number”那么这个控制器里的第 n 号引脚的号码就是:base number + n。

可以通过查看 sysfs 来确定 base number:
  1. 先在开发板的/sys/class/gpio 目录下,找到各个 gpiochipXXX 目录(这里每个目录代表一组GPIO Controller ):
  2. 进入某个 gpiochip 目录,查看文件 label 的内容  
  3. 根据 label 的内容对比设备树,lable中一般包含 GPIO Controller 的寄存器基地址

可以通过在设备树(dtsi 文件)中查找该基地址,就可以知道这对应哪一个 GPIO Controller,就可以把该组 GPIO Controller 与 base number 匹配起来了

如下图所示:

这样就确定了 96 是 gpio4 的 base number,base number + n(第几个引脚)就得到引脚号了

还可以基于 sysfs 操作引脚(除“gpio_” 函数外的另外一种操作方式):

以 100ask_imx6ull 为例,它有一个按键,原理图如下:

那么 GPIO4_14 的号码是 96+14=110,可以如下操作读取按键值:

[root@100ask:~]# echo 110 > /sys/class/gpio/export     //获取110号引脚
[root@100ask:~]# echo in > /sys/class/gpio/gpio110/direction     //设置引脚方向为in
[root@100ask:~]# cat /sys/class/gpio/gpio110/value     //打印出引脚值
[root@100ask:~]# echo 110 > /sys/class/gpio/unexport    //释放110号引脚

注意:如果驱动程序已经使用了该引脚,那么将会 export 失败,会提示下面的错误:


3.基于 GPIO 子系统的 LED 驱动程序

流程思路:

  1. 先把某个引脚配置为 GPIO 功能
    这要使用 Pinctrl 子系统,只需要在设备树中的我们的设备节点里指定就可以。在驱动代码上不需要做任何事情。 
     
  2. 设备节点中还需要写出 gpios 属性,声明GPIO子系统引用的引脚
     
  3. 设备树节点会被内核转换为 platform_device,对应的,驱动代码中要注册一个 platform_driver,在 probe 函数中:获取在GPIO子系统引用的引脚注册 file_operations。
     
  4. 在 file_operations 中就可以通过调用上面 GPIO 子系统的配置函数实现设置GIPO方向、读值/写值等操作

具体步骤(以led为例):

1.在设备树中添加 Pinctrl 信息

1.1 在设备树 iomuxc_snvs 节点中添加 pin controller

安装“Pins_Tool_for_i.MX_Processors_v6_x64.exe”后运行,打开 IMX6ULL 的配置文件“MCIMX6Y2xxx08.mex”,就可以在 GUI 界面中选择引脚生成 Pinctrl 的 pin controller 信息

可以看到,生成的信息提示我们要把该引脚的 pin contronller 信息放到设备树 iomuxc_snvs 节点下

&iomuxc_snvs { 
    …… 
     myled_for_gpio_subsys: myled_for_gpio_subsys{ 
     fsl,pins = < 
     //第一个宏表示了把GPIO5_3配置为GPIO,第二个数表示了了要设置的参数(上下拉电阻等)
     MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03 0x000110A0 
     >;
 };

1.2 在 led 设备节点添加 client device 信息

声明:

  • pinctrl-names:设备的各种状态
  • pinctrl-<x>:不同状态对应引脚复用的配置

2.在 led 设备节点中添加 GPIO 信息:

根据 GPIO Controller 节点里的 “#gpio-cells” 属性值,在 led 设备节点中添加 “[name]-gpios” 属性

指定:使用的是哪一个 GPIO Controller 里的哪一个引脚,还有其他 Flag 信息(高低电平有效等)

3.GPIO Controller 不需要改动,厂商在.dtsi文件中已经设置好了

完整的 led 设备节点(包含 Pinctrl 和 GPIO 子系统信息):

myled { 
     //用于与platform driver相匹配
     compatible = "100ask,leddrv"; 
     //状态
     pinctrl-names = "default"; 
     //0号状态pinctrl要复用的引脚及其功能
     pinctrl-0 = <&myled_for_gpio_subsys>; 
     //GPIO子系统要引用的引脚
     led-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>; 
 };

4.驱动的编写

流程:

(1)定义 platform driver 结构体

  • .of_match_table 中定义好和 led 设备节点相匹配的 compatible 
  • 定义好 probe 函数

在 probe 函数中:

  • 通过 gpiod_get 获取 gpio 引脚
  • 注册 file_operations 结构体 
  • 创建类和设备节点

源码示例:

static int chip_demo_gpio_probe(struct platform_device *pdev)
{
	//int err;
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    /*获取GPIO引脚*/
	/*设备树中定义了:led-gpios=<...>,因此通过“led”在pdev->dev获取*/
    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);
	}
    
	/*注册file_operations*/
	major = register_chrdev(0, "100ask_led", &led_drv);  /* /dev/led */
    
    /*创建类*/
	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;
    
}

(2)定义 file_operations 结构体

在 file_operations 结构体中实现对GPIO的配置

源码示例:

/*.read函数,这里没作操作*/
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函数:向GPIO引脚写入status值*/
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char status;

	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(&status, buf, 1);

	/* 根据GPIO引脚,写入status值,控制LED */
	gpiod_set_value(led_gpio, status);
	
	return 1;
}

/*.open函数:设置引脚方向*/
static int led_drv_open (struct inode *node, struct file *file)
{

	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 根据GPIO引脚,设置引脚方向,初始化LED */
	gpiod_direction_output(led_gpio, 0);
	
	return 0;
}

/*.release函数:没做任何操作*/
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,
};

完整源码:

#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;


/*.read函数,这里没作操作*/
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函数:向GPIO引脚写入status值*/
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char status;

	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(&status, buf, 1);

	/* 根据GPIO引脚,写入status值,控制LED */
	gpiod_set_value(led_gpio, status);
	
	return 1;
}


/*.open函数:设置引脚方向*/
static int led_drv_open (struct inode *node, struct file *file)
{

	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 根据GPIO引脚,设置引脚方向,初始化LED */
	gpiod_direction_output(led_gpio, 0);
	
	return 0;
}


/*.release函数:没做任何操作*/
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,
};


static int chip_demo_gpio_probe(struct platform_device *pdev)
{
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    /*获取GPIO引脚*/
	/*设备树中定义了:led-gpios=<...>,因此通过“led”在pdev->dev获取*/
    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);
	}
    
	/*注册file_operations*/
	major = register_chrdev(0, "100ask_led", &led_drv);  /* /dev/led */
    
    /*创建类*/
	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;
}

/*定义和设备节点匹配的compatible*/
static const struct of_device_id ask100_leds[] = {
    { .compatible = "100ask,leddrv" },
    { },
};


/* 定义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,
    },
};


/* 在入口函数:注册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;
}


/* 出口函数:卸载platform_driver */
static void __exit led_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    platform_driver_unregister(&chip_demo_gpio_driver);
}


module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐