cpufreq模块的主要功能是动态调整CPU的频率和电压,以在性能和功耗之间取得平衡,OPP结构允许系统根据负载需求动态调整设备的性能和功耗,以实现更高的能效。cpufreq模块负责动态调整CPU的频率和电压,而opp模块则提供了描述设备性能状态的机制,并为cpufreq模块提供了调频调压所需的频率和电压信息。这两个模块共同协作,实现了Linux内核中的动态电源管理和性能调节功能。在日常开发中,主要通过应用层的接口去完成对cpu电压和频率管理,支持的配置文件如下所示。
# 应用层访问处理
/sys/devices/system/cpu/cpu0/cpufreq # 设备时钟平吕管理目录
/sys/bus/cpu/devices/cpu0/ # 对设备路径的引用
/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq # 当前的内核设定的时钟频率
/sys/devices/system/cpu/cpu0/cpufreq/scaling_driver # 处理时钟的驱动名称
/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor # 当前的cpu工作模式
/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq # 设备允许的最大时钟
/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq # 设备允许的最小时钟
/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies # 可配置的主时钟频率
/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors # 可配置的主频工作模式
conservative - 保守模式,不支持高频运行
userspace - 用户态应用程序管理模式,用户编辑配置文件修改频率
powersave - 低功耗模式,系统尽可能以低频运行
performance - 高性能模式,系统尽可能以最高允许频率运行
/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq # 从CPU中读取的真实时钟频率
/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq # 从CPU中读取的真实允许最大时钟频率
/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq # 从CPU中读取的真实允许最小时钟频率
/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_transition_latency # cpu发生频率切换时所需要的最小时间
相关文件。
drivers/cpufreq/imx6q-cpufreq.c //管理imx6ull时钟的接口
drivers/opp/of.c //系统管理opp的接口
drivers/opp/core.c
对于imx6ull设备,通过drivers/cpufreq目录下的驱动文件获取设备树中的频率,其中设备树格式如下。
&cpu0 {
//......
operating-points = <
/* kHz uV */
900000 1275000
792000 1225000
696000 1175000
528000 1175000
396000 1025000
198000 950000
>; // operationg-points定义系统opp支持的时钟频率列表
fsl,soc-operating-points = <
/* KHz uV */
900000 1250000
792000 1175000
696000 1175000
528000 1175000
396000 1175000
198000 1175000
>; // fsl,soc-operating-points定义nxp私有的支持的时钟频率列表
};
在内核中,通过imx6q-cpufreq.c解析设备树获取支持的频率,电压范围。
//imx6ull管理时钟频率的实现
drivers/cpufreq/imx6q-cpufreq.c
// drivers/cpufreq/imx6q-cpufreq.c
// imx6q_cpufreq_probe
// 设置cpufreq时钟关键函数
static int imx6q_cpufreq_probe(struct platform_device *pdev)
{
//.....
//根据设备树cpu节点的operating-points节点,生成支持的"频率-电压"管理的opp表格
ret = dev_pm_opp_of_add_table(cpu_dev);
if (ret < 0) {
dev_err(cpu_dev, "failed to init OPP table: %d\n", ret);
goto put_reg;
}
//根据cpu标签型号,对于支持的频率-电压的opp表格进行裁剪
if (of_machine_is_compatible("fsl,imx6ul") ||
of_machine_is_compatible("fsl,imx6ull")) {
ret = imx6ul_opp_check_speed_grading(cpu_dev);
} else {
ret = imx6q_opp_check_speed_grading(cpu_dev);
}
if (ret) {
dev_err_probe(cpu_dev, ret, "failed to read ocotp\n");
goto out_free_opp;
}
//获取处理后表格中的总项数
num = dev_pm_opp_get_opp_count(cpu_dev);
if (num < 0) {
ret = num;
dev_err(cpu_dev, "no OPP table is found: %d\n", ret);
goto out_free_opp;
}
//读取此时的opp表格,写入到freq_table中
//此表格中第一行为最低频率-最低电压,最后一行为最高频率-最高电压
ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table);
if (ret) {
dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret);
goto out_free_opp;
}
//...
//获取nxp自定义的电压-频率列表资源,比对处理,保存到imx6_soc_volt列表中
prop = of_find_property(np, "fsl,soc-operating-points", NULL);
if (!prop || !prop->value)
goto soc_opp_out;
nr = prop->length / sizeof(u32);
if (nr % 2 || (nr / 2) < num)
goto soc_opp_out;
for (j = 0; j < num; j++) {
val = prop->value;
for (i = 0; i < nr / 2; i++) {
unsigned long freq = be32_to_cpup(val++);
unsigned long volt = be32_to_cpup(val++);
if (freq_table[j].frequency == freq) {
imx6_soc_volt[soc_opp_count++] = volt;
break;
}
}
dev_err(cpu_dev, "freq:%d\n", freq_table[j].frequency);
}
soc_opp_out:
/* use fixed soc opp volt if no valid soc opp info found in dtb */
if (soc_opp_count != num) {
dev_warn(cpu_dev, "can NOT find valid fsl,soc-operating-points property in dtb, use default value!\n");
for (j = 0; j < num; j++)
imx6_soc_volt[j] = PU_SOC_VOLTAGE_NORMAL;
if (freq_table[num - 1].frequency * 1000 == FREQ_1P2_GHZ)
imx6_soc_volt[num - 1] = PU_SOC_VOLTAGE_HIGH;
}
//...
// OPP要求处理后为频率增加的有序表格,则位置0频率最小,位置1频率最大
// 可以通过此方法获取最大最小频率,然后获取最大最小电压
max_freq = freq_table[--num].frequency;
opp = dev_pm_opp_find_freq_exact(cpu_dev,
freq_table[0].frequency * 1000, true);
min_volt = dev_pm_opp_get_voltage(opp);
dev_pm_opp_put(opp);
opp = dev_pm_opp_find_freq_exact(cpu_dev, max_freq * 1000, true);
max_volt = dev_pm_opp_get_voltage(opp);
dev_pm_opp_put(opp);
// 设置内核允许的最大最小电压,用于保护
ret = regulator_set_voltage_time(arm_reg, min_volt, max_volt);
if (ret > 0)
transition_latency += ret * 1000;
//注册cpufreq时钟驱动
ret = cpufreq_register_driver(&imx6q_cpufreq_driver);
if (ret) {
dev_err(cpu_dev, "failed register driver: %d\n", ret);
goto free_freq_table;
}
//...
}
// imx6ul_opp_check_speed_grading
// 检查处理时钟的函数
#define OCOTP_CFG3_6UL_SPEED_696MHZ 0x2
#define OCOTP_CFG3_6ULL_SPEED_792MHZ 0x2
#define OCOTP_CFG3_6ULL_SPEED_900MHZ 0x3
static int imx6ul_opp_check_speed_grading(struct device *dev)
{
u32 val;
int ret = 0;
// 通过读取speed_grade,获取speed grading配置
if (of_find_property(dev->of_node, "nvmem-cells", NULL)) {
ret = nvmem_cell_read_u32(dev, "speed_grade", &val);
if (ret)
return ret;
} else {
struct device_node *np;
void __iomem *base;
np = of_find_compatible_node(NULL, NULL, "fsl,imx6ul-ocotp");
if (!np)
np = of_find_compatible_node(NULL, NULL,
"fsl,imx6ull-ocotp");
if (!np)
return -ENOENT;
base = of_iomap(np, 0);
of_node_put(np);
if (!base) {
dev_err(dev, "failed to map ocotp\n");
return -EFAULT;
}
val = readl_relaxed(base + OCOTP_CFG3);
iounmap(base);
}
/*
* Speed GRADING[1:0] defines the max speed of ARM:
* 2b'00: Reserved;
* 2b'01: 528000000Hz;
* 2b'10: 696000000Hz on i.MX6UL, 792000000Hz on i.MX6ULL;
* 2b'11: 900000000Hz on i.MX6ULL only;
* We need to set the max speed of ARM according to fuse map.
*/
val >>= OCOTP_CFG3_SPEED_SHIFT;
val &= 0x3;
// 根据型号和grade, 关闭对应的时钟频率
if (of_machine_is_compatible("fsl,imx6ul")) {
if (val != OCOTP_CFG3_6UL_SPEED_696MHZ)
if (dev_pm_opp_disable(dev, 696000000))
dev_warn(dev, "failed to disable 696MHz OPP\n");
}
if (of_machine_is_compatible("fsl,imx6ull")) {
if (val != OCOTP_CFG3_6ULL_SPEED_792MHZ)
if (dev_pm_opp_disable(dev, 792000000))
dev_warn(dev, "failed to disable 792MHz OPP\n");
if (val != OCOTP_CFG3_6ULL_SPEED_900MHZ)
if (dev_pm_opp_disable(dev, 900000000))
dev_warn(dev, "failed to disable 900MHz OPP\n");
}
return ret;
}
//管理设备树中operating-points
drivers/opp/*
直接开始下一节说明: 系统时钟管理子系统