【uboot】uboot 2020.04 Pinctrl子系统分析和使用

相关文章

1.《【uboot】imx6ull uboot 2020.04源码下载和编译环境配置》
2.《【uboot】uboot 2020.04 DM驱动模式 – Demo体验》
3.《【uboot】uboot 2020.04 DM驱动模式 – 架构分析》

1. 前言

在许多soc内部都包含有pin控制器,通过pin控制器的寄存器,我们可以配置一个或者一组引脚的功能和特性。uboot提供一个类似linux的pinctrl子系统,目的是为了统一各soc厂商的pin脚管理。

2. Pinctrl子系统的功能简介

我们可以通过pinctrl子系统来设置引脚的复用、配置,可以将IO复用成GPIO、I2C等其它功能。图如下:
在这里插入图片描述

  • 管理系统中所有的可以控制的pin,在系统初始化的时候,枚举所有可以控制的pin,并标识这些pin。
  • 管理这些pin的复用(Multiplexing)。对于SOC而言,其引脚除了配置成普通的GPIO之外,若干个引脚还可以组成一个pin group,行程特定的功能。pin control subsystem要管理所有的pin group。
  • 配置这些pin的特性。例如使能或关闭引脚上的pull-up、pull-down电阻,配置引脚的driver strength。

3. Pinctrl Uclass创建流程

下面是pinctrl uclass创建和绑定的过程如下:

[root.c]dm_extended_scan_fdt(gd->fdt_blob, false); // 在设备树中搜索设备并进行驱动匹配,然后bind。
	[root.c]dm_scan_fdt(gd->fdt_blob, false); // 扫描设备树并绑定驱动程序
		[root.c]dm_scan_fdt_node(gd->dm_root, gd->fdt_blob, 0, false); // 扫描设备树并绑定一个节点的驱动程序
			[lists.c]lists_bind_fdt(gd->dm_root, offset_to_ofnode(0), NULL, false); // 绑定设备树节点,它会给绑定的设备树节点创建一个新udevice,并使用@parent作为其父设备。
				[device.c]device_bind_with_driver_data(parent, entry, name, id->data, node, &dev); // 创建一个设备并且绑定到driver。
					[device.c]device_bind_common(parent, drv, name, NULL, driver_data, node, 0, devp); // 创建一个设备并且绑定到driver。
						[uclass.c]uclass_get(drv->id, &uc); // 根据ID获取一个uclass,如果它不存在就创建它。
							[uclass.c]uclass_find(); // 根据id找到对应的uclass。
							[uclass.c]uclass_add(); //在未找到的情况下,就会在列表中创建一个新的uclass。
						[device.c]calloc(1, size); // 1:创建一个新的udevice并初始化; 2:给dev->platdata、dev->uclass_platdata、dev->parent_platdata分配空间
						[device.c]list_add_tail(&dev->sibling_node, &parent->child_head); // 将新dev放到父节点的列表中
						[uclass.c]uclass_bind_device(dev);// 将udevice与uclass进行绑定,设备连接到uclass的设备列表中。
						[U_BOOT_DRIVER]drv->bind(dev); // device绑定成功后,就会调用drv->bind。
						[U_BOOT_DRIVER]parent->driver->child_post_bind(dev); //在一个新的child被绑定后,就会调用parent driver的child_post_bind(dev); 
						[UCLASS_DRIVER]uc->uc_drv->post_bind(dev); // 在一个新设备绑定到这个uclass后被调用

经过以上的流程,它会将pinctrluclass、uclass_driver、udevice、driver进行绑定,绑定后的关系如下图所示:
在这里插入图片描述

4. Pinconfig Uclass创建流程

pinctrl设备绑定后,调用pinctrl uclass driver的post_bind方法,来遍历pinctrl的子节点,并创建的了pinconfig uclass。具体的流程如下:

