博主假设大家已经看过下面这篇文章:
本文就直接深入分析 pinctrl 子系统。
使用 pinctrl 和 gpio 子系统进行 GPIO 驱动开发,是嵌入式驱动工程师的基本操作,但大部分驱动工程师只会用子系统提供的 API 接口,不会对其底层实现进行分析,本文对其底层实现进行分析,文末有参考文章链接。
在此框架中,(1)、(2)、(3)由半导体厂商搞定,半导体厂商会利用 Linux 提供的框架,根据自己的芯片,客制化代码。普通的驱动工程师就是调用 API 即可,就算是写驱动了。
本文着重讲解(2),所以由图我们知道,pinctrl 子系统和 gpio 子系统都依赖于(2)的驱动。
工具
分析源码可以在线查看 Linux 内核源码,在线网址跳转函数和查找结构体也很方便:
https://elixir.bootlin.com/linux/latest/source
对嵌入式工程师来讲,较少谈及设计模式、重构等,因为我们受限于两个方面:硬件、系统(Linux、Android)。很多东西操作系统都规定好了,留给我们重新设计的很少,我们不做设计题,我们做填空题。
在 Linux 内核源码中,pinctrl 子系统的代码大都在 kernel/drivers/pinctrl/...,不同平台有不同的文件夹。gpio 子系统的代码大都在 kernel/drivers/gpio/...目录下。
博主买了正点原子 imx6ull 开发板,所以就以此板为例程,进行分析。
一、主要结构体
pinctrl 子系统主要结构体关系:
pinctrl_dev 是 pinctrl 子系统的根源结构体,它主要包含三条路:
pinctrl_desc:这里包含了pinctrl 子系统三个最重要的结构体,有三个操作函数集,pinctrl_ops 包含了对 PIN 的操作函数集,pinmux_ops 包含了对 PIN 的复用函数集,pinconf_ops 包含了对 PIN 的配置函数,大家可以在自己平台中点进去看看自己平台实现了哪个函数,如何实现的。
pinctrl 结构体:这里包含了 PIN 控制器所控制 PIN 的状态 state,state 里面包含了 setting,这个 setting 就是在设备树中对PIN的设置,大家点进去看相关数据结构就可以看到自己在设备树中用到的字符串。
gpio 相关的结构体,我们说过 pinctrl 子系统和 gpio 子系统是耦合的,我们从结构体就可以看得出来,它包含了最重要的结构体 gpio_chip。
我们已经阐述了pinctrl 子系统主要的数据结构,后面讲述函数调用关系。
二、函数调用逻辑
在文件 drivers/pinctrl/freescale/pinctrl-imx6ul.c 中有如下内容:
驱动的入口是 arch_initcall 中声明的函数,类似于我们经常写的 module_init,只是 arch_initcall 会先调用,具体我在这边写过:
我们从下往上看,可以看到最后调用的是 imx6ul_pinctrl_probe 函数,和普通驱动的 probe 函数一样,也就是说,pinctrl 子系统的驱动也是一个标准的 platform 驱动框架,分为:驱动、设备、总线。platform 虚拟总线会按照 of_device_id 结构体中的 compatible 属性去匹配 pinctrl 驱动和设备。
在不同芯片厂商的 probe 函数中,都是对上面所描述数据结构的初始化、填充、调用,释放。
我们看 292 行,是匹配结构体,根据 compatible 和设备树的compatible 字段进行匹配,匹配成功执行这个 probe 函数。每个平台 probe 函数都有区别,就不详讲了。
我们看 292 行的 of_device_id 定义的数组,最后一个是空元素,这是必须的,原因是 platform 本身的 match 函数中需要判断是否达到末尾,of_device_id 定义的 compatible 经常不止一个,系统需要知道是否匹配到最后一个元素,这一点一定要注意。
pinctrl_register
probe 函数后面的调用中,最重要的是调用 pinctrl_register 函数,此函数用于向 Linux 内核注册一个 PIN 控制器,,此函数原型如下:
- struct pinctrl_dev *pinctrl_register(
- struct pinctrl_desc *pctldesc,
- struct device *dev,
- void *driver_data)
参数 pctldesc 非常重要,因为此参数就是要注册的 PIN 控制器,PIN 控制器用于配置 SOC的 PIN 复用功能和电气特性。参数 pctldesc 是 pinctrl_desc 结构体类型指针,pinctrl_desc 结构体如下所示:
- struct pinctrl_desc {
- const char *name;
- struct pinctrl_pin_desc const *pins;
- unsigned int npins;
- const struct pinctrl_ops *pctlops;
- const struct pinmux_ops *pmxops;
- const struct pinconf_ops *confops;
- struct module *owner;
- #ifdef CONFIG_GENERIC_PINCONF
- unsigned int num_custom_params;
- const struct pinconf_generic_params *custom_params;
- const struct pin_config_item *custom_conf_items;
- #endif
- };
这三个 “_ops” 结构体指针非常重要!因为这三个结构体就是 PIN 控制器的“工具”,这三个结构体里面包含了很多操作函数,通过这些操作函数就可以完成对某一个 PIN 的配置。pinctrl_desc 结构体需要由用户提供,结构体里面的成员变量也是用户提供的。但是这个用户并不是我们这些使用芯片的程序员,而是半导体厂商,半导体厂商发布的 Linux 内核源码中已经把这些工作做完了。
下面是参考文章,写的比博主更深入,尤其第一个链接,大家可以查看。
参考文章:
http://www.wowotech.net/sort/gpio_subsystem
https://www.freesion.com/article/89301077969/
https://blog.csdn.net/u012830148/article/details/80609337
《正点原子 imx6u Linux 驱动开发指南》