瑞芯微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主板
第127章 猜想验证
经过了前面章节的学习,我们已经对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_proberockchip_pinctrl_registérdevm_pinctrl_registerpinctrl_registerpinctrl_enablepinctrl_claim_hogscreate_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的相关知识和疑问就都讲解和解答完成了。