本系列文章将分析Linux对于OMAP的电源管理功能,以AM33XX作为实例(目前的硬件平台先主要关注AM335xStarterKit开发板),在必要时穿插其他相关内容。
在linux-3.8.1/arch/arm/mach-omap2/board-generic.c中有如下定义(分析某个开发板的内核,自然从board file的machine desc开始):
其中与比较关键的电源管理相关的代码是am33xx_init_early()函数。该函数在linux-3.8.1/arch/arm/mach-omap2/io.c中。
下面我们从am33xx_voltagedomains_init(),am33xx_powerdomains_init(),am33xx_clockdomains_init()等几个函数分析。
下面的文档定义了什么是Voltage Domain【参考TI的PRCM文档SPRUFA5】。
根据这个定义,我们可以得出Voltage Domain的功能包括下面几项:
因此,如果要自己设计一个Voltage Domain的结构,可以考虑在其中支持一个voltage_control()的接口,参数可以是0到最大OPP电压,而全范围的OPP可以作为该结构的附加数据提供。不过我们还是先来看Linux的实现如何。
首先是am33xx_voltagedomains_init()的Voltage Domain初始化,在内核的linux-3.8.1/arch/arm/mach-omap2/voltagedomains33xx_data.c。
这里将MPU和CORE分成单独的Voltage Domain。但是实际上,根据《AM335x ARM® Cortex™-A8 Microprocessors (MPUs)》文档在"Power-Up Sequencing"一节中描述,应该可以合并这两个Voltage Domain。
{
When using the ZCZ package option, VDD_MPU and VDD_CORE power inputs may be powered from the same source if the application only uses operating performance points (OPPs) that define a common power supply voltage for VDD_MPU and VDD_CORE. The ZCE package option has the VDD_MPU domain merged with the VDD_CORE domain.
}
每个Voltage Domain由一个struct voltagedomain来表示。
注意,尽管TPS65910的文档上说明了该PMIC支持8个可编程的LDO,且在AM335xStarterKit开发板上的确通过AM335X_I2C0链接了这个PMIC的I2C,但是由于这些LDO连接的都是些常规I/O,限定了要求的电压范围(不可使用DVFS这样的方式来类似调整Voltage Domain的电压一样来调整输出电压),因此这里不把这些LDO看做独立的Voltage Domain。但是,实际上如果这些对应的模块不需要的话,应该还是可以在初始化时将其LDO电源关掉。
再注意到,TPS65910的文档上说明了该PMIC支持通过SmartReflex接口(实际上是独立的I2C接口)来控制Processor Core的Power。但是实际的AM335xStarterKit开发板上却没有连接这个SmartReflex接口。有一点需要注意,TPS65910实现的是SmartReflex Class-3,而AM335x实现的是SmartReflex Class-2B。下面的文字来自TPS65910文档。
关于SmartReflex的支持,可以参考这个GitHub的patch,但是这是针对AM335X EVM或者BeagleBone开发板的。在AM3359 - TMDXIDK3359开发板中,SmartReflex的I2C接口是接上的。
实际上,在TPS65910的文档中还说明了这个PMIC的SmartReflex寄存器是可以通过Control I2C和SmartReflex I2C访问的,但是需要通过Control I2C先配置其DEVCTRL_REG寄存器的SR_CTL_I2C_SEL位来选择。
下面简单分析对struct voltagedomain的管理。
首先是平台初始化代码通过voltdm_init()来把自己支持的Voltage Domain注册到系统中,实际上这是通过调用_voltdm_register()来实现的,并且是将这个struct voltagedomain加入到一个全局的voltdm_list中。从而,也就可以对这个voltdm_list进行查找,所以又有voltdm_lookup()函数。
另外,为了方便起见,还提供一个voltdm_for_each()函数,通过扫描这个voltdm_list,每当发现一个Voltage Domain,就调用指定的函数指针,从而可以实现对所有的Voltage Domain都执行某种操作的功能。由于每个Voltage Domain可以有多个Power Domain,因此voltdm_add_pwrdm()就被用来向一个Voltage Domain添加一个Power Domain(将这些Power Domain连接到这个Voltage Domain的pwrdm_list中)。类似的,还提供了一个voltdm_for_each_pwrdm()来对一个Voltage Domain的所有Power Domain执行指定的操作函数。
对于每个Voltage Domain,由于可以有不同的Scale的方法(比如可以使用Voltage Controller,也可以用Voltage Processor来实现),因此提供omap_change_voltscale_method()来改变相应的Scale方法。其实这一点,如果为了通用起见,应该让平台代码直接实现Scale方法就行了,也就是可以直接用xxx_set_scale_method()就可以。
omap_voltage_register_pmic()被用来设置struct voltagedomain的pmic字段。omap_voltage_get_voltdata()用来返回对应电压的相关数据(这里使用voltage table实现),即OPP数据。omap_voltage_get_volttable()就是用来返回这个voltage table的。voltdm_reset()将这个Voltage Domain的电压设置为当前OPP,实际调用的是voltdm_scale()实现的。voltdm_scale()就是用来实现Voltage Scale功能的,根据参数指定电压,在该Voltage Domain自己的voltage table中找到一项恰好大于该指定电压的"标称电压",用来设置对应的Regulator。voltdm_get_voltage()用来获得该Voltage Domain的当前标称电压nominal_volt。