瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。
【公众号】迅为电子
【粉丝群】824412014(加群获取驱动文档+例程)
【视频观看】嵌入式学习之Linux驱动(第十一期_pinctrl子系统_全新升级)_基于RK3568
【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板
Linux中的pinctrl子系统(Pin Control Subsystem)是一个用于管理和配置通用输入/输出(GPIO)引脚的框架。它提供了一种标准化的方法,以在Linux内核中对GPIO引脚进行配置、分配和控制,从而适应不同的硬件平台和设备。
pinctrl子系统也符合Linux内核的设备模型规范,所以pinctrl子系统同样可以根据设备模型规范分为设备、驱动、总线和类四个部分,本章节将从设备和驱动两个部分来引入pinctrl子系统。
在前面设备树相关的章节中已经对pinctrl节点的编写和使用进行了讲解,设备树的pinctrl可以分为客户端和服务端两个部分,在pinctrl客户端可以指定引脚描述、引脚组描述和配置描述,以满足其特定的功能和需求,不同厂商在客户端内容的编写格式是相同的。服务端是指提供GPIO引脚配置的pinctrl设备树节点,它是描述GPIO引脚配置和使用规则的节点,定义了一组GPIO引脚的配置选项,以及这些选项对应的引脚功能和电气特性。
接下来对rk3568的pinctrl设备树进行详细的讲解。首先在rk3568.dtsi设备树根节点下找到pinctrl节点,具体内容如下所示:
pinctrl: pinctrl {
compatible = "rockchip,rk3568-pinctrl";
rockchip,grf = <&grf>;
rockchip,pmu = <&pmugrf>;
#address-cells = <2>;
#size-cells = <2>;
ranges;
gpio0: gpio@fdd60000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfdd60000 0x0 0x100>;
interrupts = ;
clocks = <&pmucru PCLK_GPIO0>, <&pmucru DBCLK_GPIO0>;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 0 32>;
interrupt-controller;
#interrupt-cells = <2>;
};
gpio1: gpio@fe740000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfe740000 0x0 0x100>;
interrupts = ;
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@fe750000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfe750000 0x0 0x100>;
interrupts = ;
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@fe760000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfe760000 0x0 0x100>;
interrupts = ;
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@fe770000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfe770000 0x0 0x100>;
interrupts = ;
clocks = <&cru PCLK_GPIO4>, <&cru DBCLK_GPIO4>;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 128 32>;
interrupt-controller;
#interrupt-cells = <2>;
};
};
};
#include "rk3568-pinctrl.dtsi"
在上面的pinctrl节点中,描述了RK3568 GPIO控制器的配置和使用方式,pinctrl节点总共描述了五个GPIO控制器,分别是gpio0、gpio1、gpio2、gpio3和gpio4。通过这些GPIO控制器节点,可以在设备树中配置和控制RK3568芯片上的GPIO引脚,包括设置引脚功能、中断处理等。
在设备树的最下方通过include包含了rk3568-pinctrl.dtsi设备树,该设备树中包含了所有复用功能的配置,具体内容如下所示(更具体的内容描述可以回顾设备树pinctrl章节内容):
图 120-1
无论是rk3568.dtsi设备树中的pinctrl节点,还是上面rk3568-pinctrl.dtsi设备树中的一系列复用关系都是由瑞芯微原厂BSP工程师编写的,我们只需知道如何使用即可,而pinctrl客户端设备树是由我们自己根据特定需求来编写的,具体可以回顾前面设备树相关的章节,这里就不再进行赘述。
设备树中存放的只是设备的描述信息,而具体的功能实现取决于相应的pinctrl驱动,根据rk3568.dtsi设备树中pinctrl节点的compatible属性进行查找,可以查找到pinctrl的驱动文件是内核源码的“/driver/pinctrl/pinctrl-rockchip.c”,如下所示:
图120- 2
在下个小节中将对pinctrl的驱动部分进行简单的介绍。
首先进入到内核源码目录下的“/drivers/pinctrl/pinctrl-rockchip.c”驱动文件中,找到驱动的入口函数,具体内容如下所示:
static struct platform_driver rockchip_pinctrl_driver = {
.probe = rockchip_pinctrl_probe,
.driver = {
.name = "rockchip-pinctrl",
.pm = &rockchip_pinctrl_dev_pm_ops,
.of_match_table = rockchip_pinctrl_dt_match,
},
};
static int __init rockchip_pinctrl_drv_register(void)
{
return platform_driver_register(&rockchip_pinctrl_driver);
}
postcore_initcall(rockchip_pinctrl_drv_register);
static void __exit rockchip_pinctrl_drv_unregister(void)
{
platform_driver_unregister(&rockchip_pinctrl_driver);
}
可以看到pinctrl驱动使用的是platform总线,当设备和驱动匹配成功之后会进入rockchip_pinctrl_probe函数进行初始化,probe函数的具体内容如下所示:
static int rockchip_pinctrl_probe(struct platform_device *pdev)
{
struct rockchip_pinctrl *info; // Rockchip GPIO控制器的信息结构体指针
struct device *dev = &pdev->dev; // 设备结构体指针
struct rockchip_pin_ctrl *ctrl; // Rockchip GPIO控制器的配置结构体指针
struct device_node *np = pdev->dev.of_node, *node; // 设备节点指针
struct resource *res; // 设备资源指针
void __iomem *base; // 寄存器基地址指针
int ret; // 返回值
if (!dev->of_node) {
dev_err(dev, "device tree node not found\n");
return -ENODEV;
}
// 分配并初始化一个rockchip_pinctrl结构体
info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
info->dev = dev;
// 获取并设置与pdev相关的rockchip_pin_ctrl结构体
ctrl = rockchip_pinctrl_get_soc_data(info, pdev);
if (!ctrl) {
dev_err(dev, "driver data not available\n");
return -EINVAL;
}
info->ctrl = ctrl;
// 解析设备树中的"rockchip,grf"节点,获取寄存器映射基地址
node = of_parse_phandle(np, "rockchip,grf", 0);
if (node) {
info->regmap_base = syscon_node_to_regmap(node);
if (IS_ERR(info->regmap_base))
return PTR_ERR(info->regmap_base);
} else {
// 如果找不到"rockchip,grf"节点,则获取IORESOURCE_MEM类型的资源,得到寄存器基地址
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
// 配置寄存器映射的最大寄存器地址和名称
rockchip_regmap_config.max_register = resource_size(res) - 4;
rockchip_regmap_config.name = "rockchip,pinctrl";
info->regmap_base = devm_regmap_init_mmio(&pdev->dev, base,
&rockchip_regmap_config);
// 检查旧的dt-bindings
info->reg_size = resource_size(res);
// 如果控制器类型为RK3188且reg_size小于0x200,则获取第二个IORESOURCE_MEM类型的资源,作为pull寄存器的基地址
if (ctrl->type == RK3188 && info->reg_size < 0x200) {
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
// 配置pull寄存器映射的最大寄存器地址和名称
rockchip_regmap_config.max_register =
resource_size(res) - 4;
rockchip_regmap_config.name = "rockchip,pinctrl-pull";
info->regmap_pull = devm_regmap_init_mmio(&pdev->dev,
base,
&rockchip_regmap_config);
}
}
// 尝试查找可选的pmu syscon引用
node = of_parse_phandle(np, "rockchip,pmu", 0);
if (node) {
info->regmap_pmu = syscon_node_to_regmap(node);
if (IS_ERR(info->regmap_pmu))
return PTR_ERR(info->regmap_pmu);
}
// 对于某些SoC进行特殊处理
if (ctrl->soc_data_init) {
ret = ctrl->soc_data_init(info);
if (ret)
return ret;
}
// 注册rockchip_pinctrl设备
ret = rockchip_pinctrl_register(pdev, info);
if (ret)
return ret;
// 设置pdev的私有数据为info
platform_set_drvdata(pdev, info);
// 注册GPIO设备
ret = of_platform_populate(np, rockchip_bank_match, NULL, NULL);
if (ret) {
dev_err(&pdev->dev, "failed to register gpio device\n");
return ret;
}
dev_info(dev, "probed %s\n", dev_name(dev));
return 0;
}
上面Probe函数的作用是初始化和配置Rockchip GPIO控制器,并将相关信息存储在rockchip_pinctrl结构体中,最后注册相关设备和GPIO接口,关于Probe函数会在后面的小节中进行更加具体的分析。
最后来带领大家思考一个问题,假如我们要配置一个LED外设,该LED需要使用一个管脚来进行控制,那这个控制引脚需要复用成GPIO之后才能完成相应的功能,通过上面内容的学习之后,我们知道是pinctrl子系统将这个管脚复用为了GPIO功能,那pinctrl子系统是什么时候对该引脚进行的复用呢?带着这个疑问,让我们一起进入后面章节的学习吧。