【Linux】Pinctrl子系统和GPIO子系统

Pinctrl子系统

在许多soc内部包含了多个pin控制器,通过pin控制器的寄存器,我们可以配置一个或者一组引脚的功能和特性。Linux内核为了统一各soc厂商的pin脚管理,提供了pinctrl子系统。该系统的作用:

  • 在系统初始化的时候,枚举所有可以控制的pin,并标识这pin。
  • 设定引脚的功能复用,比如复用为GPIO还是SPI等。
  • 引脚的配置,比如上下拉,驱动强度和去抖等。

pinctrl子系统结构描述:

在这里插入图片描述

如上图所示,pinctrl核心层是内核抽象出来,向下为个SoC pin controler drvier提供底层通信接口的能力, 向上为其他驱动提供了控制pin的能力,比如pin复用、配置引脚的电气特性,同时也为GPIO子系统提供pin操作。而pin控制器驱动层,主要提供了操作pin的方法。

pinctrl子系统的源文件在内核源码/drivers/pinctrl目录下,主要包括核心文件,和其他内核驱动的接口头文件,底层的pin控制器驱动接口,还有其他厂商Pinctrl驱动文件。

pinctrl设备树节点介绍

首先,我们在arch\arm64\boot\dts\rockchip\rk3588s.dtsi文件中可以看到如下内容:

	pinctrl: pinctrl {
		compatible = "rockchip,rk3588-pinctrl";
		rockchip,grf = <&ioc>;
		#address-cells = <2>;
		#size-cells = <2>;
		ranges;

		gpio0: gpio@fd8a0000 {
			compatible = "rockchip,gpio-bank";
			reg = <0x0 0xfd8a0000 0x0 0x100>;
			interrupts = <GIC_SPI 277 IRQ_TYPE_LEVEL_HIGH>;
			clocks = <&cru PCLK_GPIO0>, <&cru DBCLK_GPIO0>;

			gpio-controller;
			#gpio-cells = <2>;
			gpio-ranges = <&pinctrl 0 0 32>;
			interrupt-controller;
			#interrupt-cells = <2>;
		};

		gpio1: gpio@fec20000 {
			compatible = "rockchip,gpio-bank";
			reg = <0x0 0xfec20000 0x0 0x100>;
			interrupts = <GIC_SPI 278 IRQ_TYPE_LEVEL_HIGH>;
			clocks = <&cru PCLK_GPIO1>, <&cru DBCLK_GPIO1>;

			gpio-controller;
			#gpio-cells = <2>;
			gpio-ranges = <&pinctrl 0 32 32>;
			interrupt-controller;
			#interrupt-cells = <2>;
		};

		gpio2: gpio@fec30000 {
			compatible = "rockchip,gpio-bank";
			reg = <0x0 0xfec30000 0x0 0x100>;
			interrupts = <GIC_SPI 279 IRQ_TYPE_LEVEL_HIGH>;
			clocks = <&cru PCLK_GPIO2>, <&cru DBCLK_GPIO2>;

			gpio-controller;
			#gpio-cells = <2>;
			gpio-ranges = <&pinctrl 0 64 32>;
			interrupt-controller;
			#interrupt-cells = <2>;
		};

		gpio3: gpio@fec40000 {
			compatible = "rockchip,gpio-bank";
			reg = <0x0 0xfec40000 0x0 0x100>;
			interrupts = <GIC_SPI 280 IRQ_TYPE_LEVEL_HIGH>;
			clocks = <&cru PCLK_GPIO3>, <&cru DBCLK_GPIO3>;

			gpio-controller;
			#gpio-cells = <2>;
			gpio-ranges = <&pinctrl 0 96 32>;
			interrupt-controller;
			#interrupt-cells = <2>;
		};

		gpio4: gpio@fec50000 {
			compatible = "rockchip,gpio-bank";
			reg = <0x0 0xfec50000 0x0 0x100>;
			interrupts = <GIC_SPI 281 IRQ_TYPE_LEVEL_HIGH>;
			clocks = <&cru PCLK_GPIO4>, <&cru DBCLK_GPIO4>;

			gpio-controller;
			#gpio-cells = <2>;
			gpio-ranges = <&pinctrl 0 128 32>;
			interrupt-controller;
			#interrupt-cells = <2>;
		};
	};
  • pinctrl节点就是在这里定义的,其他地方都是追加修改使用的&pinctrl
  • 这里还定义了5个GPIO bank,注意,这5各组后面还会使用。

