6410 中dvfs的实现

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/cpufreq.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/clk.h>

#include <asm/system.h>
#include <plat/s3c64xx-dvfs.h>

#define CPU_FREQ_EARLY_FLAG 0x100        //what this for

#if defined(CONFIG_MACH_SMDK6410)
extern int set_pmic(unsigned int pwr, unsigned int voltage);
#endif

unsigned int S3C64XX_MAXFREQLEVEL = 3;
static unsigned int s3c64xx_cpufreq_level = 3;
static char cpufreq_governor_name[CPUFREQ_NAME_LEN] = "userspace";
static char userspace_governor[CPUFREQ_NAME_LEN] = "userspace";
unsigned int s3c64xx_cpufreq_index = 0;
static DEFINE_MUTEX(dvfs_lock);
#define CLIP_LEVEL(a, b)    (a > b ? b : a)

static struct cpufreq_frequency_table freq_table_532MHZ[] = {
    {0, 532*KHZ_T},
    {1, 266*KHZ_T},
    {2, 133*KHZ_T},
#ifdef USE_DVFS_AL1_LEVEL        //AL1_LEVEL is what, never mind it, samsung's business
    {3, 133*KHZ_T},
    {4, 66*KHZ_T},
    {5, CPUFREQ_TABLE_END},
#else
    {3, 66*KHZ_T},
    {4, CPUFREQ_TABLE_END},
#endif
}

static struct cpufreq_frequency_table freq_table_800MHZ[] = {
    {0, 800*KHZ_T},
    {1, 400*KHZ_T},
    {2, 266*KHZ_T},
    {3, 133*KHZ_T},
#ifdef USE_DVFS_AL1_LEVEL
    {4, 133*KHZ_T},
    {5, (66)*KHZ_T},
    {6, CPUFREQ_TABLE_END},
#else
    {4, (66)*KHZ_T},
    {5, CPUFREQ_TABLE_END},
#endif
}

static unsigned char transition_state_800MHZ[][2] = {
    {1, 0},
    {2, 0},
    {3, 1},
    {4, 2},
#ifdef USE_DVFS_AL1_LEVEL
    {5, 3},
    {5, 4},
#else
    {4, 3},
#endif
}

static unsigned char transition_state_532MHZ[][2] = {
    {1, 0},
    {2, 0},
    {3, 1},
#ifdef USE_DVFS_AL1_LEVEL
    {4, 2},
    {4, 3},
#else
    {3, 2},
#endif
}

static const unsigned int frequency_match_532MHZ[][4] = {
    {532000, 1100, 1200, 0},
    {266000, 1050, 1200, 1},
    {133000, 1000, 1200, 2},
#ifdef USE_DVFS_AL1_LEVEL
    {133000, 1000, 1050, 3},
    {66000, 1000, 1050, 4},
#else
    {66000, 1000, 1050, 5},
#endif
}

static const unsigned int frequency_match_800MHZ[][4] = {
    {800000, 1300, 1200, 0},
    {400000, 1100, 1200, 1},
    {266000, 1050, 1200, 2},
    {133000, 1000, 1200, 3},
#ifdef USE_DVFS_AL1_LEVEL
    {133000, 1000, 1050, 4},
    {66000, 1000, 1050, 5},
#else
    {66000, 1000, 1050, 4},
#endif
}

static const unsigned int (*frequency_match[2])[4] = {
    frequency_match_532MHZ;
    frequency_match_800MHZ;
}

static unsigned char (*transition_state[2])[2] = {
    transition_state_532MHZ;
    transition_state_800MHZ;
}

static struct cpufreq_frequency_table *s3c6410_freq_table[] = {
    freq_table_532MHZ;
    freq_table_800MHZ;
}

static int dvfs_perf_lock = 0;
int dvfs_change_quick = 0;
void static sdvfs_lock(unsigned int *lock)
{
    while(*lock) {
        msleep(1);
    }
    *lock = 1;
}

