工作性能点(OPP)库

  1. 2009-2010 Nishanth Menon <nm@ti.com>, Texas Instruments Incorporated

1. 简介

1.1 什么是工作性能点(OPP)?

当今复杂的SoC由多个协同工作的子模块组成。在执行各种用例的运行系统中,并非SoC中的所有模块都需要始终以其最高性能频率运行。为了方便这一点,SoC中的子模块被分组到域中,允许一些域以较低的电压和频率运行,而其他域以较高的电压/频率对运行。

设备将支持的每个域的离散元组集合,包括频率和电压对,称为工作性能点或OPP。

例如

让我们考虑一个MPU设备,它支持以下内容:{300MHz,最小电压为1V},{800MHz,最小电压为1.2V},{1GHz,最小电压为1.3V}

我们可以将这些表示为以下{Hz,uV}元组的三个OPP

  • {300000000, 1000000}

  • {800000000, 1200000}

  • {1000000000, 1300000}

1.2 工作性能点库

OPP库提供了一组辅助函数来组织和查询OPP信息。该库位于drivers/opp/目录中,头文件位于include/linux/pm_opp.h中。可以通过从电源管理menuconfig菜单中启用CONFIG_PM_OPP来启用OPP库。某些SoC(例如德州仪器的OMAP框架)允许可选地以某个OPP启动而无需cpufreq。

OPP库的典型用法如下

(users)        -> registers a set of default OPPs              -> (library)
SoC framework  -> modifies on required cases certain OPPs      -> OPP layer
               -> queries to search/retrieve information       ->

OPP层期望每个域都由唯一的设备指针表示。SoC框架为每个设备向OPP层注册一组初始OPP。此列表预计是一个最佳的小数字,通常每个设备大约5个。此初始列表包含框架希望默认情况下在系统中安全启用的OPP集合。

关于OPP可用性的说明

随着系统继续运行,SoC框架可能会选择基于各种外部因素使某些OPP在每个设备上可用或不可用。用例:散热管理或其他特殊情况,SoC框架可能会选择禁用更高的频率OPP,以安全地继续运行,直到可以重新启用该OPP为止(如果可能)。

OPP库在其实现中促进了这一概念。以下操作函数仅在可用的opps上运行:dev_pm_opp_find_freq_{ceil, floor},dev_pm_opp_get_voltage,dev_pm_opp_get_freq,dev_pm_opp_get_opp_count。

dev_pm_opp_find_freq_exact旨在用于查找opp指针,然后可以使用该指针进行dev_pm_opp_enable/disable函数以根据需要使opp可用。

警告:OPP库的用户应在使用get_opp_count刷新其可用性计数,如果在设备上调用了dev_pm_opp_enable/disable函数,则触发这些的确切机制或其他依赖子系统(如cpufreq)的通知机制留给SoC特定的框架酌情决定使用OPP库。在这些操作的情况下,还需要注意刷新cpufreq表。

2. 初始OPP列表注册

SoC实现迭代调用dev_pm_opp_add函数以添加每个设备的OPP。预计SoC框架将以最佳方式注册OPP条目 - 典型数字范围小于5。通过注册OPP生成的列表由OPP库在整个设备操作过程中维护。SoC框架可以随后使用dev_pm_opp_enable/disable函数动态控制OPP的可用性。

dev_pm_opp_add

为由设备指针表示的特定域添加新的OPP。OPP是使用频率和电压定义的。添加后,假定OPP可用,并且可以使用dev_pm_opp_enable/disable函数控制其可用性。OPP库在dev_pm_opp结构中内部存储和管理此信息。SoC框架可以使用此函数来定义一个最佳列表,以满足SoC使用环境的需求。

警告

请勿在中断上下文中使用此函数。

示例

soc_pm_init()
{
       /* Do things */
       r = dev_pm_opp_add(mpu_dev, 1000000, 900000);
       if (!r) {
               pr_err("%s: unable to register mpu opp(%d)\n", r);
               goto no_cpufreq;
       }
       /* Do cpufreq things */
no_cpufreq:
       /* Do remaining things */
}

3. OPP搜索函数