我们再来看看别人在pinctrl节点中追加的内容:

&pinctrl {
	i2c6 {
		/omit-if-no-ref/
		i2c6m2_xfer: i2c6m2-xfer {
			rockchip,pins =
				/* i2c6_scl_m2 */
				<2 RK_PC3 9 &pcfg_pull_none_smt>,
				/* i2c6_sda_m2 */
				<2 RK_PC2 9 &pcfg_pull_none_smt>;
		};
	};
}

上述代码就是定义了一个i2c6子节点,子节点中又定义了i2c6m2_xfer节点。节点中定义了rockchip,pins属性,属性的值有两组。属性值中每个单元是啥意思呢?

  • 2:这个数字通常表示GPIO bank号(0-5),后续会具体讲解GPIO编号。
  • RK_PC3:这是rk平台上的一个具体的引脚编号,这是一个宏定义,在scripts\dtc\include-prefixes\dt-bindings\pinctrl\rockchip.h文件中,他其实也是一个数字,这个数字也涉及到GPIO编号,后续再介绍。
  • 9:这个数字表示引脚的功能,在rk平台上,每个引脚可以被配置为多种不同的功能,如GPIO、I2C或SPI等。当为0时(即RK_FUNC_GPIO)表示设置复用功能为GPIO。这里不是很明白为什么9就是i2c功能,没有看到在哪定义的。
  • &pcfg_pull_none_smt:这是一个引脚配置对象的引用,它定义了引脚的上拉/下拉配置。具体见arch\arm64\boot\dts\rockchip\rockchip-pinconf.dtsi文件。
	fiq_debugger: fiq-debugger {
		compatible = "rockchip,fiq-debugger";
		rockchip,serial-id = <2>;
		rockchip,wake-irq = <0>;
		/* If enable uart uses irq instead of fiq */
		rockchip,irq-mode-enable = <1>;
		rockchip,baudrate = <1500000>;  /* Only 115200 and 1500000 */
		interrupts = <GIC_SPI 423 IRQ_TYPE_LEVEL_LOW>;
		pinctrl-names = "default";
		pinctrl-0 = <&uart2m0_xfer>;
		status = "okay";
	};

GPIO子系统

在pinctrl子系统中把pin脚初始化成了普通GPIO后,就可以使用GPIO子系统的接口去操作IO口的电平、中断等。驱动开发者在设备树中添加gpio相关信息, 然后就可以在驱动程序中使用gpio子系统提供的API函数来操作GPIO,极大的方便了驱动开发者使用GPIO。gpio子系统的代码在内核源码/drivers/gpio/目录下。

GPIO子系统结构简单描述如下图:

在这里插入图片描述

GPIO的核心是gpiolib框架,向上提供一些gpio接口给其他驱动调用,向下提供用于gpio资源注册函数。 上层的其他驱动,比如LED的驱动,可以通过函数向gpiolib申请gpio,然后设置和使用gpio。下层的控制器驱动(一般是SOC厂商编写),启动时会注册gpio资源到gpiolib,比如引脚数量,操作函数等等。

GPIO引脚计算

Rockchip Pin的ID按照:控制器(bank)+端口(port)+索引序号(pin)组成,例如GPIO3_B2。

  • 有5组GPIO,即5个控制器(bank),对应GPIO0~GPIO4,也就是前面在dts中定义的gpio0-gpio4。
  • 端口固定为A、B、C和D。
  • 索引序号固定为0、1、2、3、4、5、6、7。

有5个GPIO控制器,每个控制器都可以控制32个IO,所以GPIO3_B2的引脚编号为:3 * 32 + 1 * 8 + 2 = 106

示例(LED驱动)

背景:定义两个GPIO,并将这两个GPIO直接相连,其中一个GPIO设置为输出,另一个GPIO设置为中断。