[UCLASS_DRIVER]uc->uc_drv->post_bind(dev); // 在一个新设备绑定到这个uclass后被调用							
	[pinctrl-uclass.c]pinctrl_post_bind(dev); // PINCTRL uclass正在post binding
		[pinctrl-uclass.c]pinconfig_post_bind(dev); // dev = pinctrl(iomuxc@20e0000) 绑定它的子节点,以便在以后的dt解析期间外设设备可以轻松地在父设备中搜索。
			[list.c]device_bind_driver_to_node(dev, "pinconfig", "i2c1grp", node, NULL); // 这将一个新设备绑定到一个给定设备树节点的驱动程序,只有在节点缺少"compatible"字符串时才需要这样做。
				[device.c]device_bind_with_driver_data(parent, entry, name, id->data, node, &dev); // 创建一个设备并且绑定到driver。
					[device.c]device_bind_common(parent, drv, name, NULL, driver_data, node, 0, devp); // 创建一个设备并且绑定到driver。
						[uclass.c]uclass_get(drv->id, &uc); // 根据ID获取一个uclass,如果它不存在就创建它。
							[uclass.c]uclass_find(); // 根据id找到对应的uclass。
							[uclass.c]uclass_add(); //在未找到的情况下,就会在列表中创建一个新的uclass。
						[device.c]calloc(1, size); // 1:创建一个新的udevice并初始化; 2:给dev->platdata、dev->uclass_platdata、dev->parent_platdata分配空间
						[device.c]list_add_tail(&dev->sibling_node, &parent->child_head); // 将新dev放到父节点的列表中
						[uclass.c]uclass_bind_device(dev);// 将udevice与uclass进行绑定,设备连接到uclass的设备列表中。
						[U_BOOT_DRIVER]drv->bind(dev); // device绑定成功后,就会调用drv->bind。
						[U_BOOT_DRIVER]parent->driver->child_post_bind(dev); //在一个新的child被绑定后,就会调用parent driver的child_post_bind(dev); 
						[UCLASS_DRIVER]uc->uc_drv->post_bind(dev); // 在一个新设备绑定到这个uclass后被调用

经过上面的流程的后,会将pinconfig的所有子节点绑定到parent pinctrl节点上,并且创建了pinconfig uclass。它们之间的联系如下:
在这里插入图片描述
在uboot的命令行,我们可以输入dm tree来打印它们之间的关系。通过DM Tree我们可以很直观的看到pinctrlpinconfig uclass它们之间的关系,具体如下:
在这里插入图片描述

5. I2C使用pinctrl功能

下面通过I2C使用pinctrl功能来举例,基本上分为三个步骤:

  1. Device Tree定义了pinctrl I2C相关定义。
  2. 根据Device Tree节点信息创建pinctrl uclass设备链表。
  3. 当申请I2C总线时,会先probe设备激活设备,这样就会使用pinctrl里面pinconfig信息初始化IO。

涉及到的代码流程如下:

[uclass.c]uclass_get_device_by_seq(UCLASS_I2C, i, &bus); // 通过序列号ID获取UCLASS_I2C总线device
	[uclass.c]uclass_find_device_by_seq(id, seq, false, &dev); // 通过序列号ID获取UCLASS_I2C总线device
	[uclass.c]uclass_get_device_tail(dev, ret, devp); // 判断是否有错误码,然后调用device_probe探测一个设备并激活它
		[device.c]device_probe(dev); // 探测一个设备并激活它
			[pinctrl_uclass.c]pinctrl_select_state(dev, "default");// 设置I2C IO为default状态,default为I2C功能。
				[uclass.c]uclass_get_device_by_phandle_id(UCLASS_PINCONFIG, phandle, &config); // 通过phandle id获取I2C pinctrl的配置设备 
				[uclass.c]pinctrl_config_one(config); // 将从device tree获取到的I2C config数据,通过pinctrl driver的set_state方法设置到相关的寄存器上。

I2C使用pinctrl功能简单的功能框图如下:
在这里插入图片描述
Device Tree中I2C定义的信息如下:

&i2c1 {
	clock-frequency = <100000>;
	pinctrl-names = "default", "gpio";
	pinctrl-0 = <&pinctrl_i2c1>;
	pinctrl-1 = <&pinctrl_i2c1_gpio>;
	scl-gpios = <&gpio1 28 GPIO_ACTIVE_HIGH>;
	sda-gpios = <&gpio1 29 GPIO_ACTIVE_HIGH>;
	status = "okay";

	mag3110@e {
		compatible = "fsl,mag3110";
		reg = <0x0e>;
	};
};
 
pinctrl_i2c1: i2c1grp {
	fsl,pins = <
		MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
		MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
	>;
};

pinctrl_i2c1_gpio: i2c1grp_gpio {
	fsl,pins = <
		MX6UL_PAD_UART4_TX_DATA__GPIO1_IO28 0x1b8b0
		MX6UL_PAD_UART4_RX_DATA__GPIO1_IO29 0x1b8b0
	>;
}; 

6. Device Tree中phandle的使用

在Device Tree中解析时会用到phandle属性,我们需要了解一下它的使用。

Property name: phandle
Value type: <u32>
Description:
phandle属性为设备树中唯一的节点指定一个数字标识符。phandle属性值是被其它node使用,其它node通过phandle属性值与这个node进行关联。

