瑞芯微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主板
经过了前面章节的学习,我们已经对pinctrl子系统中有了一定的了解,下面就来解决我们在120.2小节中提出的问题。
首先对问题进行一下复述,假如我们要配置一个LED外设,该LED需要使用一个管脚来进行亮灭的控制,那这个控制引脚需要复用成GPIO之后才能完成相应的功能,那pinctrl子系统是什么时候对该引脚进行的复用呢?
首先我们可以提出两种猜想,第一个猜想是在加载LED驱动的时候进行的pinctrl引脚复用,第二种猜想是在加载pinctrl驱动的时候完成的引脚复用。下面对这两种猜想进行验证。
1.猜想1验证
第一个猜想是在加载LED驱动的时候进行的pinctrl引脚复用,这就符合我们124章、125章、126章所讲解的内容。当LED灯的设备树和驱动匹配之后,就会进入驱动中编写的probe函数,在此之前会执行“drivers/base/dd.c”文件中的really probe函数中的子函数pinctrl_bind_pins,该函数会为给定的设备绑定引脚,并在绑定过程中选择和设置适当的pinctrl状态。具体的绑定细节可以去前面的章节中查找。
2.猜想2验证
第二种猜想是在加载pinctrl驱动的时候完成的引脚复用,因为pinctrl子系统也是符合设备模型的规范,也会执行相应的probe函数,所以同样的加在pinctrl驱动时也会执行“drivers/base/dd.c”文件中的really probe函数中的子函数pinctrl_bind_pins,那这时会进行pinctrl管脚的复用设置吗,接下来我们对此进行深入的分析。
在pinctrl的probe函数执行之前,会调用pinctrl_bind_pins函数,根据124.2小节中讲解到的内容可以知道,根据函数的嵌套,首先会调用的create_pinctrl函数创建struct pinctrl 类型的引脚控制器,create_pinctrl函数内容如下所示:
static struct pinctrl *create_pinctrl(struct device *dev,
struct pinctrl_dev *pctldev)
{
struct pinctrl *p;
const char *devname;
struct pinctrl_maps *maps_node;
int i;
const struct pinctrl_map *map;
int ret;
/*
* 为每个映射创建状态 cookie 持有者 struct pinctrl。
* 这是当使用 pinctrl_get() 请求引脚控制句柄时消费者将获得的对象。
*/
p = kzalloc(sizeof(*p), GFP_KERNEL);
if (!p)
return ERR_PTR(-ENOMEM);
p->dev = dev;
INIT_LIST_HEAD(&p->states);
INIT_LIST_HEAD(&p->dt_maps);
ret = pinctrl_dt_to_map(p, pctldev);
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;
/*
* 如果 pctldev 不为空,我们正在声明它的独占使用权,
* 这意味着它自己提供了该设置。
*
* 因此,我们必须跳过适用于此设备但由其他设备提供的映射。
*/
if (pctldev &&
strcmp(dev_name(pctldev->dev), map->ctrl_dev_name))
continue;
ret = add_setting(p, pctldev, map);
/*
* 在这一点上,添加设置可能会导致:
*
* - 延迟,如果引脚控制设备尚不可用
* - 失败,如果引脚控制设备尚不可用,
* 并且该设置是一个独占设置。我们不能推迟它,因为
* 该独占设置会在设备注册后立即生效。
*
* 如果返回的错误不是 -EPROBE_DEFER,则我们将
* 累积错误,以查看是否最终得到 -EPROBE_DEFER,
* 因为那是最糟糕的情况。
*/
if (ret == -EPROBE_DEFER) {
pinctrl_free(p, false);
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);
/* 将引脚控制句柄添加到全局列表 */
mutex_lock(&pinctrl_list_mutex);
list_add_tail(&p->node, &pinctrl_list);
mutex_unlock(&pinctrl_list_mutex);
return p;
}
在第22行会调用 pinctrl_dt_to_map 函数将设备树中定义的引脚映射信息转换为 struct pinctrl_map 结构,并将其添加到 p->dt_maps 链表中。该函数定义在内核源码目录下的“drivers/pinctrl/devicetree.c”文件中,具体内容如下所示:
int pinctrl_dt_to_map(struct pinctrl *p, struct pinctrl_dev *pctldev)
{
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 不是从设备树实例化而来 */
if (!np) {
if (of_have_populated_dt())
dev_dbg(p->dev, "no of_node; not parsing pinctrl DT\n");
return 0;
}
/* 节点内部存储属性名称的指针 */
of_node_get(np);
/* 对于每个定义的状态 ID */
for (state = 0;; state++) {
/* 获取 pinctrl-* 属性 */
propname = kasprintf(GFP_KERNEL, "pinctrl-%d", state);
prop = of_find_property(np, propname, &size);
kfree(propname);
if (!prop) {
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);
/*
* 如果未命名,则 statename 仅是整数状态 ID。但是,为了避免动态分配和之后要释放的麻烦,
* 可以直接将 statename 指向属性名称的一部分。
*/
if (ret < 0) {
/* strlen("pinctrl-") == 8 */
statename = prop->name + 8;
}
/* 对于其中的每个引用的引脚配置节点 */
for (config = 0; config < size; config++) {
phandle = be32_to_cpup(list++);
/* 查找引脚配置节点 */
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;
}
/* 解析节点 */
ret = dt_to_map_one_config(p, pctldev, statename, np_config);
of_node_put(np_config);
if (ret < 0)
goto err;
}
/* 如果在设备树中没有条目,则生成一个虚拟状态表条目 */
if (!size) {
ret = dt_remember_dummy_state(p, statename);
if (ret < 0)
goto err;
}
}
return 0;
err:
pinctrl_dt_free_maps(p);
return ret;
}
这里传递过来的是pinctrl的设备树节点,在24-76行的for循环中会获取 pinctrl-* 属性,而在pinctrl节点中并没有该属性,pinctrl-* 属性是在一系列的设备节点中添加的,所以会在这里返回错误,同样的错误会一层层的向上级函数传递,最终导致pinctrl_bind_pins函数返回错误,从而不能设置引脚的复用,所以猜想2是不正确的。
在121章中也讲解了瑞芯微的pinctrl的probe函数,在该函数中有一个这样的调用关系:
rockchip pinctrl_probe
rockchip_pinctrl_registér
devm_pinctrl_register
pinctrl_register
pinctrl_enable
pinctrl_claim_hogs
create_pinctrl
从上面的调用关系可以得到pinctrl的probe函数最后也会调用create_pinctrl来创建struct pinctrl 类型的引脚控制器,从而实现pinctrl引脚复用设置,同样的这是的设置也是不成功的,pinctrl_claim_hogs函数定义在内核源码目录下的“drivers/pinctrl/core.c”文件中,具体内容如下所示:
static int pinctrl_claim_hogs(struct pinctrl_dev *pctldev)
{
pctldev->p = create_pinctrl(pctldev->dev, pctldev);
if (PTR_ERR(pctldev->p) == -ENODEV) {
dev_dbg(pctldev->dev, "no hogs found\n");
return 0;
}
if (IS_ERR(pctldev->p)) {
dev_err(pctldev->dev, "error claiming hogs: %li\n",
PTR_ERR(pctldev->p));
return PTR_ERR(pctldev->p);
}
pctldev->hog_default =
pinctrl_lookup_state(pctldev->p, PINCTRL_STATE_DEFAULT);
if (IS_ERR(pctldev->hog_default)) {
dev_dbg(pctldev->dev,
"failed to lookup the default state\n");
} else {
if (pinctrl_select_state(pctldev->p,
pctldev->hog_default))
dev_err(pctldev->dev,
"failed to select default state\n");
}
pctldev->hog_sleep =
pinctrl_lookup_state(pctldev->p,
PINCTRL_STATE_SLEEP);
if (IS_ERR(pctldev->hog_sleep))
dev_dbg(pctldev->dev,
"failed to lookup the sleep state\n");
return 0;
}
该函数和pinctrl_bind_pins函数内容相似,所以也会在第二行的create_pinctrl函数返回错误,所以也就无法执行后面的pinctrl状态的选择和设置了,所以猜想2不成立。
至此,关于pinctrl的相关知识和疑问就都讲解和解答完成了。