&pinctrl {
		haptics_pins {
			reset_pins:reset_pins {
				rockchip,pins=<1 RK_PB3 RK_FUNC_GPIO &pcfg_pull_none>;
			};

			irq_pins:irq_pins{
				rockchip,pins=<2 RK_PB2 RK_FUNC_GPIO &pcfg_pull_none>;
			};
		};
	};

/{
	si_haptics { 
		status = "okay";
		compatible = "si,haptics"; 
		reset-gpios = <&gpio1 RK_PB3 GPIO_ACTIVE_HIGH>; 

		irq-gpios = <&gpio2 RK_PB2 GPIO_ACTIVE_HIGH>;
        /*与irq-gpios属性的作用相同,都是定义中断的引脚,但是在驱动中解析时使用的接口不一样*/
		interrupt-parent = <&gpio2>;
        interrupts = <RK_PB2 IRQ_TYPE_EDGE_RISING>;

		// pinctrl-names = "default";
		// pinctrl-0 =<&reset_pins>;
	};
};

/*
 * @Date: 2024-10-17 16:08:08
 * @LastEditors: zdk
 * @LastEditTime: 2024-11-05 16:26:23
 * @FilePath: \kernel\drivers\haptics\haptic_drv.c
 */

#include <linux/init.h>  //包含宏定义的头文件
#include <linux/module.h>   //包含初始化加载模块的头文件
#include <linux/fs.h>
#include <linux/sysfs.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include "haptic_ioctl.h"


typedef struct
{
	struct miscdevice miscdev;//定义一个杂项设备结构体
	int reset_gpio;
	int irq_gpio;
}haptic_miscdev_t;

ssize_t reset_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	haptic_miscdev_t * haptic_dev= (haptic_miscdev_t *)dev->driver_data;
	
	return sprintf(buf, "reset_show reset=%d\n",gpio_get_value(haptic_dev->reset_gpio));
}
 
ssize_t reset_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
	int ret= 0;
	int val=0;
	haptic_miscdev_t * haptic_dev= (haptic_miscdev_t *)dev->driver_data;
	ret = kstrtoint(buf, 0 , &val);
	gpio_set_value(haptic_dev->reset_gpio,val);
    	printk("reset_store reset=%d\n",val);
    	return count;
}

ssize_t irq_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	return sprintf(buf, "hello i am irq_show\n");
}
 
ssize_t irq_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
    	printk("%s:%s\n", __func__,buf);
    	return count;
}

static DEVICE_ATTR(reset, 0664, reset_show, reset_store);
static DEVICE_ATTR(irq, 0664, irq_show, irq_store);

static struct attribute* haptic_attributes[] =
{
	&dev_attr_reset.attr,
	&dev_attr_irq.attr,
	NULL,
};

static struct attribute_group haptic_attr_group = 
{
	.attrs = haptic_attributes,
};

static irqreturn_t haptic_irq_hander(int irq, void *dev_id)
{
	haptic_miscdev_t * haptic_dev = (haptic_miscdev_t *)dev_id;
	printk("haptic_irq_hander:reset_value=%d  irq_value=%d\n",
	gpio_get_value(haptic_dev->reset_gpio),gpio_get_value(haptic_dev->irq_gpio));
    	
    	return IRQ_HANDLED;
}

static void haptic_parse_dts(struct platform_device *pdev,haptic_miscdev_t * haptic_dev)
{
	int ret = 0;
	struct device_node* si_haptics_node=NULL;

    	si_haptics_node = of_find_node_by_path("/si_haptics");
    	if(si_haptics_node == NULL)
    	{
      		printk(KERN_EMERG "get si_haptics_node failed!  \n");
    	}

    	haptic_dev->reset_gpio = of_get_named_gpio(si_haptics_node, "reset-gpios", 0);
	haptic_dev->irq_gpio = of_get_named_gpio(si_haptics_node,"irq-gpios",0);
	
	if(gpio_is_valid(haptic_dev->reset_gpio))
	{	
		ret = devm_gpio_request_one(&pdev->dev,haptic_dev->reset_gpio,GPIOF_OUT_INIT_LOW,"reset");
		printk("reset_gpio_number = %d init low\n",haptic_dev->reset_gpio);
	}

	if(gpio_is_valid(haptic_dev->irq_gpio))
	{
		printk("irq_gpio_number = %d \n",haptic_dev->irq_gpio);

        /*这种方式是使用interrupt属性解析中断号*/
        //ret = devm_request_irq(&pdev->dev,irq_of_parse_and_map(si_haptics_node, 0),haptic_irq_hander,IRQF_TRIGGER_RISING,"haptic_interrupt",haptic_dev);
		ret = devm_request_irq(&pdev->dev,gpio_to_irq(haptic_dev->irq_gpio),haptic_irq_hander,IRQF_TRIGGER_RISING,"haptic_interrupt",haptic_dev);//这种方式是使用GPIO编号来映射中断号
		if(ret < 0)
		{
			printk("devm_request_irq error %d \n",ret);
		}
	}
}

