概述pinctrl子系统工作原理

文章详细介绍了Linux内核如何通过pinctrl子系统来管理和配置GPIO引脚的复用功能和电气属性,特别是通过设备树来定义I2C、SPI等外设的pin配置信息,并展示了相关驱动如何解析和应用这些信息。pinctrl子系统简化了不同平台上的硬件配置,降低了开发难度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        使用过单片机的同学都知道,要用一个gpio点亮一个小灯可以分为两步,一是设置“配置寄存器“来配置cpu引脚的复用功能,电气属性(上下拉,速度,驱动能力等),输入输出;二是设置“数据寄存器”的值为0/1,就可以使对应的引脚输出高低电平,让小灯亮灭。

        那么linux内核对于不同平台提供了通用的pinctrl子系统,其主要的工作是:获取设备树的pin信息,来配置pin的复用功能和电气属性。

        那么比如i2c外设的scl和sda对应的pin的复用功能和电气属性等信息,就会放到这个pinctrl节点里面,下面这个的信息就可以解析为:gpio1的15和16(指定配的是那些具体的pin脚);复用功能为FUNC2功能;且没有上下拉,

i2c0 {
			i2c0_xfer: i2c0-xfer {
				rockchip,pins =
					<1 15 RK_FUNC_2 &pcfg_pull_none>,
					<1 16 RK_FUNC_2 &pcfg_pull_none>;
			};
		};

对用的i2c0节点就会引用这个信息 ,在i2c0的控制器驱动就会根据pinctrl-name来选用pin的配置信息,有的不仅有defalut,也会有sleep来供特殊情况来配置,驱动通过pinctrl_lookup_state和pinctrl_select_state来选择用那个复用信息

i2c0: i2c@ff3c0000 {
		compatible = "rockchip,rk3399-i2c";
		reg = <0x0 0xff3c0000 0x0 0x1000>;
		clocks =  <&pmucru SCLK_I2C0_PMU>, <&pmucru PCLK_I2C0_PMU>;
		clock-names = "i2c", "pclk";
		interrupts = <GIC_SPI 57 IRQ_TYPE_LEVEL_HIGH 0>;
		pinctrl-names = "default";
		pinctrl-0 = <&i2c0_xfer>;
		#address-cells = <1>;
		#size-cells = <0>;
		status = "disabled";
	};

在内核目录driver/pinctrl就是这个子系统跑起来的关键文件。先看一下设备树中的pinctrl节点,根据兼容属性可以找到对应的驱动,其驱动就会去做上面提到的工作,下面也可以看到gpio节点也放进来了,有些平台gpio节点是作为独立节点的,大差不差的

pinctrl: pinctrl {
		compatible = "rockchip,rk3399-pinctrl";
		rockchip,grf = <&grf>;
		rockchip,pmu = <&pmugrf>;
		#address-cells = <2>;
		#size-cells = <2>;
		ranges;
        
        gpio0: gpio0@ff720000 {
			compatible = "rockchip,gpio-bank";
			reg = <0x0 0xff720000 0x0 0x100>;
			clocks = <&pmucru PCLK_GPIO0_PMU>;
			interrupts = <GIC_SPI 14 IRQ_TYPE_LEVEL_HIGH 0>;

			gpio-controller;
			#gpio-cells = <0x2>;

			interrupt-controller;
			#interrupt-cells = <0x2>;
		};

		gpio1: gpio1@ff730000 {
			compatible = "rockchip,gpio-bank";
			reg = <0x0 0xff730000 0x0 0x100>;
			clocks = <&pmucru PCLK_GPIO1_PMU>;
			interrupts = <GIC_SPI 15 IRQ_TYPE_LEVEL_HIGH 0>;

			gpio-controller;
			#gpio-cells = <0x2>;

			interrupt-controller;
			#interrupt-cells = <0x2>;
		};

		gpio2: gpio2@ff780000 {
			compatible = "rockchip,gpio-bank";
			reg = <0x0 0xff780000 0x0 0x100>;
			clocks = <&cru PCLK_GPIO2>;
			interrupts = <GIC_SPI 16 IRQ_TYPE_LEVEL_HIGH 0>;

			gpio-controller;
			#gpio-cells = <0x2>;

			interrupt-controller;
			#interrupt-cells = <0x2>;
		};

		gpio3: gpio3@ff788000 {
			compatible = "rockchip,gpio-bank";
			reg = <0x0 0xff788000 0x0 0x100>;
			clocks = <&cru PCLK_GPIO3>;
			interrupts = <GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH 0>;

			gpio-controller;
			#gpio-cells = <0x2>;

			interrupt-controller;
			#interrupt-cells = <0x2>;
		};

		gpio4: gpio4@ff790000 {
			compatible = "rockchip,gpio-bank";
			reg = <0x0 0xff790000 0x0 0x100>;
			clocks = <&cru PCLK_GPIO4>;
			interrupts = <GIC_SPI 18 IRQ_TYPE_LEVEL_HIGH 0>;

			gpio-controller;
			#gpio-cells = <0x2>;

			interrupt-controller;
			#interrupt-cells = <0x2>;
		};
}

