本次我们来说一下CPU动态调频子系统.

首先来看一下三星Exynos 4412的datasheet,如下:

上图就是Exynos 4412的时钟分布图,可以看到CPU的频率可以在1.4GHz~200MHz之间调整,频率的调整意味着功耗的变化,从datasheet上可以看到,CPU电压的幅度变化是0.875V~1.30V,也就是工作在200MHz时,电压为0.875V,工作在1.4G时,则电压为1.30V,工作800MHz时,电压为1.0V,1GHz时,电压为1.1V,从这个现象,我们可以获得这样的启发,当电流固定之后,我们通过降低电压的方式,可以功耗,这就有了现在器件电压的发展,从最初的51单片机的电压是5v,后边发展了32位机,比如STM32,电压则降为了3.3V,再到Exynos 4412的1.3V,上次参加一个电源大会,已经有一些采用0.8v了.

然后,回到主题,CPU动态调频可以达到相对较好的功耗比,所以,动态调频技术应运而生,而不同的CPU厂商,由于技术能力不同,架构差异等等多方面原因,可能导致一些厂商的CPU动态调频技术会后所区别,少了还好,要是多了,就会出现乱套的情况,比如这家的调频代码放在通用字符设备,那个放在杂项字符设备,,另一个放在块设备里,为了对这种情况进行有效的管控,Linux cpufreq子系统就产生了,专门用来管理CPU动态调频技术,通过驱动框架,把通用驱动部分和CPU具体驱动分离开来,这样,对上层用户就可以统一接口,CPU厂商只需实现,特定CPU的部分即可.本次,我们就来实现Exynos 4412的动态调频代码.下面是子系统的部分驱动架构.

从代码角度大体上分为3层,1,驱动适配器层(特定厂商CPU相关代码,exynos-cpufreq.c), 2,驱动核心层(cpufreq.c), 3,驱动控制层(常见的有6种:cpufreq_conservative.c, cpufreq_interactive.c, cpufreq_performance.c等等一些模式,还有一些其它代码没列出来).其中1,驱动适配器层,我们代码写代码实现,3,一些控制模式我们下面来讲一下:

1.performance

顾名思义只注重效率,将CPU频率固定工作在其支持的最高运行频率上,而不动态调节。

2.powersave

将CPU频率设置为最低的所谓“省电”模式,CPU会固定工作在其支持的最低运行频率上。因此这两种governors 都属于静态governor,即在使用它们时CPU 的运行频率不会根据系统运行时负载的变化动态作出调整。这两种governors 对应的是两种极端的应用场景,使用performance governor 是对系统高性能的最大追求,而使用powersave governor 则是对系统低功耗的最大追求。

3.Userspace

最早的cpufreq 子系统通过userspace governor为用户提供了这种灵活性。系统将变频策略的决策权交给了用户态应用程序,并提供了相应的接口供用户态应用程序调节CPU 运行频率使用。也就是长期以来都在用的那个模式。可以通过手动编辑配置文件进行配置

4.ondemand

按需快速动态调整CPU频率, 一有cpu计算量的任务,就会立即达到最大频率运行,等执行完毕就立即回到最低频率;ondemand:userspace是内核态的检测,用户态调整,效率低。而ondemand正是人们长期以来希望看到的一个完全在内核态下工作并且能够以更加细粒度的时间间隔对系统负载情况进行采样分析的governor。 在 ondemand governor 监测到系统负载超过 up_threshold 所设定的百分比时,说明用户当前需要 CPU 提供更强大的处理能力,因此 ondemand governor 会将CPU设置在最高频率上运行。但是当 ondemand governor 监测到系统负载下降,可以降低 CPU 的运行频率时,到底应该降低到哪个频率呢? ondemand governor 的最初实现是在可选的频率范围内调低至下一个可用频率,例如 CPU 支持三个可选频率,分别为 1.67GHz、 1.33GHz 和 1GHz ,如果 CPU 运行在 1.67GHz 时 ondemand governor 发现可以降低运行频率,那么 1.33GHz 将被选作降频的目标频率。

5.conservative

与ondemand不同,平滑地调整CPU频率,频率的升降是渐变式的,会自动在频率上下限调整,和ondemand的区别 在于它会按需分配频率,而不是一味追求最高频率。