Example:
参见以下devicetree引用:

pic@10000000 {
	phandle = <1>;
	interrupt-controller;
};

pic定义了一个phandle值为1,另一个设备节点可以用phandle值为1来引用pic节点:

another-device-node {
	interrupt-parent = <1>;
};

注意:DTS中的大多数设备树不会包含显式的phandle属性。当DTS被编译成二进制DTB格式时,DTC工具会自动插入phandle属性。

8. 相关函数的使用

下面会具体分析所使用到的函数。

8.1 pinconfig_post_bind()函数分析

UCLASS_DRIVER(pinctrl) = {
	.id = UCLASS_PINCTRL,
	.post_bind = pinctrl_post_bind,
	.flags = DM_UC_FLAG_SEQ_ALIAS,
	.name = "pinctrl",
};

/**
 * pinctrl_post_bind() - PINCTRL uclass正在post binding
 * 
 * 在full pinctl的情况下,递归地将子节点pinconfig设备进行绑定。
 */
static int __maybe_unused pinctrl_post_bind(struct udevice *dev)
{
	const struct pinctrl_ops *ops = pinctrl_get_ops(dev); // 获取pin control操作指针

	 /**
	  * 如果设置了set_state回调,我们假设这个pinctrl驱动程序就是完整的实现。
	  * 在这种情况下,应该绑定它的子节点,以便在以后的dt解析期间外设设备可以轻松地在父设备中搜索。
	  */
	if (ops->set_state)
		return pinconfig_post_bind(dev);

	return 0;
}

/**
 * pinconfig_post_bind() - PINCONFIG uclass正在post binding
 * 递归地将其子设备pinconfig进行绑定。
 *
 pinctrl       0  [ + ]   imx6-pinctrl          |   |   `-- iomuxc@20e0000
 pinconfig     0  [   ]   pinconfig             |   |       |-- csi1grp
 pinconfig     1  [ + ]   pinconfig             |   |       |-- enet1grp
 pinconfig     2  [ + ]   pinconfig             |   |       |-- enet2grp
 pinconfig     3  [   ]   pinconfig             |   |       |-- flexcan1grp
 pinconfig     4  [   ]   pinconfig             |   |       |-- flexcan2grp
 pinconfig     5  [   ]   pinconfig             |   |       |-- i2c1grp

 */
static int pinconfig_post_bind(struct udevice *dev)
{
	bool pre_reloc_only = !(gd->flags & GD_FLG_RELOC);
	const char *name;
	ofnode node;
	int ret;


	// 遍历该设备pinctrl的子节点pinconfig
	dev_for_each_subnode(node, dev) {
		if (pre_reloc_only &&
		    !ofnode_pre_reloc(node))
			continue;
			
		// 如果该设备包含"compatible"属性,那么这个节点不是一个pin config节点。但它是一个普通device,所以这里跳过。
		ofnode_get_property(node, "compatible", &ret);
		if (ret >= 0)
			continue;
		// 如果该节点的属性包含"gpio-controller",则跳过。
		if (ofnode_read_bool(node, "gpio-controller"))
			continue;
		// 如果不是FDT_ERR_NOTFOUND,则它不是一个pin config节点,直接返回。
		if (ret != -FDT_ERR_NOTFOUND)
			return ret;

		// 获取该节点的名称
		name = ofnode_get_name(node);

		ret = device_bind_driver_to_node(dev, "pinconfig", name, node, NULL);
	}

	return 0;
}

8.2 device_bind_driver_to_node()函数分析

/**
 * device_bind_driver_to_node() - 将节点设备绑定到driver
 *
 * 这将一个新设备绑定到一个给定设备树节点的驱动程序,只有在节点缺少"compatible"字符串时才需要这样做。
 */
int device_bind_driver_to_node(struct udevice *parent, const char *drv_name,
			       const char *dev_name, ofnode node,
			       struct udevice **devp)
{
	struct driver *drv;
	int ret;

	// 根据driver name查找对应的驱动程序。
	drv = lists_driver_lookup_name(drv_name); // drv_name = "pinconfig"

	/**
	 * device_bind_with_driver_data() - 创建一个设备并且绑定到driver。
	 * 设置一个新device连接到driver,在这种情况下,驱动通过提供driver_data的匹配表与设备匹配。
	 * 一旦绑定,设备就存在,但在调用 device_probe() 之前尚未激活。
	 */
	ret = device_bind_with_driver_data(parent, drv, dev_name, 0 /* data */, node, devp); // parent = pinctrl, drv = pinconf_drv, dev_name= "i2c1grp"、"csi1grp"...