驱动文件通过兼容属性就可以找到,然后看probe函数,主要都是获取设备的信息;然后注册pinctrl,最终注册的pinctrl_des结构的ctrldesc实例的三个ops成员就是半导体厂商实现的对pin的配置

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;

	ctrldesc->name = "rockchip-pinctrl";
	ctrldesc->owner = THIS_MODULE;
	ctrldesc->pctlops = &rockchip_pctrl_ops;//对应那个gpio组,多少号
	ctrldesc->pmxops = &rockchip_pmx_ops;//对应复用功能设置
	ctrldesc->confops = &rockchip_pinconf_ops;//对应电气属性设置

	pindesc = devm_kzalloc(&pdev->dev, sizeof(*pindesc) *
			info->ctrl->nr_pins, GFP_KERNEL);
	if (!pindesc)
		return -ENOMEM;

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

	pdesc = pindesc;
	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++;
		}
	}

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

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

	for (bank = 0; bank < info->ctrl->nr_banks; ++bank) {
		pin_bank = &info->ctrl->pin_banks[bank];
		pin_bank->grange.name = pin_bank->name;
		pin_bank->grange.id = bank;
		pin_bank->grange.pin_base = pin_bank->pin_base;
		pin_bank->grange.base = pin_bank->gpio_chip.base;
		pin_bank->grange.npins = pin_bank->gpio_chip.ngpio;
		pin_bank->grange.gc = &pin_bank->gpio_chip;
		pinctrl_add_gpio_range(info->pctl_dev, &pin_bank->grange);
	}

	return 0;
}

rockchip_pinctrl_parse_dt调用rockchip_pinctrl_parse_functions来解析上面pinctrl节点里面的诸如spi0,i2c0等所有子节点(虽然gpio的bank也是其子节点,但是被下面的代码排除了)

static int rockchip_pinctrl_parse_dt(struct platform_device *pdev,
					      struct rockchip_pinctrl *info)
{
	struct device *dev = &pdev->dev;
	struct device_node *np = dev->of_node;
	struct device_node *child;
	int ret;
	int i;

	rockchip_pinctrl_child_count(info, np);

	dev_dbg(&pdev->dev, "nfunctions = %d\n", info->nfunctions);
	dev_dbg(&pdev->dev, "ngroups = %d\n", info->ngroups);

	info->functions = devm_kzalloc(dev, info->nfunctions *
					      sizeof(struct rockchip_pmx_func),
					      GFP_KERNEL);
	if (!info->functions)
		return -EINVAL;

	info->groups = devm_kzalloc(dev, info->ngroups *
					    sizeof(struct rockchip_pin_group),
					    GFP_KERNEL);
	if (!info->groups)
		return -EINVAL;

	i = 0;

	for_each_child_of_node(np, child) {
		if (of_match_node(rockchip_bank_match, child))
			continue;

		ret = rockchip_pinctrl_parse_functions(child, info, i++);
		if (ret) {
			dev_err(&pdev->dev, "failed to parse function\n");
			of_node_put(child);
			return ret;
		}
	}

	return 0;
}

从下面代码也可以看出来,对于功能和组的定义是什么?功能就是pinctrl节点去除bank节点的其他子节点,nfunctions就是pinctrl所有子节点的组合;组就是子节点的子节点,ngroups就是pinctrl某个子节点的所有子节点的组合

static void rockchip_pinctrl_child_count(struct rockchip_pinctrl *info,
						struct device_node *np)
{
	struct device_node *child;

	for_each_child_of_node(np, child) {
		if (of_match_node(rockchip_bank_match, child))
			continue;

		info->nfunctions++;
		info->ngroups += of_get_child_count(child);
	}
}

 rockchip_pinctrl_parse_functions就是解析pinctrl的单个功能节点,比如spi0功能节点的每个组

