Linux学习_Pinctrl子系统与GPIO子系统(基础版)


为啥要学这俩子系统呢,就是方便,用的时候谁一个个配寄存器啊跟怨种似的,就用pinctrl或者gpio这俩子系统带的函数,直接便捷使用了

Pinctrl子系统

原本我们写代码时,不论是直接写也好,总线模式也好,都是手写调用IOMUX对引脚进行配置,将PIN配置为GPIO、I2C等等,一个一个搞这些引脚其实还是比较麻烦的,所以BSP工程师们把其改成用软件来操作,制作成Pinctrl子系统。
在这里插入图片描述
在设备树中,想使用上pinctrl很简单,在设备属性中加入pinctrl-names,再用pinctrl-n来代表第n个状态由尖括号中的子节点(们)来设置,并在pincontroller中添加对应的子节点就OK了,节点的具体配置方法如下图:
在这里插入图片描述
右边device这边配置是非常标准的,就是这样的规范化书写,没啥可说的,然而。。。左边的pincontroller部分,写法五花八门,各家都不一样,不总结了就。
注意:图中的pincontroller和device都是代词,表示这个意思而已,不是实际命名,实际应用中根据芯片和设备的不同可能命名为各种具体的名字。
用imx6ull举个例子吧,pincontroller写成这德行了:
在这里插入图片描述
ABC__xxx 0xnnnnn,这个格式表示的意思是,ABC引脚,复用为xxx功能,配置参数为0xnnnnn,大概率是有个宏吧能这样定义的。

GPIO子系统

还是搬出来本文第一个图:
在这里插入图片描述
是不是有点怪了,哈哈。都有了 pinctrl 子系统了,还整GPIO子系统干啥,因为很多辣鸡芯片没有IOMUX,引脚的复用和配置就是在GPIO模块内部实现的,所以这玩意也得学。
总的来说要操作一个 GPIO 引脚,先用 Pinctrl 把引脚配置为 GPIO ,然后用 GPIO 带的函数具体设置,方便的一b。
在 GPIO 子系统下,我们可以

  1. 在设备树里指定 GPIO 引脚
  2. 在驱动代码中:使用 GPIO 子系统的标准函数获得 GPIO、设置 GPIO 方向、读取/设置 GPIO 值

那么,一个一个说。

在设备树里指定 GPIO 引脚

还记得之前“点亮一个LED时”用的啥 GPIO 吗,是不是 GPIO5_3 ,这种写法就是ARM芯片中 GPIO 的典型写法,代表了第五组第三个。
在设备树中,组体现为gpio子节点,如imx6ull.dtsi中:
在这里插入图片描述
这里面的gpio1就表示第一组gpio,gpio2就表示第二组,(感觉在说废话。。。)只需要关注其中两个属性:

  1. gpio-controller;:代表表示这个节点是一个 GPIO Controller,也就是代表是一组GPIO,它下面有很多引脚
  2. #gpio-cells=<2>;:表示这个控制器下每一个引脚要用 2 个 32 位的数(cell)来描述

一般来说,第一个cell指定具体引脚,第二个cell表示有效电平,在自己写的设备树.dts文件中,可以使用gpiosname-gpios来引用引脚,赋值时遵循name-gpios = <gpio组名 cell cell ....>的格式,如下所示:
在这里插入图片描述

在驱动代码中获得、设置、读写GPIO引脚

首先是包含头文件,其中legacy系列比较老了,推荐使用d-b系列

#include <linux/gpio/consumer.h> // descriptor-based
或者
#include <linux/gpio.h> // legacy

常用的函数有这么一大坨,推荐使用devm系列的,因为它具有自动释放机制,在设备不存在时资源就会自动释放,比较方便
在这里插入图片描述
举个栗子:
设备树中有这么个玩意:

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

那么驱动里面可以这样获得引脚:gpiod_get_index(dev,“name”-gpios,组内第几个,状态设置)

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

red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_HIGH);     //led-gpios中的第0个,设置为输出、高有效
green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_HIGH);   //led-gpios中的第1个,设置为输出、高有效
blue = gpiod_get_index(dev, "led", 2, GPIOD_OUT_HIGH);    //led-gpios中的第2个,设置为输出、高有效
power = gpiod_get(dev, "power", GPIOD_OUT_HIGH);          //power-gpios就这一个直接get,设置为输出、高有效

注意:这里面的高有效低有效是逻辑高低,不是实际电压高低,有的设备树很烦就指定低电压为high,高电压为low,可能是叛逆吧。

GPIO子系统LED驱动

大致思路:

  1. 设备树节点会被内核转换为 platform_device。
  2. 对应的,驱动代码中要注册一个 platform_driver,
  3. 在 probe 函数中:获得引脚、注册 file_operations。
  4. 在 file_operations 中:设置方向、读值/写值。

其中,设备树节点这些有NXP官方给的设备树工具,设置生成挺好用,要改的就pincontroller和根节点下新加个子节点。
所以从2开始是我们要写在驱动里的,都写在驱动代码里

与设备树配对,注册platform_driver

加一个id表:

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);//主要是这句register
	return err;
}

在probe里get GPIO

在probe里面 1. 用gpiod_get获得GPIO,2. 注册file_operations结构体:

static struct gpio_desc *led_gpio;

static int chip_demo_gpio_probe(struct platform_device *pdev){
    
    	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
	/* 4.1 设备树中定义有: led-gpios=<...>;	*/
    led_gpio = gpiod_get(&pdev->dev, "led", 0);   
     
	/* 4.2 注册file_operations 	*/
	major = register_chrdev(0, "100ask_led", &led_drv);  /* /dev/led */
	led_class = class_create(THIS_MODULE, "100ask_led_class");
	device_create(led_class, NULL, MKDEV(major, 0), NULL, "100ask_led%d", 0); /* /dev/100ask_led0 */
        
    return 0;
}

在open里设置GPIO为output,gpiod_direction_output( )

static int led_drv_open (struct inode *node, struct file *file){
    
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 根据次设备号初始化LED */
	gpiod_direction_output(led_gpio, 0);
	
	return 0;
}

在write里设置具体引脚值,gpiod_set_value( )

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__);
	/* 根据次设备号和status控制LED */
	err = copy_from_user(&status, buf, 1);
	gpiod_set_value(led_gpio, status);
	
	return 1;
}

实现解除注册和占用的remove函数

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

猜你喜欢

转载自blog.csdn.net/muzi_taibai/article/details/129652461