诸如cpufreq之类的高级框架以频率运行。为了将频率映射回相应的OPP,OPP库提供了便捷的函数来搜索OPP库在内部管理的OPP列表。如果找到匹配项,这些搜索函数将返回表示opp的匹配指针,否则返回错误。这些错误应由标准错误检查(例如IS_ERR())处理,并且调用方应采取适当的操作。

这些函数的调用者在使用OPP后应调用dev_pm_opp_put()。否则,OPP的内存将永远不会被释放,并导致内存泄漏。

dev_pm_opp_find_freq_exact

基于确切的频率和可用性搜索OPP。此函数对于启用默认情况下不可用的OPP特别有用。示例:在SoC框架检测到可以使更高频率可用的情况下,它可以使用此函数在调用dev_pm_opp_enable以实际使其可用之前查找OPP。

opp = dev_pm_opp_find_freq_exact(dev, 1000000000, false);
dev_pm_opp_put(opp);
/* dont operate on the pointer.. just do a sanity check.. */
if (IS_ERR(opp)) {
       pr_err("frequency not disabled!\n");
       /* trigger appropriate actions.. */
} else {
       dev_pm_opp_enable(dev,1000000000);
}
注意

这是唯一可以在不可用的OPP上运行的搜索函数。

dev_pm_opp_find_freq_floor

搜索至多为提供的频率的可用OPP。此函数在搜索较小的匹配项或以降低频率的顺序处理OPP信息时很有用。示例:查找设备的最高opp

freq = ULONG_MAX;
opp = dev_pm_opp_find_freq_floor(dev, &freq);
dev_pm_opp_put(opp);
dev_pm_opp_find_freq_ceil

搜索至少为提供的频率的可用OPP。此函数在搜索更高的匹配项或以增加频率的顺序处理OPP信息时很有用。示例1:查找设备的最低opp

freq = 0;
opp = dev_pm_opp_find_freq_ceil(dev, &freq);
dev_pm_opp_put(opp);

示例2:SoC cpufreq_driver->target的简化实现

soc_cpufreq_target(..)
{
       /* Do stuff like policy checks etc. */
       /* Find the best frequency match for the req */
       opp = dev_pm_opp_find_freq_ceil(dev, &freq);
       dev_pm_opp_put(opp);
       if (!IS_ERR(opp))
               soc_switch_to_freq_voltage(freq);
       else
               /* do something when we can't satisfy the req */
       /* do other stuff */
}

4. OPP可用性控制函数

使用OPP库注册的默认OPP列表可能无法满足所有可能的情况。OPP库提供了一组函数来修改OPP列表中OPP的可用性。这允许SoC框架对哪些OPP在操作上可用进行细粒度的动态控制。这些函数旨在临时删除在诸如散热考虑因素(例如,在温度降至某个温度之前不要使用OPPx)之类的条件下的OPP。

警告

请勿在中断上下文中使用这些函数。

dev_pm_opp_enable

使OPP可用于操作。示例:假设只有当SoC温度低于某个阈值时才能使用1GHz OPP。SoC框架实现可能会选择执行以下操作

if (cur_temp < temp_low_thresh) {
       /* Enable 1GHz if it was disabled */
       opp = dev_pm_opp_find_freq_exact(dev, 1000000000, false);
       dev_pm_opp_put(opp);
       /* just error check */
       if (!IS_ERR(opp))
               ret = dev_pm_opp_enable(dev, 1000000000);
       else
               goto try_something_else;
}
dev_pm_opp_disable

使OPP不可用于操作示例:假设如果温度超过阈值,则禁用1GHz OPP。SoC框架实现可能会选择执行以下操作

if (cur_temp > temp_high_thresh) {
       /* Disable 1GHz if it was enabled */
       opp = dev_pm_opp_find_freq_exact(dev, 1000000000, true);
       dev_pm_opp_put(opp);
       /* just error check */
       if (!IS_ERR(opp))
               ret = dev_pm_opp_disable(dev, 1000000000);
       else
               goto try_something_else;
}

5. OPP数据检索函数

由于OPP库抽象了OPP信息,因此有必要提供一组从dev_pm_opp结构中提取信息的函数。使用搜索函数检索OPP指针后,SoC框架可以使用以下函数来检索OPP层内部表示的信息。