static int rockchip_pinctrl_parse_functions(struct device_node *np,
						struct rockchip_pinctrl *info,
						u32 index)
{
	struct device_node *child;
	struct rockchip_pmx_func *func;
	struct rockchip_pin_group *grp;
	int ret;
	static u32 grp_index;
	u32 i = 0;

	dev_dbg(info->dev, "parse function(%d): %s\n", index, np->name);

	func = &info->functions[index];

	/* Initialise function */
	func->name = np->name;
	func->ngroups = of_get_child_count(np);
	if (func->ngroups <= 0)
		return 0;

	func->groups = devm_kzalloc(info->dev,
			func->ngroups * sizeof(char *), GFP_KERNEL);
	if (!func->groups)
		return -ENOMEM;

	for_each_child_of_node(np, child) {
		func->groups[i] = child->name;
		grp = &info->groups[grp_index++];
		ret = rockchip_pinctrl_parse_groups(child, grp, info, i++);
		if (ret) {
			of_node_put(child);
			return ret;
		}
	}

	return 0;
}

rockchip_pinctrl_parse_groups解析spi0节点下面的单个”rockchip,pins“组里面pin的配置信息(既然是pins,那么里面的pin就会是单个或多个);从下面看spi0这个功能节点有5个组,这个函数也就需要执行5次来解析

.....................
        spi0 {
			spi0_clk: spi0-clk {
				rockchip,pins =
					<3 6 RK_FUNC_2 &pcfg_pull_up>;
			};
			spi0_cs0: spi0-cs0 {
				rockchip,pins =
					<3 7 RK_FUNC_2 &pcfg_pull_up>;
			};
			spi0_cs1: spi0-cs1 {
				rockchip,pins =
					<3 8 RK_FUNC_2 &pcfg_pull_up>;
			};
			spi0_tx: spi0-tx {
				rockchip,pins =
					<3 5 RK_FUNC_2 &pcfg_pull_up>;
			};
			spi0_rx: spi0-rx {
				rockchip,pins =
					<3 4 RK_FUNC_2 &pcfg_pull_up>;
			};
		};
      .....................

组的定义在下面是<bank pin mux CONFIG>,跟最上面的<1 15 RK_FUNC_2 &pcfg_pull_none>完美对应

static int rockchip_pinctrl_parse_groups(struct device_node *np,
					      struct rockchip_pin_group *grp,
					      struct rockchip_pinctrl *info,
					      u32 index)
{
	struct rockchip_pin_bank *bank;
	int size;
	const __be32 *list;
	int num;
	int i, j;
	int ret;

	dev_dbg(info->dev, "group(%d): %s\n", index, np->name);

	/* Initialise group */
	grp->name = np->name;

	/*
	 * the binding format is rockchip,pins = <bank pin mux CONFIG>,
	 * do sanity check and calculate pins number
	 */
	list = of_get_property(np, "rockchip,pins", &size);
	/* we do not check return since it's safe node passed down */
	size /= sizeof(*list);
	if (!size || size % 4) {
		dev_err(info->dev, "wrong pins number or pins and configs should be by 4\n");
		return -EINVAL;
	}

	grp->npins = size / 4;

	grp->pins = devm_kzalloc(info->dev, grp->npins * sizeof(unsigned int),
						GFP_KERNEL);
	grp->data = devm_kzalloc(info->dev, grp->npins *
					  sizeof(struct rockchip_pin_config),
					GFP_KERNEL);
	if (!grp->pins || !grp->data)
		return -ENOMEM;

	for (i = 0, j = 0; i < size; i += 4, j++) {
		const __be32 *phandle;
		struct device_node *np_config;

		num = be32_to_cpu(*list++);
		bank = bank_num_to_bank(info, num);
		if (IS_ERR(bank))
			return PTR_ERR(bank);

		grp->pins[j] = bank->pin_base + be32_to_cpu(*list++);
		grp->data[j].func = be32_to_cpu(*list++);

		phandle = list++;
		if (!phandle)
			return -EINVAL;

		np_config = of_find_node_by_phandle(be32_to_cpup(phandle));
		ret = pinconf_generic_parse_dt_config(np_config, NULL,
				&grp->data[j].configs, &grp->data[j].nconfigs);
		if (ret)
			return ret;
	}

	return 0;
}

