Bootstrap

【Linux】基于IMX6ULL平台Pinctrl架构分析总结(二)-- Client Device使用过程

相关文章

  1. 《【Linux】基于IMX6ULL平台Pinctrl架构分析总结(一)-- Pin Controller注册过程》
  2. 《【Linux】基于IMX6ULL平台Pinctrl架构分析总结(二)-- Client Device使用过程》

1. 前言

在上一篇文章已经介绍了Pinctrl子系统中Pin Controller注册过程《【Linux】基于IMX6ULL平台Pinctrl架构分析总结(一)-- Pin Controller注册过程》,本篇文章主要是介绍Client Device是如何设置Pin的状态。在Device Tree中,Pinctrl主要分为2个部分:Pin ControllerClinet Device。Device可能会有多个状态,不同的状态下,Pin的状态的作用可能不相同。I2C1 Client Device只有一个default状态,它对应配置pinctrl-0。它的配置pinctrl-0指向了Pin Controller的pinctrl_i2c1,通过这些配置将Pin设置I2C功能。
在这里插入图片描述
下面的截图是基于IMX6ULL平台Pinctrl I2C1加载后,总结的结构体之间大致的关系,方便我们理解Pinctrl子系统Client Device是如何使用的。后面会详细的一步步分析代码,来更加深刻的理解这个过程。
在这里插入图片描述

2. 源码分析

源码分析主要关注在I2C1设备是如何使用Pinctrl子系统来设置Pin状态,I2C1就是Pinctrl子系统中的Client Device。我们都知道设备注册成功之后调用platform_driver的probe函数,但在调用之前会先绑定Driver的Pin到默认状态。在这里也就是在加载I2C1驱动时,会先设置Pin为I2C功能。

Path:Kernel\drivers\base\dd.c
在设备驱动匹配成功后,会先调用really_probepinctrl_bind_pins函数会将I2C1的pin设置为I2C功能,然后调用I2C驱动的probe函数dev->bus->probe

static int really_probe(struct device *dev, struct device_driver *drv)
{
	...
	/* 设备被加载时,在调用probe之前先绑定pin。 */
	ret = pinctrl_bind_pins(dev);
	/* 调用设备驱动对应的probe函数。 */
	ret = dev->bus->probe(dev);
	...
}

Path:Kernel\drivers\base\pinctrl.c
在Linux驱动中都会用struct device来表示当前的设备,然后通过它的成员struct dev_pin_info访问设备的pin相关信息。devm_pinctrl_get(dev)函数会创建并初始化pinctrl,然后赋值给struct pinctrl *p。它从Device Tree中解析并保存pin所有状态下的配置信息,以备后续使用。
在这里插入图片描述

int pinctrl_bind_pins(struct device *dev)
{
	/* 给struct dev_pin_info申请空间,并将地址赋值给struct device中的pins */
	dev->pins = devm_kzalloc(dev, sizeof(*(dev->pins)), GFP_KERNEL);
	/* 创建并初始化pinctrl */
	dev->pins->p = devm_pinctrl_get(dev);
	/* 通过name = default在dev->pins->p中查找对应的pinctrl_state */
	dev->pins->default_state = pinctrl_lookup_state(dev->pins->p,
					PINCTRL_STATE_DEFAULT);
	/* 将pin设置成default状态,也就是将pin设置成I2C功能。 */
	ret = pinctrl_select_state(dev->pins->p, dev->pins->default_state);

	return 0;
}

Path:Kernel\drivers\pinctrl\core.c
根据上面函数devm_pinctrl_get(dev),追踪到create_pinctrl(dev, NULL)函数。在create_pinctrl函数中,会创建并初始化struct pinctrl,然后从devicetree中当前设备的pinctrl数据解析到dt_maps中。pinctrl_dt_to_map会将解析的pin数据保存到全局的LIST_HEAD(pinctrl_maps),后面遍历它找到当前设备的maps。最后设备设置好后,将对应设备的pinctrl p添加到全局的LIST_HEAD(pinctrl_list)链表中。
在这里插入图片描述
我们可以通过运行起来的设备,cat /sys/kernel/debug/pinctrl/pinctrl-handles来查看当前的pinctrl。大致的架构如下:
在这里插入图片描述

