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,所以GPIO2_B3的引脚编号为: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,不能使用printfprintk(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-parent
和interrupts
属性即可定义中断的引脚,与irq-gpios
属性好像是重复的,只定义一种即可,上述代码中用到了irq-gpios
属性,然后将GPIO编号映射为中断编号。如果要使用interrupts
属性则需要使用其他的接口,使用irq_of_parse_and_map(si_haptics_node, 0)
函数即可获取中断号问题
关于
pinctrl
节点中子节点定义,到底有啥作用呢?很多代码都会有pinctrl-names = "default"
属性,但是将上述示例代码中的pinctrl
节点定义删除好像也是可以的。其实pinctrl-names
和pinctrl-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");}