RK3568驱动指南|第十一篇 pinctrl子系统-第121章 pinctrl probe函数讲解

瑞芯微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主板


第121章 pinctrl probe函数讲解

由于pinctrl驱动的probe函数理解起来较为复杂,所以在讲解pinctrl驱动的probe函数之前,我们先来了解一些与pinctrl相关的数据结构,本章首先会学习pinctrl_desc结构体和rockchip_pinctrl 结构体,最后对pinctrl probe函数进行分析。

121.1 pinctrl_desc结构体分析

pinctrl_desc结构体用于描述引脚控制器(pinctrl)的属性和操作。引脚控制器是硬件系统中的一个组件,用于管理和控制引脚的功能和状态。pinctrl_desc结构体的作用是提供一个统一的接口,用于配置和管理引脚控制器的行为。pinctrl_desc结构体定义在内核源码目录的“/include/linux/pinctrl/pinctrl.h”文件中,具体内容如下所示:

struct pinctrl_desc {
    const char *name;                                // 引脚控制器的名称
    const struct pinctrl_pin_desc *pins;             // 引脚描述符数组
    unsigned int npins;                              // 引脚描述符数组的大小
    const struct pinctrl_ops *pctlops;               // 引脚控制操作函数指针
    const struct pinmux_ops *pmxops;                 // 引脚复用操作函数指针
    const struct pinconf_ops *confops;               // 引脚配置操作函数指针
    struct module *owner;                            // 拥有该结构体的模块
#ifdef CONFIG_GENERIC_PINCONF
    unsigned int num_custom_params;                  // 自定义参数数量
    const struct pinconf_generic_params *custom_params;    // 自定义参数数组
    const struct pin_config_item *custom_conf_items;       // 自定义配置项数组
#endif
};

(1)const char *name: 引脚控制器的名称,用于标识引脚控制器的唯一性。

(2)const struct pinctrl_pin_desc *pins: 引脚描述符数组,是一个指向引脚描述符的指针,用于描述引脚的属性和配置。每个引脚描述符包含了引脚的名称、编号、模式等信息。

(3)unsigned int npins: 表示引脚描述符数组中元素的数量,用于确定引脚描述符数组的长度。

(4)const struct pinctrl_ops *pctlops: 指向引脚控制操作函数的指针,用于定义引脚控制器的操作接口。通过这些操作函数,可以对引脚进行配置、使能、禁用等操作。

(5)const struct pinmux_ops *pmxops: 指向引脚复用操作函数的指针,用于定义引脚的复用功能。复用功能允许将引脚的功能切换为不同的模式,以适应不同的设备需求。

(6)const struct pinconf_ops *confops: 指向引脚配置操作函数的指针,用于定义引脚的其他配置选项。这些配置选项可以包括引脚的上拉、下拉配置、电气特性等。

(7)struct module *owner: 指向拥有该引脚控制器结构体的模块的指针。这个字段用于跟踪引脚控制器结构体的所有者。

(8)unsigned int num_custom_params: 表示自定义配置参数的数量,用于描述引脚控制器的自定义配置参数。

(9)const struct pinconf_generic_params *custom_params: 指向自定义配置参数的指针,用于描述引脚控制器的自定义配置参数的属性。自定义配置参数可以根据具体需求定义,用于扩展引脚控制器的配置选项。

(10)const struct pin_config_item *custom_conf_items: 指向自定义配置项的指针,用于描述引脚控制器的自定义配置项的属性。自定义配置项可以根据具体需求定义,用于扩展引脚控制器的配置选项。

121.2 rockchip_pinctrl 结构体分析

而瑞芯微为了适应瑞芯微芯片的特定需求和功能,对struct pinctrl_desc进行了再一次封装。封装后的struct rockchip_pinctrl结构体在struct pinctrl_desc的基础上增加了与瑞芯微芯片相关的字段和指针,这种封装可以提供更好的集成性、易用性和扩展性,同时保持与通用引脚控制器框架的兼容性。

rockchip_pinctrl结构体定义在内核源码目录下的“/drivers/pinctrl/pinctrl-rockchip.h”文件中,具体内容如下所示:

struct rockchip_pinctrl {
    struct regmap           *regmap_base;        // 基本寄存器映射指针
    int                     reg_size;            // 寄存器大小
    struct regmap           *regmap_pull;        // 拉取寄存器映射指针
    struct regmap           *regmap_pmu;         // 电源管理单元寄存器映射指针
    struct device           *dev;                // 设备指针
    struct rockchip_pin_ctrl *ctrl;              // 瑞芯微芯片引脚控制器指针
    struct pinctrl_desc     pctl;                // 引脚控制器描述符
    struct pinctrl_dev      *pctl_dev;           // 引脚控制器设备指针
    struct rockchip_pin_group *groups;           // 瑞芯微芯片引脚组指针
    unsigned int            ngroups;             // 引脚组数量
    struct rockchip_pmx_func *functions;         // 瑞芯微芯片引脚功能指针
    unsigned int            nfunctions;          // 引脚功能数量
};

(1)struct regmap *regmap_base:指向基本寄存器映射(regmap)的指针。基本寄存器映射是一个用于访问芯片寄存器的接口,它提供了对芯片寄存器的读写操作。

(2)int reg_size:表示寄存器的字节大小,用于确定寄存器的地址范围。

(3)struct regmap *regmap_pull:指向拉取寄存器映射的指针。拉取寄存器映射用于控制引脚上的上拉和下拉功能。

(4)struct regmap *regmap_pmu:指向电源管理单元(PMU)寄存器映射的指针。PMU寄存器映射用于控制引脚的电源管理功能。

(5)struct device *dev:指向设备结构体的指针。设备结构体用于表示与硬件相关的设备,包括设备的物理地址、中断等信息。

(6)struct rockchip_pin_ctrl *ctrl:指向瑞芯微芯片引脚控制器的指针。这个结构体存储了瑞芯微芯片特定的引脚控制器的相关信息和操作。

(7)struct pinctrl_desc pctl:包含了struct pinctrl_desc结构体的一个实例。用于描述引脚控制器的属性和操作,包括引脚控制器的名称、引脚描述符数组、函数指针等。

(8)struct pinctrl_dev *pctl_dev:指向引脚控制器设备结构体的指针。引脚控制器设备结构体用于表示引脚控制器在系统中的设备实例,包含了与引脚控制器相关的设备信息和操作接口。

(9)struct rockchip_pin_group *groups:指向瑞芯微芯片引脚组的指针。引脚组是一组相关的引脚,可以一起进行配置和管理。

(10)unsigned int ngroups:表示引脚组数组的大小,用于确定引脚组数组的长度。

(11)struct rockchip_pmx_func *functions:指向瑞芯微芯片引脚功能的指针。引脚功能定义了引脚可以承担的不同功能,例如UART、SPI、I2C等。

(12)unsigned int nfunctions:引脚功能的数量。它表示引脚功能数组的大小,用于确定引脚功能数组的长度。

121.3 pinctrl probe函数分析

在上一章中已经找到了瑞芯微pinctrl驱动文件的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;
}

接下来对该函数进行详细的分析:

(1)第11-14行:检查设备结构体中的设备树节点是否存在,如果不存在则报错并返回错误码。

(2)第17-19行:使用devm_kzalloc函数分配一个rockchip_pinctrl结构体的内存,并将其初始化为0。然后将设备结构体指针赋值给info->dev,以便在后续代码中可以使用设备结构体的信息。

(3)第23-29行:调用rockchip_pinctrl_get_soc_data函数,根据设备信息获取与该设备相关的rockchip_pin_ctrl结构体。如果获取失败,则报错并返回错误码。将获取到的结构体指针赋值给info->ctrl。

(4)第32-69行使用of_parse_phandle函数解析设备树中名为"rockchip,grf"的节点。如果解析成功,则调用syscon_node_to_regmap函数将节点转换为寄存器映射的基地址,并将结果存储在info->regmap_base中。如果解析失败,则进入"else"分支。

在"else"分支中,通过platform_get_resource函数获取IORESOURCE_MEM类型的资源,以获取寄存器的基地址。然后使用devm_ioremap_resource函数将资源映射到内存中,并将结果存储在base中。接下来,配置寄存器映射的最大寄存器地址和名称,并使用devm_regmap_init_mmio函数初始化寄存器映射,将结果存储在info->regmap_base中。

如果控制器类型为RK3188且reg_size小于0x200,则获取第二个IORESOURCE_MEM类型的资源,作为pull寄存器的基地址。类似地,配置pull寄存器映射的最大寄存器地址和名称,并使用devm_regmap_init_mmio函数初始化pull寄存器映射,将结果存储在info->regmap_pull中。