6,interactive

类似于conservative吧,升降频变化幅度较大.

下面,我们来实现一下驱动适配器层的代码,因为已经写了很多注释,所以就不讲了:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/cpufreq.h>
#include <linux/suspend.h>
#include <linux/regulator/consumer.h>#include <mach/cpufreq.h>#include <plat/cpu.h>// 构建设备对象
struct exynos_cpufreq_object{struct cpufreq_freqs freqs;unsigned int locking_frequency;bool frequency_locked;int lock_count;
};struct exynos_dvfs_info *exynos_info;
struct regulator *arm_regulator;
struct exynos_cpufreq_object *cpufreq_obj = NULL;
static DEFINE_MUTEX(cpufreq_lock);int
cpufreq_frequency_table_target_old(struct cpufreq_policy *policy,struct cpufreq_frequency_table *table,unsigned int target_freq,unsigned int relation,unsigned int *index)
{struct cpufreq_frequency_table optimal = {.index      = ~0,.frequency  = 0,}, suboptimal = {.index      = ~0,.frequency  = 0,};unsigned int i;switch(relation){case CPUFREQ_RELATION_H:suboptimal.frequency = ~0;break;case CPUFREQ_RELATION_L:optimal.frequency = ~0;break;default:break;}if(!cpu_online(policy->cpu))return -EINVAL;for(i = 0; (table[i].frequency != CPUFREQ_TABLE_END); i++){unsigned int freq = table[i].frequency;if(CPUFREQ_ENTRY_INVALID == freq)continue;switch(relation){case CPUFREQ_RELATION_H:if(freq <= target_freq){if(freq >= optimal.frequency){optimal.frequency = freq;optimal.index     = i;}}else{if(freq <= suboptimal.frequency){suboptimal.frequency = freq;suboptimal.index     = i;}}break;case CPUFREQ_RELATION_L:if(freq >= target_freq){if(freq <= optimal.frequency){optimal.frequency = freq;optimal.index     = i;}}else{if(freq >= suboptimal.frequency){suboptimal.frequency = freq;suboptimal.index     = i;}}break;default: break;}}if(optimal.index > i){if(suboptimal.index > i)return -EINVAL;*index = suboptimal.index;}else{*index = optimal.index;}return 0;
}int
exynos_cpufreq_driver_target(struct cpufreq_policy *policy,unsigned int target_freq,unsigned int relation)
{unsigned int index, old_index, arm_volt, safe_arm_volt = 0;int ret = 0;struct cpufreq_frequency_table *freq_table = exynos_info->freq_table;unsigned int *volt_table = exynos_info->volt_table;unsigned int mpll_freq_khz = exynos_info->mpll_freq_khz;mutex_lock(&cpufreq_lock);cpufreq_obj->freqs.old = policy->cur;if(cpufreq_obj->frequency_locked && (target_freq != cpufreq_obj->locking_frequency)){ret = -EAGAIN;goto out;}if(cpufreq_frequency_table_target_old(policy, freq_table,cpufreq_obj->freqs.old, relation, &old_index)){ret = -EINVAL;goto out;}// 获取目标板的最大或者最小频率,根据传参决定if(cpufreq_frequency_table_target(policy, freq_table,target_freq, relation, &index)){ret = -EINVAL;goto out;}cpufreq_obj->freqs.new = freq_table[index].frequency;cpufreq_obj->freqs.cpu = policy->cpu;// ARM时钟源将重APLL改变为MPLL,需要控制regulator改变电压if(exynos_info->need_apll_change != NULL){if(exynos_info->need_apll_change(old_index, index)&& (freq_table[index].frequency < mpll_freq_khz)&& (freq_table[old_index].frequency < mpll_freq_khz))safe_arm_volt = volt_table[exynos_info->pll_safe_idx];}arm_volt = volt_table[index];cpufreq_notify_transition(&cpufreq_obj->freqs, CPUFREQ_PRECHANGE);// 如果新的频率比当前的频率高if((cpufreq_obj->freqs.new > cpufreq_obj->freqs.old) && !safe_arm_volt){// 通过升高电压的方式提升频率regulator_set_voltage(arm_regulator, arm_volt, arm_volt);}if(safe_arm_volt)regulator_set_voltage(arm_regulator, safe_arm_volt, safe_arm_volt);if(cpufreq_obj->freqs.new != cpufreq_obj->freqs.old)exynos_info->set_freq(old_index, index);cpufreq_notify_transition(&cpufreq_obj->freqs, CPUFREQ_POSTCHANGE);// 如果新的频率比之前的频率低if((cpufreq_obj->freqs.new < cpufreq_obj->freqs.old)|| ((cpufreq_obj->freqs.new > cpufreq_obj->freqs.old) && safe_arm_volt)){// 频率改变之后,降低电压regulator_set_voltage(arm_regulator, arm_volt, arm_volt);}out:mutex_unlock(&cpufreq_lock);return ret;
}int
exynos_cpufreq_driver_verify(struct cpufreq_policy *policy)
{// 关联的freq_table在exynos4x12-cpufreq.creturn cpufreq_frequency_table_verify(policy, exynos_info->freq_table);
}unsigned int
exynos_cpufreq_driver_get(unsigned int cpu)
{// 子系统中cpu以kHz为单位,所以除以1000return clk_get_rate(exynos_info->cpu_clk) / 1000;
}#ifdef CONFIG_PM
int
exynos_cpufreq_driver_suspend(struct cpufreq_policy *policy)
{return 0;
}int
exynos_cpufreq_driver_resume(struct cpufreq_policy *policy)
{return 0;
}
#else
#define exynos_cpufreq_driver_suspend   NULL
#define exynos_cpufreq_driver_resume    NULL
#endif#if 0
void exynos_cpufreq_lock_freq(bool lock_en, unsigned int freq)
{struct cpufreq_policy *policy = cpufreq_cpu_get(0);static unsigned int saved_freq;static unsigned int temp;mutex_lock(&cpufreq_lock);if(lock_en){cpufreq_obj->lock_count++;if(cpufreq_obj->lock_count > 1)goto out;if(cpufreq_obj->frequency_locked)goto out;if(freq){cpufreq_obj->frequency_locked = true;temp = cpufreq_obj->locking_frequency;cpufreq_obj->locking_frequency = freq;saved_freq = exynos_cpufreq_driver_get(0);mutex_unlock(&cpufreq_lock);exynos_cpufreq_driver_target(policy, freq, CPUFREQ_RELATION_H);mutex_lock(&cpufreq_lock);}}else{cpufreq_obj->lock_count--;if(cpufreq_obj->lock_count > 0)goto out;cpufreq_obj->locking_frequency = saved_freq;mutex_unlock(&cpufreq_lock);exynos_cpufreq_driver_target(policy, saved_freq, CPUFREQ_RELATION_H);mutex_lock(&cpufreq_lock);cpufreq_obj->locking_frequency = temp;cpufreq_obj->frequency_locked = false;}out:mutex_unlock(&cpufreq_lock);
}
#endifint
exynos_cpufreq_pm_notifier(struct notifier_block *nb, unsigned long pm_event, void *param)
{struct cpufreq_policy *policy = cpufreq_cpu_get(0);static unsigned int saved_frequency;unsigned int temp;mutex_lock(&cpufreq_lock);switch(pm_event){case PM_SUSPEND_PREPARE:if(cpufreq_obj->frequency_locked)goto out;cpufreq_obj->frequency_locked = true;if(cpufreq_obj->locking_frequency){saved_frequency = exynos_cpufreq_driver_get(0);mutex_unlock(&cpufreq_lock);exynos_cpufreq_driver_target(policy, cpufreq_obj->locking_frequency, CPUFREQ_RELATION_H);mutex_lock(&cpufreq_lock);}            break;case PM_POST_SUSPEND:if(saved_frequency){/*** 当frequency_locked为真,仅仅只有locking_frequency对于target()是有效的.为了* 使用saved_frequency同时保持frequency_locked,我们临时重写locking_frequency*/temp = cpufreq_obj->locking_frequency;cpufreq_obj->locking_frequency = saved_frequency;mutex_unlock(&cpufreq_lock);exynos_cpufreq_driver_target(policy, cpufreq_obj->locking_frequency, CPUFREQ_RELATION_H);mutex_lock(&cpufreq_lock);cpufreq_obj->locking_frequency = temp;}cpufreq_obj->frequency_locked = false;break;default:break;}out:mutex_unlock(&cpufreq_lock);return NOTIFY_OK;
}struct notifier_block exynos_cpufreq_nb = {.notifier_call = exynos_cpufreq_pm_notifier,
};int
exynos_cpufreq_driver_init(struct cpufreq_policy *policy)
{// 设置频率的初始值,当前值,最大值和最小值都相等policy->cur = policy->min = policy->max = exynos_cpufreq_driver_get(policy->cpu);// 获取频率表cpufreq_frequency_table_get_attr(exynos_info->freq_table, policy->cpu);cpufreq_obj->locking_frequency = exynos_cpufreq_driver_get(0);// 设置两个不同频率直接调节需要的时间,单位nspolicy->cpuinfo.transition_latency = 100000;/*** Exynos4412 CPU有两个核的频率不能独立地修改,同时,* 这个CPU的所有CPU的频率是要一样的,不能异频,* 目前大小核架构是可以异频工作的.*/if(1 == num_online_cpus()){cpumask_copy(policy->related_cpus, cpu_possible_mask);cpumask_copy(policy->cpus, cpu_online_mask);}else{cpumask_setall(policy->cpus);}return cpufreq_frequency_table_cpuinfo(policy, exynos_info->freq_table);
}struct cpufreq_driver exynos_cpufreq_driver = {.name       = "exynos_cpufreq",.flags      = CPUFREQ_STICKY,.init       = exynos_cpufreq_driver_init,.verify     = exynos_cpufreq_driver_verify,.target     = exynos_cpufreq_driver_target,.get        = exynos_cpufreq_driver_get,.suspend    = exynos_cpufreq_driver_suspend,.resume     = exynos_cpufreq_driver_resume,
};static void __exit
exynos_cpufreq_exit(void)
{cpufreq_unregister_driver(&exynos_cpufreq_driver);unregister_pm_notifier(&exynos_cpufreq_nb);if(!IS_ERR(arm_regulator))regulator_put(arm_regulator);kfree(exynos_info);kfree(cpufreq_obj);
}static int __init
exynos_cpufreq_init(void)
{int ret = -1;// 1,申请设备对象cpufreq_obj = kzalloc(sizeof(struct exynos_cpufreq_object), GFP_KERNEL);if(NULL == cpufreq_obj){printk("kzalloc failed !\n");return -ENOMEM;}exynos_info = kzalloc(sizeof(struct exynos_dvfs_info), GFP_KERNEL);if(NULL == exynos_info){printk("kzalloc 2 failed !\n");goto err1;}cpufreq_obj->lock_count = 0;// 2,初始化目标板cpufreqif(soc_is_exynos4210())ret = exynos4210_cpufreq_init(exynos_info);else if(soc_is_exynos4212() || soc_is_exynos4412())ret = exynos4x12_cpufreq_init(exynos_info);else if(soc_is_exynos5250())ret = exynos5250_cpufreq_init(exynos_info);elseprintk("no target cpufreq driver !\n");if(ret)goto err1;// 3,判断设置的频率是否为空if(NULL == exynos_info->set_freq){printk("set freq is NULL !\n");goto err1;}// 4,通过regular_get获得regular结构arm_regulator = regulator_get(NULL, "vdd_arm");if(IS_ERR(arm_regulator)){printk("cpufreq init %s failed !\n", __func__);goto err1;}// 5,注册电源管理通知链register_pm_notifier(&exynos_cpufreq_nb);// 6,注册CPU freq驱动if(cpufreq_register_driver(&exynos_cpufreq_driver)){printk("exynos cpufreq %s failed !\n", __func__);goto err2;}return 0;err2:unregister_pm_notifier(&exynos_cpufreq_nb);if(!IS_ERR(arm_regulator))regulator_put(arm_regulator);
err1:if(exynos_info != NULL)kfree(exynos_info);kfree(cpufreq_obj);return -EINVAL;
}module_init(exynos_cpufreq_init);
module_exit(exynos_cpufreq_exit);MODULE_LICENSE("GPL v2");