void static sdvfs_unlock(unsigned int *lock)
{
    *lock = 0;
}

void set_dvfs_perf_level(void)        //seem to adjust the cpu frequency to the highest level
{
    sdvfs_lock(&dvfs_perf_lock);

    s3c64xx_cpufreq_index = 0;
    dvfs_change_quick = 1;

    sdvfs_unlock(&dvfs_perf_lock);
}
EXPORT_SYMBOL(set_dvfs_perf_level);

void set_dvfs_level(int flag)
{
    mutex_lock(&dvfs_lock);
    if(flag == 0)
#ifdef USE_DVFS_AL1_LEVEL
        s3c64xx_cpufreq_level = S3C64XX_MAXFREQLEVEL - 2;
#else
        s3c64xx_cpufreq_level = S3C64XX_MAXFREQLEVEL - 1;
#endif
    else
        s3c64xx_cpufreq_level = S3C64XX_MAXFREQLEVEL;
    mutex_unlock(&dvfs_lock);
}
EXPORT_SYMBOL(set_dvfs_level);

#ifdef USE_DVS
static unsigned int s_arm_voltage, s_int_voltage;
int set_voltage(unsigned int freq_index)
{
    static int index = 0;
    unsigned int arm_voltage, int_voltage;

    if(index == freq_index)
        return 0;

    index = freq_index;

    arm_voltage = frequency_match[S3C64XX_FREQ_TAB][index][1];
    int_voltage = frequency_match[S3C64XX_FREQ_TRB][index][2];

    if(arm_voltage != s_arm_voltage) {
        set_pmic(VCC_ARM, arm_voltage);
        s_arm_voltage = arm_voltage;
    }
    if(int_voltage != s_int_voltage) {
        set_pmic(VCC_INT, int_voltage);
        s_int_voltage = int_voltage;
    }

    return 0;
}
#endif /* USE_DVS*/

unsigned int s3c64xx_target_freq(unsigned int pred_freq,        /* this function not called in this .c, flag can only be -1 or 1 */
                int flag)
{
    int index;
    unsigned int freq;
    struct cpufreq_frequency_table *freq_tab = s3c6410_freq_table[S3C64XX_FREQ_TAB];

    if(freq_tab[0].frequency < pred_freq) {
        index = 0;
        goto *s3c64xx_target_frq_end;
    }

    if((flag != 1) && (flag != -1)) {
        printk("s3c64xx_target_frq: flag error!!!!!!!!!!!!!");
    }

    sdvfs_lock(&dvfs_perf_lock);
    index = s3c64xx_cpufreq_index;

    if(freq_tab[index].frequency == pred_freq) {
        if(flag == 1)
            index = transition_state[S3C64XX_FREQ_TAB][index][1];
        else
            index = transition_state[S3C64XX_FREQ_TAB][index][0];
    }
    else if(flag == -1) {
        index = 1;
    }
    else {
        index = 0;
    }

s3cc64xx_target_frq_end:
    mutex_lock(&dvfs_lock);
    index = CLIP_LEVEL(index, s3c64xx_cpufreq_level);
    mutex_unlock(&dvfs_lock);
    s3c64xx_cpufreq_index = index;

    freq = freq_tab[index].frequency;
    sdvfs_unlock(&dvfs_perf_lock);
    return freq;
}

