Platform: RK3288
OS: Android 6.0
Kernel: 3.10.92
DVFS即Dynamic Voltage and Frequency Scaling.
有看到RK将DDR的控制也放在DVFS中,也就是说DDR会在系统运行时被根据不同的情况
做出不同的频率调整以降低系统功耗.DVFS驱动在dvfs.c中,这里不做描述,只要关注调用的接口即可.
相关文件:
ddr_rk32.c
ddr驱动,直接和硬件打交道.
rk3288.c
ddr控制接口.
ddr_freq.c
ddr和用户空间交互接口.
dvfs.c
dvfs驱动.
rk_system_status.c
system status控制接口.
dts:
&clk_ddr_dvfs_table {
/不同的频率需要对应不同的电压./
operating-points = <
/* KHz uV */
200000 1050000
300000 1050000
400000 1100000
533000 1150000
;
/*系统有两种控制方法:
一种是根据当前的video 分辨率(这里称作系统状态)来决定ddr freq;
还有一种就是下面的auto-freq-table,系统根据当前的loading来自动控制频率.
前者会被优先判断,当系统状态不是前者任何一种时,再选择使用后者策略。
*/
freq-table = <
/*status freq(KHz)*/
SYS_STATUS_NORMAL 400000
SYS_STATUS_SUSPEND 200000
SYS_STATUS_VIDEO_1080P 240000
SYS_STATUS_VIDEO_4K 400000
SYS_STATUS_PERFORMANCE 528000
SYS_STATUS_DUALVIEW 400000
SYS_STATUS_BOOST 324000
SYS_STATUS_ISP 400000
>;
auto-freq-table = <
240000
324000
396000
528000
>;
auto-freq=<1>;
status="okay";
};
DVFS初始化:
解析rk3288.dtsi中的dvfs node.
rk3288_dt_init_timer -> rk3288.c
of_dvfs_init -> dvfs.c
of_find_node_by_name //找到dts中的dvfs node.
for_each_available_child_of_node //依次查找dvfs下的vd node, 有vd_arm, vd_logic, vd_gpu.
of_property_read_string //获取regulator_name
vd->vd_dvfs_target = dvfs_target //vd对应的vd_dvfs_target()函数是dvfs_target, 后面会大量用到.
rk_regist_vd //注册vd到rk_dvfs_tree list中
for_each_available_child_of_node //依次查找vd node下的pd node, 有pd_core, pd_ddr, pd_vio, pd_gpu.
rk_regist_pd //注册pd到vd->pd_list中.
for_each_available_child_of_node //依次查找pd下的clk node, 有clk_core, clk_ddr, clk_gpu, aclk_vio1.
dvfs_node_parse_dt //解析clk node下的各个属性, 注意,主dts中(比如rk3288-tb_8846.dts)中也有一部分配置要一起解析.
rk_regist_clk //注册clk node到pd->clk_list中.
ddr frequnecy的初始化:
ddrfreq_init -> ddr_freq.c
clk_get_dvfs_node //获取名字是clk_core的node,dvfs.c中获取的和这个是两个变量,不是全局的。
clk_get_dvfs_node //获取名字为clk_ddr的node.
clk_enable_dvfs -> //enable ddr dvfs, clk gpu/core也会在各自驱动中相应被enable.
dvfs_regulator_get //获取对应的regulator,失败的话会enable failed.之所以把这句拿出来强调是因为项目有遇到过vd_gpu没有被开起来导致整个kernel都起不来的问题,后来发现是dts配置不对.
dvfs_clk_register_set_rate_callback //callback是ddrfreq_scale_rate_for_dvfs,后面看到set ddr rate是会被调用.
ddr.normal_rate = dvfs_clk_get_rate //系统根据不同的模式有不同ddr rate,比如video 4k rate, video 1080 rate等,这里获取ddr normal类型的rate.
rockchip_get_system_status //通过不同的system status来得知当前不同的mode, mode是指当前在待机状态,播放1080p video,4k video等.
of_init_ddr_freq_table //从dts的clk_ddr_dvfs_table node中获取"auto-freq", "auto-freq-table", "freq-table"这些配置信息,后面调整ddr rate会用上.
input_register_handler //注册一个ddr_freq_input_handler,只要匹配ddr_freq_ids变量中的flag,input事件就会调用ddr_freq_input_event(). 开机后有input事件就在boost_rate(of_init_ddr_freq_table()中赋值)和new rate取一个最大值(ddr_auto_freq()中比较),不太理解为什么input影响ddr rate选择?
misc_register(&video_state_dev); //注册一个video 字符设备,播放视频的时候通过它来改变ddr freq.
misc_register(&ddr_freq_dev); //注册一个ddr freq的字符设备,目前看到display有使用它来改变ddr freq.
kthread_create //创建名为ddrfreqd的thread,处理函数是ddrfreq_task,它会根据不同running status来调用设置ddr freq接口.
rockchip_register_system_status_notifier //设置system status时会调用此callback:ddrfreq_system_status_notifier_call,然后它会唤醒上面提到的thread ddrfreqd.
fb_register_client //callback是ddr_freq_suspend_notifier_call, display suspend/wakeup的时候也会调用它,进而唤醒ddrfreqd thread去设置ddr freq.
设置频率:
ddr freq字符设备例子:
ddr_freq_ioctl ->
wake_up(&ddr.wait) ->
ddrfreq_task ->
ddrfreq_work ->
ddrfreq_mode ->
dvfs_clk_set_rate -> dvfs.c
clk_dvfs_node->vd->vd_dvfs_target ->
dvfs_target -> //of_dvfs_init()初始化时赋值
clk_dvfs_node->clk_dvfs_target ->
ddrfreq_scale_rate_for_dvfs -> ddrfreq_init()初始化中设置
ddr_change_freq ->
_ddr_change_freq -> rk3288.c //rk3288_ddr_init()中初始化
__ddr_change_freq ->
call_with_single_cpu ->
ddr_change_freq_sram
video state字符设备控制例子(1080p):
video_state_write ->
update_video_info ->
rockchip_set_system_status -> //status是SYS_STATUS_VIDEO_1080P
rockchip_system_status_notifier_call_chain ->
ddrfreq_system_status_notifier_call ->
wake_up(&ddr.wait) ->
ddrfreq_task ->
ddrfreq_work ->
ddrfreq_mode //同上
另外,display的休眠和唤醒也会引起ddr freq的改变.
不过不管哪种方式,最终都是调用thread ddrfreqd来实现ddr freq的改变.
原作者:KrisFei