【IMX6ULL驱动开发学习】13.Pinctrl子系统与GPIO子系统

上一篇博客中,已经实现了设备树的添加
【IMX6ULL驱动开发学习】12.Linux驱动之设备树

这篇博客介绍Pinctrl子系统与GPIO子系统的使用
Pinctrl子系统参考文档:
内核文档链接:https://www.kernel.org/doc/Documentation/
内核源码doc:Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt

GPIO子系统参考文档:
内核文档链接:https://www.kernel.org/doc/Documentation/
内核源码doc:Documentation/devicetree/bindings/gpio/各个芯片厂商文件

代码自取【13.led_button_drv_tree_gpio_pinctrl】:
https://gitee.com/chenshao777/imx6-ull_-drivers


为什么要用Pinctrl子系统与GPIO子系统?

驱动开发中,我们可以使用寄存器对外设进行操作,但是那样太麻烦了
所以引入了 Pinctrl子系统与GPIO子系统

Pinctrl子系统: 可以用它来复用引脚、配置引脚的电气属性等等。
GPIO子系统: 控制引脚,设置输入输出。


之前在设备树中定义硬件资源的方法
(1)设备树节点中自定义 pin 属性
(2)在驱动代码中通过 of_property_read_u32 函数读取属性值,得到具体引脚
(3)根据GPIO组和pin对寄存器地址进行映射 (ioremap)
在这里插入图片描述
弊端: 还是需要在驱动代码中进行寄存器的操作,进行地址映射操作 ( ioremap )
在这里插入图片描述


引入Pinctrl子系统和GPIO子系统

总结图片,一览无遗
在这里插入图片描述

1、Pinctrl子系统
Pinctrl子系统是负责引脚的复用,和属性定义
可以认为它对应IOMUX──用来复用引脚,还可以配置引脚(比如上下拉电阻等)

//client端:
@节点名字 {
    
    
    pinctrl-names = "default, sleep";    // 定义有几个状态
    pinctrl-0 = <&pinctrl_自定义名字A>;   // 第一个状态对应的引脚属性
    pinctrl-1 = <&pinctrl_自定义名字B>;   // 第二个状态对应的引脚属性
    xxx-gpio  = <&gpiox  n  flag>;       // 
    status = "okay";
};

//pincontroller服务端
pinctrl_自定义名字A: 自定义名字 {
    
    
    fsl,pins = <
            引脚复用宏定义   PAD(引脚)属性, // 引脚 A
    >;
};
pinctrl_自定义名字B: 自定义名字 {
    
    
    fsl,pins = <
            引脚复用宏定义   PAD(引脚)属性; // 引脚 B
    >;
};

PS:
一个引脚可以有多种状态 ,例如可以配置成串口模式,睡眠的时候为了省点配置成GPIO模式

2、添加gpio属性,指定引脚和有效状态

@节点名字 {
    
    
    pinctrl-names = "default, sleep";    // 定义有几个状态
    pinctrl-0 = <&pinctrl_自定义名字A>;   // 第一个状态对应的引脚属性
    led-gpio  = <&gpio1  3  GPIO_ACTIVE_LOW>;   //指定GPIO1_3引脚,模式低电平有效 
    status = "okay";
};

使用实例:

设备节点

在这里插入图片描述
pinctrl端

在这里插入图片描述
提问:

为什么设备节点中 led-gpio 后面是<&gpio1 3 flag>
&gpio1 是 组
为什么后面要加上两个位呢?这是因为在 imx6ull.dtsi 文件中定义了 gpio1节点,且定义了 pinctrl ,其中 #gpio-cells = <2>; ,这个属性就表明了,GPIO组后面要跟两个参数

在这里插入图片描述
提问:

pinctrl端 “MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0” 是怎么得来的?

想要获得IMX6ULL 的pinctrl 代码,可以使用官方提供的工具Pins_Tool_for_i.MX_Processors_v6_x64 来获取,下载链接,使用方式自行百度吧嘿嘿
这个 0x10B0 其实就是 参考手册中 IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 寄存器设置的值

MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 是一个宏,在 imx6ul-pinfunc.h 文件中定义

3、GPIO子系统API使用

在这里插入图片描述
推荐使用新的一套GPIO子系统

(1)获得GPIO (gpiod_get)

struct gpio_desc *my_dev_gpio;
........
//从设备树中获取GPIO引脚,并设置成默认输出模式,无效(LOW表示无效逻辑)
my_dev_gpio = gpiod_get(dev, "led", GPIOD_OUT_LOW);

第二个参数对应设备树中 xxx-gpio 中的 xxx

led-gpio   = <&gpio1 3 GPIO_ACTIVE_LOW>;

