之前在做高通平台CPU性能和温度的控制模块,Thermal-Engine 的策略调整.
通过温度的变化范围来控制CPU Cluster 0 和Cluster 1 的频率变化.
那现在我们从底层出发,来研究CPU频率 how to change .
该模块的分析先从代码开始,分析Cpufreq ,cpuidle 的架构.
下面是高通平台MSM8939 自定义的 频率表.
qcom,msm-cpufreq {
compatible = "qcom,msm-cpufreq";
clock-names = "l2_clk", "cpu0_clk", "cpu1_clk", "cpu2_clk",
"cpu3_clk", "cpu4_clk", "cpu5_clk",
"cpu6_clk", "cpu7_clk";
clocks = <&clock_cpu clk_a53ssmux_cci>,
<&clock_cpu clk_a53ssmux_bc>,
<&clock_cpu clk_a53ssmux_bc>,
<&clock_cpu clk_a53ssmux_bc>,
<&clock_cpu clk_a53ssmux_bc>,
<&clock_cpu clk_a53ssmux_lc>,
<&clock_cpu clk_a53ssmux_lc>,
<&clock_cpu clk_a53ssmux_lc>,
<&clock_cpu clk_a53ssmux_lc>;
qcom,governor-per-policy;
qcom,cpufreq-table-0 =
< 200000 >,
< 345600 >,
< 400000 >,
< 533330 >,
< 800000 >,
< 960000 >,
< 1113600 >,
< 1344000 >,
< 1459200 >,
< 1497600 >,
< 1651200 >;
qcom,cpufreq-table-4 =
< 200000 >,
< 249600 >,
< 400000 >,
< 499200 >,
< 800000 >,
< 998400 >,
< 1113600 >,
< 1209600 >;
};
上面我们可以看到频率从200M到1.2G 几个离散的频点. 并且有2个单独core的频率表.
qcom,cpufreq-table-4 和 qcom,cpufreq-table-0
static struct of_device_id match_table[] = {
{ .compatible = "qcom,msm-cpufreq" },// ID匹配上面dtsi 中定义的table 数组.
{}
};
static struct platform_driver msm_cpufreq_plat_driver = {
.driver = {
.name = "msm-cpufreq",
.of_match_table = match_table,
.owner = THIS_MODULE,
},
};
下面来看看probe 函数的执行过程.
static int __init msm_cpufreq_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
char clk_name[] = "cpu??_clk";
char tbl_name[] = "qcom,cpufreq-table-??";
struct clk *c;
int cpu;
struct cpufreq_frequency_table *ftbl;
l2_clk = devm_clk_get(dev, "l2_clk");
if (IS_ERR(l2_clk))
l2_clk = NULL;
//获取上面平台定义的clk , 一共有9个,因为是八核心U.
/* clock-names = "l2_clk", "cpu0_clk", "cpu1_clk", "cpu2_clk", "cpu3_clk", "cpu4_clk", "cpu5_clk", "cpu6_clk", "cpu7_clk"; */
for_each_possible_cpu(cpu) {
snprintf(clk_name, sizeof(clk_name), "cpu%d_clk", cpu);
c = devm_clk_get(dev, clk_name);
if (IS_ERR(c))
return PTR_ERR(c);
cpu_clk[cpu] = c;
}
hotplug_ready = true;
/* Use per-policy governor tunable for some targets */
if (of_property_read_bool(dev->of_node, "qcom,governor-per-policy"))
msm_cpufreq_driver.flags |= CPUFREQ_HAVE_GOVERNOR_PER_POLICY;
/* Parse commong cpufreq table for all CPUs */ 尝试获取common 就是普通的频率表
ftbl = cpufreq_parse_dt(dev, "qcom,cpufreq-table", 0);
if (!IS_ERR(ftbl)) {
for_each_possible_cpu(cpu)
per_cpu(freq_table, cpu) = ftbl;
return 0;
}
/* * No common table. Parse individual tables for each unique * CPU clock. */
// 如果没有普通的频率表, 尝试针对每个唯一类型的CPU时钟获取独享的 CPU tables
for_each_possible_cpu(cpu) {
snprintf(tbl_name, sizeof(tbl_name),
"qcom,cpufreq-table-%d", cpu);
ftbl = cpufreq_parse_dt(dev, tbl_name, cpu);
/* CPU0 must contain freq table */
if (cpu == 0 && IS_ERR(ftbl)) {
dev_err(dev, "Failed to parse CPU0's freq table\n");
return PTR_ERR(ftbl);
}
if (cpu == 0) {
per_cpu(freq_table, cpu) = ftbl;
continue;
}
if (cpu_clk[cpu] != cpu_clk[cpu - 1] && IS_ERR(ftbl)) {
dev_err(dev, "Failed to parse CPU%d's freq table\n",
cpu);
return PTR_ERR(ftbl);
}
/* Use previous CPU's table if it shares same clock */
// 判断是否有冲突的CLK配置
if (cpu_clk[cpu] == cpu_clk[cpu - 1]) {
if (!IS_ERR(ftbl)) {
dev_warn(dev, "Conflicting tables for CPU%d\n",
cpu);
kfree(ftbl);
}
ftbl = per_cpu(freq_table, cpu - 1);
}
per_cpu(freq_table, cpu) = ftbl;
}
return 0;
}
从上面的probe来看.基本就是获取了频率 table 和clk资源.
frequency table是CPU core可以正确运行的一组频率/电压组合,一般情况下,会在项目启动的初期,通过“try频点”的方法,确定出稳定性、通用性都符合要求的频点。
frequency table之所以存在的一个思考点是:table是频率和电压之间的一个一一对应的组合,因此cpufreq framework只需要关心频率,所有的策略都称做“调频”策略。而cpufreq driver可以在“调频”的同时,通过table取出和频率对应的电压,进行修改CPU core电压,实现“调压”的功能。
static struct notifier_block msm_cpufreq_pm_notifier = {
.notifier_call = msm_cpufreq_pm_event,
};
static struct freq_attr *msm_freq_attr[] = {
&cpufreq_freq_attr_scaling_available_freqs,
NULL,
};
static struct cpufreq_driver msm_cpufreq_driver = {
/* lps calculations are handled here. */
.flags = CPUFREQ_STICKY | CPUFREQ_CONST_LOOPS,
.init = msm_cpufreq_init,
.verify = msm_cpufreq_verify,
.target = msm_cpufreq_target,
.get = msm_cpufreq_get_freq,
.name = "msm",
.attr = msm_freq_attr,
};
static int __init msm_cpufreq_register(void)
{
int cpu, rc;
// 设置每个U的状态为非挂起.
for_each_possible_cpu(cpu) {
mutex_init(&(per_cpu(cpufreq_suspend, cpu).suspend_mutex));
per_cpu(cpufreq_suspend, cpu).device_suspended = 0;
}
// 注册driver
rc = platform_driver_probe(&msm_cpufreq_plat_driver,
msm_cpufreq_probe);
if (rc < 0) {
/* Unblock hotplug if msm-cpufreq probe fails */
unregister_hotcpu_notifier(&msm_cpufreq_cpu_notifier);
for_each_possible_cpu(cpu)
mutex_destroy(&(per_cpu(cpufreq_suspend, cpu).
suspend_mutex));
return rc;
}
// 通过notifer 机制注册 通知.
register_pm_notifier(&msm_cpufreq_pm_notifier);
// 向cpufreq 的core层注册driver,相当于向bus 注册driver,然后会自动匹配设备 ,也就是policy设备.
return cpufreq_register_driver(&msm_cpufreq_driver);
}