⑭tiny4412 Linux驱动开发之cpufreq子系统驱动程序

本次我们来说一下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.c
    return cpufreq_frequency_table_verify(policy, exynos_info->freq_table);
}

unsigned int
exynos_cpufreq_driver_get(unsigned int cpu)
{
    // 子系统中cpu以kHz为单位,所以除以1000
    return 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);
}
#endif

int 
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);

    // 设置两个不同频率直接调节需要的时间,单位ns
    policy->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,初始化目标板cpufreq
    if(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);
    else
        printk("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
本次先就到这吧,后边有机会再深入讲一下.

猜你喜欢

转载自blog.csdn.net/qq_23922117/article/details/80714425