int s3c64xx_target_freq_index(unsigned int freq)
{
    int index = 0;

    struct cpufreq_frequency_table *freq_tab = s3c6410_freq_table[S3C64XX_FREQ_TAB];

    if(freq >= freq_tab[index].frequency) {
        goto *s3c64xx_target_freq_index_end;
    }

    if(freq_tab[s3c64xx_cpufreq_index].frequency == freq) {
        return s3c64xx_cpufreq_index;
    }

    while((freq < freq_tab[index].frequency) &&
        (freq_tab[index].frequency != CPUFREQ_TABLE_END)) {
        index ++;
    }

    if(index > 0){
        if(freq != freq_tab[index].frequency) {
            index--;
        }
    }
   
    if(freq_tab[index].frequency == CPUFREQ_TABLE_END) {
        index--;
    }

s3c64xx_target_freq_index_end:
    mutex_lock(&dvfs_lock);
    index = CLIP_LEVEL(index, s3c64xx_cpufreq_level);        //level is cetain index
    mutex_unlock(&dvfs_lock);
    s3c64xx_cpufreq_index = index;

    return index;
}

int is_userspace_gov(void)
{
    int ret = 0;

    if(!strnicmp(cpufreq_governor_name, userspace_governor, CPUFREQ_NAME_LEN)) {        //case insensitive comparition
        ret = 1;
    }

    return ret;
}

int s3c6410_verify_speed(struct cpufreq_policy *policy)
{
#ifdef USE_FREQ_TABLE
    struct clk *mpu_clk;
#endif    /* USE_FREQ_TABLE */
    if(policy->cpu)
        return -EINVAL;
#ifdef USE_FREQ_TABLE
    return cpufreq_frequency_table_verify(policy, s3c6410_freq_table[S3C64XX_FREQ_TAB]);    //this variable defined in clock.c
    //find the proper freq margin in freq table
#else
    cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq,        //make sure the freq in a margin
                        policy->cpuinfo.max_freq);
    mpu_clk = clk_get(NULL, mpu_clk);

    policy->min = clk_round_rate(mpu_clk, policy->min * KHZ_T) / KHZ_T;    //find what rate should be set if certain another rate needed
    policy->max = clk_round_rate(mpu_clk, policy->max * KHZ_T) / KHZ_T;

    cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq,
                        policy->cpuinfo.max_freq);

    clk_put(mpu_clk);
#endif

    return 0;

}

extern unsigned long s3c_fclk_get_rate(void);
unsigned int s3c6410_getspeed(unsigned int cpu)
{
    struct clk *mpu_clk;
    unsigned long rate;

    if(cpu)
        return 0;

    mpu_clk = clk_get(NULL, MPU_CLK);        //why MPU_CLK, get/put 难道是用来锁定mpu_clk
    if (IS_ERR(mpu_clk))
        return 0;

    rate = s3c_fclk_get_rate() / KHZ_T;
    clk_put(mpu_clk);

    return rate;
}

static int s3c6410_target(struct cpufreq_policy *policy,
                unsigned int target_freq,
                unsigned int relation)
{
    struct clk *mpu_clk;
    struct cpufreq_freqs freqs;
    static int prevIndex = 0;
    int ret = 0;
    unsigned long arm_clk;
    unsigned int index;

    mpu_clk = clk_get(NULL, mpu_clk);
    if(IS_ERR(mpu_clk))
        return PTR_ERR(mpu_clk);

    if(policy != NULL) {
        if(policy->governor) {
            if(strnicmp(cpufreq_governor_name, policy->governor->name, CPUFREQ_NAME_LEN)) {
                strcpy(cpufreq_governor_name, policy->governor->name);
            }
        }
    }

    freqs.old = s3c6410_getspeed(0);

    if(freqs.old == s3c6410_freq_table[S3C64XX_FREQ_TAB][0].frequency) {
        prevIndex = 0;
    }
   
    index = s3c64xx_target_freq_index(target_freq);
    if(index == INDX_ERROR) {
        printk("s3c6410_target: INDX_ERROR /n");
        return -EINVAL;
    }

    if(prevIndex == index)
        return ret;

    arm_clk = s3c6410_freq_table[S3C64XX_FREQ_TAB][index].frequency;
    freqs.new = arm_clk;
    freqs.cpu = 0;
    freqs.new_hclk = 133000;

    if(index > S3C64XX_MAXFREQLEVEL) {
        freqs.new_hclk = 66000;            //why this ? isn't hclk fixed, why changed according to freq ? confirmed: not used
    }

    target_freq = arm_clk;

    cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);