dev_pm_opp_get_voltage

检索opp指针表示的电压。示例:在cpufreq转换到不同频率时,SoC框架需要使用稳压器框架将OPP表示的电压设置为提供电压的电源管理芯片

soc_switch_to_freq_voltage(freq)
{
       /* do things */
       opp = dev_pm_opp_find_freq_ceil(dev, &freq);
       v = dev_pm_opp_get_voltage(opp);
       dev_pm_opp_put(opp);
       if (v)
               regulator_set_voltage(.., v);
       /* do other things */
}
dev_pm_opp_get_freq

检索opp指针表示的频率。示例:假设SoC框架使用了一些辅助函数,我们可以传递opp指针而不是传递其他参数来处理相当多的数据参数

soc_cpufreq_target(..)
{
       /* do things.. */
        max_freq = ULONG_MAX;
        max_opp = dev_pm_opp_find_freq_floor(dev,&max_freq);
        requested_opp = dev_pm_opp_find_freq_ceil(dev,&freq);
        if (!IS_ERR(max_opp) && !IS_ERR(requested_opp))
               r = soc_test_validity(max_opp, requested_opp);
        dev_pm_opp_put(max_opp);
        dev_pm_opp_put(requested_opp);
       /* do other things */
}
soc_test_validity(..)
{
        if(dev_pm_opp_get_voltage(max_opp) < dev_pm_opp_get_voltage(requested_opp))
                return -EINVAL;
        if(dev_pm_opp_get_freq(max_opp) < dev_pm_opp_get_freq(requested_opp))
                return -EINVAL;
       /* do things.. */
}
dev_pm_opp_get_opp_count

检索设备可用的opp数量示例:假设SoC中的协处理器需要知道表中的可用频率,则主处理器可以按以下方式通知

soc_notify_coproc_available_frequencies()
{
       /* Do things */
       num_available = dev_pm_opp_get_opp_count(dev);
       speeds = kcalloc(num_available, sizeof(u32), GFP_KERNEL);
       /* populate the table in increasing order */
       freq = 0;
       while (!IS_ERR(opp = dev_pm_opp_find_freq_ceil(dev, &freq))) {
               speeds[i] = freq;
               freq++;
               i++;
               dev_pm_opp_put(opp);
       }

       soc_notify_coproc(AVAILABLE_FREQs, speeds, num_available);
       /* Do other things */
}

6. 数据结构

通常,SoC包含多个可变的电压域。每个域由一个设备指针表示。与OPP的关系可以表示如下

SoC
 |- device 1
 |    |- opp 1 (availability, freq, voltage)
 |    |- opp 2 ..
 ...  ...
 |    `- opp n ..
 |- device 2
 ...
 `- device m

OPP库维护一个内部列表,SoC框架会填充该列表,并由上述各种函数访问。但是,表示实际OPP和域的结构是OPP库内部的,以便允许适合跨系统可重用的抽象。

struct dev_pm_opp

OPP库的内部数据结构,用于表示OPP。除了频率,电压,可用性信息外,它还包含OPP库运行所需的内部帐簿信息。此结构的指针会提供回给用户(例如SoC框架),以用作OPP层交互中OPP的标识符。

警告

struct dev_pm_opp指针不应由用户解析或修改。实例的默认值由dev_pm_opp_add填充,但是OPP的可用性可以通过dev_pm_opp_enable/disable函数修改。

struct device

这用于将域标识到OPP层。设备的性质及其实现留给OPP库的用户(例如SoC框架)。

总体而言,在一个简单的视图中,数据结构操作表示如下

Initialization / modification:
            +-----+        /- dev_pm_opp_enable
dev_pm_opp_add --> | opp | <-------
  |         +-----+        \- dev_pm_opp_disable
  \-------> domain_info(device)

Search functions:
             /-- dev_pm_opp_find_freq_ceil  ---\   +-----+
domain_info<---- dev_pm_opp_find_freq_exact -----> | opp |
             \-- dev_pm_opp_find_freq_floor ---/   +-----+

Retrieval functions:
+-----+     /- dev_pm_opp_get_voltage
| opp | <---
+-----+     \- dev_pm_opp_get_freq

domain_info <- dev_pm_opp_get_opp_count