static struct pinctrl *create_pinctrl(struct device *dev,
				      struct pinctrl_dev *pctldev)
{
	struct pinctrl *p;
	/* 创建并初始化struct pinctrl */
	p = kzalloc(sizeof(*p), GFP_KERNEL);
	p->dev = dev;
	INIT_LIST_HEAD(&p->states);
	INIT_LIST_HEAD(&p->dt_maps);
	/* 从devicetree中当前设备的pinctrl数据解析到dt_maps中 */
	ret = pinctrl_dt_to_map(p, pctldev);
	/* 遍历pinctrl_maps中所有的map */
	for_each_maps(maps_node, i, map) {
		/* 对比设备的name,必须保证该map当前Device的。 */
		if (strcmp(map->dev_name, devname))
			continue;

		if (pctldev &&
		    strcmp(dev_name(pctldev->dev), map->ctrl_dev_name))
			continue;
		/* 将对应驱动的map信息转换成setting,并且保存在pinctrl p中 */
		ret = add_setting(p, pctldev, map);
	}

	/* 将对应设备的pinctrl p添加到全局的pinctrl_list链表中 */
	list_add_tail(&p->node, &pinctrl_list);

	return p;
}

Path:Kernel\drivers\pinctrl\devicetree.c
pinctrl_dt_to_map函数中,主要是解析Device Tree中I2C1的数据。通过pinctrl-name找到对应的pinctrl-0配置。在pinctrl-0存放的是i2c1grp的phandle值。通过phandle ID就可以拿到存放在Pin作为I2C功能的配置数据,解析它并且存放到map中。
在这里插入图片描述

int pinctrl_dt_to_map(struct pinctrl *p, struct pinctrl_dev *pctldev)
{
	struct device_node *np = p->dev->of_node;
	/* 从当前设备中解析pinctrl-assert-gpios属性,I2C1设备并没有该属性跳过。 */
	ret = dt_gpio_assert_pinctrl(p);

	/* 循环遍历state ID,在I2C1中只有一个pinctrl-0。 */
	for (state = 0; ; state++) {
		/* 获取I2C1 pinctrl-0的属性 */
		propname = kasprintf(GFP_KERNEL, "pinctrl-%d", state);
		prop = of_find_property(np, propname, &size);

		list = prop->value; // 获取pinctrl-0属性的地址
		size /= sizeof(*list); // 获取config的个数,i2c1这里只有一个

		/* 获取pinctrl-names对应state */
		ret = of_property_read_string_index(np, "pinctrl-names",
						    state, &statename);

		/* For every referenced pin configuration node in it */
		for (config = 0; config < size; config++) {
			/* 从pinctrl-0获取phandle,这里phandle=0x2a */
			phandle = be32_to_cpup(list++);
			/* 通过phandle,找到i2c1grp设备node */
			np_config = of_find_node_by_phandle(phandle);
			/* 解析i2c1grp设备node */
			ret = dt_to_map_one_config(p, pctldev, statename,
						   np_config);
		}
	}

	return 0;
}

Path:Kernel\drivers\pinctrl\devicetree.c
dt_to_map_one_config函数中,通过get_pinctrl_dev_from_of_node函数遍历pinctrldev_list,找到以前注册的Pin Controller(iomuxc@20e0000)。在struct pinctrl_dev中保存有dt_node_to_map的方法,这也是Pin Controller注册时提供的方法,最终会调用imx实现的方法imx_dt_node_to_map生成映射表项。最后通过dt_remember_or_free_map将映射表块存储起来,以备以后使用。
在这里插入图片描述

static int dt_to_map_one_config(struct pinctrl *p,
				struct pinctrl_dev *hog_pctldev,
				const char *statename,
				struct device_node *np_config)
{
	struct pinctrl_dev *pctldev = NULL;
	struct device_node *np_pctldev;
	const struct pinctrl_ops *ops;

	/* np_pctldev这里指向的是i2c1grp node */
	np_pctldev = of_node_get(np_config);
	for (;;) {
		/* 获取i2c1grp的parent node,iomuxc@20e0000 */
		np_pctldev = of_get_next_parent(np_pctldev);
		/* 从pinctrldev_list中对比iomuxc@20e0000 pin controller是否已经注册 */
		pctldev = get_pinctrl_dev_from_of_node(np_pctldev);
		if (pctldev)
			break;
	}
	/* 调用pinctrl driver解析设备树节点,生成映射表项。 */
	ops = pctldev->desc->pctlops;
	ret = ops->dt_node_to_map(pctldev, np_config, &map, &num_maps);
	/* 将映射表块存储起来,以备以后使用。 */
	return dt_remember_or_free_map(p, statename, pctldev, map, num_maps);
}

Path:Kernel\drivers\pinctrl\freescale\pinctrl-imx.c
imx_dt_node_to_map函数中,主要是根据I2C1 group的数据来创建pinctrl_map。i2c1grp里面保存了2个pin,但是这里确创建了3个pinctrl_map。因为map[0]默认规定是group的相关信息,。map[1]map[2]保存才是pin相关的配置数据。
在这里插入图片描述