static int haptic_drv_probe(struct platform_device *pdev)
{
	int ret = 0;
	haptic_miscdev_t *haptic_dev=NULL;

	haptic_dev = devm_kzalloc(&pdev->dev,sizeof(haptic_miscdev_t), GFP_KERNEL);
	haptic_dev->miscdev.name = pdev->name;
	haptic_dev->miscdev.minor = MISC_DYNAMIC_MINOR,

	ret = misc_register(&haptic_dev->miscdev);

	/* save as drvdata */
    	//platform_set_drvdata函数,将设备数据信息存入在平台驱动结构体中pdev->dev->driver_data中
    	platform_set_drvdata(pdev, haptic_dev);

	ret = sysfs_create_group(&pdev->dev.kobj,&haptic_attr_group);

	haptic_parse_dts(pdev,haptic_dev);
	return ret;
}

static int haptic_drv_remove(struct platform_device *pdev)
{
	int ret = 0;
	haptic_miscdev_t *hap_miscdev = platform_get_drvdata(pdev);

	misc_deregister(&hap_miscdev->miscdev);

	return ret;
}

static struct of_device_id haptic_dev_ids[] =
{
	{.compatible="si,haptics",},
	{.compatible="aw,haptics",},
};

static struct platform_driver haptic_drv=
{
	.probe = haptic_drv_probe,
	.remove = haptic_drv_remove,
	.driver=
	{
		.name="haptics_drv",
		.owner = THIS_MODULE,
		.of_match_table =of_match_ptr(haptic_dev_ids),
	},
};


static int __init haptic_drv_init(void)
{
	int ret = 0;
	//内核层只能使用printk,不能使用printf
    	printk(KERN_EMERG "%s\n",__FUNCTION__); 

	ret = platform_driver_register(&haptic_drv);
    	return ret;
}

static void __exit haptic_drv_exit(void)
{
	platform_driver_unregister(&haptic_drv);
	printk(KERN_EMERG "%s\n",__FUNCTION__); 
}

module_init(haptic_drv_init);//模块入口
module_exit(haptic_drv_exit);//模块出口

MODULE_AUTHOR("<daokuan.zhug@si-in.com>");//声明作者信息
MODULE_DESCRIPTION("Haptics Device V1.0.0"); //对这个模块作一个简单的描述
MODULE_LICENSE("GPL v2");//声明开源许可证
			 // "GPL" 是指明 这是GNU General Public License的任意版本
                         // “GPL v2” 是指明 这仅声明为GPL的第二版本

在这里插入图片描述

在这里插入图片描述

