pinctrl子系统
一、Pinctrl子系统使用示例
1.1 要做什么
- 查看原理图确定使用哪些引脚:比如pinA、pinB
- 生成pincontroller设备树信息
- 选择功能:比如把pinA配置为I2C_SCL、把pinB配置为I2C_SDA
- 配置:比如把pinA、pinB配置为open drain
- 使用pincontroller设备树信息:比如在i2c节点里定义”pinctrl-names”、”pinctrl-0”
1.2 pincontroller设备树
生成pincontroller设备树信息,有3中方法:
- 有些芯片有图形化的工具,可以点点鼠标就可以配置引脚信息,得到pincontroller中的信息
- 有些芯片,只能看厂家给的设备树文档或者参考设备树的例子
- 最差的就是需要阅读驱动代码才能构造设备树信息。
1 | &iomuxc { |
1.3 client节点使用pincontroller
1 | &i2c1 { |
1.4 使用过程
设备驱动基本不用关心流程。当设备切换状态时,对应的pinctrl就会被调用。
比如在platform_device和platform_driver的probe过程中,流程如下:
二、Pinctrl子系统主要数据结构
2.1 设备树
理想模型:
实际的例子(imx6ull):
2.2 pincontroller的数据结构
记住pinctrl的三大作用,有助于理解所涉及的数据结构:
- 引脚枚举与命名(Enumerating and naming)
- 引脚复用(Multiplexing):比如用作GPIO、I2C或其他功能
- 引脚配置(Configuration):比如上拉、下拉、open drain、驱动强度等
2.2.1 pinctrl_desc和pinctrl_dev
pincontroller虽然是一个软件的概念,但是它也用一个结构体来表示:pinctrl_dev。
怎么构造出pinctrl_dev?我们只需要描述它:提供一个pinctrl_desc,然后调用pinctrl_register就可以:
1 | struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc, struct device *dev, void *driver_data); |
怎么使用pinctrl_desc、pinctrl_dev来描述一个pincontroller?这两个结构体定义如下:
pinctrl_desc示例如下:
2.2.2 作用1:引脚枚举与命名
分为2部分:
- 描述、获得单个引脚的信息
- 描述、获得某组引脚的信息
使用结构体pinctrl_pin_desc来描述一个引脚,一个pincontroller有多个引脚:
单个pins 的信息在 imx6ull 是代码写死的,info中的pins是在pincontroller构造时赋值的,后文有分析。
1 | imx_pinctrl_desc->pins = info->pins; |
使用pinctrl_ops来操作引脚,主要功能有二:
- 取出某组的引脚的信息:get_groups_count、get_group_pins
- 处理设备树中pin controller中的某个节点:client pinctrl端回调pincontroller端的dt_node_to_map,把device_node转换为一系列的pinctrl_map
2.2.3 作用2:引脚复用
用来把某组引脚(group)复用为某个功能(function)。
2.2.4 作用3:引脚配置
用来配置某个引脚(pin)或某组引脚(group)。
2.2.5 pinctrl_desc注册得到pinctrl_dev
调用devm_pinctrl_register或pinctrl_register,就可以根据pinctrl_desc构造出pinctrl_dev,并且把pinctrl_dev放入链表:
1 | devm_pinctrl_register |
2.3 client的数据结构
在设备树中,使用pinctrl时格式如下:
1 | /* For a client device requiring named states */ |
设备节点要么被转换为platform_device,或者其他结构体(比如i2c_client),但是里面都会有一个device结构体,比如:
2.3.1 dev_pin_info
每个device结构体里都有一个dev_pin_info结构体,用来保存设备的pinctrl信息:
2.3.2 pinctrl
假设芯片上有多个pincontroller,那么这个设备使用哪个pincontroller?这需要通过设备树来确定:
- 分析设备树,找到pincontroller
- 对于每个状态,比如default、init,去分析pincontroller中的设备树节点
- 使用pincontroller的pinctrl_ops.dt_node_to_map来处理设备树的pinctrl节点信息,得到一系列的pinctrl_map
- 这些pinctrl_map放在pinctrl.dt_maps链表中
- 每个pinctrl_map都被转换为pinctrl_setting,放在对应的pinctrl_state.settings链表中
2.3.3 pinctrl_map和pinctrl_setting
设备引用pincontroller中的某个节点时,这个节点会被转换为一些列的pinctrl_map:
- 转换为多少个pinctrl_map,完全由具体的驱动决定
- 每个pinctrl_map,又被转换为一个pinctrl_setting,里面保存有引脚的mux和config
- 举例,设备节点里有:
pinctrl-0 = <&state_0_node_a>
- pinctrl-0对应一个状态,会得到一个pinctrl_state
- state_0_node_a节点被解析为一系列的pinctrl_map
- 这一系列的pinctrl_map被转换为一系列的pinctrl_setting
- 这些pinctrl_setting被放入pinctrl_state的settings链表
2.3.4 使用pinctrl_setting
调用过程:
1 | really_probe |
三、Pincontroller构造情景分析
3.1 整体执行流程
驱动程序位置:
1 | drivers\pinctrl\freescale\pinctrl-imx6ul.c |
调用过程:
1 | imx6ul_pinctrl_probe |
3.2 构造pinctrl_desc
pincontroller的最终目的:提供一个pinctrl_desc,然后调用pinctrl_register注册。所以我们要完善pinctrl_desc。
- 引脚枚举与命名(单个引脚):info的pins在imx6ull是代码写死的:
1 | //[root@qing:/sys/kernel/debug/pinctrl/20e0000.iomuxc]# cat pins |
- 引脚枚举与命名(某组引脚):某组功能中有哪些引脚?这要解析设备树,由imx_pinctrl_probe_dt函数解析设备树后放在imx_pinctrl_soc_info结构体中作为私有数据传入注册。具体解析流程省略,调用如下:
1 | //[root@qing:/sys/kernel/debug/pinctrl/20e0000.iomuxc]# cat pingroups |
imx_pinctrl_soc_info:
imx_pin_group:存放各组信息
imx_pin:存放各组引脚信息,reg addr,reg val
- 引脚复用、引脚配置、client端回调获取引脚组信息和设备树引脚节点转换成map
1
2
3imx_pinctrl_desc->pctlops = &imx_pctrl_ops; //client端回调
imx_pinctrl_desc->pmxops = &imx_pmx_ops; //引脚复用
imx_pinctrl_desc->confops = &imx_pinconf_ops; //引脚配置
整体框图如下:
四、client pinctrl构造情景分析
client pinctrl的整体流程:
- 设备树转换为pinctrl_map;使用pincontroller的pinctrl_ops.dt_node_to_map来处理设备pinctrl节点信息,得到一系列的pinctrl_map。
- pinctrl_map转换为pinctrl_setting;每个pinctrl_map被转换为pinctrl_setting,里面保存有引脚的mux和config。
- 切换state;在platform_device和platform_driver的设备probe过程前,也即really_probe中使用pincontroller相关ops函数,将引脚的pinctrl_setting都进行设置。
第三点切换state的调用流程如下:
1 | really_probe |
总结如下图:
五、编写虚拟的pinctrl子系统
5.1 整体步骤
- pin controller:
- 创建设备树节点
- 编写驱动程序
- 测试:
- 创建client设备树节点
- 编写驱动程序
5.2 硬件功能
假设这个虚拟的pin controller有4个引脚:
- pin0,1,2,3都可以配置为GPIO功能
- pin0,1还可以配置为I2C功能
- pin2,3还可以配置为UART功能
5.3 编写设备树
1 | virtual_pincontroller { |
5.4 编写pinctrl驱动
核心:pinctrl_desc
- 分配pinctrl_desc结构体
- 设置pinctrl_desc结构体
- 注册pinctrl_desc结构体
1 |
|
5.5 编写client驱动
1 |
|
5.6 使用方法
依次装载驱动
- virtual_pinctrl_driver.ko 注册了我们的虚拟的pinctrl控制器,里面有我们的虚拟pinctrl的操作函数。
- virtual_pinctrl_client.ko 这个client的pinctrl控制器在设备树中指定为了我们的虚拟的pinctrl控制器,在执行他的probe函数之前,系统会先执行really_probe函数,会用这个驱动对应的pinctrl控制器的操作函数设置成设备树中指定的配置。
开发板的/sys/kernel/debug/pinctrl/
目录下,每一个pin controller都有一个目录,比如virtual_pincontroller。里面有很多文件,作用如下:
Pinctrl的虚拟文件 | 作用 | 解释 |
---|---|---|
pins | 单个引脚信息 | |
pingroups | 引脚的组信息 | |
pinmux-pins | 单个引脚的复用信息 | |
pinmux-functions | function下的group(支持该function的group) | |
pinconf-pins | 单个引脚的配置 | |
pinconf-groups | 引脚组的配置 | |
pinconf-config | 可以通过写它修改指定设备、指定状态下、指定(组)引脚的config值 |
- 单个引脚信息
1 | cat /sys/kernel/debug/pinctrl/virtual_pincontroller/pins |
- 引脚的组信息(模仿stm32mp157的每个pin都当作一个group)
1 | cat /sys/kernel/debug/pinctrl/virtual_pincontroller/pingroups |
- 单个引脚的复用信息
1 | cat /sys/kernel/debug/pinctrl/virtual_pincontroller/pinmux-pins |
- function下的group(支持该function的group,模仿stm32mp157的每个pin都当作一个group)
1 | cat /sys/kernel/debug/pinctrl/virtual_pincontroller/pinmux-functions |
- 单个引脚的配置
1 | cat /sys/kernel/debug/pinctrl/virtual_pincontroller/pinconf-pins |
- 引脚组的配置(模仿stm32mp157的每个pin都当作一个group)
1 | cat /sys/kernel/debug/pinctrl/virtual_pincontroller/pinconf-groups |
我们可以在pinctrl驱动的ops组中加入打印信息,这样就可以确认这些cat操作分别对应的ops函数。