第19章 Linux电源管理的系统架构和驱动之OPP

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的频率表*/

                result = dev_pm_opp_init_cpufreq_table(mpu_dev, &freq_table);
                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


猜你喜欢

转载自blog.csdn.net/xiezhi123456/article/details/80620238
今日推荐