(5)第72-77行:使用of_parse_phandle函数解析设备树中名为"rockchip,pmu"的可选节点。如果解析成功,则调用syscon_node_to_regmap函数将节点转换为寄存器映射的基地址,并将结果存储在info->regmap_pmu中。

(6)第80-84行:如果ctrl->soc_data_init不为空,则调用该函数指针所指向的函数,对特定的SoC进行特殊处理。处理完成后,如果返回值不为0,则返回该错误码。

(7)第87-89行:调用rockchip_pinctrl_register函数注册rockchip_pinctrl设备。如果注册失败,则返回错误码。

(8)第92行:使用platform_set_drvdata函数将info设置为pdev的私有数据。

(9)第95-100行:调用of_platform_populate函数注册GPIO设备。如果注册失败,则返回错误码。

在probe函数中需要特别注意的是第87行的rockchip_pinctrl_register注册rockchip_pinctrl设备函数,传入的参数分别为pdev和rockchip_pinctrl类型的info,然后跳转到rockchip_pinctrl_register函数的定义,该函数的具体内容如下所示:

static int rockchip_pinctrl_register(struct platform_device *pdev,
					struct rockchip_pinctrl *info)
{
	struct pinctrl_desc *ctrldesc = &info->pctl;
	struct pinctrl_pin_desc *pindesc, *pdesc;
	struct rockchip_pin_bank *pin_bank;
	int pin, bank, ret;
	int k;

	// 初始化pinctrl描述结构体
	ctrldesc->name = "rockchip-pinctrl";
	ctrldesc->owner = THIS_MODULE;
	ctrldesc->pctlops = &rockchip_pctrl_ops;
	ctrldesc->pmxops = &rockchip_pmx_ops;
	ctrldesc->confops = &rockchip_pinconf_ops;

	// 为每个引脚分配内存
	pindesc = devm_kcalloc(&pdev->dev,
			       info->ctrl->nr_pins, sizeof(*pindesc),
			       GFP_KERNEL);
	if (!pindesc)
		return -ENOMEM;

	ctrldesc->pins = pindesc;
	ctrldesc->npins = info->ctrl->nr_pins;

	pdesc = pindesc;
	// 遍历每个引脚所属的bank,为每个引脚设置编号和名称
	for (bank = 0, k = 0; bank < info->ctrl->nr_banks; bank++) {
		pin_bank = &info->ctrl->pin_banks[bank];
		for (pin = 0; pin < pin_bank->nr_pins; pin++, k++) {
			pdesc->number = k;
			pdesc->name = kasprintf(GFP_KERNEL, "%s-%d",
						pin_bank->name, pin);
			pdesc++;
		}
	}

	// 解析设备树中的pinctrl信息
	ret = rockchip_pinctrl_parse_dt(pdev, info);
	if (ret)
		return ret;

	// 注册pinctrl设备
	info->pctl_dev = devm_pinctrl_register(&pdev->dev, ctrldesc, info);
	if (IS_ERR(info->pctl_dev)) {
		dev_err(&pdev->dev, "could not register pinctrl driver\n");
		return PTR_ERR(info->pctl_dev);
	}

	return 0;
}

(1)第4行:将info的pctl参数传递给了struct pinctrl_desc *类型的变量ctrldesc,从而使得rockchip_pinctrl结构体和pinctrl_desc 结构体建立了联系。

(2)第11-15行,对pinctrl描述结构体进行初始化。这些结构体成员包括name(pinctrl控制器的名称),owner(拥有该pinctrl控制器的模块),pctlops(pinctrl控制操作函数),pmxops(pinctrl引脚复用操作函数)和confops(pinctrl引脚配置操作函数)。

(3)第18-22行,使用devm_kcalloc函数在设备的内存上分配一块连续的内存区域,用于存储引脚描述结构体。该函数分配的内存大小为info->ctrl->nr_pins * sizeof(*pindesc)字节。如果内存分配失败,则返回-ENOMEM错误码。

(4)第24-25行:通过将引脚描述结构体的指针pindesc赋值给pinctrl描述结构体的pins成员,将引脚数量info->ctrl->nr_pins赋值给pinctrl描述结构体的npins成员。

(5)第27-37行:通过遍历每个引脚所属的bank,为每个引脚设置编号和名称。首先,定义变量pdesc指向引脚描述结构体的起始地址。然后,使用两个嵌套的循环,外层循环遍历每个引脚所属的bank,内层循环遍历每个bank中的引脚。