	return ret;
}


/**
 * device_bind_with_driver_data() - 创建一个设备并且绑定到driver。
 * 设置一个新device连接到driver,在这种情况下,驱动通过提供driver_data的匹配表与设备匹配。
 * 一旦绑定,设备就存在,但在调用 device_probe() 之前尚未激活。
 */
int device_bind_with_driver_data(struct udevice *parent,
				 const struct driver *drv, const char *name,
				 ulong driver_data, ofnode node,
				 struct udevice **devp)
{
	return device_bind_common(parent, drv, name, NULL, driver_data, node,
				  0, devp);
}

8.3 pinctrl_select_state_full()函数分析

/**
 * pinctrl_select_state_full() - 真正实现了pinctrl_select_state的功能,将设备设置为给定的状态
 */
static int pinctrl_select_state_full(struct udevice *dev, const char *statename) // statename: "default"
{
	char propname[32]; /* long enough */
	const fdt32_t *list;
	uint32_t phandle;
	struct udevice *config;
	int state, size, i, ret;

	/**
	 * pinctrl-names = "default", "gpio";
	 * statename = "default";
	 * 通过dev_read_stringlist_search查找"default"的index是0,所以这里state = 0。
	 */
	state = dev_read_stringlist_search(dev, "pinctrl-names", statename);  // state = 0


	/**
	 * 因为state = 0,所以这里propname = pinctrl-0。
	 * dev_read_prop函数功能是从设备节点读取一个属性,它会获取到pinctrl-0的属性value。
	 * list是指向属性的指针,如果没有找到,则为NULL。 list = &pinctrl_i2c1;
	 */
	snprintf(propname, sizeof(propname), "pinctrl-%d", state);
	list = dev_read_prop(dev, propname, &size);


	size /= sizeof(*list);
	for (i = 0; i < size; i++) {
		phandle = fdt32_to_cpu(*list++);
		/**
		 * uclass_get_device_by_phandle_id() - 通过phandle id获取一个uclass设备
		 */
		ret = uclass_get_device_by_phandle_id(UCLASS_PINCONFIG, phandle,
						      &config);

		ret = pinctrl_config_one(config);

	}

	return 0;
}

/**
 * pinctrl_select_state() - 将设备设置为给定的状态
 *
 */
int pinctrl_select_state(struct udevice *dev, const char *statename)
{

	if (!ofnode_valid(dev->node))
		return 0;

	if (pinctrl_select_state_full(dev, statename))
		return pinctrl_select_state_simple(dev);

	return 0;
}

8.4 uclass_get_device_by_phandle_id()函数分析

/**
 * uclass_get_device_by_phandle_id() - 通过phandle id获取一个uclass设备
 *
 * 这将在uclass中的设备中搜索具有给定phandle id的设备。
 *
 * DTS中的大多数设备树不会包含显式的phandle属性。当DTS被编译成二进制DTB格式时,DTC工具会自动插入phandle属性。
 *
 * uclass_get_device_by_phandle_id(UCLASS_PINCONFIG, phandle, &config);
 */
int uclass_get_device_by_phandle_id(enum uclass_id id, uint phandle_id,
				    struct udevice **devp)
{
	struct udevice *dev;
	struct uclass *uc;
	int ret;

	ret = uclass_get(id, &uc); // 获取UCLASS_PINCONFIG

	/**
		遍历UCLASS_PINCONFIG下所有的设备,并且匹配它的phandle id。
		 pinctrl       0  [ + ]   imx6-pinctrl          |   |   `-- iomuxc@20e0000
		 pinconfig     0  [   ]   pinconfig             |   |       |-- csi1grp
		 pinconfig     1  [ + ]   pinconfig             |   |       |-- enet1grp
		 pinconfig     2  [ + ]   pinconfig             |   |       |-- enet2grp
		 pinconfig     3  [   ]   pinconfig             |   |       |-- flexcan1grp
		 pinconfig     4  [   ]   pinconfig             |   |       |-- flexcan2grp
		 pinconfig     5  [   ]   pinconfig             |   |       |-- i2c1grp
	*/
	uclass_foreach_dev(dev, uc) {
		uint phandle;

		phandle = dev_read_phandle(dev); // 获取当前设备在device tree node的phandle id
		// 比较phandle id,是否和目标的一致。
		if (phandle == phandle_id) {
			*devp = dev;
					// 判断是否有错误码,然后调用device_probe探测一个设备并激活它
			return uclass_get_device_tail(dev, ret, devp);
		}
	}