static int imx_dt_node_to_map(struct pinctrl_dev *pctldev,
			struct device_node *np,
			struct pinctrl_map **map, unsigned *num_maps)
{
	const struct group_desc *grp;
	struct pinctrl_map *new_map;
	/* 通过name查找group_desc, pctldev=iomuxc@20e0000,np->name=i2c1grp */
	grp = imx_pinctrl_find_group_by_name(pctldev, np->name);
	/* 从group中过去pin的数据,并统计需要pinctrl_map的个数 */
	for (i = 0; i < grp->num_pins; i++) {
		pin = &((struct imx_pin *)(grp->data))[i];
		if (!(pin->conf.mmio.config & IMX_NO_PAD_CTL))
			map_num++;
	}
	/* 给pinctrl_map申请空间 */
	new_map = kmalloc_array(map_num, sizeof(struct pinctrl_map),
				GFP_KERNEL);
	/* 返回map表的地址和个数 */
	*map = new_map;
	*num_maps = map_num;

	/* 根据np(i2c1grp)获取parent(iomuxc@20e0000) device node */
	parent = of_get_parent(np);
	/* new_map[0]是固定存放group type */
	new_map[0].type = PIN_MAP_TYPE_MUX_GROUP;
	/* name = iomuxc@20e0000 */
	new_map[0].data.mux.function = parent->name;
	/* name = i2c1grp */
	new_map[0].data.mux.group = np->name;

	/* create config map */
	new_map++;
	for (i = j = 0; i < grp->num_pins; i++) {
		/* 获取imx_pin的pin相关信息 */
		pin = &((struct imx_pin *)(grp->data))[i];
		new_map[j].type = PIN_MAP_TYPE_CONFIGS_PIN;
		/* 通过parent(iomuxc@20e0000)获取pin name */
		new_map[j].data.configs.group_or_pin = pin_get_name(pctldev, pin->pin);
		/* 获取该pin对应的config */
		new_map[j].data.configs.configs = &pin->conf.mmio.config;
		new_map[j].data.configs.num_configs = 1;
		j++;
	}

	return 0;
}

Path:Kernel\drivers\pinctrl\devicetree.c
dt_remember_or_free_map函数中,i2c1grp的3个pinctrl_map赋值新创建的struct pinctrl_dt_map,然后添加到struct pinctrl成员dt_maps列表中。
在这里插入图片描述

static int dt_remember_or_free_map(struct pinctrl *p, const char *statename,
				   struct pinctrl_dev *pctldev,
				   struct pinctrl_map *map, unsigned num_maps)
{
	struct pinctrl_dt_map *dt_map;
	/* 初始化map表项common部分 */
	for (i = 0; i < num_maps; i++) {
		const char *devname;
		devname = kstrdup_const(dev_name(p->dev), GFP_KERNEL);
		map[i].dev_name = devname; //devnames = i2c@21a0000
		map[i].name = statename; //statename = default
		map[i].ctrl_dev_name = dev_name(pctldev->dev); //ctrl_dev_name = iomuxc@20e0000
	}

	/* 给pinctrl_dt_map申请空间并初始化,然后添加到pinctrl的dt_maps队列中. */
	dt_map = kzalloc(sizeof(*dt_map), GFP_KERNEL);
	dt_map->pctldev = pctldev;
	dt_map->map = map;
	dt_map->num_maps = num_maps;
	list_add_tail(&dt_map->node, &p->dt_maps);
	/* 将map添加到core.c的pinctrl_maps队列中 */
	return pinctrl_register_map(map, num_maps, false);
}

Path:Kernel\drivers\pinctrl\core.c
pinctrl_register_map函数中,会创建一个struct pinctrl_maps来保存当前设备的所有pinctrl_map。然后将该设备的struct pinctrl_maps添加到全局的LIST_HEAD(pinctrl_maps)列表中,方便后面遍历pinctrl_maps就可以找到对应设备的map。
在这里插入图片描述

int pinctrl_register_map(const struct pinctrl_map *maps, unsigned num_maps,
			 bool dup)
{
	struct pinctrl_maps *maps_node;
	/* 检测map的是否有效 */
	switch (maps[i].type) {
	case PIN_MAP_TYPE_DUMMY_STATE:
		break;
	case PIN_MAP_TYPE_MUX_GROUP:
		ret = pinmux_validate_map(&maps[i], i);
		break;
	case PIN_MAP_TYPE_CONFIGS_PIN:
	case PIN_MAP_TYPE_CONFIGS_GROUP:
		ret = pinconf_validate_map(&maps[i], i);
		break;
	default:
		return -EINVAL;
	}
	/* 将map添加到core.c的pinctrl_maps队列中 */
	maps_node = kzalloc(sizeof(*maps_node), GFP_KERNEL);
	maps_node->num_maps = num_maps;
	maps_node->maps = maps;
	list_add_tail(&maps_node->node, &pinctrl_maps);

	return 0;
}