前面提到的pinctrl_select_state就会使用pinctrl驱动用devm_pinctrl_register注册时,放进去的那三个ops(下面的代码前几行就可以看出)来真正的配置pin的复用功能和电气属性;pinctrl_select_state会调用pinctrl_commit_state,pinctrl_commit_state会调用pinmux_enable_setting 来设置复用功能

int pinmux_enable_setting(struct pinctrl_setting const *setting)
{
	struct pinctrl_dev *pctldev = setting->pctldev;
	const struct pinctrl_ops *pctlops = pctldev->desc->pctlops;
	const struct pinmux_ops *ops = pctldev->desc->pmxops;
	int ret = 0;
	const unsigned *pins = NULL;
	unsigned num_pins = 0;
	int i;
	struct pin_desc *desc;

	if (pctlops->get_group_pins)
		ret = pctlops->get_group_pins(pctldev, setting->data.mux.group,
					      &pins, &num_pins);

	if (ret) {
		const char *gname;

		/* errors only affect debug data, so just warn */
		gname = pctlops->get_group_name(pctldev,
						setting->data.mux.group);
		dev_warn(pctldev->dev,
			 "could not get pins for group %s\n",
			 gname);
		num_pins = 0;
	}

	/* Try to allocate all pins in this group, one by one */
	for (i = 0; i < num_pins; i++) {
		ret = pin_request(pctldev, pins[i], setting->dev_name, NULL);
		if (ret) {
			const char *gname;
			const char *pname;

			desc = pin_desc_get(pctldev, pins[i]);
			pname = desc ? desc->name : "non-existing";
			gname = pctlops->get_group_name(pctldev,
						setting->data.mux.group);
			dev_err(pctldev->dev,
				"could not request pin %d (%s) from group %s "
				" on device %s\n",
				pins[i], pname, gname,
				pinctrl_dev_get_name(pctldev));
			goto err_pin_request;
		}
	}

	/* Now that we have acquired the pins, encode the mux setting */
	for (i = 0; i < num_pins; i++) {
		desc = pin_desc_get(pctldev, pins[i]);
		if (desc == NULL) {
			dev_warn(pctldev->dev,
				 "could not get pin desc for pin %d\n",
				 pins[i]);
			continue;
		}
		desc->mux_setting = &(setting->data.mux);
	}

	ret = ops->set_mux(pctldev, setting->data.mux.func,
			   setting->data.mux.group);

	if (ret)
		goto err_set_mux;

	return 0;

err_set_mux:
	for (i = 0; i < num_pins; i++) {
		desc = pin_desc_get(pctldev, pins[i]);
		if (desc)
			desc->mux_setting = NULL;
	}
err_pin_request:
	/* On error release all taken pins */
	while (--i >= 0)
		pin_free(pctldev, pins[i], NULL);

	return ret;
}

也会调用pinconf_apply_setting来设置电气属性,至此完成了单片的两三行完成的工作,但也大大降低了开发人员对不同平台的使用门槛

int pinconf_apply_setting(struct pinctrl_setting const *setting)
{
	struct pinctrl_dev *pctldev = setting->pctldev;
	const struct pinconf_ops *ops = pctldev->desc->confops;
	int ret;

	if (!ops) {
		dev_err(pctldev->dev, "missing confops\n");
		return -EINVAL;
	}

	switch (setting->type) {
	case PIN_MAP_TYPE_CONFIGS_PIN:
		if (!ops->pin_config_set) {
			dev_err(pctldev->dev, "missing pin_config_set op\n");
			return -EINVAL;
		}
		ret = ops->pin_config_set(pctldev,
				setting->data.configs.group_or_pin,
				setting->data.configs.configs,
				setting->data.configs.num_configs);
		if (ret < 0) {
			dev_err(pctldev->dev,
				"pin_config_set op failed for pin %d\n",
				setting->data.configs.group_or_pin);
			return ret;
		}
		break;
	case PIN_MAP_TYPE_CONFIGS_GROUP:
		if (!ops->pin_config_group_set) {
			dev_err(pctldev->dev,
				"missing pin_config_group_set op\n");
			return -EINVAL;
		}
		ret = ops->pin_config_group_set(pctldev,
				setting->data.configs.group_or_pin,
				setting->data.configs.configs,
				setting->data.configs.num_configs);
		if (ret < 0) {
			dev_err(pctldev->dev,
				"pin_config_group_set op failed for group %d\n",
				setting->data.configs.group_or_pin);
			return ret;
		}
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值