写好了驱动之后,我们把这个驱动命名为exynos-cpufreq.c,然后替换掉drivers/cpufreq目录下的exynos-cpufreq.c,重新编译内核,然后加载新的内核,开机之后,我们对我们写的驱动程序进行测试,如下效果:

[root@tiny4412]#pwd
/sys/devices/system/cpu/cpu0/cpufreq
[root@tiny4412]#ls
affected_cpus                scaling_cur_freq
cpuinfo_cur_freq             scaling_driver
cpuinfo_max_freq             scaling_governor
cpuinfo_min_freq             scaling_max_freq
cpuinfo_transition_latency   scaling_min_freq
related_cpus                 scaling_setspeed
scaling_available_governors  stats
[root@tiny4412]#cat cpuinfo_cur_freq
200000
[root@tiny4412]#cat cpuinfo_min_freq
200000
[root@tiny4412]#cat cpuinfo_max_freq
1400000
[root@tiny4412]#cat scaling_governor
interactive
[root@tiny4412]#echo performance > scaling_governor
[root@tiny4412]#cat cpuinfo_cur_freq
1400000
[root@tiny4412]#echo conservative > scaling_governor
[root@tiny4412]#cat cpuinfo_cur_freq
600000
[root@tiny4412]#cat cpuinfo_cur_freq
400000
[root@tiny4412]#cat cpuinfo_cur_freq
300000
[root@tiny4412]#cat cpuinfo_cur_freq
200000
[root@tiny4412]#echo usersapce > scaling_governor
sh: write error: Invalid argument
[root@tiny4412]#echo userspace > scaling_governor
[root@tiny4412]#cat cpuinfo_cur_freq
200000
[root@tiny4412]#echo 1400000 > scaling_setspeed
[root@tiny4412]#cat cpuinfo_cur_freq
1400000
[root@tiny4412]#echo 700000 > scaling_setspeed
[root@tiny4412]#cat cpuinfo_cur_freq
700000
[root@tiny4412]#cd ../..
[root@tiny4412]#pwd
/sys/devices/system/cpu
[root@tiny4412]#cat online
0
[root@tiny4412]#cat offline
1-3
[root@tiny4412]#echo "1" > cpu2/online
[root@tiny4412]#cat online
0,2
[root@tiny4412]#cat offline
1,3
[root@tiny4412]#echo "1" > cpu3/online
[root@tiny4412]#cat online
0,2-3
[root@tiny4412]#cat offline
1
[root@tiny4412]#echo "1" > cpu1/online
[root@tiny4412]#cat online
0-3
[root@tiny4412]#cat offline[root@tiny4412]#