Path:Kernel\drivers\pinctrl\core.c
add_setting函数中,create_state会创建一个struct pinctrl_state来保存map里面信息到setting中。最后将当前设备的struct pinctrl_state添加设备的struct pinctrl的states list列表中,如下图:
在这里插入图片描述

static int add_setting(struct pinctrl *p, struct pinctrl_dev *pctldev,
		       const struct pinctrl_map *map)
{
	struct pinctrl_state *state;
	struct pinctrl_setting *setting;
	/* 第一次加载设备驱动在p->states中未找到对应default name的state, 然后会调用creat_state进行添加。 */
	state = find_state(p, map->name);
	state = create_state(p, map->name);
	/* 给pinctrl_setting申请空间 */
	setting = kzalloc(sizeof(*setting), GFP_KERNEL);
	/* type = PIN_MAP_TYPE_MUX_GROUP、PIN_MAP_TYPE_CONFIGS_PIN */
	setting->type = map->type;
	/* 从pinctrldev_list中对比name=iomuxc@20e0000,然后拿到pin controller device。 */
	setting->pctldev =
		get_pinctrl_dev_from_devname(map->ctrl_dev_name);
	/* devnames = i2c@21a0000 */
	setting->dev_name = map->dev_name;

	switch (map->type) {
	case PIN_MAP_TYPE_MUX_GROUP:
		/* 根据map的信息,将group和function的name信息转换成存储位置信息保存到setting中。 */
		ret = pinmux_map_to_setting(map, setting);
		break;
	case PIN_MAP_TYPE_CONFIGS_PIN:
	case PIN_MAP_TYPE_CONFIGS_GROUP:
		/* 根据map的信息,将pin的name信息转换成存储位置信息保存到setting中,config信息直接赋值。 */
		ret = pinconf_map_to_setting(map, setting);
		break;
	default:
		ret = -EINVAL;
		break;
	}
	/* 将pinctrl_setting添加到state->settings队列中 */
	list_add_tail(&setting->node, &state->settings);

	return 0;
}

Path:Kernel\drivers\pinctrl\pinmux.c
pinmux_map_to_setting函数中,通过Pinctrl核心结构体struct pinctrl_dev获取pinmux复用的操作方法,然后将pin的mux复用信息保存到setting中。
在这里插入图片描述

int pinmux_map_to_setting(const struct pinctrl_map *map,
			  struct pinctrl_setting *setting)
{
	struct pinctrl_dev *pctldev = setting->pctldev;
	const struct pinmux_ops *pmxops = pctldev->desc->pmxops;
	/* 通过function name(iomuxc@20e0000)查找在pinctrl_dev的pin_function_tree中的位置。 */
	ret = pinmux_func_name_to_selector(pctldev, map->data.mux.function);
	setting->data.mux.func = ret; // 并且将function(iomuxc@20e0000)位置保存到setting中
	/* 获取iomuxc@20e0000 groups的name和number个数 */
	ret = pmxops->get_function_groups(pctldev, setting->data.mux.func,
					  &groups, &num_groups);
	/* group = i2c1grp,匹配iomuxc@20e0000 groups中是否有对应的i2c1grp string */
	group = map->data.mux.group;
	ret = match_string(groups, num_groups, group);
	/* group = i2c1grp,匹配iomuxc@20e0000 groups中是否有对应的i2c1grp group的位置 */
	ret = pinctrl_get_group_selector(pctldev, group);
	setting->data.mux.group = ret; // 并且将group(i2c1grp)位置保存到setting中

	return 0;
}

Path:Kernel\drivers\pinctrl\pinconf.c
pinconf_map_to_setting函数中,根据pin的name找到pin number,并保存到setting中。然后,将根据map中pin config信息保存到setting。
在这里插入图片描述

int pinconf_map_to_setting(const struct pinctrl_map *map,
			  struct pinctrl_setting *setting)
{
	struct pinctrl_dev *pctldev = setting->pctldev;

