往期内容
本专栏往期内容:
- Pinctrl子系统和其主要结构体引入
- Pinctrl子系统pinctrl_desc结构体进一步介绍
- Pinctrl子系统中client端设备树相关数据结构介绍和解析
- inctrl子系统中Pincontroller构造过程驱动分析:imx_pinctrl_soc_info结构体
input子系统专栏:
- 专栏地址:input子系统
- input角度:I2C触摸屏驱动分析和编写一个简单的I2C驱动程序
– 末片,有往期内容观看顺序I2C子系统专栏:
- 专栏地址:IIC子系统
- 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
– 末篇,有往期内容观看顺序总线和设备树专栏:
- 专栏地址:总线和设备树
- 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
– 末篇,有往期内容观看顺序
前言
本节就不提供相关源码文件了,各个代码块处都有标明该函数的文件路径,需要的可以自行去查看。
主要讲解作为使用者来说去使用pinctrl,其相关驱动程序是如何去进行获取pinctrl信息,对其进行解析将引脚转为map,在转为setting存储起来,去进行使用,也就是其如何去配置、复用引脚的。
1.回顾client的数据结构
看之前的文章:Pinctrl子系统中client端设备树相关数据结构介绍和解析
右侧是Pinctrl节点,左侧是client端节点。Pinctrl节点中设置了要使用的引脚的信息以及复用的功能,client节点则是对要使用的引脚进行引用,其实就是模块的设备节点。
Pin Controller:有自己的驱动程序
virtual_pincontroller {
compatible = "100ask,virtual_pinctrl";
myled_pin: myled_pin {
functions = "gpio";
groups = "pin0";
configs = <0x11223344>;
};
};
GPIO Controller:有自己的驱动程序
gpio_virt: virtual_gpiocontroller {
compatible = "100ask,virtual_gpio";
gpio-controller;
#gpio-cells = <2>;
ngpios = <4>;
};
Client:有自己的驱动程序
myled {
compatible = "100ask,leddrv";
led-gpios = <&gpio_virt 0 GPIO_ACTIVE_LOW>;
pinctrl-names = "default";
pinctrl-0 = <&myled_pin>;
};
另一种写法(正确写法):让GPIO和Pinctrl之间建立联系
/ {
pinctrl_virt: virtual_pincontroller {
compatible = "100ask,virtual_pinctrl";
myled_pin: myled_pin {
functions = "gpio";
groups = "pin0";
configs = <0x11223344>;
};
i2cgrp: i2cgrp {
functions = "i2c", "i2c";
groups = "pin0", "pin1";
configs = <0x11223344 0x55667788>;
};
};
gpio_virt: virtual_gpiocontroller {
compatible = "100ask,virtual_gpio";
gpio-controller;
#gpio-cells = <2>;
ngpios = <4>;
gpio-ranges = <&pinctrl_virt 0 0 4>;
//GPIO控制器的第0号引脚对应pinctrl_virt的第0号引脚(也就是对应myled_pin),数量为4
};
myled {
compatible = "100ask,leddrv";
led-gpios = <&gpio_virt 2 GPIO_ACTIVE_LOW>;
};
};
2.client的pinctrl构造过程
相同state的描述引脚的设备树节点如何转换为pinctrl_map,这些pinctrl_map又如何转换为pinctrl_setting存放在pinctrl_state结构体中的settings链表中
2.1 总图
比较长,看下图就行了。
really_probe
pinctrl_bind_pins
dev->pins = devm_kzalloc(dev, sizeof(*(dev->pins)), GFP_KERNEL);
dev->pins->p = devm_pinctrl_get(dev);
create_pinctrl(dev);
ret = pinctrl_dt_to_map(p);
for_each_maps(maps_node, i, map) {
ret = add_setting(p, map);
}
dev->pins->default_state = pinctrl_lookup_state(dev->pins->p,
PINCTRL_STATE_DEFAULT);
2.2 解析
如果想深入了解各个代码的含义,可以继续看:
really_probe //\Linux-4.9.88\drivers\base\dd.c
//* If using pinctrl, bind pins now before probing */
pinctrl_bind_pins //\Linux-4.9.88\drivers\base\dd.c
dev->pins->p = devm_pinctrl_get(dev);
//直接从该函数进入看
p = pinctrl_get(dev);//D\Linux-4.9.88\drivers\pinctrl\core.c
pinctrl_get内部实现如下,根据其注释可以知道,去获取到pinctrl
的句柄,但是在第一次的时候肯定是没有的,所以最后会去调用create_pinctrl
函数去创建pinctrl
\Linux-4.9.88\drivers\pinctrl\core.c
/**
* pinctrl_get() - retrieves the pinctrl handle for a device
* @dev: the device to obtain the handle for
*/
struct pinctrl *pinctrl_get(struct device *dev)
{
struct pinctrl *p;
if (WARN_ON(!dev))
return ERR_PTR(-EINVAL);
/*
* See if somebody else (such as the device core) has already
* obtained a handle to the pinctrl for this device. In that case,
* return another pointer to it.
*/
p = find_pinctrl(dev);
if (p != NULL) {
dev_dbg(dev, "obtain a copy of previously claimed pinctrl\n");
kref_get(&p->users);
return p;
}
return create_pinctrl(dev);
}
进入create_pinctrl
函数看看:
\Linux-4.9.88\drivers\pinctrl\core.c
/static struct pinctrl *create_pinctrl(struct device *dev)
{
struct pinctrl *p; // 指向创建的引脚控制结构体
const char *devname; // 设备的名称
struct pinctrl_maps *maps_node; // 引脚映射节点指针
int i; // 循环计数器
struct pinctrl_map const *map; // 当前的引脚映射
int ret; // 存储返回值,用于错误检查
/*
* 为每个映射创建状态存储结构体 pinctrl
* 消费者可以通过 pinctrl_get() 请求该引脚控制句柄
*/
p = kzalloc(sizeof(*p), GFP_KERNEL);
if (p == NULL) {
dev_err(dev, "failed to alloc struct pinctrl\n");
return ERR_PTR(-ENOMEM);
}
p->dev = dev; // 关联设备指针
INIT_LIST_HEAD(&p->states); // 初始化引脚控制的状态列表
INIT_LIST_HEAD(&p->dt_maps); // 初始化设备树的引脚映射列表
// 将设备树映射到引脚控制结构体中
ret = pinctrl_dt_to_map(p);
if (ret < 0) { // 检查映射是否成功
kfree(p); // 释放已分配的内存
return ERR_PTR(ret); // 返回错误指针
}
devname = dev_name(dev); // 获取设备名称
mutex_lock(&pinctrl_maps_mutex); // 加锁以保护全局映射
/* 遍历所有引脚控制映射以找到合适的映射 */
for_each_maps(maps_node, i, map) {
/* 检查映射是否属于当前设备 */
if (strcmp(map->dev_name, devname))
continue;
// 将映射添加到引脚控制结构体中
ret = add_setting(p, map);
/*
* 此时添加设置可能会:
* - 延迟:如果引脚控制设备尚未可用
* - 失败:如果设置是 hog 类型并且引脚控制设备尚不可用
* 如果返回的错误不是 -EPROBE_DEFER,则积累错误
* 以检查是否有 -EPROBE_DEFER 的情况,因为那是最糟糕的情况。
*/
if (ret == -EPROBE_DEFER) {
pinctrl_free(p, false); // 释放 pinctrl 结构体
mutex_unlock(&pinctrl_maps_mutex); // 解锁
return ERR_PTR(ret); // 返回延迟错误指针
}
}
mutex_unlock(&pinctrl_maps_mutex); // 解锁映射互斥量
if (ret < 0) {
/* 如果发生延迟以外的其他错误,返回错误 */
pinctrl_free(p, false);
return ERR_PTR(ret);
}
kref_init(&p->users); // 初始化引用计数
/* 将 pinctrl 句柄添加到全局列表中 */
mutex_lock(&pinctrl_list_mutex);
list_add_tail(&p->node, &pinctrl_list);
mutex_unlock(&pinctrl_list_mutex);
return p; // 返回创建的引脚控制结构体
}
其中最主要的就是ret = pinctrl_dt_to_map(p);
将设备树节点转为mapping,ret = add_setting(p, map);
将mapping转为setting并添加进pinctrl
结构体中。这个在之前对client端的相关结构体介绍的时候也有讲解过,下面来看看代码中是如何实现的:
2.2.1 转mapping
\Linux-4.9.88\drivers\pinctrl\devicetree.c
int pinctrl_dt_to_map(struct pinctrl *p)
{
struct device_node *np = p->dev->of_node; // 获取设备树节点
int state, ret;
char *propname;
struct property *prop;
const char *statename;
const __be32 *list;
int size, config;
phandle phandle;
struct device_node *np_config;
/* CONFIG_OF 启用时,p->dev 可能没有从 DT 中实例化 */
if (!np) {
if (of_have_populated_dt())
dev_dbg(p->dev, "no of_node; not parsing pinctrl DT\n");
return 0;
}
// 断言引脚控制设置
ret = dt_gpio_assert_pinctrl(p);
if (ret) {
dev_dbg(p->dev, "failed to assert pinctrl setting: %d\n", ret);
return ret;
}
// 获取节点引用
of_node_get(np);
/* 遍历每个状态 ID */
for (state = 0; ; state++) {
// 获取当前状态的引脚控制属性名,如 "pinctrl-0", "pinctrl-1" 等
propname = kasprintf(GFP_KERNEL, "pinctrl-%d", state);
prop = of_find_property(np, propname, &size); // 查找属性
kfree(propname);
if (!prop) {
// 若状态为 0 但找不到属性,说明没有定义
if (state == 0) {
of_node_put(np);
return -ENODEV;
}
break; // 没有更多状态
}
list = prop->value; // 获取属性值列表
size /= sizeof(*list); // 计算列表中的项数
// 从 "pinctrl-names" 中读取状态名
ret = of_property_read_string_index(np, "pinctrl-names", state, &statename);
/*
* 如果没有在 `pinctrl-names` 中找到状态名,将状态名设置为 `pinctrl-*`
* 属性名中的 ID 值。例如,"pinctrl-0" 中的 `0` 可以作为状态名
*/
if (ret < 0) {
// 跳过 "pinctrl-" 的 8 个字符,使用属性名中的 ID
statename = prop->name + 8;
}
// 遍历每个引用的引脚配置节点
for (config = 0; config < size; config++) {
phandle = be32_to_cpup(list++); // 获取 phandle 值
// 查找引脚配置节点 ---- 下图中标注 1
np_config = of_find_node_by_phandle(phandle);
if (!np_config) {
dev_err(p->dev, "prop %s index %i invalid phandle\n",
prop->name, config);
ret = -EINVAL;
goto err;
}
// 解析引脚配置节点,并创建对应的映射 --- 下图中标注 2
ret = dt_to_map_one_config(p, statename, np_config);
of_node_put(np_config);
if (ret < 0)
goto err;
}
// 如果 DT 中没有项,生成一个空状态表项
if (!size) {
ret = dt_remember_dummy_state(p, statename);
if (ret < 0)
goto err;
}
}
return 0;
err:
pinctrl_dt_free_maps(p); // 如果有错误,释放分配的映射
return ret;
}
来看看ret = dt_to_map_one_config(p, statename, np_config);
是如何解析设备树节点中的引脚,转为map。
dt_to_map_one_config
函数用于解析一个设备树中的引脚配置节点 (np_config
),并将其转换为内核 pinctrl
映射表。这个过程涉及查找引脚控制器设备 (pinctrl_dev
),并调用设备特定的解析函数来生成映射表。 其中最重要的就是调用到了pinctrl_desc->pinctrl_ops->dt_node_to_map,这个在之前的pincontroller的数据结构讲解中有提过(Pinctrl子系统pinctrl_desc结构体进一步介绍)。接下来看代码:
\Linux-4.9.88\Linux-4.9.88\drivers\pinctrl\devicetree.c
static int dt_to_map_one_config(struct pinctrl *p, const char *statename,
struct device_node *np_config)
{
struct device_node *np_pctldev;
struct pinctrl_dev *pctldev;
const struct pinctrl_ops *ops;
int ret;
struct pinctrl_map *map;
unsigned num_maps;
/* 查找包含 np_config 的引脚控制器节点 */
np_pctldev = of_node_get(np_config); // 获取配置节点的引用
for (;;) {
np_pctldev = of_get_next_parent(np_pctldev); // 获取上级节点
if (!np_pctldev || of_node_is_root(np_pctldev)) {
dev_info(p->dev, "could not find pctldev for node %s, deferring probe\n",
np_config->full_name);
of_node_put(np_pctldev); // 释放节点引用
/* 如果未找到引脚控制器,假设稍后会出现 */
return -EPROBE_DEFER;
}
pctldev = get_pinctrl_dev_from_of_node(np_pctldev); // 获取引脚控制器设备
if (pctldev)
break; // 找到引脚控制器设备,退出循环
/* 不要延迟对 hog 配置的探测(避免循环依赖) */
if (np_pctldev == p->dev->of_node) {
of_node_put(np_pctldev);
return -ENODEV;
}
}
of_node_put(np_pctldev); // 释放父节点的引用
/*
* 调用引脚控制器驱动解析设备树节点,并生成映射表项
*/
ops = pctldev->desc->pctlops; // 获取引脚控制器的操作函数
if (!ops->dt_node_to_map) {
dev_err(p->dev, "pctldev %s doesn't support DT\n",
dev_name(pctldev->dev));
return -ENODEV;
}
ret = ops->dt_node_to_map(pctldev, np_config, &map, &num_maps); // 解析配置
if (ret < 0)
return ret;
/* 将映射表项保存以备后用 */
return dt_remember_or_free_map(p, statename, pctldev, map, num_maps);
}
- 查找引脚控制器设备:函数首先查找与
np_config
对应的引脚控制器节点。通过向上遍历父节点,找到第一个包含配置的pinctrl_dev
,这是引脚控制器的核心设备。如果找不到合适的引脚控制器,返回-EPROBE_DEFER
表示稍后再次尝试探测。 - 避免循环依赖:在遍历父节点时,如果发现父节点是当前设备自身(hog 配置),则返回
-ENODEV
以避免循环依赖。 - 解析设备树节点:找到引脚控制器设备后,通过
pctlops->dt_node_to_map
调用设备特定的函数来解析np_config
节点并生成映射表项。 - 存储映射表项:最后,通过
dt_remember_or_free_map
函数将生成的映射表项存储到pinctrl
结构体中,以便后续使用。
ret = ops->dt_node_to_map(pctldev, np_config, &map, &num_maps);
调用的是哪些map函数呢????这就得去Pincontroller的驱动程序中去看了,因为这个函数是pinctrl_desc->pinctrl_ops的,是属于Pincontroller的:\Linux-4.9.88\drivers\pinctrl\freescale\pinctrl-imx.c📎pinctrl-imx.c
static const struct pinctrl_ops imx_pctrl_ops = {
.get_groups_count = imx_get_groups_count,
.get_group_name = imx_get_group_name,
.get_group_pins = imx_get_group_pins,
.pin_dbg_show = imx_pin_dbg_show,
.dt_node_to_map = imx_dt_node_to_map, //就是这个函数
.dt_free_map = imx_dt_free_map,
};
static int imx_dt_node_to_map(struct pinctrl_dev *pctldev,
struct device_node *np,
struct pinctrl_map **map, unsigned *num_maps)
{
struct imx_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev);
const struct imx_pinctrl_soc_info *info = ipctl->info;
const struct imx_pin_group *grp;
struct pinctrl_map *new_map;
struct device_node *parent;
int map_num = 1;
int i, j;
/*
* 首先找到该节点的引脚组,并检查是否需要为引脚创建配置映射
*/
grp = imx_pinctrl_find_group_by_name(info, np->name);
if (!grp) {
dev_err(info->dev, "unable to find group for node %s\n",
np->name);
return -EINVAL;
}
// 确定所需的映射数量
if (info->flags & IMX8_USE_SCU) {
map_num += grp->npins;
} else {
for (i = 0; i < grp->npins; i++) {
if (!(grp->pins[i].pin_conf.pin_memmap.config &
IMX_NO_PAD_CTL))
map_num++;
}
}
// 分配内存存储新映射
new_map = kmalloc(sizeof(struct pinctrl_map) * map_num, GFP_KERNEL);
if (!new_map)
return -ENOMEM;
*map = new_map;
*num_maps = map_num;
/* 创建复用映射 */
parent = of_get_parent(np);
if (!parent) {
kfree(new_map);
return -EINVAL;
}
new_map[0].type = PIN_MAP_TYPE_MUX_GROUP;
new_map[0].data.mux.function = parent->name;
new_map[0].data.mux.group = np->name;
of_node_put(parent);
/* 创建配置映射 */
new_map++;
for (i = j = 0; i < grp->npins; i++) {
if (info->flags & IMX8_USE_SCU) {
new_map[j].type = PIN_MAP_TYPE_CONFIGS_PIN;
new_map[j].data.configs.group_or_pin =
pin_get_name(pctldev, grp->pins[i].pin);
new_map[j].data.configs.configs =
(unsigned long *)&grp->pins[i].pin_conf.pin_scu.mux;
new_map[j].data.configs.num_configs = 2;
j++;
} else if (!(grp->pins[i].pin_conf.pin_memmap.config & IMX_NO_PAD_CTL)) {
new_map[j].type = PIN_MAP_TYPE_CONFIGS_PIN;
new_map[j].data.configs.group_or_pin =
pin_get_name(pctldev, grp->pins[i].pin);
new_map[j].data.configs.configs =
&grp->pins[i].pin_conf.pin_memmap.config;
new_map[j].data.configs.num_configs = 1;
j++;
}
}
dev_dbg(pctldev->dev, "maps: function %s group %s num %d\n",
(*map)->data.mux.function, (*map)->data.mux.group, map_num);
return 0;
}
imx_dt_node_to_map
函数用于将指定设备树节点 (np
) 转换为 pinctrl_map
映射表,并将其存储在 map
指针中,供引脚控制子系统使用。此函数特定于 i.MX 系列硬件平台,通过设备树节点的信息生成引脚配置和复用映射。
-
获取引脚组:
- 使用
imx_pinctrl_find_group_by_name
函数,根据节点名称 (np->name
) 查找对应的引脚组 (grp
)。
- 使用
-
计算映射数量:
- 如果
info->flags
标记中设置了IMX8_USE_SCU
,则每个引脚都需要一个配置映射,因此总映射数为1 + grp->npins
。 - 否则,遍历每个引脚,仅为需要配置的引脚增加映射数。
- 如果
-
内存分配:
- 使用
kmalloc
为映射数组分配内存,并初始化map
和num_maps
指针。 - 如果内存分配失败,函数返回
-ENOMEM
错误。
- 使用
-
创建复用映射:
- 获取父节点,检查其名称并为其创建复用 (
MUX
) 映射。 - 将父节点的
name
作为function
,当前节点的name
作为group
。
- 获取父节点,检查其名称并为其创建复用 (
-
创建配置映射:
- 遍历引脚组中的每个引脚,若符合条件,则创建配置映射。
- 使用
pin_get_name
函数获取引脚名称,并设置相应的配置。 IMX8_USE_SCU
标志影响配置内容;如果未设置此标志,则仅为未设置IMX_NO_PAD_CTL
的引脚创建配置。
-
调试信息:
- 使用
dev_dbg
输出映射的function
、group
和映射数量 (map_num
) 以供调试。
- 使用
2.2.2 mapping转setting
那么解析获取到mapping后,就要去转化为setting,回到create_pinctrl
函数:
\Linux-4.9.88\drivers\pinctrl\core.c
/static struct pinctrl *create_pinctrl(struct device *dev)
{
struct pinctrl *p; // 指向创建的引脚控制结构体
const char *devname; // 设备的名称
struct pinctrl_maps *maps_node; // 引脚映射节点指针
int i; // 循环计数器
struct pinctrl_map const *map; // 当前的引脚映射
int ret; // 存储返回值,用于错误检查
//...............
// 将设备树映射到引脚控制结构体中
ret = pinctrl_dt_to_map(p);
devname = dev_name(dev); // 获取设备名称
mutex_lock(&pinctrl_maps_mutex); // 加锁以保护全局映射
/* 遍历所有引脚控制映射以找到合适的映射 */
for_each_maps(maps_node, i, map) {
/* 检查映射是否属于当前设备 */
if (strcmp(map->dev_name, devname))
continue;
// 将映射添加到引脚控制结构体中
ret = add_setting(p, map);
}
//...............
}
直接省略掉其它内容,对于pinctrl_dt_to_map
在上文讲到过,那么接下来就是add_setting
函数,把每一个pinctrl_map转换为pinctrl_setting:
static int add_setting(struct pinctrl *p, struct pinctrl_map const *map)
{
struct pinctrl_state *state;
struct pinctrl_setting *setting;
int ret;
// 查找指定状态,如果不存在则创建一个新的状态
state = find_state(p, map->name);
if (!state)
state = create_state(p, map->name);
if (IS_ERR(state))
return PTR_ERR(state);
// 如果映射类型是 PIN_MAP_TYPE_DUMMY_STATE,则无需添加设置,直接返回
if (map->type == PIN_MAP_TYPE_DUMMY_STATE)
return 0;
// 为 pinctrl_setting 结构体分配内存,用于存储此设置的信息
setting = kzalloc(sizeof(*setting), GFP_KERNEL);
if (setting == NULL) {
dev_err(p->dev,
"failed to alloc struct pinctrl_setting\n");
return -ENOMEM;
}
// 设置类型,表示这是一个复用组或引脚配置
setting->type = map->type;
// 获取映射中指定的 pinctrl_dev(引脚控制设备)
setting->pctldev = get_pinctrl_dev_from_devname(map->ctrl_dev_name);
if (setting->pctldev == NULL) {
// 如果设备名称无法解析,释放内存并判断是否需要延迟加载驱动
kfree(setting);
// 对于 hog 引脚,不允许延迟加载,防止循环依赖
if (!strcmp(map->ctrl_dev_name, map->dev_name))
return -ENODEV;
// 设备驱动还未加载,输出提示信息并返回 -EPROBE_DEFER 表示延迟加载
dev_info(p->dev, "unknown pinctrl device %s in map entry, deferring probe",
map->ctrl_dev_name);
return -EPROBE_DEFER;
}
// 设置设备名称
setting->dev_name = map->dev_name;
// 根据映射类型,将映射转换为具体的设置内容
switch (map->type) {
case PIN_MAP_TYPE_MUX_GROUP:
// 如果是引脚复用组,调用 pinmux_map_to_setting 进行设置转换
ret = pinmux_map_to_setting(map, setting);
break;
case PIN_MAP_TYPE_CONFIGS_PIN:
case PIN_MAP_TYPE_CONFIGS_GROUP:
// 如果是引脚或组的配置映射,调用 pinconf_map_to_setting 进行设置转换
ret = pinconf_map_to_setting(map, setting);
break;
default:
// 其他类型无效,返回错误
ret = -EINVAL;
break;
}
if (ret < 0) {
// 如果设置转换失败,释放分配的内存并返回错误码
kfree(setting);
return ret;
}
// 将新的设置添加到状态的设置列表末尾
list_add_tail(&setting->node, &state->settings);
return 0;
}
主要就是pinmux_map_to_setting
和pinconf_map_to_setting
函数, 根据映射类型(复用组、引脚配置等),调用相应的函数 (pinmux_map_to_setting
或 pinconf_map_to_setting
) 将映射转换为具体的引脚设置。 pinmux和pinconf在Pinccontroller结构体的讲解中也讲过
先来看pinmux_map_to_setting
函数, 将一个 pinctrl_map
(引脚控制映射)条目中的复用功能映射转换为一个具体的 pinctrl_setting
设置,用于配置指定设备的引脚复用功能。
- 设备和操作集获取:获取
pinctrl_dev
(引脚控制设备)和pinmux_ops
操作集,用于查询功能对应的引脚组并设置引脚复用。 - 功能选择器索引转换:将
map->data.mux.function
中指定的功能名称转换为功能选择器索引,并存储在setting->data.mux.func
中。 - 功能支持组查询:通过
pmxops->get_function_groups
函数获取该功能支持的引脚组列表和数量,确保该功能可以选择特定的引脚组。 - 引脚组验证:如果映射条目中指定了具体的引脚组名称,则检查该组是否在功能的支持列表中;如果未指定,则使用默认的第一个组。
- 组选择器索引转换:将引脚组名称转换为组选择器索引,并存储在
setting->data.mux.group
中。 - 返回设置:将转换后的功能和组选择器索引存入
pinctrl_setting
,用于后续的引脚控制配置操作。如果过程中发生错误,则返回相应的错误码。
\Linux-4.9.88\drivers\pinctrl\pinmux.c
int pinmux_map_to_setting(struct pinctrl_map const *map,
struct pinctrl_setting *setting)
{
// 获取 pinctrl_dev 设备和 pinmux_ops 操作集,用于执行引脚复用操作
struct pinctrl_dev *pctldev = setting->pctldev;
const struct pinmux_ops *pmxops = pctldev->desc->pmxops;
char const * const *groups; // 存储功能所支持的组名
unsigned num_groups; // 功能所支持的组的数量
int ret;
const char *group;
// 如果设备不支持引脚复用操作,返回错误
if (!pmxops) {
dev_err(pctldev->dev, "does not support mux function\n");
return -EINVAL;
}
// 将功能名称转换为功能选择器索引
ret = pinmux_func_name_to_selector(pctldev, map->data.mux.function);
if (ret < 0) {
dev_err(pctldev->dev, "invalid function %s in map table\n",
map->data.mux.function);
return ret;
}
setting->data.mux.func = ret; // 设置功能选择器
// 查询该功能对应的引脚组,获取组的列表和组数
ret = pmxops->get_function_groups(pctldev, setting->data.mux.func,
&groups, &num_groups);
if (ret < 0) {
dev_err(pctldev->dev, "can't query groups for function %s\n",
map->data.mux.function);
return ret;
}
// 如果功能不支持任何引脚组,返回错误
if (!num_groups) {
dev_err(pctldev->dev,
"function %s can't be selected on any group\n",
map->data.mux.function);
return -EINVAL;
}
// 如果映射指定了特定的引脚组,验证该组是否存在于支持的组列表中
if (map->data.mux.group) {
group = map->data.mux.group;
ret = match_string(groups, num_groups, group);
if (ret < 0) {
dev_err(pctldev->dev,
"invalid group \"%s\" for function \"%s\"\n",
group, map->data.mux.function);
return ret;
}
} else {
// 如果未指定引脚组,则使用默认的第一个组
group = groups[0];
}
// 将组名转换为组选择器索引
ret = pinctrl_get_group_selector(pctldev, group);
if (ret < 0) {
dev_err(pctldev->dev, "invalid group %s in map table\n",
map->data.mux.group);
return ret;
}
setting->data.mux.group = ret; // 设置组选择器
return 0; // 成功返回 0
}
再来看看pinconf_map_to_setting
函数, 将 pinctrl_map
(引脚控制映射)中的引脚或引脚组的配置映射转换为 pinctrl_setting
设置,用于对指定设备的引脚或引脚组进行特定的配置。
-
设备获取:从
pinctrl_setting
中获取pinctrl_dev
设备,用于引脚或引脚组的配置映射。 -
配置类型判断:根据
setting->type
判断配置类型。- 单引脚配置 (PIN_MAP_TYPE_CONFIGS_PIN):根据引脚名称获取引脚索引,并存储在
setting->data.configs.group_or_pin
字段中。 - 引脚组配置 (PIN_MAP_TYPE_CONFIGS_GROUP):根据引脚组名称获取引脚组选择器索引,并存储在
setting->data.configs.group_or_pin
字段中。
- 单引脚配置 (PIN_MAP_TYPE_CONFIGS_PIN):根据引脚名称获取引脚索引,并存储在
-
配置项存储:将
map
中的配置数量和配置数组存储到setting
中,以便后续的配置应用。 -
返回状态:如果成功执行映射和存储,则返回 0;如果引脚或组未找到,或类型无效,则返回相应的错误码。
int pinconf_map_to_setting(struct pinctrl_map const *map,
struct pinctrl_setting *setting)
{
// 获取与当前设置相关联的 pinctrl_dev 设备
struct pinctrl_dev *pctldev = setting->pctldev;
int pin;
// 根据配置类型执行相应的操作
switch (setting->type) {
case PIN_MAP_TYPE_CONFIGS_PIN: // 单个引脚配置
// 根据引脚名称获取引脚索引
pin = pin_get_from_name(pctldev, map->data.configs.group_or_pin);
if (pin < 0) { // 如果获取失败,打印错误信息并返回错误码
dev_err(pctldev->dev, "could not map pin config for \"%s\"",
map->data.configs.group_or_pin);
return pin;
}
// 将引脚索引存储在设置的 group_or_pin 字段中
setting->data.configs.group_or_pin = pin;
break;
case PIN_MAP_TYPE_CONFIGS_GROUP: // 引脚组配置
// 根据引脚组名称获取引脚组选择器索引
pin = pinctrl_get_group_selector(pctldev, map->data.configs.group_or_pin);
if (pin < 0) { // 如果获取失败,打印错误信息并返回错误码
dev_err(pctldev->dev, "could not map group config for \"%s\"",
map->data.configs.group_or_pin);
return pin;
}
// 将组选择器索引存储在设置的 group_or_pin 字段中
setting->data.configs.group_or_pin = pin;
break;
default:
// 如果配置类型无效,返回 -EINVAL 错误码
return -EINVAL;
}
// 设置配置项的数量和配置数组
setting->data.configs.num_configs = map->data.configs.num_configs;
setting->data.configs.configs = map->data.configs.configs;
return 0; // 成功返回 0
}
3.切换state情景分析
这一部分看下图简单了解一下就行了。
really_probe
pinctrl_bind_pins
pinctrl_select_state
/* Apply all the settings for the new state */
list_for_each_entry(setting, &state->settings, node) {
switch (setting->type) {
case PIN_MAP_TYPE_MUX_GROUP:
ret = pinmux_enable_setting(setting);
ret = ops->set_mux(...);
break;
case PIN_MAP_TYPE_CONFIGS_PIN:
case PIN_MAP_TYPE_CONFIGS_GROUP:
ret = pinconf_apply_setting(setting);
ret = ops->pin_config_group_set(...);
break;
default:
ret = -EINVAL;
break;
}