我们再来写个测试程序:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>#define GOVERNOR    "/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor"
#define CAT_CUR     "cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq"int main(void)
{int fd = -1, i = 0;char SCAL[15] = {0};fd = open(GOVERNOR, O_RDWR);if(fd < 0){perror("open failed");exit(1);}sprintf(SCAL, "performance", 11);write(fd, SCAL, 11);printf("performance\n");system(CAT_CUR);sprintf(SCAL, "conservative", 12);printf("conservative\n");write(fd, SCAL, 12);for(i = 0; i < 13; i++){system(CAT_CUR);usleep(200 * 1000);}return 0;
}

下面是效果图:

⑭tiny4412 Linux驱动开发之cpufreq子系统驱动程序相关推荐

  1. linux 串口驱动 4412,⑮tiny4412 Linux驱动开发之tty子系统(UART)驱动程序

    本次说一下tty子系统的驱动编程,因为UART相关的寄存器比较多,同时,应用比较广泛,所以本次的驱动程序量也不少,而且只是完成和特定CPU相关的一部分,通用的部分本次都没有涉及到.在写驱动之前,我们先 ...

  2. ⑨tiny4412 Linux驱动开发之1-wire子系统(DS18B20)驱动程序

    本来这次想做LCD背光灯的调节的,但是没有调通,时间很紧迫,就转向了其它东西,昨天调了一下DHT11,今天又调了一下DS18B20,还算有个安慰,本来是想用1-wire子系统做的,但是时间上有点紧,要 ...

  3. ㉕AW-A33 Linux驱动开发之audio子系统驱动程序

    在Linux源码里,Aduio这一部分现在是一个独立文件夹叫sound,在2.x的版本时,sound这个目录是在drivers里的,后来从这个里面剥离出来了,很多人不知道其中的原因,我也不知道,我们先 ...

  4. linux编译input驱动,Linux驱动开发之input子系统

    本文对mousedev.Amimouse和input子系统进行分析,旨在提纲挈领,给出它们之间的调用关系(或者说关联).阅读本文,需要与阅读Linux 2.6内核源码交叉进行,除非你是超人. 背景: ...

  5. Linux驱动开发之platform设备驱动实验【完整教程】

    为了方便驱动的编写,提高软件的重用性和跨平台性能,于是就提出了Linux驱动的分离和分层   驱动的分层,分层的目的时为了在不同的层处理不同的内容,最简单的驱动分层是input子系统负责管理所有跟输入 ...

  6. ㉔AW-H3 Linux驱动开发之HDMI驱动程序

    HDMI: High Definition Multimedia Interface,高清多媒体接口,是一种全数字化视频和声音发送接口,可以发送未压缩的音频及视频信号.HDMI有4种类型的接口,分别为 ...

  7. linux gpio设备驱动程序,嵌入式Linux设备驱动开发之:GPIO驱动程序实例-嵌入式系统-与非网...

    11.3  GPIO驱动程序实例 11.3.1  GPIO工作原理 FS2410开发板的S3C2410处理器具有117个多功能通用I/O(GPIO)端口管脚,包括GPIO 8个端口组,分别为GPA(2 ...

  8. ㉓AW-H3 Linux驱动开发之mipi camera(CSI)驱动程序

    本次说一下mipi camera的驱动开发,平台用的是全志的H3芯片,项目代号:sun8iw7p1,这次使用运行在H3上面的Ubuntu进行验证的. Linux代码:https://github.co ...

  9. 嵌入式linux设备驱动程序是,嵌入式Linux设备驱动开发之:按键驱动程序实例-嵌入式系统-与非网...

    11.6  按键驱动程序实例 11.6.1  按键工作原理 LED和蜂鸣器是最简单的GPIO的应用,都不需要任何外部输入或控制.按键同样使用GPIO接口,但按键本身需要外部的输入,即在驱动程序中要处理 ...

最新文章

  1. 数据、算法岗的几点经验分享!
  2. [code]字母重排
  3. Visual Studio 2008 每日提示(二十三)
  4. TCP/IP协议之网络链接的背后故事
  5. QT 默认环境路径配置方法
  6. 【LeetCode】回文数
  7. html选择文本框后提示消失,两种方法实现文本框输入内容提示消失
  8. P4449-于神之怒加强版【莫比乌斯反演】
  9. resteasy_Tomcat 7上具有RESTeasy JAX-RS的RESTful Web服务– Eclipse和Maven项目
  10. 考研数学三部曲之大话线性代数
  11. Android学习笔记---08_短信发送器的制作
  12. sql ddl中key_SQL DDL:SQL Server中SQL DDL命令入门
  13. c语言上机字符串,二级C语言上机题库100套(最新)
  14. 花开蝶自来——回到梦开始的地方
  15. 数据分析案例 |【01】电影数据分析
  16. 华为——IS-IS理论+实验,L1,L2,L1-2彼此之间的邻居关系建立
  17. Houdini 求中点,点连成线
  18. ABP VNext学习日记18
  19. 电脑桌面文件不见了怎么恢复?
  20. Observability:Influx

热门文章

  1. 摩尔定律即将走向终结?对未来更广阔世界影响的55个预测!
  2. 基于R语言的关联规则分析项目
  3. R语言基础数据分析—单因素方差分析
  4. 云计算入门教程普通用户
  5. Unsupervised Degradation Representation Learning for Blind Super-Resolution(基于无监督退化表示学习的盲超分辨率处理)
  6. 新概念英语第二册61-96课(转)
  7. dubbo学习笔记(一)——dubbo的作用及简单应用
  8. 嵌入式OS的现状、智能的物联网与未来的机器人
  9. C简单动态规划——爬数塔
  10. 第4章 Spring的IoC容器之BeanFactory(四)