作者|赵庆尧审稿人|SunShujuanRegulator是几乎每个驱动开发者都会用到的一个模块。在处理了几个与Regulator相关的bug后,我终于弄明白了。接下来分享一下如何控制Regulator?本文将从以下五个方面来讲解如何在内核中控制Regulator:什么是Regulator设备树配置核心API接口驱动控制方法调试方法(Lk和uefi阶段的开机控制不属于这篇文章的范围。)1.什么是Regulator一般来说SoC中都会有有限数量的PMU,Regulator就是这个PMU的抽象。说白了就是我们控制Regulator,再控制PMU,从而实现电气控制。下图是内核中Regulator的整体框架图,由三部分组成,分别为其他驱动提供API接口和sysfs端口,可以控制硬件PMIC等设备的寄存器。在本文中,我们将介绍前两部分。2、设备树配置常用的设备树配置主要涉及4个部分,共5个属性,分别是配置对应的Regulator,设备工作需要的电压范围,设置always-on属性,设置boot-on属性。以下是一个典型的设备树配置供您参考。xxx{test-avdd-supply=<&L5A>;test-avdd-min-uv=<30??00000>;test-avdd-max-uv=<30??00000>;regulator-always-on;稳压器启动;}test属性-avdd-supply用于指示设备xxx使用哪个调节器。设置该属性时,需要先从原理图中获取相应的电源信息,然后将其转换为软件上的标识(通常原理图中的标识与平台代码dts中的标识相同,即易于识别),从而配置该属性;test-avdd-min-uv和test-avdd-max-uv这两个属性用来表示Regulator对应的电压范围,这个范围不能随意设置,因为pmu有自己驱动能力的范围。驱动能力范围可以通过以下方式获取:在平台代码的设备树中搜索。在上面的设备树配置中,我使用的是L5A,那么我会在平台的设备树配置中找到L5A的配置。下面的例子可以看出L5A的驱动范围在1.65V到3.05V之间。L5A:pmxxx_l5:regulator-l5{regulator-min-microvolt=<1650000>;regulator-max-microvolt=<30??50000>;qcom,init-voltage=<2960000>;status=“okay”;}虽然我们知道驱动Capabilityrange,但并不意味着我们可以通过配置Regulator来设置这个范围内的任意电压值(具体如何配置,后面会有说明)。从Regulator或pmu的手册可以看出,每个Regulator只能取这个范围内的离散值。属性regulator-always-on有两个含义。第一个意思是设置系统启动时对应稳压器的上电操作。下图中基于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函数。structregulator*regulator_get(structdevice*dev,constchar*id)该函数用于获取对应的Regulator,对应本文中的设备xxx,函数调用时regulator_get(structdevice*dev对应xxx,"test-avdd"),注意这个函数中的第二个参数是test-avdd,但是在devicetree中是test-avdd-supply,devicetree和function参数不一样的原因是红框标注的代码在下图中引起的。intregulator_is_enabled(structregulator*regulator)该函数用于判断对应的调节器是否启用。当它返回0时,表示相应的Regulator处于禁用状态。如果配置了always_on,则函数直接返回1,表示相应的Regulator已经使能,否则会读取相应的寄存器,获取相应Regulator的使能状态寄存器。这个功能有一个非常重要的作用,但是也是一个容易被大家忽略的功能,后面会说明它的重要性。intregulator_set_volatage(structregulator*regulator,intmin_uV,intmax_uV)该函数中的第二个和第三个参数可以相同也可以不同。不同时为设定电压范围;相同时为设定电压值。只有当设定值与当前值不同,且设定数据合理时,才能设定范围。设置范围当设置范围超出Regulator的驱动能力范围,且第三个参数大于第二个参数时,此时regulator_set_voltage会在内部将范围减小到Regulator可以驱动的最大范围。同时用于设置电压为min_uV和max_uV范围内最接近min_uV的电压。设置值如下代码所示,目的是对应电压为2.8V。前面说过,每个Regulator只能取一定范围内的离散值。当2.8V不属于这些离散值中的任何一个时,就会设置失败。regulator_set_voltage(regulator,2800000,2800000)intregulator_enable(structregulator*regulator)该函数用于使能相应的Regulator。只有启用后,才能提供真正的电力。如果配置了always_on属性,函数直接返回0。其他情况需要根据实际情况判断,然后进行相应的操作。下图为使能时的具体执行函数。从上图可以看出,在调用regulator_enable时,只有use_count为0时才会执行enable动作,use_count会加1。use_count是一个比较重要的变量,在regulator_disable时也会用到。接下来,我们将在下面查看regulator_disable。intregulator_disable(structregulator*regulator)该函数用于禁用相应的Regulator。如果配置了always_on属性,函数直接返回0。regulator_disable函数会在内部调用_regulator_disable函数。下图是_regulator_disable的实现代码。从图中可以看出,当use_count不为1时,不会执行disable动作。与Regulator相关的API函数也有很多,比如regulator_put、regulator_set_load等,不过以上五个函数比较常用。4.驱动控制方法需要在驱动中按照以下步骤执行(针对一个稳压器只给一个设备供电的情况):通过regulator_get获取对应的稳压器通过regulator_set_voltage设置电压并判断当前状态regulator的通过regulator_is_enabled根据上一步的结果,如果没有启用,则调用regulator_enable,否则不需要调用regulator_enableregulator_disable在实际工作中,我遇到过这样的情况,没有使用regulator_is_enabled进行条件判断,但是不小心调用了两次regulator_enable,会导致use_count=2。regulator_disable时,因为use_count!=1,所以没有disable动作,最后发现对应的电路无法断电。举一个简单的例子://step1:设置regulatorrdev=regulator_get(dev,"test-avdd");regulator_set_voltage(rdev,28000000,28000000);//step2:判断是否使能并使能if(!regulator_is_enabled(rdev)){ret=regulator_enable(rdev);if(ret!=0)printk("%s:regulator_enablefail,ret:%d\n",__func__,ret);}//step3:禁用regulatorif(regulator_is_enabled(rdev)){ret=regulator_disable(rdev);if(ret!=0)printk("%s:regulator_disablefail,ret:%d\n",__func__,ret);}}另一个驱动在接收应用层命令下发后进行regulator的enable或disable时,建议使用regulator_is_enable来判断,这样可以有效避免上层多次发送enable命令的情况并导致use_count增加。当某个Regulator给多台设备供电时,需要考虑多台设备的情况。不建议使用regulator_is_enabled。因此,当多个设备由一个Regulator控制时,会比较复杂。比如设备A开启了某个Regulator,而某个时候设备B也需要开启,但是因为通过regulator_is_enabled发现开启了,所以并没有执行开启操作,而是在后面的某个时刻,deviceA需要进行掉电操作,因为之前只调用了一次regulator_enable,那么use_count=1,此时deviceA可以成功使用regulator_disable,但是此时deviceB不想掉电,但是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(catopen_count)或者进行enableordisable控制(实际上只是echo1或0到对应的节点)。至此,Regulator的使用和调试就介绍完了。上面的介绍比较简单,属于入门级的内容,但是这些内容已经足够大部分驱动使用和调试了。希望大家能通过这篇文章。真正了解如何使用稳压器。作者介绍社区编辑赵庆尧,从事驱动开发多年。