	switch (setting->type) {
	case PIN_MAP_TYPE_CONFIGS_PIN:
		/* 根据pin的name找到pin number,并保存到setting中 */
		pin = pin_get_from_name(pctldev,map->data.configs.group_or_pin);
		setting->data.configs.group_or_pin = pin;
		break;
	case PIN_MAP_TYPE_CONFIGS_GROUP:
		/* I2C1未使用该类型,暂不分析。 */
		pin = pinctrl_get_group_selector(pctldev,map->data.configs.group_or_pin);
		setting->data.configs.group_or_pin = pin;
		break;
	default:
		return -EINVAL;
	}
	/* 根据pin config信息保存到setting中 */
	setting->data.configs.num_configs = map->data.configs.num_configs;
	setting->data.configs.configs = map->data.configs.configs;

	return 0;
}

Path:Kernel\drivers\pinctrl\core.c
pinctrl_select_state函数中,I2C1只有一个default状态,这里选择并使能。Pin就会被设置为I2C功能。

int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *state)
{
	if (p->state == state)
		return 0;

	return pinctrl_commit_state(p, state);
}

static int pinctrl_commit_state(struct pinctrl *p, struct pinctrl_state *state)
{
	struct pinctrl_setting *setting, *setting2;
	struct pinctrl_state *old_state = p->state;

	p->state = NULL;
	/* 对于new state应用它所有的settings */
	list_for_each_entry(setting, &state->settings, node) {
		switch (setting->type) {
		case PIN_MAP_TYPE_MUX_GROUP:
			ret = pinmux_enable_setting(setting);
			break;
		case PIN_MAP_TYPE_CONFIGS_PIN:
		case PIN_MAP_TYPE_CONFIGS_GROUP:
			ret = pinconf_apply_setting(setting);
			break;
		default:
			ret = -EINVAL;
			break;
	}
	/* 将new state赋值给p->state,保存设备当前状态。 */
	p->state = state;

	return 0;
}

Path:Kernel\drivers\pinctrl\pinmux.c
pinmux_enable_setting函数中,获取pin group的数据,然后设置到对应的寄存器。

int pinmux_enable_setting(const struct pinctrl_setting *setting)
{
	struct pinctrl_dev *pctldev = setting->pctldev;
	const struct pinctrl_ops *pctlops = pctldev->desc->pctlops;
	const struct pinmux_ops *ops = pctldev->desc->pmxops;
	/* 根据setting中的group存储位置,在pctldev->pin_group_tree中找到对于的group所有的pin,如:i2c1grp */
	ret = pctlops->get_group_pins(pctldev, setting->data.mux.group,
					      &pins, &num_pins);
	/* 将所使用的pin都进行申请 */
	for (i = 0; i < num_pins; i++) {
		ret = pin_request(pctldev, pins[i], setting->dev_name, NULL);
	}
	/* 将所使用的pin的pin_desc中的mux_setting进行配置 */
	for (i = 0; i < num_pins; i++) {
		desc = pin_desc_get(pctldev, pins[i]);
		desc->mux_setting = &(setting->data.mux);
	}
	/* 获取pin的配置数据,然后设置到对应的寄存器。 */
	ret = ops->set_mux(pctldev, setting->data.mux.func,
			   setting->data.mux.group);

	return 0;
}

Path:Kernel\drivers\pinctrl\pinconf.c
pinconf_apply_setting函数中,会获取对应pin的setting数据,并且设置到对应的寄存器。

int pinconf_apply_setting(const struct pinctrl_setting *setting)
{
	struct pinctrl_dev *pctldev = setting->pctldev;
	const struct pinconf_ops *ops = pctldev->desc->confops;
	/* 设置对应pin的配置 */
	switch (setting->type) {
	case PIN_MAP_TYPE_CONFIGS_PIN:
		ret = ops->pin_config_set(pctldev,
				setting->data.configs.group_or_pin,
				setting->data.configs.configs,
				setting->data.configs.num_configs);
		break;
	case PIN_MAP_TYPE_CONFIGS_GROUP:
		ret = ops->pin_config_group_set(pctldev,
				setting->data.configs.group_or_pin,
				setting->data.configs.configs,
				setting->data.configs.num_configs);
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

Path:Kernel\drivers\pinctrl\freescale\pinctrl-imx.c
设备的配置最终会调用它来设置对应Pin的寄存器。

static int imx_pinconf_set(struct pinctrl_dev *pctldev,
			   unsigned pin_id, unsigned long *configs,
			   unsigned num_configs)
{
	/* 将pin的配置设置到对应的寄存器 */
	imx_pinconf_set_mmio(pctldev, pin_id, configs, num_configs);
}

3. 分析过程的笔记草图

下面的截图是分析过程做的笔记,可以参考一起看。
请添加图片描述
请添加图片描述

;