	return -ENODEV;
}

8.5 uclass_get_device_tail()函数分析

/**
 * uclass_get_device_tail() - 判断是否有错误码,然后调用device_probe探测一个设备并激活它
 */
int uclass_get_device_tail(struct udevice *dev, int ret, struct udevice **devp)
{
	if (ret)
		return ret;

	// 探测一个设备并激活它
	ret = device_probe(dev);
	if (ret)
		return ret;

	*devp = dev;

	return 0;
}

8.6 device_probe()函数分析

/**
 * device_probe() - 探测一个设备并激活它
 *
 * 激活一个设备以便它可以随时使用。首先探查它的所有父节点。
 */
int device_probe(struct udevice *dev)
{
	const struct driver *drv;
	int ret;
	int seq;

	// 检测该device是否已经激活,已激活就直接返回。
	if (dev->flags & DM_FLAG_ACTIVATED)
		return 0;

	drv = dev->driver; // 获取该设备对应的driver

	/**
	 * device_ofdata_to_platdata() - 设备读取平台数据
	 * 
	 * 读取设备的平台数据(通常是从设备树中),以便提供探测设备所需的信息。
	 */
	ret = device_ofdata_to_platdata(dev);

	// 如果该设备存在parent,那么先probe parent设备,确保所有的parent dev都被probed。
	if (dev->parent) {
		ret = device_probe(dev->parent);

		if (dev->flags & DM_FLAG_ACTIVATED)
			return 0;
	}

	/**
	 * uclass_resolve_seq() - 解析device的序列号
	 *
	 * 在"dev->seq = -1"时,然后"dev->req_seq = -1"代表自动分配一个序列号,"dev->req_seq > 0"代表分配
	 * 指定的序列号。如果请求的序列号正在使用中,那么该设备将被分配另一个序列号。
	 *
	 * 注意,该函数不会改变设备的seq值,dev->seq需要手动赋值修改。
	 */
	seq = uclass_resolve_seq(dev);
	dev->seq = seq;

	// 标记该设备处于激活状态。
	dev->flags |= DM_FLAG_ACTIVATED;

	 //处理除root device的pinctrl之外的所有设备,对于pinctrl device不进行pinctrl的设置,因为设备可能还没有被probed。
	if (dev->parent && device_get_uclass_id(dev) != UCLASS_PINCTRL)
		pinctrl_select_state(dev, "default");

	/**
	 * uclass_pre_probe_device() - 处理一个即将被probed的设备
	 *
	 * 先执行uclass需要的任何预处理,然后才可以探测它。这包括uclass的pre-probe()方法和父uclass的child_pre_probe()方法。
	 */
	ret = uclass_pre_probe_device(dev);

	if (dev->parent && dev->parent->driver->child_pre_probe) {
		ret = dev->parent->driver->child_pre_probe(dev);
	}

	// 只处理具有有效ofnode的设备
	if (dev_of_valid(dev) && !(dev->driver->flags & DM_FLAG_IGNORE_DEFAULT_CLKS)) {
		// 处理{clocks/clock-parents/clock-rates}属性配置时钟
		ret = clk_set_defaults(dev, 0);
	}

	// 执行该设备的driver的probe函数,激活该设备。
	if (drv->probe) {
		ret = drv->probe(dev);
	}

	/**
	 * uclass_post_probe_device() - 处理一个刚刚被probed过的设备
	 *
	 * 对uclass探测设备后,需要执行的操作。
	 */
	ret = uclass_post_probe_device(dev);

	if (dev->parent && device_get_uclass_id(dev) == UCLASS_PINCTRL)
		pinctrl_select_state(dev, "default");

	return 0;
}

8.7 pinctrl_config_one()函数分析

/**
 * pinctrl_config_one() - 为单个节点应用pinctrl Settings
 */
static int pinctrl_config_one(struct udevice *config)
{
	struct udevice *pctldev;
	const struct pinctrl_ops *ops;

	pctldev = config;
	for (;;) {
		// 获取dev的父节点
		pctldev = dev_get_parent(pctldev);
		if (!pctldev) {
			dev_err(config, "could not find pctldev\n");
			return -EINVAL;
		}
		// 判断该dev的uclass是否是UCLASS_PINCTRL
		if (pctldev->uclass->uc_drv->id == UCLASS_PINCTRL)
			break;
	}
	// 获取设备driver的操作指针
	ops = pinctrl_get_ops(pctldev);
	return ops->set_state(pctldev, config); // 调用driver的set_state方法,设置pinctrl到配置的状态。
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值