#ifdef USE_DVS
/*  Quote:
  *  If increase freq, first set rate, then set voltage
  *  If decrease freq, first set voltage, then set rate
  */
    if(prevIndex < index) {
        ret = clk_set_rate(mpu_clk, target_freq * KHZ_T);
        if(ret != 0 ) {
            printk("frequency scaling error/n");
            ret = -EINVAL;
            goto s3c6410_target_end;
        }
        set_voltage(index);
    }
    else {
        set_voltage(index);
        ret = clk_set_rate(mpu_clk, target_freq * KHZ_T);
        if(ret != 0) {

            printk("frequency scaling error/n");
            ret = -EINVAL;
            goto s3c6410_target_end;
        }
    }
#else
    ret = clk_set_rate(mpu_clk, target_freq * KHZ_T);
    if(ret != 0) {
        printk("frequency scaling error/n");
        ret = -EINVAL;
        goto s3c6410_target_end;
    }
#endif
    cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
    prevIndex = index;
    clk_put(mpu_clk);
s3c6410_target_end:
    return ret;
}

int s3c6410_pm_target(unsigned int target_freq)
{
    struct clk *mpu_clk;
    int ret = 0;
    unsigned long arm_clk;
    unsigned int index;

    mpu_clk = clk_get(NULL, MPU_CLK);
    if(IS_ERR(mpu_clk))
        return PTR_ERR(mpu_clk);

    index = s3c64xx_target_freq_index(target_freq);
    if(index == INDX_ERROR) {
        printk("s3c6410_target: INDX_ERROR/n");
        return -EINVAL;
    }

    arm_clk = s3c6410_freq_table[S3C64XX_FREQ_TAB][index].frequency;
    target_freq = arm_clk;

#ifdef USE_DVS
    set_voltage(index);
#endif

    ret = clk_set_rate(mpu_clk, target_freq * KHZ_T);
    if(ret != 0) {
        printk("frequency scaling error/n");
        return -EINVAL;
    }
   
    clk_put(mpu_clk);
    return ret;
}

unsigned int get_min_cpufreq(void)
{

    return (s3c6410_freq_table[S3C64XX_FREQ_TAB][S3C64XX_MAXFREQLEVEL].frequency);
}

static int __init s3c6410_cpu_init(struct cpufreq_policy *policy)
{
    struct clk *mpu_clk;

    mpu_clk = clk_get(NULL, MPU_CLK);
    if(IS_ERR(mpu_clk))
        return PTR_ERR(mpu_clk);

    if(policy->cpu != 0)
        return -EINVAL;
    policy->cur = policy->min = policy->max = s3c6410_getspeed(0);

    if(policy->max == MAXIMUM_FREQ) {
        /*this macro is used for mark whether 800mhz is considered, if this ture then
         *800mhz is considered, it is defined in clock.c in 2.6.27 samsung kernel
         */
        S3C64XX_FREQ_TAB = 1;
#ifdef USE_DVFS_AL1_LEVEL
        S3C64XX_MAXFREQLEVEL = 4;
#else
        S3C64XX_MAXFREQLEVEL = 3;
#endif
    }
    s3c64xx_cpufreq_level = S3C64XX_MAXFREQLEVEL;



}

static struct cpufreq_driver_s3c6410_driver = {
    .flags        = CPUFREQ_STICKY,
    .verify        = s3c6410_verify_speed,
    .target        = s3c6410_target,
    .get        = s3c6410_getspeed,
    .init        = s3c6410_cpu_init,
    .name        = "s3c6410",
}

static init __init s3c6410_cpufreq_init(void)
{
    return cpufreq_register_driver(&s3c6410_driver);
}

device_initcall(s3c6410_cpufreq_init);

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值