审校 | 孙淑娟
Regulator几乎是每一位驱动开发者都会使用到的模块,在处理过几起与Regulator相关的bug后,我终于弄明白了。接下来我来分享下,到底该如何控制Regulator?
本文将从以下五个方面来阐述内核中Regulator该如何控制:
- 什么是Regulator
- 设备树配置
- 核心API接口
- 驱动控制方法
- 调试方法
(Lk和uefi阶段的上电控制不属于本文的范畴。)
1.什么是Regulator
一般来说,soc都会有配套的有限数量的pmu,而Regulator就是这个pmu的抽象,直白来说就是我们通过控制Regulator,进而控制了pmu,从而达到对电的控制。
下图是内核中Regulator的整体框架图,由三部分组成,分别提供了供其他驱动使用的API接口和sysfs口,并可以控制硬件PMIC等这类器件的register,在本文中,将会介绍前两部分。
2.设备树配置
常用的设备树配置主要涉及4个部分,共5个属性,分别是配置对应的Regulator,设备工作需要的电压范围,设置always-on属性,设置boot-on属性。
下面是一个典型的设备树配置,供大家参考。
test-avdd-supply
这个属性是用来指明设备xxx使用的是哪一个Regulator,该属性设置时,需要先从原理图中获取对应的供电信息,然后转化到软件上的标识(通常原理图中的标识和平台代码dts中的相同,很容易识别到),从而配置该属性;
test-avdd-min-uv和test-avdd-max-uv
这两个属性是用来指明该Regulator对应的电压范围,这个范围不能随便设置,因为pmu有它自身的驱动能力范围。驱动能力的范围可以通过以下方式获取:
在平台代码的设备树中查找,上面设备树配置中,我采用了L5A,那我就在平台的设备树配置中找L5A的配置,如下样例可以看出L5A的驱动范围是在1.65V到3.05V之间。
我们虽然知道了驱动能力范围,但并不意味着我们就可以通过配置Regulator(后面会说明如何配置),设置这个范围内的任意电压值。通过查看Regulator或者pmu的手册都可以看出,每一个Regulator只能取这个范围内的离散值。
regulator-always-on
该属性有两个含义,第一层含义就是设置系统启动的时候,进行相应Regulator的上电操作,下图基于MTK平台的代码就是对应的上电操作。
第二层含义就是禁止对该Regulator进行掉电的操作,如下图的代码所示,rdev->constraints->always_on在系统启动的时候会进行设置,该变量代表了设备树中是否设置了regulator-always-on属性,当设置该属性时,对应的rdev->constraints->always_on = 1,则函数regulator_do_disable就不会执行,从而该Regulator无法掉电。
regulator-boot-on
该属性实际上同regulator-always-on属性的第一个含义相同,但我个人建议在配置需要开机就上电的Regulator的时候,即使有regulator-always-on属性,最好同时加上regulator-boot-on属性,以防有些平台regulator-always-on属性没有第一个含义的情况。
3.核心API接口
首先给大家介绍一下Regulator相关的API函数。
struct regulator *regulator_get(struct device *dev, const char *id)
该函数用来获取对应的Regulator,对应到本文中的设备xxx,其函数调用方法时regulator_get(对应xxx的struct device *dev,“test-avdd”),注意该函数中第2个参数是test-avdd,但设备树中是test-avdd-supply,之所以设备树和函数传参不相同的原因是下图中红色方框标注的代码导致的。
int regulator_is_enabled(struct regulator *regulator)
该函数用来判断对应的regulator是否已经enable。
当返回0表示对应Regulator处于disable状态。
如果配置了always_on,该函数直接返回1,表示相应的Regulator已经enabled,否则会去读取相应的寄存器来获取相应Regulator的使能状态寄存器。
该函数有着很重要的作用,但也是大家容易忽略的函数,后面会给大家展示其重要性。
int regulator_set_volatage(struct regulator *regulator, int min_uV, int max_uV)
该函数中的第二个参数和第三个参数可以相同,也可以不同。当不同的时候,就是设置的电压范围;当相同的时候,就是设置的电压值。
只有在设置值和当前值不一样,且设置的数据合理,才会进行范围设置。
- 设置范围
当设置的范围要超出该Regulator的驱动能力范围时,且第三个参数大于第二个参数,这种情况下,regulator_set_voltage会内部把范围缩小到该Regulator能驱动的最大范围。
同时用于将电压设置为min_uV和max_uV范围内,和min_uV最接近的电压。
- 设置值
如下面的代码所示,其目的是对应的电压为2.8V,前面有介绍过,每一个Regulator只能取一定范围内的离散值,当2.8V不属于这些离散值中的任意值时,就会设置失败。
int regulator_enable(struct regulator *regulator)
该函数用来enable对应的Regulator,只有enable后,才能真正的供上电。
如果配置了always_on属性,该函数直接返回0,其他情况下,需要根据实际情况来判断,然后执行相应的操作,下图是enable时,具体的执行函数。
从上图可以看出,调用regulator_enable时,只有在use_count为0的情况下才会做enable动作,且use_count会自加1。use_count是比较重要的变量,在regulator_disable时也会用到,接下来我们就看以下regulator_disable。
int regulator_disable(struct regulator *regulator)
该函数用来disable对应的Regulator。
如果配置了always_on属性,该函数直接返回0。
regulator_disable函数内部会调用_regulator_disable函数,下图是_regulator_disable的实现代码,从图中可以看出,当use_count不为1时,不会执行disable动作。
还有很多与Regulator相关的API函数,如regulator_put,regulator_set_load等,但常用的就是上面的5个函数。
4. 驱动控制方法
在驱动中需要按照下面的步骤来执行(针对一个Regulator只给一个设备供电的情况):
- 通过regulator_get获取对应的Regulator
- 通过regulator_set_voltage设置电压
- 通过regulator_is_enabled来判断当前Regulator的状态
- 根据上一步的结果,如果未enable,则调用 regulator_enable,否则不需要调用regulator_enable
- regulator_disable
在实际工作中,我遇到过这样的情景,没有使用regulator_is_enabled进行条件判断,但无意中调用了两次regulator_enable,这样就会导致use_count = 2,在regulator_disable时,由于use_count != 1,从而没有进行disable动作,导致最后发现相应的这路电无法掉电。
下面是一个简单的例子:
还有一个驱动是在收到应用层的命令后,才进行regulator的enable或者disable的情况下,建议使用regulator_is_enable来进行判断,这样就可以有效避免上层多次发送enable命令导致use_count增加的情况。
当某一个Regulator给多个设备供电时,需要考虑多个设备的情况,就不建议使用regulator_is_enabled,因此多设备通过一路Regulator控制时,会比较复杂,比如设备A已经enable了某一路Regulator,某一时刻设备B也需要enable,但由于通过regulator_is_enabled发现已经enable时,从而不进行enable操作,但之后的某一时刻,设备A需要进行掉电操作,因为之前regulator_enable只调用了一次,那use_count = 1,那此时设备A就可以regulator_disable成功,但这个时候设备B不希望掉电,但设备A把电掉了,导致设备B就异常了,因此同一路电给多个设备供电时,不建议使用regulator_is_enable。针对多种设备,最简单的处理方式就是使用regulator-always-on属性。
5.调试方法
此处我主要给大家介绍下sys节点的调试方式。节点的路径是/sys/kernel/debug/regulator/,在这个路径下面,大家会看到很多Regulator,如下图所示:
从上图我们可以看出,根据名称就可以找到我们需要的Regulator,比如从原理图中看出来我们使用的是ldoe9,那么就可以进入路径/sys/kernel/debug/regulator/18200000.rsc:rpmh-regulator-ldoe9-pm6150a_l9,在该路径下可以查看对应的open_count(cat open_count)或者进行enable或者disable控制(实际上就是echo 1或者0到对应的节点即可)。
至此,Regulator的使用以及调试就给大家介绍完了,上面的介绍比较简单,属于入门级别的内容,但这些内容已经足够大部分驱动的使用进行调试了,希望大家都能通过这篇文章,真正了解到Regulator该如何使用。
作者介绍
赵青窕,51CTO社区编辑,从事多年驱动开发。