19.6 OPP
如今的SoC一般包含很多集成组件,在系统运行过程中,并不需要所有的模块都运行于最高频率和最高性能。在SoC内,某些domain可以运行在较低的频率和电压下,而其他domain可以运行在较高的频率和电压下,某个domain所支持的<频率,电压>对的集合被称为Operating Performance Point,缩写为OPP。
TI OMAP CPUFreq驱动的底层就使用OPP机制来获取CPU所支持的频率和电压列表。在开机的过程中,TI OMAP芯片会注册针对CPU设备的OPP表,如代码清单19.10所示。
代码清单19.10 TI OMAP4 CPU的OPP表
arch/arm/mach-omap2/opp4xxx_data.c
static struct omap_opp_def __initdata omap446x_opp_def_list[] = {
/* MPU OPP1 - OPP50 */
OPP_INITIALIZER("mpu", true, 350000000, OMAP4460_VDD_MPU_OPP50_UV),
/* MPU OPP2 - OPP100 */
OPP_INITIALIZER("mpu", true, 700000000, OMAP4460_VDD_MPU_OPP100_UV),
/* MPU OPP3 - OPP-Turbo */
OPP_INITIALIZER("mpu", true, 920000000, OMAP4460_VDD_MPU_OPPTURBO_UV),
/*
* MPU OPP4 - OPP-Nitro + Disabled as the reference schematics
* recommends TPS623631 - confirm and enable the opp in board file
* XXX: May be we should enable these based on mpu capability and
* Exception board files disable it...
*/
OPP_INITIALIZER("mpu", false, 1200000000, OMAP4460_VDD_MPU_OPPNITRO_UV),
/* MPU OPP4 - OPP-Nitro SpeedBin */
OPP_INITIALIZER("mpu", false, 1500000000, OMAP4460_VDD_MPU_OPPNITRO_UV),
/* L3 OPP1 - OPP50 */
OPP_INITIALIZER("l3_main_1", true, 100000000, OMAP4460_VDD_CORE_OPP50_UV),
/* L3 OPP2 - OPP100 */
OPP_INITIALIZER("l3_main_1", true, 200000000, OMAP4460_VDD_CORE_OPP100_UV),
/* IVA OPP1 - OPP50 */
OPP_INITIALIZER("iva", true, 133000000, OMAP4460_VDD_IVA_OPP50_UV),
/* IVA OPP2 - OPP100 */
OPP_INITIALIZER("iva", true, 266100000, OMAP4460_VDD_IVA_OPP100_UV),
/*
* IVA OPP3 - OPP-Turbo + Disabled as the reference schematics
* recommends Phoenix VCORE2 which can supply only 600mA - so the ones
* above this OPP frequency, even though OMAP is capable, should be
* enabled by board file which is sure of the chip power capability
*/
OPP_INITIALIZER("iva", false, 332000000, OMAP4460_VDD_IVA_OPPTURBO_UV),
/* IVA OPP4 - OPP-Nitro */
OPP_INITIALIZER("iva", false, 430000000, OMAP4460_VDD_IVA_OPPNITRO_UV),
/* IVA OPP5 - OPP-Nitro SpeedBin*/
OPP_INITIALIZER("iva", false, 500000000, OMAP4460_VDD_IVA_OPPNITRO_UV),
/* TODO: add DSP, aess, fdif, gpu */
};
/**
* omap4_opp_init() - initialize omap4 opp table
*/
int __init omap4_opp_init(void)
{
int r = -ENODEV;
if (cpu_is_omap443x())
r = omap_init_opp_table(omap443x_opp_def_list,
ARRAY_SIZE(omap443x_opp_def_list));
else if (cpu_is_omap446x())
r = omap_init_opp_table(omap446x_opp_def_list,
ARRAY_SIZE(omap446x_opp_def_list));
return r;
}
omap_device_initcall(omap4_opp_init);
譬如,当温度超过某个范围后,系统不允许1GHz的工作频率,可采用类似下面的代码实现:
if (cur_temp > temp_high_thresh) {
/* Disable 1GHz if it was enabled */
rcu_read_lock();
opp = opp_find_freq_exact(dev, 1000000000, true);//用于寻找与一个确定频率和available匹配的OPP
rcu_read_unlock();
/* just error check */
if (!IS_ERR(opp))
ret = opp_disable(dev, 1000000000);
else
goto do_something;
}
1、opp_find_freq_exact()用于查询与一个确定频率和available匹配的OPP
struct opp *opp_find_freq_exact(struct device *dev, unsigned long freq, bool available);
2、opp_find_freq_floor()用于查询小于或等于指定频率的OPP,在返回OPP的同时,从freq指针中返回实际的freq值
struct opp *opp_find_freq_floor(struct device *dev, unsigned long *freq);
3、opp_find_freq_ceil()用于查询大于或等于指定频率的OPP,在返回OPP的同时,从freq指针中返回实际的freq值
struct opp *opp_find_freq_ceil(struct device *dev, unsigned long *freq);
在频率降低的同时,支撑该频率运行所需的电压也往往可以动态调低;反之,则可能需要调高,下面这两个API分别用于获取与某OPP对应的电压和频率:
unsigned long opp_get_voltage(struct opp *opp);
unsigned long opp_get_freq(struct opp *opp);
当某CPUFreq驱动想将CPU设置为某一频率的时候,可能会同时设置电压,其代码流程为:
soc_switch_to_freq_voltage(freq)
{
/* do things */
rcu_read_lock();
opp = opp_find_freq_ceil(dev, &freq);
v = opp_get_voltage(opp);
rcu_read_unlock();
if (v)
regulator_set_voltage(.., v);
/* do other things */
}
如下API可用于获取某设备所支持的OPP的个数:
int opp_get_opp_count(struct device *dev);
TI OMAP CPUFreq驱动的底层使用OPP机制获取CPU所支持的频率和电压列表。
arch/arm/mach-omap2/opp.c
/**
* omap_init_opp_table() - Initialize opp table as per the CPU type
* @opp_def: opp default list for this silicon
* @opp_def_size: number of opp entries for this silicon
*
* Register the initial OPP table with the OPP library based on the CPU
* type. This is meant to be used only by SoC specific registration.
*/
int __init omap_init_opp_table(struct omap_opp_def *opp_def,
u32 opp_def_size)
{
.........................................................................................................................
/* Lets now register with OPP library */
for (i = 0; i < opp_def_size; i++, opp_def++) {
struct omap_hwmod *oh;
struct device *dev;
if (!opp_def->hwmod_name) {
pr_err("%s: NULL name of omap_hwmod, failing [%d].\n",
__func__, i);
return -EINVAL;
}
if (!strncmp(opp_def->hwmod_name, "mpu", 3)) {
/*
* All current OMAPs share voltage rail and
* clock source, so CPU0 is used to represent
* the MPU-SS.
*/
dev = get_cpu_device(0);
} else {
oh = omap_hwmod_lookup(opp_def->hwmod_name);
if (!oh || !oh->od) {
pr_debug("%s: no hwmod or odev for %s, [%d] cannot add OPPs.\n",
__func__, opp_def->hwmod_name, i);
continue;
}
dev = &oh->od->pdev->dev;
}
r = dev_pm_opp_add(dev, opp_def->freq, opp_def->u_volt);// 添加opp
if (r) {
dev_err(dev, "%s: add OPP %ld failed for %s [%d] result=%d\n",
__func__, opp_def->freq,
opp_def->hwmod_name, i, r);
} else {
if (!opp_def->default_available)
r = dev_pm_opp_disable(dev, opp_def->freq);
if (r)
dev_err(dev, "%s: disable %ld failed for %s [%d] result=%d\n",
__func__, opp_def->freq,
opp_def->hwmod_name, i, r);
}
}
.........................................................................................................................
}
drivers/cpufreq/omap-cpufreq.c
static int omap_cpu_init(struct cpufreq_policy *policy)
{
int result;
policy->clk = clk_get(NULL, "cpufreq_ck");
if (IS_ERR(policy->clk))
return PTR_ERR(policy->clk);
if (!freq_table) {
/* 根据注册的OPP建立 CPUFreq的频率表*/
if (result) {
dev_err(mpu_dev, "%s: cpu%d: failed creating freq table[%d]\n", __func__, policy->cpu, result);
goto fail;
}
}
atomic_inc_return(&freq_table_users);
/* FIXME: what's the actual transition time? */
result = cpufreq_generic_init(policy, freq_table, 300 * 1000);
if (!result)
return 0;
freq_table_free();
fail:
clk_put(policy->clk);
return result;
}
static int omap_target(struct cpufreq_policy *policy, unsigned int index)
{
int r, ret;
struct dev_pm_opp *opp;
unsigned long freq, volt = 0, volt_old = 0, tol = 0;
unsigned int old_freq, new_freq;
old_freq = policy->cur;
new_freq = freq_table[index].frequency;
freq = new_freq * 1000;
ret = clk_round_rate(policy->clk, freq);
if (IS_ERR_VALUE(ret)) {
dev_warn(mpu_dev,
"CPUfreq: Cannot find matching frequency for %lu\n",
freq);
return ret;
}
freq = ret;
if (mpu_reg) {
rcu_read_lock();
opp = dev_pm_opp_find_freq_ceil(mpu_dev, &freq);
if (IS_ERR(opp)) {
rcu_read_unlock();
dev_err(mpu_dev, "%s: unable to find MPU OPP for %d\n",
__func__, new_freq);
return -EINVAL;
}
volt = dev_pm_opp_get_voltage(opp); // 获取电压
rcu_read_unlock();
tol = volt * OPP_TOLERANCE / 100;
volt_old = regulator_get_voltage(mpu_reg);
}
dev_dbg(mpu_dev, "cpufreq-omap: %u MHz, %ld mV --> %u MHz, %ld mV\n",
old_freq / 1000, volt_old ? volt_old / 1000 : -1,
new_freq / 1000, volt ? volt / 1000 : -1);
/* scaling up? scale voltage before frequency */
if (mpu_reg && (new_freq > old_freq)) {
r = regulator_set_voltage(mpu_reg, volt - tol, volt + tol);
if (r < 0) {
dev_warn(mpu_dev, "%s: unable to scale voltage up.\n",
__func__);
return r;
}
}
ret = clk_set_rate(policy->clk, new_freq * 1000);
/* scaling down? scale voltage after frequency */
if (mpu_reg && (new_freq < old_freq)) {
r = regulator_set_voltage(mpu_reg, volt - tol, volt + tol);
if (r < 0) {
dev_warn(mpu_dev, "%s: unable to scale voltage down.\n",
__func__);
clk_set_rate(policy->clk, old_freq * 1000);
return r;
}
}
return ret;
}
备注:
新的驱动在相应的节点处添加operating-points属性,如imx27.dtsi中的:
cpus {
#size-cells = <0>;
#address-cells = <1>;
cpu: cpu@0 {
device_type = "cpu";
compatible = "arm,arm926ej-s";
operating-points = <
/* kHz uV */
266000 1300000
399000 1450000
>;
clock-latency = <62500>;
clocks = <&clks IMX27_CLK_CPU_DIV>;
voltage-tolerance = <5>;
};
};
如果CPUFreq的变化可以使用非常标准的regulator、clk API,甚至可以直接使用drivers/cpufreq/cpufreq-dt.c这个驱动。这样只需要在CPU节点上填充好频率电压表,然后在平台代码里面注册cpufreq-dt设备就可以了,在arch/arm/mach-imx/imx27-dt.c中可以找到类似的例子:
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <asm/mach/arch.h>
#include <asm/mach/time.h>
#include "common.h"
#include "mx27.h"
static void __init imx27_dt_init(void)
{
struct platform_device_info devinfo = { .name = "cpufreq-dt", };
of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);
platform_device_register_full(&devinfo);
}
static const char * const imx27_dt_board_compat[] __initconst = {
"fsl,imx27",
NULL
};
DT_MACHINE_START(IMX27_DT, "Freescale i.MX27 (Device Tree Support)")
.map_io = mx27_map_io,
.init_early = imx27_init_early,
.init_irq = mx27_init_irq,
.init_machine = imx27_dt_init,
.dt_compat = imx27_dt_board_compat,
MACHINE_END