linux opp怎么使用

简介Operating Performance Points (OPP)

SoC是高度集成的,不是SoC中的所有模块需要一直以最高的频率运作。来为了便于实现这一点,SoC中的子模块被分组到不同的域中,允许一些域运行在较低的电压和频率,而其他域运行在更高的电压/频率对。

由频率和电压对组成的离散元组的集合称为工作性能点(opp)。

例如:让我们考虑一个支持以下功能的MPU设备:
{300MHz,最小电压1V},
{800MHz,最小电压1.2V},
{最低电压1.3V时1GHz},

我们可以用{Hz, uV}元组来表示这三个opp:

{300000000, 1000000}
{800000000, 1200000}
{1000000000, 1300000}

OPP API接口

OPP库提供了一组辅助函数来组织和查询OPP信息。库位于drivers/opp/目录中,头文件位于include/linux/pm_opp.h中。可以通过从电源管理菜单CONFIG菜单启用CONFIG_pm_opp来启用opp库。某些SoC,如Texas Instrument的OMAP框架,允许在不需要cpufreq的情况下选择性地在某个OPP启动。

OPP层期望每个域由一个唯一的设备指针来表示。SoC框架在OPP层为每个设备注册一组初始OPP。这个列表的长度一般比较小,通常每个设备大约5个。这个初始列表包含一组 OPP,框架希望系统默认情况下能够安全地启用这些 OPP。

通常使用opp的方式有以下几步:
a. 用户为设备(比如CPU)配置/注册一些默认的opp信息。
b. Soc会根据具体的运行情况,通过opp层去改变/查询设备的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。
示例用法:热管理或其他特殊情况,SoC框架可能选择禁用更高频率的OPP以安全地继续操作,直到该OPP可以在可能的情况下重新启用。

警告:如果设备调用了dev_pm_opp_enable/deisable函数,则OPP库的用户应使用get_opp_count刷新其可用性计数,在进行这些操作的情况下,需要注意刷新cpufreq表。

如何使用OPP

OPP列表初始化

迭代调用dev_ pm_opp_dd 函数为每个设备添加 OPP列表。每个设备的opp个数最好小于5个。

OPP是用频率和电压定义的。一旦完成添加,OPP被认为是可用的,可以用dev_pm_opp_enable/disable函数来控制其可用性。OPP库内部用dev_pm_opp结构体存储并管理这些信息。

注意:不要在中断上下文中使用此函数。

例如,多次调用dev_pm_opp_add为mpu_dev添加多个opp。

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 */
}

OPP查询获取函数

cpufreq等高层框架对频率进行操作,为了将频率映射到相应的OPP,OPP库提供了便利的函数来搜索OPP库内部管理的OPP链表。这些搜索函数如果找到匹配的OPP,将返回指向该OPP的指针,否则返回错误。这些错误预计由标准的错误检查,如IS_ERR()来处理,并由调用者采取适当的行动。

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

函数 作用
dev_pm_opp_find_freq_ceil, dev_pm_opp_find_freq_floor} 查找某个频率附近的最高频率,或者最低频率
dev_pm_opp_find_freq_exact 此功能对于启用默认情况下不可用的OPP特别有用

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 */
}

OPP可用性控制功能

dev_pm_opp_enable:使OPP可用。示例:假设只有当SoC温度低于某个阈值时,1GHz OPP才可用

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。

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;
}

OPP数据检索功能

dev_pm_opp_get_voltage:检索opp指针表示的电压。示例:在cpufreq转换到不同频率时,需要使用regulator框架将由OPP表示的电压来配置到pmic:

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 = kzalloc(sizeof(u32) * num_available, 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 */
}

OPP数据结构

通常,一个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的标识符。

警告:结构体dev_pm_opp的指针不应该由用户解析或修改。一个实例的默认值由dev_pm_opp_add填充,但OPP的可用性由dev_pm_opp_enable/disable函数修改。

struct device
这用于向OPP层标识一个域。设备的性质和它的实现是由OPP库的用户决定的,如SoC框架。
总体来说,以一个简化的视角看,对数据结构的操作可以描述为下面各图::

初始化和修改相关:
            +-----+        /- dev_pm_opp_enable
dev_pm_opp_add --> | opp | <-------
  |         +-----+        \- dev_pm_opp_disable
  \-------> domain_info(device)

查找函数:
             /-- dev_pm_opp_find_freq_ceil  ---\   +-----+
domain_info<---- dev_pm_opp_find_freq_exact -----> | opp |
             \-- dev_pm_opp_find_freq_floor ---/   +-----+

检索函数:
+-----+     /- dev_pm_opp_get_voltage
| opp | <---
+-----+     \- dev_pm_opp_get_freq

domain_info <- dev_pm_opp_get_opp_count

OPP的DTS配置

operating-points 和 operating-points-v2区别

在 Linux 内核中,operating-points 和 operating-points-v2 是设备树(Device Tree)中用于描述设备的电源和性能配置的属性。

operating-points:这是旧版的设备树属性,用于描述设备的电源和性能配置。它使用一个包含多个键值对的列表,其中每个键值对表示一个操作点(operating point)。操作点描述了设备在不同工作状态下的电压和频率配置,以及可能的功耗和性能特性。每个操作点由电压(Voltage)和频率(Frequency)组成。

operating-points-v2:这是新版的设备树属性,引入了更灵活的配置方式。它使用一个包含多个子节点的列表,其中每个子节点表示一个操作点。每个操作点可以包含各种属性,如电压、频率、功耗、性能指标、温度等。这样可以更精确地描述设备在不同工作状态下的配置,并支持更复杂的配置场景。

总结来说,operating-points-v2 是对 operating-points 的扩展和改进。它提供更丰富的属性和配置选项,可以更准确地描述设备的电源和性能配置。对于新的设备树,特别是在需要更精细配置的场景下,推荐使用 operating-points-v2。但在一些老的设备树和兼容性要求较高的情况下,可能仍然会使用 operating-points。

怎么配置DTS

https://www.kernel.org/doc/Documentation/devicetree/bindings/opp/opp-v2.yaml

调试

/sys/kernel/debug/opp
只能查看相关opp的信息,比如

节点 含义 DTS必须项
available 是否可用 Y
level Performance level N
rate_hz 频率 Y
turbo 是否是turbo频率 N
clock_latency_ns 准到此opp需要的硬件latency N
of_name dts的节点名 Y
supply-0 电压大小 N
dynamic 是否是DTS中的配置 N
performance_state 是否是最大性能 N
suspend 是否是system suspend时使用的频率 N

Ref

https://docs.kernel.org/power/opp.html
http://www.wowotech.net/pm_subsystem/pm_opp.html

posted @ 2023-05-29 20:48  zephyr~  阅读(566)  评论(0编辑  收藏  举报