(2)【可选】获取自定义设备名称属性(of_property_read_string

char a[20];
const char *str = a;
of_property_read_string(np, "my_name", &str);

(3)创建设备节点(device_create)

//创建设备节点 /dev/xxx
device_create(my_dev_class, NULL, MKDEV(major, 0), NULL, str);

(4)写GPIO(gpiod_set_value)

gpiod_set_value(my_dev_gpio, status);

(5)释放GPIO,销毁设备节点

//释放GPIO
gpiod_put(my_dev_gpio);
//销毁设备
device_destroy(my_dev_class, MKDEV(major, 0));

全部代码:

我将led和beep都加进来了,所以打开设备节点、销毁节点时要区分是哪一个,所以将下面这两个该成了数组,通过设备树中自定义的 my_name 属性区分设备

char dev_names[10][20]={
    
    };    //保存设备树中的名字
struct gpio_desc *my_dev_gpio[10];
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/uaccess.h>
#include <linux/string.h>

int major;				//设备号
static struct class *my_dev_class;
int dev_cnt;
char dev_names[10][20]={
    
    };    //保存设备树中的名字
struct gpio_desc *my_dev_gpio[10];


/*=============================file_operations ==============================*/
static ssize_t my_drv_read (struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
    
    
	printk("drv_read function run....\n");
	return 1;
}

static ssize_t my_drv_write (struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
    
    
	struct inode *inode = file_inode(filp);
	int minor = iminor(inode);
	char status;
	int err;
	err = copy_from_user(&status, buf, 1);
	gpiod_set_value(my_dev_gpio[minor], status);
	
	printk("drv_write function run....\n");
	return 0;
}

static int my_drv_open (struct inode *node, struct file *filp)
{
    
    
	struct inode *inode = file_inode(filp);
	int minor = iminor(inode);

	printk("drv_open function run....\n");
	gpiod_set_value(my_dev_gpio[minor], 0);
	return 0;
}

static int my_drv_release (struct inode *node, struct file *filp)
{
    
    
	printk("drv_release function run....\n");
	return 0;
}

/* operations结构体:为应用层提供驱动接口 */
static struct file_operations my_dev_ops = {
    
    
	.owner		= 	THIS_MODULE,
	.read 		=	my_drv_read,
	.write		=	my_drv_write,
	.open		=	my_drv_open,
	.release	=	my_drv_release,
};


/*=============================platform_driver==============================*/
/*  如果匹配到了内核根据设备树生成的platform_device,
	该函数会被调用,如果有多个匹配的设备节点,该函数
	会被多次调用
*/
static int my_probe(struct platform_device *pdev)
{
    
    
	/*  从内核根据设备树生成的 platform_device 
		结构体中获取到设备节点
	*/
	struct device *dev = &pdev->dev;
	struct device_node *np = dev->of_node;
	
	int gpio_pin;
	char a[20];
	const char *str = a;
	
	of_property_read_string(np, "my_name", &str);
	//保存设备的名字
	strcpy(dev_names[dev_cnt], str);
	
	//从设备树中获取GPIO引脚,并设置成默认输出模式,无效(LOW表示无效逻辑)
	my_dev_gpio[dev_cnt] = gpiod_get(dev, str, GPIOD_OUT_LOW);
	
	//从struct desc结构体转成GPIO子系统标号
	gpio_pin = desc_to_gpio(my_dev_gpio[dev_cnt]);  

	//创建设备节点 /dev/xxx
	device_create(my_dev_class, NULL, MKDEV(major, dev_cnt), NULL, str);
	dev_cnt++;

	printk("my_probe run, my_name = %s\n", str);
	
	return 0;
}

static int my_remove(struct platform_device *pdev)
{
    
    
	/*  从内核根据设备树生成的 platform_device 
		结构体中获取到设备节点
	*/
	struct device *dev = &pdev->dev;
	struct device_node *np = dev->of_node;
	
	int gpio_pin, i;
	char a[20];
	const char *str = a;
	int flag = 0;
	
	of_property_read_string(np, "my_name", &str);

	for(i = 0; i < dev_cnt; i++){
    
    
		if(strcmp(dev_names[i],str) == 0){
    
    
			strcpy(dev_names[i], "");
			gpio_pin = desc_to_gpio(my_dev_gpio[i]); 
			//释放GPIO
			gpiod_put(my_dev_gpio[i]);
			//销毁设备
			device_destroy(my_dev_class, MKDEV(major, i));
			printk("my_remove run, device_destroy %s, my_gpio = %d\n", str, gpio_pin);
		}
		if(dev_names[i])
			flag = 1;
	}
	if(flag == 0){
    
    
		dev_cnt = 0;
		printk("all removed\n");
	}
	return 0;
}

static struct of_device_id my_dev_match[] = {
    
    
	{
    
    .compatible = "hc-led-beep"}, 
	{
    
    .compatible = "hc-led-beep"}, 
//	{.compatible = "hc-key"}, 
	{
    
    },
};

static struct platform_driver dev_driver = {
    
    
	.probe		=	my_probe,	
	.remove		= 	my_remove,
	.driver		= {
    
    
		.name	= "my_platform_driver",
		.of_match_table = my_dev_match,
	},
};

/*=============================驱动出入口函数==============================*/
/* 驱动入口函数:insmod xx.ko 时会被调用 */
static int dev_init(void)
{
    
    	
	major = register_chrdev(0, "hc_dev_drv", &my_dev_ops);
	if(major < 0){
    
    
		printk("register_chrdev famy\n");
		return major;
	}

	my_dev_class = class_create(THIS_MODULE, "my_dev_class");
	if(IS_ERR(my_dev_class)){
    
    
		printk("class_create failed\n");
		return 1;
	}

	platform_driver_register(&dev_driver);

	return 0;
}

/* 驱动出口函数: rmmod xx.ko 时会被调用 */
static void dev_exit(void)
{
    
    
	platform_driver_unregister(&dev_driver);
	class_destroy(my_dev_class);
	unregister_chrdev(major, "hc_dev_drv");
	printk("my_dev driver exit\n");
}

module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");

猜你喜欢

转载自blog.csdn.net/HuangChen666/article/details/131418687