在内层循环中,首先将当前引脚的编号k赋值给引脚描述结构体的number成员,然后使用kasprintf函数动态分配内存储引脚名称的字符串,并将该字符串赋值给引脚描述结构体的name成员。kasprintf函数在内核堆中分配内存,并格式化生成字符串。格式化的字符串为"%s-%d",其中pin_bank->name是当前bank的名称,pin是当前引脚在bank中的索引。

(6)第39-42行:调用rockchip_pinctrl_parse_dt函数来解析设备树中的pinctrl信息。该函数根据设备树中的描述,设置引脚的默认配置。

(7)第45-49行:调用devm_pinctrl_register函数注册pinctrl设备。该函数将pinctrl描述结构体、pinctrl相关操作函数和私有数据作为参数,将pinctrl设备注册到系统中。

其中需要注意第45行的devm_pinctrl_register函数,该函数定义在内核源码目录下的“drivers/pinctrl/core.c”文件中,该函数的具体内容如下所示:

struct pinctrl_dev *devm_pinctrl_register(struct device *dev,
					  struct pinctrl_desc *pctldesc,
					  void *driver_data)
{
	struct pinctrl_dev **ptr, *pctldev;

	// 分配用于存储pinctrl_dev指针的内存
	ptr = devres_alloc(devm_pinctrl_dev_release, sizeof(*ptr), GFP_KERNEL);
	if (!ptr)
		return ERR_PTR(-ENOMEM);

	// 注册pinctrl设备
	pctldev = pinctrl_register(pctldesc, dev, driver_data);
	if (IS_ERR(pctldev)) {
		devres_free(ptr);
		return pctldev;
	}

	// 将pinctrl_dev指针存储到devres中
	*ptr = pctldev;
	devres_add(dev, ptr);

	return pctldev;
}

这个函数用于注册pinctrl设备,并将其与设备关联起来。下面是对每个部分的详细解释:

(1)第8-10行:使用devres_alloc函数为存储pinctrl_dev指针的变量ptr分配内存。devres_alloc函数是用于管理设备资源的函数,它在设备的资源列表中分配内存。这里分配的内存大小为sizeof(*ptr)字节,即一个pinctrl_dev指针的大小。如果内存分配失败,则返回-ENOMEM错误码。

(2)第13-17行:调用pinctrl_register函数注册pinctrl设备。该函数将pinctrl_desc结构体、设备指针dev和驱动程序数据driver_data作为参数,并返回注册后的pinctrl_dev指针。如果注册失败(返回错误码),则释放之前分配的内存,并返回相应的错误码。

(3)第20-21行:将pinctrl_dev指针存储到ptr指向的内存位置。接下来,使用devres_add函数将ptr添加到设备的资源列表中。这样,在设备释放时,会自动释放之前分配的内存。

然后我们继续关注第13行的pinctrl设备注册函数pinctrl_register,然后跳转到他的定义,该函数的具体内容如下所示:

struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc,
				    struct device *dev, void *driver_data)
{
	struct pinctrl_dev *pctldev;
	int error;

	// 初始化pinctrl控制器
	pctldev = pinctrl_init_controller(pctldesc, dev, driver_data);
	if (IS_ERR(pctldev))
		return pctldev;

	// 启用pinctrl控制器
	error = pinctrl_enable(pctldev);
	if (error)
		return ERR_PTR(error);

	return pctldev;
}

这个函数用于注册并启用pinctrl设备。以下是对每个部分的详细解释:

(1)第8-10行:调用pinctrl_init_controller函数初始化pinctrl控制器。该函数接受pinctrl_desc结构体、设备指针dev和驱动程序数据driver_data作为参数,并返回一个指向已初始化的pinctrl_dev结构体的指针。

(2)第13-15行:调用pinctrl_enable函数启用pinctrl控制器。该函数接受一个pinctrl_dev结构体指针作为参数,并返回一个代表错误码的整数值。

至此,关于rk3568的pinctrl probe函数的分析就完成了,这个时候可能大家还是感觉乱乱的,大家不要着急,会在下面的章节中继续填充pinctrl子系统的框架。

你可能感兴趣的:(RK3568驱动开发指南,#,RK3568驱动指南,第十一期,linux,驱动开发)