总结

  • of_get_named_gpio接口其实就是将设备树中定义的GPIO名称转换为GPIO编号,因为代码中都是使用编号来指定GPIO。

  • 上面的架构和平台设备驱动架构很像,但是好像没有看到设备的代码?个人理解DTS中的节点就是设备,并且在驱动和设备匹配的时候,我们也是使用到了设备树节点中的compatible属性,而且我们的设备名称就是节点名称。

  • 关于DTS中的interrupt-parentinterrupts属性即可定义中断的引脚,与irq-gpios属性好像是重复的,只定义一种即可,上述代码中用到了irq-gpios属性,然后将GPIO编号映射为中断编号。如果要使用interrupts属性则需要使用其他的接口,使用irq_of_parse_and_map(si_haptics_node, 0)函数即可获取中断号

    问题

    关于pinctrl节点中子节点定义,到底有啥作用呢?很多代码都会有pinctrl-names = "default"属性,但是将上述示例代码中的pinctrl节点定义删除好像也是可以的。其实pinctrl-namespinctrl-x是搭配着使用的,并且和其他属性没有直接的联系。因为这组属性就是定义GPIO的复用功能的,比如pinctrl-0定义为i2c功能,pinctrl-1定义为gpio功能,这样在驱动中,我们就可以快速切换GPIO的配置。

    &pinctrl {
    		haptics_pins {
    			reset_pins:reset_pins {
    				rockchip,pins=<1 RK_PB3 RK_FUNC_GPIO &pcfg_pull_down>;
    			};
    
    			reset1_pins:reset1_pins{
    				rockchip,pins = <1 RK_PB3 RK_FUNC_GPIO &pcfg_pull_up>;
    			};
    
    			irq_pins:irq_pins{
    				rockchip,pins=<2 RK_PB2 RK_FUNC_GPIO &pcfg_pull_none>;
    			};
    		};
    	};
    
    /{
    	si_haptics { 
    		status = "okay";
    		compatible = "si,haptics"; 
    		reset-gpios = <&gpio1 RK_PB3 GPIO_ACTIVE_HIGH>; 
    
    		irq-gpios = <&gpio2 RK_PB2 GPIO_ACTIVE_HIGH>;
    		interrupt-parent = <&gpio2>;
                	interrupts = <RK_PB2 IRQ_TYPE_EDGE_RISING>;
    
    		pinctrl-names = "default","up";
    		pinctrl-0 = <&reset_pins>;
    		pinctrl-1 = <&reset1_pins>;
    	};
    };
    

    上述pinctrl中,我们定义了reset_pins引脚的两种功能,但其实我们只是修改了它的上下拉状态,这样方便举例,一般的使用场景不会这样,而是会定义不同的功能,也就是rockchip,pins属性的第3个参数会不一致。然后在我们的设备节点中,我们定义了两个状态,pinctrl-0对应"default"pinctrl-1对应"up",这些names是可以自定义的。在驱动代码中,我们做了一些修改。

typedef struct
{
	struct miscdevice miscdev;//定义一个杂项设备结构体
	int reset_gpio;
	int irq_gpio;
	struct pinctrl* reset_pinctrl;
}haptic_miscdev_t;

我们自定义的设备结构体中增加了struct pinctrl*字段。

	haptic_dev->reset_pinctrl = devm_pinctrl_get(&pdev->dev);
	if(IS_ERR_OR_NULL(haptic_dev->reset_pinctrl))
	{
		printk("devm_pinctrl_get error\n");
	}

上述代码就是从DTS文件中解析struct pinctrl*字段。然后在设备属性节点中我们去切换这个配置,然后通过读取GPIO的状态来确实pinctrl的状态是否切换成功。

ssize_t reset_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
	int ret= 0;
	int val=0;
	struct pinctrl_state* state=NULL;
	haptic_miscdev_t * haptic_dev= (haptic_miscdev_t *)dev->driver_data;
	ret = kstrtoint(buf, 0 , &val);	
    	

	if(val==0)
	{
		state = pinctrl_lookup_state(haptic_dev->reset_pinctrl,"default");
	}
	else
	{
		state = pinctrl_lookup_state(haptic_dev->reset_pinctrl,"up");
	}
	if(IS_ERR_OR_NULL(state))
	{
		printk("pinctrl_lookup_state error\n");
		return count;
	}

	ret = pinctrl_select_state(haptic_dev->reset_pinctrl,state);
	if(ret != 0)
	{
		printk("pinctrl_select_state error\n");
	}

	printk("reset_store reset=%d reset_value=%d\n",val,gpio_get_value(haptic_dev->reset_gpio));
    	return count;
}

在这里插入图片描述

补充

在解析DTS的时候,我们自定义了节点名称。但是如果使用设备树的方式匹配成功了,我们可以直接从struct platform_device *中获取设备树节点,即设备节点。

	struct device_node* si_haptics_node = pdev->dev.of_node;

    // si_haptics_node = of_find_node_by_path("/si_haptics");
    if(si_haptics_node == NULL)
    {
      	printk(KERN_EMERG "get si_haptics_node failed!  \n");
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值