From: chenxing Date: Tue, 14 Aug 2012 10:45:41 +0000 (+0800) Subject: rk2928:sdk: add cpufreq and dvfs support, BUT NOT compile X-Git-Tag: firefly_0821_release~8912^2~3^2~2 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=32eb239e65f54982bd2f85754fbedfa12eb05265;p=firefly-linux-kernel-4.4.55.git rk2928:sdk: add cpufreq and dvfs support, BUT NOT compile --- diff --git a/arch/arm/mach-rk2928/Makefile b/arch/arm/mach-rk2928/Makefile index 13a891691d1d..6acd3f120855 100644 --- a/arch/arm/mach-rk2928/Makefile +++ b/arch/arm/mach-rk2928/Makefile @@ -6,6 +6,8 @@ obj-y += devices.o obj-y += iomux.o obj-y += clock.o obj-y += clock_data.o +obj-$(CONFIG_CPU_FREQ) += cpufreq.o +obj-$(CONFIG_DVFS) += dvfs.o obj-$(CONFIG_PM) += pm.o CFLAGS_pm.o += -Os -mthumb diff --git a/arch/arm/mach-rk2928/board-rk2928-sdk.c b/arch/arm/mach-rk2928/board-rk2928-sdk.c index b44fdc5b5038..fb48103c0245 100755 --- a/arch/arm/mach-rk2928/board-rk2928-sdk.c +++ b/arch/arm/mach-rk2928/board-rk2928-sdk.c @@ -33,7 +33,7 @@ #include #include #include -//#include +#include #include #include @@ -762,10 +762,56 @@ static void __init rk2928_reserve(void) #endif board_mem_reserved(); } +/** + * dvfs_cpu_logic_table: table for arm and logic dvfs + * @frequency : arm frequency + * @cpu_volt : arm voltage depend on frequency + * @logic_volt : logic voltage arm requests depend on frequency + * comments : min arm/logic voltage + */ +static struct dvfs_arm_table dvfs_cpu_logic_table[] = { + {.frequency = 216 * 1000, .cpu_volt = 1200 * 1000, .logic_volt = 1200 * 1000},//0.975V/1.000V + {.frequency = 312 * 1000, .cpu_volt = 1200 * 1000, .logic_volt = 1200 * 1000},//0.975V/1.000V + {.frequency = 408 * 1000, .cpu_volt = 1200 * 1000, .logic_volt = 1200 * 1000},//1.000V/1.025V + {.frequency = 504 * 1000, .cpu_volt = 1200 * 1000, .logic_volt = 1200 * 1000},//1.000V/1.025V + {.frequency = 600 * 1000, .cpu_volt = 1200 * 1000, .logic_volt = 1200 * 1000},//1.025V/1.050V + {.frequency = 696 * 1000, .cpu_volt = 1400 * 1000, .logic_volt = 1200 * 1000},//1.000V/1.025V + {.frequency = 816 * 1000, .cpu_volt = 1400 * 1000, .logic_volt = 1200 * 1000},//1.100V/1.050V + {.frequency = 912 * 1000, .cpu_volt = 1400 * 1000, .logic_volt = 1200 * 1000},//1.100V/1.050V +#if 0 + {.frequency = 1008 * 1000, .cpu_volt = 1400 * 1000, .logic_volt = 1200 * 1000},//1.100V/1.050V + {.frequency = 1104 * 1000, .cpu_volt = 1400 * 1000, .logic_volt = 1200 * 1000},//1.100V/1.050V + {.frequency = 1200 * 1000, .cpu_volt = 1400 * 1000, .logic_volt = 1200 * 1000},//1.100V/1.050V + {.frequency = 1104 * 1000, .cpu_volt = 1400 * 1000, .logic_volt = 1200 * 1000},//1.100V/1.050V + {.frequency = 1248 * 1000, .cpu_volt = 1400 * 1000, .logic_volt = 1200 * 1000},//1.100V/1.050V +#endif + //{.frequency = 1000 * 1000, .cpu_volt = 1225 * 1000, .logic_volt = 1200 * 1000},//1.150V/1.100V + {.frequency = CPUFREQ_TABLE_END}, +}; + +static struct cpufreq_frequency_table dvfs_gpu_table[] = { + {.frequency = 266 * 1000, .index = 1050 * 1000}, + {.frequency = 400 * 1000, .index = 1275 * 1000}, + {.frequency = CPUFREQ_TABLE_END}, +}; + +static struct cpufreq_frequency_table dvfs_ddr_table[] = { + {.frequency = 300 * 1000, .index = 1050 * 1000}, + {.frequency = 400 * 1000, .index = 1125 * 1000}, + {.frequency = CPUFREQ_TABLE_END}, +}; + +#define DVFS_CPU_TABLE_SIZE (ARRAY_SIZE(dvfs_cpu_logic_table)) +static struct cpufreq_frequency_table cpu_dvfs_table[DVFS_CPU_TABLE_SIZE]; +static struct cpufreq_frequency_table dep_cpu2core_table[DVFS_CPU_TABLE_SIZE]; void __init board_clock_init(void) { rk2928_clock_data_init(periph_pll_default, codec_pll_default, RK30_CLOCKS_DEFAULT_FLAGS); + dvfs_set_arm_logic_volt(dvfs_cpu_logic_table, cpu_dvfs_table, dep_cpu2core_table); + dvfs_set_freq_volt_table(clk_get(NULL, "gpu"), dvfs_gpu_table); + //dvfs_set_freq_volt_table(clk_get(NULL, "ddr"), dvfs_ddr_table); + printk("%s end\n", __func__); } diff --git a/arch/arm/mach-rk2928/clock.c b/arch/arm/mach-rk2928/clock.c old mode 100755 new mode 100644 index 9fe7ae46d5b1..b9bb52d2adc2 --- a/arch/arm/mach-rk2928/clock.c +++ b/arch/arm/mach-rk2928/clock.c @@ -23,7 +23,7 @@ #include #include #include "clock.h" -//#include +#include #include #define CLOCK_PRINTK_DBG(fmt, args...) pr_debug(fmt, ## args); @@ -126,6 +126,7 @@ int clk_register(struct clk *clk) return -EINVAL; //INIT_LIST_HEAD(&clk->sibling); INIT_LIST_HEAD(&clk->children); + /* * trap out already registered clocks */ @@ -138,11 +139,10 @@ int clk_register(struct clk *clk) else if (clk->parents) clk->parent =clk_default_get_parent(clk); - if (clk->parent){ + if (clk->parent) list_add(&clk->sibling, &clk->parent->children); - } else { + else list_add(&clk->sibling, &root_clks); - } list_add(&clk->node, &clocks); mutex_unlock(&clocks_mutex); return 0; @@ -318,7 +318,7 @@ int clk_set_parent_nolock(struct clk *clk, struct clk *parent) return ret; } /**********************************dvfs****************************************************/ -#if 0 + struct clk_node *clk_get_dvfs_info(struct clk *clk) { return clk->dvfs_info; @@ -338,7 +338,7 @@ void clk_register_dvfs(struct clk_node *dvfs_clk, struct clk *clk) { clk->dvfs_info = dvfs_clk; } -#endif + /*------------------------------------------------------------------------- * Optional clock functions defined in include/linux/clk.h @@ -398,10 +398,9 @@ int clk_set_rate(struct clk *clk, unsigned long rate) } if (rate == clk->rate) return 0; -#if 0 if (clk->dvfs_info!=NULL&&is_support_dvfs(clk->dvfs_info)) return dvfs_set_rate(clk, rate); -#endif + LOCK(); ret = clk_set_rate_nolock(clk, rate); UNLOCK(); @@ -703,7 +702,8 @@ void clk_register_dump_ops(struct clk_dump_ops *ops) { dump_def_ops=ops; } - + +#ifdef CONFIG_RK_CLOCK_PROC static int proc_clk_show(struct seq_file *s, void *v) { struct clk* clk; @@ -747,4 +747,5 @@ static int __init clk_proc_init(void) } late_initcall(clk_proc_init); +#endif /* CONFIG_RK_CLOCK_PROC */ diff --git a/arch/arm/mach-rk2928/clock_data.c b/arch/arm/mach-rk2928/clock_data.c index 8584e6d4d955..8b311cc95776 100644 --- a/arch/arm/mach-rk2928/clock_data.c +++ b/arch/arm/mach-rk2928/clock_data.c @@ -24,6 +24,7 @@ #include #include #include +#include #include "clock.h" //#include @@ -126,8 +127,19 @@ struct pll_clk_set { } static const struct apll_clk_set apll_clks[] = { - _APLL_SET_CLKS( 650, 6, 325, 2, 1, 1, 0, 41, 21, 81, 21, 21), - _APLL_SET_CLKS(1000, 3, 125, 1, 1, 1, 0, 41, 21, 41, 21, 21), + _APLL_SET_CLKS(1248, 1, 52, 1, 1, 1, 0, 41, 21, 41, 21, 21), + _APLL_SET_CLKS(1200, 1, 50, 1, 1, 1, 0, 41, 21, 41, 21, 21), + _APLL_SET_CLKS(1104, 1, 46, 1, 1, 1, 0, 41, 21, 41, 21, 21), + _APLL_SET_CLKS(1008, 1, 42, 1, 1, 1, 0, 41, 21, 41, 21, 21), + _APLL_SET_CLKS( 912, 1, 38, 1, 1, 1, 0, 41, 21, 41, 21, 21), + _APLL_SET_CLKS( 816, 1, 34, 1, 1, 1, 0, 41, 21, 41, 21, 21), + _APLL_SET_CLKS( 696, 1, 29, 1, 1, 1, 0, 41, 21, 41, 21, 21), + _APLL_SET_CLKS( 600, 1, 25, 1, 1, 1, 0, 41, 21, 41, 21, 21), + _APLL_SET_CLKS( 504, 1, 21, 1, 1, 1, 0, 41, 21, 41, 21, 21), + _APLL_SET_CLKS( 408, 1, 17, 1, 1, 1, 0, 41, 21, 41, 21, 21), + _APLL_SET_CLKS( 312, 1, 52, 2, 2, 1, 0, 41, 21, 41, 21, 21), + _APLL_SET_CLKS( 216, 1, 36, 2, 2, 1, 0, 41, 21, 41, 21, 21), + _APLL_SET_CLKS( 0, 1, 0, 1, 1, 1, 0, 41, 21, 41, 21, 21), }; #define _PLL_SET_CLKS(_mhz, _refdiv, _fbdiv, _postdiv1, _postdiv2, _dsmpd, _frac) \ @@ -734,7 +746,7 @@ static int apll_clk_set_rate(struct clk *clk, unsigned long rate) CLKDATA_DBG("pllcon0 %08x\n", cru_readl(PLL_CONS(0,0))); CLKDATA_DBG("pllcon1 %08x\n", cru_readl(PLL_CONS(0,1))); CLKDATA_DBG("pllcon2 %08x\n", cru_readl(PLL_CONS(0,2))); - CLKDATA_DBG("pllcon3 %08x\n", cru_readl(PLL_CONS(0,3))); + //CLKDATA_DBG("pllcon3 %08x\n", cru_readl(PLL_CONS(0,3))); CLKDATA_DBG("clksel0 %08x\n", cru_readl(CRU_CLKSELS_CON(0))); CLKDATA_DBG("clksel1 %08x\n", cru_readl(CRU_CLKSELS_CON(1))); if(clk_set->rate==rate) { @@ -761,7 +773,7 @@ static int apll_clk_set_rate(struct clk *clk, unsigned long rate) //rk2928_clock_udelay(5); //wating lock state - rk2928_clock_udelay(clk_set->rst_dly); + rk2928_clock_udelay(clk_set->rst_dly); pll_wait_lock(pll_id); //return form slow @@ -1003,7 +1015,7 @@ static int arm_core_clk_set_rate(struct clk *c, unsigned long rate) //set arm pll div 1 //set_cru_bits_w_msk(0,c->div_mask,c->div_shift,c->clksel_con); - CLKDATA_DBG("Failed to change clk pll %s to %lu\n",c->name,rate); + CLKDATA_DBG("change clk pll %s to %lu\n",c->name,rate); ret = clk_set_rate_nolock(c->parent, rate); if (ret) { CLKDATA_ERR("Failed to change clk pll %s to %lu\n",c->name,rate); @@ -1013,6 +1025,7 @@ static int arm_core_clk_set_rate(struct clk *c, unsigned long rate) return 0; } static struct clk clk_core_pre = { + //.name = "cpu", .name = "core_pre", .parent = &arm_pll_clk, .recalc = clksel_recalc_div, @@ -1599,6 +1612,7 @@ static struct clk clk_saradc = { // name: gpu_aclk static struct clk *clk_gpu_pre_parents[] = SELECT_FROM_2PLLS_CG; static struct clk clk_gpu_pre = { + //.name = "gpu", .name = "gpu_pre", .parent = &general_pll_clk, .mode = gate_mode, @@ -2428,7 +2442,7 @@ static void __init rk2928_clock_common_init(unsigned long gpll_rate,unsigned lon { CLKDATA_DBG("ENTER %s\n", __func__); - clk_set_rate_nolock(&clk_core_pre, 1000 * MHZ);//816 + clk_set_rate_nolock(&clk_core_pre, 816 * MHZ);//816 //general clk_set_rate_nolock(&general_pll_clk, gpll_rate); //code pll @@ -2530,8 +2544,8 @@ void __init _rk2928_clock_data_init(unsigned long gpll,unsigned long cpll,int fl void __init rk2928_clock_data_init(unsigned long gpll,unsigned long cpll,u32 flags) { - printk("%s version: 2012-8-7\n", __func__); + printk("%s version: 2012-8-14\n", __func__); _rk2928_clock_data_init(gpll,cpll,flags); - //rk2928_dvfs_init(); + rk30_dvfs_init(); } diff --git a/arch/arm/mach-rk2928/cpufreq.c b/arch/arm/mach-rk2928/cpufreq.c new file mode 100644 index 000000000000..4f796715415d --- /dev/null +++ b/arch/arm/mach-rk2928/cpufreq.c @@ -0,0 +1,711 @@ +/* + * Copyright (C) 2012 ROCKCHIP, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +//#define DEBUG 1 +#define pr_fmt(fmt) "cpufreq: " fmt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef DEBUG +#define FREQ_PRINTK_DBG(fmt, args...) pr_debug(fmt, ## args) +#define FREQ_PRINTK_LOG(fmt, args...) pr_debug(fmt, ## args) +#else +#define FREQ_PRINTK_DBG(fmt, args...) do {} while(0) +#define FREQ_PRINTK_LOG(fmt, args...) do {} while(0) +#endif +#define FREQ_PRINTK_ERR(fmt, args...) pr_err(fmt, ## args) + +/* Frequency table index must be sequential starting at 0 */ +static struct cpufreq_frequency_table default_freq_table[] = { + {.frequency = 816 * 1000, .index = 1100 * 1000}, + {.frequency = CPUFREQ_TABLE_END}, +}; + +static struct cpufreq_frequency_table *freq_table = default_freq_table; +static unsigned int max_freq = -1; + +/*********************************************************/ + +/* additional symantics for "relation" in cpufreq with pm */ +#define DISABLE_FURTHER_CPUFREQ 0x10 +#define ENABLE_FURTHER_CPUFREQ 0x20 +#define MASK_FURTHER_CPUFREQ 0x30 +/* With 0x00(NOCHANGE), it depends on the previous "further" status */ +static int no_cpufreq_access; +static unsigned int suspend_freq = 816 * 1000; + +static struct workqueue_struct *freq_wq; +static struct clk *cpu_clk; +static struct clk *cpu_pll; +static struct clk *cpu_gpll; + + +static DEFINE_MUTEX(cpufreq_mutex); + +static struct clk *gpu_clk; +#define GPU_MAX_RATE 350*1000*1000 + +static int cpufreq_scale_rate_for_dvfs(struct clk *clk, unsigned long rate, dvfs_set_rate_callback set_rate); + +/*******************************************************/ +static unsigned int rk30_getspeed(unsigned int cpu) +{ + unsigned long rate; + + if (cpu >= NR_CPUS) + return 0; + + rate = clk_get_rate(cpu_clk) / 1000; + return rate; +} + +static bool rk30_cpufreq_is_ondemand_policy(struct cpufreq_policy *policy) +{ + char c = 0; + if (policy && policy->governor) + c = policy->governor->name[0]; + return (c == 'o' || c == 'i' || c == 'c' || c == 'h'); +} + +/**********************thermal limit**************************/ +#define CONFIG_RK30_CPU_FREQ_LIMIT_BY_TEMP + +#ifdef CONFIG_RK30_CPU_FREQ_LIMIT_BY_TEMP +static void rk30_cpufreq_temp_limit_work_func(struct work_struct *work); + +static DECLARE_DELAYED_WORK(rk30_cpufreq_temp_limit_work, rk30_cpufreq_temp_limit_work_func); + +static unsigned int temp_limt_freq = -1; +module_param(temp_limt_freq, uint, 0444); + +#define TEMP_LIMIT_FREQ 816000 + +static const struct cpufreq_frequency_table temp_limits[] = { + {.frequency = 1416 * 1000, .index = 50}, + {.frequency = 1200 * 1000, .index = 55}, + {.frequency = 1008 * 1000, .index = 60}, + {.frequency = 816 * 1000, .index = 75}, +}; + +//extern int rk30_tsadc_get_temp(unsigned int chn); + +//#define get_cpu_thermal() rk30_tsadc_get_temp(0) +static void rk30_cpufreq_temp_limit_work_func(struct work_struct *work) +{ + struct cpufreq_policy *policy; + int temp = 25, i; + unsigned int new = -1; + + if (clk_get_rate(gpu_clk) > GPU_MAX_RATE) + goto out; + + //temp = max(rk30_tsadc_get_temp(0), rk30_tsadc_get_temp(1)); + FREQ_PRINTK_LOG("cpu_thermal(%d)\n", temp); + + for (i = 0; i < ARRAY_SIZE(temp_limits); i++) { + if (temp > temp_limits[i].index) { + new = temp_limits[i].frequency; + } + } + if (temp_limt_freq != new) { + temp_limt_freq = new; + if (new != -1) { + FREQ_PRINTK_DBG("temp_limit set rate %d kHz\n", temp_limt_freq); + policy = cpufreq_cpu_get(0); + cpufreq_driver_target(policy, policy->cur, CPUFREQ_RELATION_L); + cpufreq_cpu_put(policy); + } + } + +out: + queue_delayed_work(freq_wq, &rk30_cpufreq_temp_limit_work, HZ); +} + +static int rk30_cpufreq_notifier_policy(struct notifier_block *nb, + unsigned long val, void *data) +{ + struct cpufreq_policy *policy = data; + + if (val != CPUFREQ_NOTIFY) + return 0; + + if (rk30_cpufreq_is_ondemand_policy(policy)) { + FREQ_PRINTK_DBG("queue work\n"); + queue_delayed_work(freq_wq, &rk30_cpufreq_temp_limit_work, 0); + } else { + FREQ_PRINTK_DBG("cancel work\n"); + cancel_delayed_work_sync(&rk30_cpufreq_temp_limit_work); + } + + return 0; +} + +static struct notifier_block notifier_policy_block = { + .notifier_call = rk30_cpufreq_notifier_policy +}; +#endif + +/************************************dvfs tst************************************/ +//#define CPU_FREQ_DVFS_TST +#ifdef CPU_FREQ_DVFS_TST +static unsigned int freq_dvfs_tst_rate; +static void rk30_cpufreq_dvsf_tst_work_func(struct work_struct *work); +static DECLARE_DELAYED_WORK(rk30_cpufreq_dvsf_tst_work, rk30_cpufreq_dvsf_tst_work_func); +static int test_count; +#define TEST_FRE_NUM 11 +static int test_tlb_rate[TEST_FRE_NUM] = { 504, 1008, 504, 1200, 252, 816, 1416, 252, 1512, 252, 816 }; +//static int test_tlb_rate[TEST_FRE_NUM]={504,1008,504,1200,252,816,1416,126,1512,126,816}; + +#define TEST_GPU_NUM 3 + +static int test_tlb_gpu[TEST_GPU_NUM] = { 360, 400, 180 }; +static int test_tlb_ddr[TEST_GPU_NUM] = { 401, 200, 500 }; + +static int gpu_ddr = 0; + +static void rk30_cpufreq_dvsf_tst_work_func(struct work_struct *work) +{ + struct cpufreq_policy *policy = cpufreq_cpu_get(0); + + gpu_ddr++; + +#if 0 + FREQ_PRINTK_LOG("cpufreq_dvsf_tst,ddr%u,gpu%u\n", + test_tlb_ddr[gpu_ddr % TEST_GPU_NUM], + test_tlb_gpu[gpu_ddr % TEST_GPU_NUM]); + clk_set_rate(ddr_clk, test_tlb_ddr[gpu_ddr % TEST_GPU_NUM] * 1000 * 1000); + clk_set_rate(gpu_clk, test_tlb_gpu[gpu_ddr % TEST_GPU_NUM] * 1000 * 1000); +#endif + + test_count++; + freq_dvfs_tst_rate = test_tlb_rate[test_count % TEST_FRE_NUM] * 1000; + FREQ_PRINTK_LOG("cpufreq_dvsf_tst,cpu set rate %d\n", freq_dvfs_tst_rate); + cpufreq_driver_target(policy, policy->cur, CPUFREQ_RELATION_L); + cpufreq_cpu_put(policy); + + queue_delayed_work(freq_wq, &rk30_cpufreq_dvsf_tst_work, msecs_to_jiffies(1000)); +} +#endif /* CPU_FREQ_DVFS_TST */ + +/***********************************************************************/ +static int rk30_verify_speed(struct cpufreq_policy *policy) +{ + if (!freq_table) + return -EINVAL; + return cpufreq_frequency_table_verify(policy, freq_table); +} + +static int rk30_cpu_init(struct cpufreq_policy *policy) +{ + if (policy->cpu == 0) { + int i; + struct clk *ddr_clk; + gpu_clk = clk_get(NULL, "gpu"); + if (!IS_ERR(gpu_clk)) + clk_enable_dvfs(gpu_clk); +#if 0 + + ddr_clk = clk_get(NULL, "ddr"); + if (!IS_ERR(ddr_clk)) + { + clk_enable_dvfs(ddr_clk); + clk_set_rate(ddr_clk,clk_get_rate(ddr_clk)-1); + } + +#endif + cpu_clk = clk_get(NULL, "cpu"); + cpu_pll = clk_get(NULL, "arm_pll"); + + cpu_gpll = clk_get(NULL, "arm_gpll"); + if (IS_ERR(cpu_clk)) + return PTR_ERR(cpu_clk); + + dvfs_clk_register_set_rate_callback(cpu_clk, cpufreq_scale_rate_for_dvfs); + freq_table = dvfs_get_freq_volt_table(cpu_clk); + if (freq_table == NULL) { + freq_table = default_freq_table; + } + max_freq = freq_table[0].frequency; + for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++) { + max_freq = max(max_freq, freq_table[i].frequency); + } + clk_enable_dvfs(cpu_clk); + + /* Limit gpu frequency between 133M to 400M */ +#if 0 + dvfs_clk_enable_limit(gpu_clk, 133000000, 400000000); +#endif + + freq_wq = create_singlethread_workqueue("rk30_cpufreqd"); +#ifdef CONFIG_RK30_CPU_FREQ_LIMIT_BY_TEMP + if (rk30_cpufreq_is_ondemand_policy(policy)) { + queue_delayed_work(freq_wq, &rk30_cpufreq_temp_limit_work, 0*HZ); + } + cpufreq_register_notifier(¬ifier_policy_block, CPUFREQ_POLICY_NOTIFIER); +#endif +#ifdef CPU_FREQ_DVFS_TST + queue_delayed_work(freq_wq, &rk30_cpufreq_dvsf_tst_work, msecs_to_jiffies(20 * 1000)); +#endif + } + //set freq min max + cpufreq_frequency_table_cpuinfo(policy, freq_table); + //sys nod + cpufreq_frequency_table_get_attr(freq_table, policy->cpu); + + policy->cur = rk30_getspeed(0); + + policy->cpuinfo.transition_latency = 40 * NSEC_PER_USEC; // make ondemand default sampling_rate to 40000 + + /* + * On rk30 SMP configuartion, both processors share the voltage + * and clock. So both CPUs needs to be scaled together and hence + * needs software co-ordination. Use cpufreq affected_cpus + * interface to handle this scenario. Additional is_smp() check + * is to keep SMP_ON_UP build working. + */ + if (is_smp()) + cpumask_setall(policy->cpus); + + return 0; +} + +static int rk30_cpu_exit(struct cpufreq_policy *policy) +{ + if (policy->cpu != 0) + return 0; + + cpufreq_frequency_table_cpuinfo(policy, freq_table); + clk_put(cpu_clk); +#ifdef CONFIG_RK30_CPU_FREQ_LIMIT_BY_TEMP + cpufreq_unregister_notifier(¬ifier_policy_block, CPUFREQ_POLICY_NOTIFIER); + if (freq_wq) + cancel_delayed_work(&rk30_cpufreq_temp_limit_work); +#endif + if (freq_wq) { + flush_workqueue(freq_wq); + destroy_workqueue(freq_wq); + freq_wq = NULL; + } + + return 0; +} + +static struct freq_attr *rk30_cpufreq_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, +}; + +/**************************earlysuspend freeze cpu frequency******************************/ +static struct early_suspend ff_early_suspend; + +#define FILE_GOV_MODE "/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor" +#define FILE_SETSPEED "/sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed" +#define FILE_CUR_FREQ "/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq" + +#define FF_DEBUG(fmt, args...) printk(KERN_DEBUG "FREEZE FREQ DEBUG:\t"fmt, ##args) +#define FF_ERROR(fmt, args...) printk(KERN_ERR "FREEZE FREQ ERROR:\t"fmt, ##args) + +static int ff_read(char *file_path, char *buf) +{ + struct file *file = NULL; + mm_segment_t old_fs; + loff_t offset = 0; + + FF_DEBUG("read %s\n", file_path); + file = filp_open(file_path, O_RDONLY, 0); + + if (IS_ERR(file)) { + FF_ERROR("%s error open file %s\n", __func__, file_path); + return -1; + } + + old_fs = get_fs(); + set_fs(KERNEL_DS); + + file->f_op->read(file, (char *)buf, 32, &offset); + sscanf(buf, "%s", buf); + + set_fs(old_fs); + filp_close(file, NULL); + + file = NULL; + + return 0; + +} + +static int ff_write(char *file_path, char *buf) +{ + struct file *file = NULL; + mm_segment_t old_fs; + loff_t offset = 0; + + FF_DEBUG("write %s %s size = %d\n", file_path, buf, strlen(buf)); + file = filp_open(file_path, O_RDWR, 0); + + if (IS_ERR(file)) { + FF_ERROR("%s error open file %s\n", __func__, file_path); + return -1; + } + + old_fs = get_fs(); + set_fs(KERNEL_DS); + + file->f_op->write(file, (char *)buf, strlen(buf), &offset); + + set_fs(old_fs); + filp_close(file, NULL); + + file = NULL; + + return 0; + +} + +static void ff_scale_votlage(char *name, int volt) +{ + struct regulator* regulator; + int ret = 0; + + FF_DEBUG("enter %s\n", __func__); + regulator = dvfs_get_regulator(name); + if (!regulator) { + FF_ERROR("get regulator %s ERROR\n", name); + return ; + } + + ret = regulator_set_voltage(regulator, volt, volt); + if (ret != 0) { + FF_ERROR("set voltage error %s %d, ret = %d\n", name, volt, ret); + } + +} +int clk_set_parent_force(struct clk *clk, struct clk *parent); +static void ff_early_suspend_func(struct early_suspend *h) +{ + char buf[32]; + FF_DEBUG("enter %s\n", __func__); + if (ff_read(FILE_GOV_MODE, buf) != 0) { + FF_ERROR("read current governor error\n"); + return ; + } else { + FF_DEBUG("current governor = %s\n", buf); + } + + strcpy(buf, "userspace"); + if (ff_write(FILE_GOV_MODE, buf) != 0) { + FF_ERROR("set current governor error\n"); + return ; + } + + strcpy(buf, "252000"); + if (ff_write(FILE_SETSPEED, buf) != 0) { + FF_ERROR("set speed to 252MHz error\n"); + return ; + } + + if (!IS_ERR(cpu_pll)&&!IS_ERR(cpu_gpll)&&!IS_ERR(cpu_clk)) + { + clk_set_parent_force(cpu_clk,cpu_gpll); + clk_set_rate(cpu_clk,300*1000*1000); + + clk_disable_dvfs(cpu_clk); + } + if (!IS_ERR(gpu_clk)) + dvfs_clk_enable_limit(gpu_clk,75*1000*1000,133*1000*1000); + + //ff_scale_votlage("vdd_cpu", 1000000); + //ff_scale_votlage("vdd_core", 1000000); +#ifdef CONFIG_HOTPLUG_CPU + cpu_down(1); +#endif +} + +static void ff_early_resume_func(struct early_suspend *h) +{ + char buf[32]; + FF_DEBUG("enter %s\n", __func__); + + if (!IS_ERR(cpu_pll)&&!IS_ERR(cpu_gpll)&&!IS_ERR(cpu_clk)) + { + clk_set_parent_force(cpu_clk,cpu_pll); + clk_set_rate(cpu_clk,300*1000*1000); + clk_enable_dvfs(cpu_clk); + } + + if (!IS_ERR(gpu_clk)) + dvfs_clk_disable_limit(gpu_clk); +#ifdef CONFIG_HOTPLUG_CPU + cpu_up(1); +#endif + if (ff_read(FILE_GOV_MODE, buf) != 0) { + FF_ERROR("read current governor error\n"); + return ; + } else { + FF_DEBUG("current governor = %s\n", buf); + } + + if (ff_read(FILE_CUR_FREQ, buf) != 0) { + FF_ERROR("read current frequency error\n"); + return ; + } else { + FF_DEBUG("current frequency = %s\n", buf); + } + + strcpy(buf, "interactive"); + if (ff_write(FILE_GOV_MODE, buf) != 0) { + FF_ERROR("set current governor error\n"); + return ; + } + + strcpy(buf, "interactive"); + if (ff_write(FILE_GOV_MODE, buf) != 0) { + FF_ERROR("set current governor error\n"); + return ; + } +} + +static int __init ff_init(void) +{ + FF_DEBUG("enter %s\n", __func__); + ff_early_suspend.suspend = ff_early_suspend_func; + ff_early_suspend.resume = ff_early_resume_func; + ff_early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB + 100; + register_early_suspend(&ff_early_suspend); + return 0; +} + +static void __exit ff_exit(void) +{ + FF_DEBUG("enter %s\n", __func__); + unregister_early_suspend(&ff_early_suspend); +} + + +/**************************target freq******************************/ +static unsigned int cpufreq_scale_limt(unsigned int target_freq, struct cpufreq_policy *policy) +{ + bool is_ondemand = rk30_cpufreq_is_ondemand_policy(policy); + static bool is_booting = true; + + if (is_ondemand && clk_get_rate(gpu_clk) > GPU_MAX_RATE) // high performance? + return max_freq; + if (is_ondemand && is_booting && target_freq >= 1600 * 1000) { + s64 boottime_ms = ktime_to_ms(ktime_get_boottime()); + if (boottime_ms > 30 * MSEC_PER_SEC) { + is_booting = false; + } else { + target_freq = 1416 * 1000; + } + } +#ifdef CONFIG_RK30_CPU_FREQ_LIMIT_BY_TEMP + if (is_ondemand && target_freq > policy->cur && policy->cur >= TEMP_LIMIT_FREQ) { + unsigned int i; + if (cpufreq_frequency_table_target(policy, freq_table, policy->cur + 1, CPUFREQ_RELATION_L, &i) == 0) { + unsigned int f = freq_table[i].frequency; + if (f < target_freq) { + target_freq = f; + } + } + } + /* + * If the new frequency is more than the thermal max allowed + * frequency, go ahead and scale the mpu device to proper frequency. + */ + if (is_ondemand) { + target_freq = min(target_freq, temp_limt_freq); + } +#endif +#ifdef CPU_FREQ_DVFS_TST + if (freq_dvfs_tst_rate) { + target_freq = freq_dvfs_tst_rate; + freq_dvfs_tst_rate = 0; + } +#endif + return target_freq; +} + +int cpufreq_scale_rate_for_dvfs(struct clk *clk, unsigned long rate, dvfs_set_rate_callback set_rate) +{ + unsigned int i; + int ret = -EINVAL; + struct cpufreq_freqs freqs; + + freqs.new = rate / 1000; + freqs.old = rk30_getspeed(0); + + for_each_online_cpu(freqs.cpu) { + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + } + FREQ_PRINTK_DBG("cpufreq_scale_rate_for_dvfs(%lu)\n", rate); + ret = set_rate(clk, rate); + +#ifdef CONFIG_SMP + /* + * Note that loops_per_jiffy is not updated on SMP systems in + * cpufreq driver. So, update the per-CPU loops_per_jiffy value + * on frequency transition. We need to update all dependent CPUs. + */ + for_each_possible_cpu(i) { + per_cpu(cpu_data, i).loops_per_jiffy = loops_per_jiffy; + } +#endif + + freqs.new = rk30_getspeed(0); + /* notifiers */ + for_each_online_cpu(freqs.cpu) { + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + } + return ret; + +} + +static int rk30_target(struct cpufreq_policy *policy, unsigned int target_freq, unsigned int relation) +{ + unsigned int i, new_rate = 0; + int ret = 0; + + if (!freq_table) { + FREQ_PRINTK_ERR("no freq table!\n"); + return -EINVAL; + } + + mutex_lock(&cpufreq_mutex); + + if (relation & ENABLE_FURTHER_CPUFREQ) + no_cpufreq_access--; + if (no_cpufreq_access) { +#ifdef CONFIG_PM_VERBOSE + pr_err("denied access to %s as it is disabled temporarily\n", __func__); +#endif + ret = -EINVAL; + goto out; + } + if (relation & DISABLE_FURTHER_CPUFREQ) + no_cpufreq_access++; + relation &= ~MASK_FURTHER_CPUFREQ; + + ret = cpufreq_frequency_table_target(policy, freq_table, target_freq, relation, &i); + if (ret) { + FREQ_PRINTK_ERR("no freq match for %d(ret=%d)\n", target_freq, ret); + goto out; + } + new_rate = freq_table[i].frequency; + if (!no_cpufreq_access) + new_rate = cpufreq_scale_limt(new_rate, policy); + + FREQ_PRINTK_LOG("cpufreq req=%u,new=%u(was=%u)\n", target_freq, new_rate, rk30_getspeed(0)); + if (new_rate == rk30_getspeed(0)) + goto out; + ret = clk_set_rate(cpu_clk, new_rate * 1000); +out: + mutex_unlock(&cpufreq_mutex); + FREQ_PRINTK_DBG("cpureq set rate (%u) end\n", new_rate); + return ret; +} + +static int rk30_cpufreq_pm_notifier_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + int ret = NOTIFY_DONE; + struct cpufreq_policy *policy = cpufreq_cpu_get(0); + + if (!policy) + return ret; + + if (!rk30_cpufreq_is_ondemand_policy(policy)) + goto out; + + switch (event) { + case PM_SUSPEND_PREPARE: + ret = cpufreq_driver_target(policy, suspend_freq, DISABLE_FURTHER_CPUFREQ | CPUFREQ_RELATION_H); + if (ret < 0) { + ret = NOTIFY_BAD; + goto out; + } + ret = NOTIFY_OK; + break; + case PM_POST_RESTORE: + case PM_POST_SUSPEND: + cpufreq_driver_target(policy, suspend_freq, ENABLE_FURTHER_CPUFREQ | CPUFREQ_RELATION_H); + ret = NOTIFY_OK; + break; + } +out: + cpufreq_cpu_put(policy); + return ret; +} + +static struct notifier_block rk30_cpufreq_pm_notifier = { + .notifier_call = rk30_cpufreq_pm_notifier_event, +}; + +static int rk30_cpufreq_reboot_notifier_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + struct cpufreq_policy *policy = cpufreq_cpu_get(0); + + if (policy) { + cpufreq_driver_target(policy, suspend_freq, DISABLE_FURTHER_CPUFREQ | CPUFREQ_RELATION_H); + cpufreq_cpu_put(policy); + } + + return NOTIFY_OK; +} + +static struct notifier_block rk30_cpufreq_reboot_notifier = { + .notifier_call = rk30_cpufreq_reboot_notifier_event, +}; + +static struct cpufreq_driver rk30_cpufreq_driver = { + .flags = CPUFREQ_CONST_LOOPS, + .verify = rk30_verify_speed, + .target = rk30_target, + .get = rk30_getspeed, + .init = rk30_cpu_init, + .exit = rk30_cpu_exit, + .name = "rk30", + .attr = rk30_cpufreq_attr, +}; + +static int __init rk30_cpufreq_init(void) +{ + register_pm_notifier(&rk30_cpufreq_pm_notifier); + register_reboot_notifier(&rk30_cpufreq_reboot_notifier); + return cpufreq_register_driver(&rk30_cpufreq_driver); +} + +static void __exit rk30_cpufreq_exit(void) +{ + cpufreq_unregister_driver(&rk30_cpufreq_driver); +} + +MODULE_DESCRIPTION("cpufreq driver for rock chip rk30"); +MODULE_LICENSE("GPL"); +device_initcall(rk30_cpufreq_init); +module_exit(rk30_cpufreq_exit); diff --git a/arch/arm/mach-rk2928/dvfs.c b/arch/arm/mach-rk2928/dvfs.c new file mode 100644 index 000000000000..5e1111939953 --- /dev/null +++ b/arch/arm/mach-rk2928/dvfs.c @@ -0,0 +1,1923 @@ +/* arch/arm/mach-rk30/rk30_dvfs.c + * + * Copyright (C) 2012 ROCKCHIP, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "clock.h" +#include +#include +#include +#include +#include +#include + +#if 1 +#define DVFS_DBG(fmt, args...) {while(0);} +#else +#define DVFS_DBG(fmt, args...) printk(KERN_DEBUG "DVFS DBG:\t"fmt, ##args) +#endif +#define DVFS_ERR(fmt, args...) printk(KERN_ERR "DVFS ERR:\t"fmt, ##args) +#define DVFS_LOG(fmt, args...) printk(KERN_DEBUG "DVFS LOG:\t"fmt, ##args) + +#define dvfs_regulator_get(dev,id) regulator_get((dev),(id)) +#define dvfs_regulator_put(regu) regulator_put((regu)) +#define dvfs_regulator_set_voltage(regu,min_uV,max_uV) regulator_set_voltage((regu),(min_uV),(max_uV)) +#define dvfs_regulator_get_voltage(regu) regulator_get_voltage((regu)) + +#define dvfs_clk_get(a,b) clk_get((a),(b)) +#define dvfs_clk_get_rate_kz(a) (clk_get_rate((a))/1000) +#define dvfs_clk_set_rate(a,b) clk_set_rate((a),(b)) +#define dvfs_clk_enable(a) clk_enable((a)) +#define dvfs_clk_disable(a) clk_disable((a)) + +#define DVFS_MHZ (1000*1000) +#define DVFS_KHZ (1000) + +#define DVFS_V (1000*1000) +#define DVFS_MV (1000) + +static LIST_HEAD(rk_dvfs_tree); +static DEFINE_MUTEX(mutex); +static DEFINE_MUTEX(rk_dvfs_mutex); + +extern int rk30_clk_notifier_register(struct clk *clk, struct notifier_block *nb); +extern int rk30_clk_notifier_unregister(struct clk *clk, struct notifier_block *nb); + +static int dump_dbg_map(char* buf); + +#define PD_ON 1 +#define PD_OFF 0 + +#define get_volt_up_delay(new_volt, old_volt) \ + ((new_volt) > (old_volt) ? (((new_volt) - (old_volt)) >> 9) : 0) + +int dvfs_regulator_set_voltage_readback(struct regulator *regulator, int min_uV, int max_uV) +{ + int ret = 0, read_back = 0; + ret = dvfs_regulator_set_voltage(regulator, max_uV, max_uV); + if (ret < 0) { + DVFS_ERR("%s now read back to check voltage\n", __func__); + + /* read back to judge if it is already effect */ + mdelay(2); + read_back = dvfs_regulator_get_voltage(regulator); + if (read_back == max_uV) { + DVFS_ERR("%s set ERROR but already effected, volt=%d\n", __func__, read_back); + ret = 0; + } else { + DVFS_ERR("%s set ERROR AND NOT effected, volt=%d\n", __func__, read_back); + } + } + return ret; +} + +struct regulator* dvfs_get_regulator(char *regulator_name) +{ + struct vd_node *vd; + list_for_each_entry(vd, &rk_dvfs_tree, node) { + if (strcmp(regulator_name, vd->regulator_name) == 0) { + return vd->regulator; + } + } + return NULL; +} + +int dvfs_clk_enable_limit(struct clk *clk, unsigned int min_rate, unsigned max_rate) +{ + struct clk_node* dvfs_clk; + dvfs_clk = clk->dvfs_info; + + dvfs_clk->freq_limit_en = 1; + dvfs_clk->min_rate = min_rate; + dvfs_clk->max_rate = max_rate; + + return 0; +} + +int dvfs_clk_disable_limit(struct clk *clk) +{ + struct clk_node* dvfs_clk; + dvfs_clk = clk->dvfs_info; + + dvfs_clk->freq_limit_en = 0; + + return 0; +} + +int is_support_dvfs(struct clk_node *dvfs_info) +{ + return (dvfs_info->vd && dvfs_info->vd->vd_dvfs_target && dvfs_info->enable_dvfs); +} + +int dvfs_set_rate(struct clk *clk, unsigned long rate) +{ + int ret = 0; + struct vd_node *vd; + DVFS_DBG("%s(%s(%lu))\n", __func__, clk->name, rate); + if (!clk->dvfs_info) { + DVFS_ERR("%s :This clk do not support dvfs!\n", __func__); + ret = -1; + } else { + vd = clk->dvfs_info->vd; + // mutex_lock(&vd->dvfs_mutex); + mutex_lock(&rk_dvfs_mutex); + ret = vd->vd_dvfs_target(clk, rate); + mutex_unlock(&rk_dvfs_mutex); + // mutex_unlock(&vd->dvfs_mutex); + } + DVFS_DBG("%s(%s(%lu)),is end\n", __func__, clk->name, rate); + return ret; +} + +static int dvfs_clk_get_ref_volt_depend(struct depend_list *depend, int rate_khz, + struct cpufreq_frequency_table *clk_fv) +{ + int i = 0; + if (rate_khz == 0 || !depend || !depend->dep_table) { + return -1; + } + clk_fv->frequency = rate_khz; + clk_fv->index = 0; + + for (i = 0; (depend->dep_table[i].frequency != CPUFREQ_TABLE_END); i++) { + if (depend->dep_table[i].frequency >= rate_khz) { + clk_fv->frequency = depend->dep_table[i].frequency; + clk_fv->index = depend->dep_table[i].index; + return 0; + } + } + clk_fv->frequency = 0; + clk_fv->index = 0; + return -1; +} +static int dvfs_clk_get_ref_volt(struct clk_node *dvfs_clk, int rate_khz, + struct cpufreq_frequency_table *clk_fv) +{ + int i = 0; + if (rate_khz == 0 || !dvfs_clk || !dvfs_clk->dvfs_table) { + /* since no need */ + return -1; + } + clk_fv->frequency = rate_khz; + clk_fv->index = 0; + + for (i = 0; (dvfs_clk->dvfs_table[i].frequency != CPUFREQ_TABLE_END); i++) { + if (dvfs_clk->dvfs_table[i].frequency >= rate_khz) { + clk_fv->frequency = dvfs_clk->dvfs_table[i].frequency; + clk_fv->index = dvfs_clk->dvfs_table[i].index; + // DVFS_DBG("%s,%s rate=%ukhz(vol=%d)\n",__func__,dvfs_clk->name, + // clk_fv->frequency, clk_fv->index); + return 0; + } + } + clk_fv->frequency = 0; + clk_fv->index = 0; + // DVFS_DBG("%s get corresponding voltage error! out of bound\n", dvfs_clk->name); + return -1; +} + +static int dvfs_pd_get_newvolt_byclk(struct pd_node *pd, struct clk_node *dvfs_clk) +{ + struct clk_list *child; + int volt_max = 0; + + if (!pd || !dvfs_clk) + return 0; + + if (dvfs_clk->set_volt >= pd->cur_volt) { + return dvfs_clk->set_volt; + } + + list_for_each_entry(child, &pd->clk_list, node) { + // DVFS_DBG("%s ,pd(%s),dvfs(%s),volt(%u)\n",__func__,pd->name, + // dvfs_clk->name,dvfs_clk->set_volt); + volt_max = max(volt_max, child->dvfs_clk->set_volt); + } + return volt_max; +} + +void dvfs_update_clk_pds_volt(struct clk_node *dvfs_clk) +{ + struct pd_node *pd; + int i; + if (!dvfs_clk) + return; + for (i = 0; (dvfs_clk->pds[i].pd != NULL); i++) { + pd = dvfs_clk->pds[i].pd; + // DVFS_DBG("%s dvfs(%s),pd(%s)\n",__func__,dvfs_clk->name,pd->name); + pd->cur_volt = dvfs_pd_get_newvolt_byclk(pd, dvfs_clk); + } +} + +static int dvfs_vd_get_newvolt_bypd(struct vd_node *vd) +{ + struct pd_node *pd; + struct depend_list *depend; + int volt_max_vd = 0; + list_for_each_entry(pd, &vd->pd_list, node) { + // DVFS_DBG("%s pd(%s,%u)\n",__func__,pd->name,pd->cur_volt); + volt_max_vd = max(volt_max_vd, pd->cur_volt); + } + + /* some clks depend on this voltage domain */ + if (!list_empty(&vd->req_volt_list)) { + list_for_each_entry(depend, &vd->req_volt_list, node2vd) { + volt_max_vd = max(volt_max_vd, depend->req_volt); + } + } + return volt_max_vd; +} + +static int dvfs_vd_get_newvolt_byclk(struct clk_node *dvfs_clk) +{ + if (!dvfs_clk) + return -1; + dvfs_update_clk_pds_volt(dvfs_clk); + return dvfs_vd_get_newvolt_bypd(dvfs_clk->vd); +} + +void dvfs_clk_register_set_rate_callback(struct clk *clk, clk_dvfs_target_callback clk_dvfs_target) +{ + struct clk_node *dvfs_clk = clk_get_dvfs_info(clk); + dvfs_clk->clk_dvfs_target = clk_dvfs_target; +} + +struct cpufreq_frequency_table *dvfs_get_freq_volt_table(struct clk *clk) +{ + struct clk_node *info = clk_get_dvfs_info(clk); + struct cpufreq_frequency_table *table; + if (!info || !info->dvfs_table) { + return NULL; + } + mutex_lock(&mutex); + table = info->dvfs_table; + mutex_unlock(&mutex); + return table; +} + +int dvfs_set_freq_volt_table(struct clk *clk, struct cpufreq_frequency_table *table) +{ + struct clk_node *info = clk_get_dvfs_info(clk); + if (!table || !info) + return -1; + + mutex_lock(&mutex); + info->dvfs_table = table; + mutex_unlock(&mutex); + return 0; +} + +int dvfs_set_depend_table(struct clk *clk, char *vd_name, struct cpufreq_frequency_table *table) +{ + struct vd_node *vd; + struct depend_list *depend; + struct clk_node *info; + + info = clk_get_dvfs_info(clk); + if (!table || !info || !vd_name) { + DVFS_ERR("%s :DVFS SET DEPEND TABLE ERROR! table or info or name empty\n", __func__); + return -1; + } + + list_for_each_entry(vd, &rk_dvfs_tree, node) { + if (0 == strcmp(vd->name, vd_name)) { + DVFS_DBG("FOUND A MATCH\n"); + mutex_lock(&mutex); + list_for_each_entry(depend, &info->depend_list, node2clk) { + if (vd == depend->dep_vd && info == depend->dvfs_clk) { + depend->dep_table = table; + break; + } + } + mutex_unlock(&mutex); + return 0; + } + } + DVFS_ERR("%s :DVFS SET DEPEND TABLE ERROR! can not find vd:%s\n", __func__, vd_name); + + return 0; +} + +int dvfs_set_arm_logic_volt(struct dvfs_arm_table *dvfs_cpu_logic_table, + struct cpufreq_frequency_table *cpu_dvfs_table, + struct cpufreq_frequency_table *dep_cpu2core_table) +{ + int i = 0; + for (i = 0; dvfs_cpu_logic_table[i].frequency != CPUFREQ_TABLE_END; i++) { + cpu_dvfs_table[i].frequency = dvfs_cpu_logic_table[i].frequency; + cpu_dvfs_table[i].index = dvfs_cpu_logic_table[i].cpu_volt; + + dep_cpu2core_table[i].frequency = dvfs_cpu_logic_table[i].frequency; + dep_cpu2core_table[i].index = dvfs_cpu_logic_table[i].logic_volt; + } + + cpu_dvfs_table[i].frequency = CPUFREQ_TABLE_END; + dep_cpu2core_table[i].frequency = CPUFREQ_TABLE_END; + + dvfs_set_freq_volt_table(clk_get(NULL, "cpu"), cpu_dvfs_table); + dvfs_set_depend_table(clk_get(NULL, "cpu"), "vd_core", dep_cpu2core_table); + return 0; +} + +int clk_enable_dvfs(struct clk *clk) +{ + struct clk_node *dvfs_clk; + struct cpufreq_frequency_table clk_fv; + if (!clk) { + DVFS_ERR("clk enable dvfs error\n"); + return -1; + } + dvfs_clk = clk_get_dvfs_info(clk); + if (!dvfs_clk || !dvfs_clk->vd) { + DVFS_ERR("%s clk(%s) not support dvfs!\n", __func__, clk->name); + return -1; + } + if (dvfs_clk->enable_dvfs == 0) { + + if (IS_ERR_OR_NULL(dvfs_clk->vd->regulator)) { + //regulator = NULL; + if (dvfs_clk->vd->regulator_name) + dvfs_clk->vd->regulator = dvfs_regulator_get(NULL, dvfs_clk->vd->regulator_name); + if (!IS_ERR_OR_NULL(dvfs_clk->vd->regulator)) { + // DVFS_DBG("dvfs_regulator_get(%s)\n",dvfs_clk->vd->regulator_name); + dvfs_clk->vd->cur_volt = dvfs_regulator_get_voltage(dvfs_clk->vd->regulator); + } else { + //dvfs_clk->vd->regulator = NULL; + dvfs_clk->enable_dvfs = 0; + DVFS_ERR("%s can't get regulator in %s\n", dvfs_clk->name, __func__); + return -1; + } + } else { + dvfs_clk->vd->cur_volt = dvfs_regulator_get_voltage(dvfs_clk->vd->regulator); + // DVFS_DBG("%s(%s) vd volt=%u\n",__func__,dvfs_clk->name,dvfs_clk->vd->cur_volt); + } + + dvfs_clk->set_freq = dvfs_clk_get_rate_kz(clk); + // DVFS_DBG("%s ,%s get freq%u!\n",__func__,dvfs_clk->name,dvfs_clk->set_freq); + + if (dvfs_clk_get_ref_volt(dvfs_clk, dvfs_clk->set_freq, &clk_fv)) { + if (dvfs_clk->dvfs_table[0].frequency == CPUFREQ_TABLE_END) { + DVFS_ERR("%s table empty\n", __func__); + dvfs_clk->enable_dvfs = 0; + return -1; + } else { + DVFS_ERR("WARNING: %s table all value are smaller than default, use default, just enable dvfs\n", __func__); + dvfs_clk->enable_dvfs++; + return 0; + } + } + + dvfs_clk->set_volt = clk_fv.index; + // DVFS_DBG("%s,%s,freq%u(ref vol %u)\n",__func__,dvfs_clk->name, + // dvfs_clk->set_freq,dvfs_clk->set_volt); +#if 0 + if (dvfs_clk->dvfs_nb) { + // must unregister when clk disable + rk30_clk_notifier_register(clk, dvfs_clk->dvfs_nb); + } +#endif + dvfs_vd_get_newvolt_byclk(dvfs_clk); + dvfs_clk->enable_dvfs++; + } else { + DVFS_ERR("dvfs already enable clk enable = %d!\n", dvfs_clk->enable_dvfs); + dvfs_clk->enable_dvfs++; + } + return 0; +} + +int clk_disable_dvfs(struct clk *clk) +{ + struct clk_node *dvfs_clk; + dvfs_clk = clk->dvfs_info; + if (!dvfs_clk->enable_dvfs) { + DVFS_DBG("clk is already closed!\n"); + return -1; + } else { + dvfs_clk->enable_dvfs--; + if (0 == dvfs_clk->enable_dvfs) { + DVFS_ERR("clk closed!\n"); + rk30_clk_notifier_unregister(clk, dvfs_clk->dvfs_nb); + DVFS_DBG("clk unregister nb!\n"); + } + } + return 0; +} + +static int rk_dvfs_clk_notifier_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + struct clk_notifier_data *noti_info; + struct clk *clk; + struct clk_node *dvfs_clk; + noti_info = (struct clk_notifier_data *)ptr; + clk = noti_info->clk; + dvfs_clk = clk->dvfs_info; + + switch (event) { + case CLK_PRE_RATE_CHANGE: + DVFS_DBG("%s CLK_PRE_RATE_CHANGE\n", __func__); + break; + case CLK_POST_RATE_CHANGE: + DVFS_DBG("%s CLK_POST_RATE_CHANGE\n", __func__); + break; + case CLK_ABORT_RATE_CHANGE: + DVFS_DBG("%s CLK_ABORT_RATE_CHANGE\n", __func__); + break; + case CLK_PRE_ENABLE: + DVFS_DBG("%s CLK_PRE_ENABLE\n", __func__); + break; + case CLK_POST_ENABLE: + DVFS_DBG("%s CLK_POST_ENABLE\n", __func__); + break; + case CLK_ABORT_ENABLE: + DVFS_DBG("%s CLK_ABORT_ENABLE\n", __func__); + break; + case CLK_PRE_DISABLE: + DVFS_DBG("%s CLK_PRE_DISABLE\n", __func__); + break; + case CLK_POST_DISABLE: + DVFS_DBG("%s CLK_POST_DISABLE\n", __func__); + dvfs_clk->set_freq = 0; + break; + case CLK_ABORT_DISABLE: + DVFS_DBG("%s CLK_ABORT_DISABLE\n", __func__); + + break; + default: + break; + } + return 0; +} + +static struct notifier_block rk_dvfs_clk_notifier = { + .notifier_call = rk_dvfs_clk_notifier_event, +}; + +static struct clk_node *dvfs_get_dvfs_clk_byname(char *name) +{ + struct vd_node *vd; + struct pd_node *pd; + struct clk_list *child; + list_for_each_entry(vd, &rk_dvfs_tree, node) { + list_for_each_entry(pd, &vd->pd_list, node) { + list_for_each_entry(child, &pd->clk_list, node) { + if (0 == strcmp(child->dvfs_clk->name, name)) { + return child->dvfs_clk; + } + } + } + } + return NULL; +} +static int rk_regist_vd(struct vd_node *vd) +{ + if (!vd) + return -1; + mutex_lock(&mutex); + //mutex_init(&vd->dvfs_mutex); + list_add(&vd->node, &rk_dvfs_tree); + INIT_LIST_HEAD(&vd->pd_list); + INIT_LIST_HEAD(&vd->req_volt_list); + + mutex_unlock(&mutex); + return 0; +} + +static int rk_regist_pd(struct pd_node_lookup *pd_lookup) +{ + struct vd_node *vd; + struct pd_node *pd; + + mutex_lock(&mutex); + pd = pd_lookup->pd; + + list_for_each_entry(vd, &rk_dvfs_tree, node) { + if (vd == pd->vd) { + list_add(&pd->node, &vd->pd_list); + INIT_LIST_HEAD(&pd->clk_list); + break; + } + } + mutex_unlock(&mutex); + return 0; +} + +static int rk_regist_clk(struct clk_node *dvfs_clk) +{ + struct pd_node *pd; + struct clk_list *child; + struct clk *clk; + int i = 0; + + if (!dvfs_clk) + return -1; + + if (!dvfs_clk->pds) + return -1; + mutex_lock(&mutex); + dvfs_clk->enable_dvfs = 0; + dvfs_clk->vd = dvfs_clk->pds[0].pd->vd; + for (i = 0; dvfs_clk->pds[i].pd != NULL; i++) { + child = &(dvfs_clk->pds[i].clk_list); + child->dvfs_clk = dvfs_clk; + pd = dvfs_clk->pds[i].pd; + list_add(&child->node, &pd->clk_list); + } + clk = dvfs_clk_get(NULL, dvfs_clk->name); + dvfs_clk->clk = clk; + clk_register_dvfs(dvfs_clk, clk); + INIT_LIST_HEAD(&dvfs_clk->depend_list); + mutex_unlock(&mutex); + return 0; +} + +static int rk_regist_depends(struct depend_lookup *dep_node) +{ + struct depend_list *depend_list; + struct clk_node *dvfs_clk; + + if (!dep_node) { + DVFS_ERR("%s : DVFS BAD depend node!\n", __func__); + return -1; + } + + if (!dep_node->clk_name || !dep_node->dep_vd) { + DVFS_ERR("%s : DVFS BAD depend members!\n", __func__); + return -1; + } + + depend_list = &dep_node->dep_list; + dvfs_clk = dvfs_get_dvfs_clk_byname(dep_node->clk_name); + + mutex_lock(&mutex); + + depend_list->dvfs_clk = dvfs_clk; + depend_list->dep_vd = dep_node->dep_vd; + depend_list->dep_table = dep_node->dep_table; + + list_add(&depend_list->node2clk, &dvfs_clk->depend_list); + list_add(&depend_list->node2vd, &depend_list->dep_vd->req_volt_list); + + mutex_unlock(&mutex); + return 0; +} +#if 0 +static int dvfs_set_depend_pre(struct clk_node *dvfs_clk, unsigned long rate_old, unsigned long rate_new) +{ + struct depend_list *depend; + struct cpufreq_frequency_table clk_fv; + int ret = -1; + int volt = 0; + struct regulator *regulator; + + if (rate_old >= rate_new) { + return 0; + } + list_for_each_entry(depend, &dvfs_clk->depend_list, node2clk) { + ret = dvfs_clk_get_ref_volt_depend(depend, rate_new / 1000, &clk_fv); + if (ret != 0) { + DVFS_ERR("%s LOGIC DVFS CAN NOT GET REF VOLT!, frequency too large!\n", __func__); + return -1; + } + + if (!depend->dep_vd->regulator) { + DVFS_DBG("%s regulator empty\n", __func__); + regulator = dvfs_regulator_get(NULL, depend->dep_vd->regulator_name); + if (!regulator) { + DVFS_ERR("%s get regulator err\n", __func__); + return -1; + } + + depend->dep_vd->regulator = regulator; + } + if (IS_ERR_OR_NULL(depend->dep_vd->regulator)) { + DVFS_ERR("%s vd's(%s) regulator not NULL but error\n", __func__, depend->dep_vd->name); + return -1; + } + + if (clk_fv.index == dvfs_regulator_get_voltage(depend->dep_vd->regulator)) { + depend->req_volt = clk_fv.index; + DVFS_DBG("%s same voltage\n", __func__); + return 0; + } + + depend->req_volt = clk_fv.index; + volt = dvfs_vd_get_newvolt_bypd(depend->dep_vd); + DVFS_DBG("%s setting voltage = %d\n", __func__, volt); + ret = dvfs_regulator_set_voltage_readback(depend->dep_vd->regulator, volt, volt); + if (0 != ret) { + DVFS_ERR("%s set voltage = %d ERROR, ret = %d\n", __func__, volt, ret); + return -1; + } + udelay(200); + DVFS_DBG("%s set voltage = %d OK, ret = %d\n", __func__, volt, ret); + if (ret != 0) { + DVFS_ERR("%s err, ret = %d\n", __func__, ret); + return -1; + } + } + + return 0; +} + +static int dvfs_set_depend_post(struct clk_node *dvfs_clk, unsigned long rate_old, unsigned long rate_new) +{ + struct depend_list *depend; + struct cpufreq_frequency_table clk_fv; + int ret = -1; + int volt = 0; + struct regulator *regulator; + + if (rate_old <= rate_new) + return 0; + list_for_each_entry(depend, &dvfs_clk->depend_list, node2clk) { + ret = dvfs_clk_get_ref_volt_depend(depend, rate_new / 1000, &clk_fv); + if (ret != 0) { + DVFS_ERR("%s LOGIC DVFS CAN NOT GET REF VOLT!, frequency too large!\n", __func__); + return -1; + } + + if (!depend->dep_vd->regulator) { + DVFS_DBG("%s regulator empty\n", __func__); + regulator = dvfs_regulator_get(NULL, depend->dep_vd->regulator_name); + if (!regulator) { + DVFS_ERR("%s get regulator err\n", __func__); + return -1; + } + + depend->dep_vd->regulator = regulator; + } + if (IS_ERR_OR_NULL(depend->dep_vd->regulator)) { + DVFS_ERR("%s vd's(%s) regulator not NULL but error\n", __func__, depend->dep_vd->name); + return -1; + } + + if (clk_fv.index == dvfs_regulator_get_voltage(depend->dep_vd->regulator)) { + depend->req_volt = clk_fv.index; + DVFS_DBG("%s same voltage\n", __func__); + return 0; + } + + depend->req_volt = clk_fv.index; + volt = dvfs_vd_get_newvolt_bypd(depend->dep_vd); + DVFS_DBG("%s setting voltage = %d\n", __func__, volt); + ret = dvfs_regulator_set_voltage_readback(depend->dep_vd->regulator, volt, volt); + if (0 != ret) { + DVFS_ERR("%s set voltage = %d ERROR, ret = %d\n", __func__, volt, ret); + return -1; + } + udelay(200); + DVFS_DBG("%s set voltage = %d OK, ret = %d\n", __func__, volt, ret); + if (ret != 0) { + DVFS_ERR("%s err, ret = %d\n", __func__, ret); + return -1; + } + } + + return 0; +} +#endif +#define DVFS_SET_VOLT_FAILURE 1 +#define DVFS_SET_VOLT_SUCCESS 0 +#define ARM_HIGHER_LOGIC (150 * 1000) +#define LOGIC_HIGHER_ARM (100 * 1000) + +int check_volt_correct(int volt_old, int *volt_new, int volt_dep_old, int *volt_dep_new, + int clk_biger_than_dep, int dep_biger_than_clk) +{ + int up_boundary = 0, low_boundary = 0; + DVFS_DBG("%d %d\n", clk_biger_than_dep, dep_biger_than_clk); + up_boundary = volt_old + dep_biger_than_clk; + low_boundary = volt_old - clk_biger_than_dep; + + if (volt_dep_old < low_boundary || volt_dep_old > up_boundary) { + DVFS_ERR("%s current volt out of bondary volt=%d(old=%d), volt_dep=%d(dep_old=%d), up_bnd=%d(dn=%d)\n", + __func__, *volt_new, volt_old, *volt_dep_new, volt_dep_old, up_boundary, low_boundary); + return -1; + } + + up_boundary = *volt_new + dep_biger_than_clk; + low_boundary = *volt_new - clk_biger_than_dep; + + if (*volt_dep_new < low_boundary || *volt_dep_new > up_boundary) { + + if (*volt_dep_new < low_boundary) { + *volt_dep_new = low_boundary; + + } else if (*volt_dep_new > up_boundary) { + *volt_new = *volt_dep_new - dep_biger_than_clk; + } + DVFS_LOG("%s target volt out of bondary volt=%d(old=%d), volt_dep=%d(dep_old=%d), up_bnd=%d(dn=%d)\n", + __func__, *volt_new, volt_old, *volt_dep_new, volt_dep_old, up_boundary, low_boundary); + return 0; + } + return 0; + +} +int dvfs_scale_volt(struct vd_node *vd_clk, struct vd_node *vd_dep, + int volt_old, int volt_new, int volt_dep_old, int volt_dep_new, int clk_biger_than_dep, int dep_biger_than_clk) +{ + struct regulator *regulator, *regulator_dep; + int volt = 0, volt_dep = 0, step = 0, step_dep = 0; + int volt_pre = 0, volt_dep_pre = 0; + int ret = 0; + + DVFS_DBG("ENTER %s, volt=%d(old=%d), volt_dep=%d(dep_old=%d)\n", __func__, volt_new, volt_old, volt_dep_new, volt_dep_old); + regulator = vd_clk->regulator; + regulator_dep = vd_dep->regulator; + + if (IS_ERR_OR_NULL(regulator) || IS_ERR(regulator_dep)) { + DVFS_ERR("%s dvfs_clk->vd->regulator or depend->dep_vd->regulator == NULL\n", __func__); + return -1; + } + + volt = volt_old; + volt_dep = volt_dep_old; + + step = volt_new - volt_old > 0 ? 1 : (-1); + step_dep = volt_dep_new - volt_dep_old > 0 ? 1 : (-1); + + DVFS_DBG("step=%d step_dep=%d %d\n", step, step_dep, step * step_dep); + + DVFS_DBG("Volt_new=%d(old=%d), volt_dep_new=%d(dep_old=%d)\n", + volt_new, volt_old, volt_dep_new, volt_dep_old); + do { + volt_pre = volt; + volt_dep_pre = volt_dep; + if (step * step_dep < 0) { + // target is between volt_old and volt_dep_old, just + // need one step + DVFS_DBG("step * step_dep < 0\n"); + volt = volt_new; + volt_dep = volt_dep_new; + + } else if (step > 0) { + // up voltage + DVFS_DBG("step > 0\n"); + + if (volt > volt_dep) { + if (volt_dep == volt_dep_new) { + volt = volt_dep + clk_biger_than_dep; + } else { + volt_dep = volt + dep_biger_than_clk; + } + } else { + if (volt == volt_new) { + volt_dep = volt + dep_biger_than_clk; + } else { + volt = volt_dep + clk_biger_than_dep; + } + } + volt = volt > volt_new ? volt_new : volt; + volt_dep = volt_dep > volt_dep_new ? volt_dep_new : volt_dep; + + } else if (step < 0) { + // down voltage + DVFS_DBG("step < 0\n"); + if (volt > volt_dep) { + if (volt == volt_new) { + volt_dep = volt - clk_biger_than_dep; + } else { + volt = volt_dep - dep_biger_than_clk; + } + } else { + if (volt_dep == volt_dep_new) { + volt = volt_dep - dep_biger_than_clk; + } else { + volt_dep = volt - clk_biger_than_dep; + } + } + volt = volt < volt_new ? volt_new : volt; + volt_dep = volt_dep < volt_dep_new ? volt_dep_new : volt_dep; + + } else { + DVFS_ERR("Oops, some bugs here:Volt_new=%d(old=%d), volt_dep_new=%d(dep_old=%d)\n", + volt_new, volt_old, volt_dep_new, volt_dep_old); + goto fail; + } + + DVFS_DBG("\t\tNOW:Volt=%d, volt_dep=%d\n", volt, volt_dep); + + if (vd_clk->cur_volt != volt) { + ret = dvfs_regulator_set_voltage_readback(regulator, volt, volt); + udelay(get_volt_up_delay(volt, volt_pre)); + if (ret < 0) { + DVFS_ERR("%s %s set voltage up err ret = %d, Vnew = %d(was %d)mV\n", + __func__, vd_clk->name, ret, volt_new, volt_old); + goto fail; + } + vd_clk->cur_volt = volt; + } + + if (vd_dep->cur_volt != volt_dep) { + ret = dvfs_regulator_set_voltage_readback(regulator_dep, volt_dep, volt_dep); + udelay(get_volt_up_delay(volt_dep, volt_dep_pre)); + if (ret < 0) { + DVFS_ERR("depend %s %s set voltage up err ret = %d, Vnew = %d(was %d)mV\n", + __func__, vd_dep->name, ret, volt_dep_new, volt_dep_old); + goto fail; + } + vd_dep->cur_volt = volt_dep; + } + + } while (volt != volt_new || volt_dep!= volt_dep_new); + + vd_clk->volt_set_flag = DVFS_SET_VOLT_SUCCESS; + vd_clk->cur_volt = volt_new; + + return 0; +fail: + DVFS_ERR("+++++++++++++++++FAIL AREA\n"); + vd_clk->cur_volt = volt_old; + vd_dep->cur_volt = volt_dep_old; + vd_clk->volt_set_flag = DVFS_SET_VOLT_FAILURE; + ret = dvfs_regulator_set_voltage_readback(regulator, volt_old, volt_old); + if (ret < 0) { + vd_clk->volt_set_flag = DVFS_SET_VOLT_FAILURE; + DVFS_ERR("%s %s set callback voltage err ret = %d, Vnew = %d(was %d)mV\n", + __func__, vd_clk->name, ret, volt_new, volt_old); + } + + ret = dvfs_regulator_set_voltage_readback(regulator_dep, volt_dep_old, volt_dep_old); + if (ret < 0) { + vd_dep->volt_set_flag = DVFS_SET_VOLT_FAILURE; + DVFS_ERR("%s %s set callback voltage err ret = %d, Vnew = %d(was %d)mV\n", + __func__, vd_dep->name, ret, volt_dep_new, volt_dep_old); + } + + return -1; +} + +int dvfs_scale_volt_direct(struct vd_node *vd_clk, int volt_new) +{ + int ret = 0; + DVFS_DBG("ENTER %s, volt=%d(old=%d)\n", __func__, volt_new, vd_clk->cur_volt); + if (IS_ERR_OR_NULL(vd_clk)) { + DVFS_ERR("%s vd_node error\n", __func__); + return -1; + } + + DVFS_DBG("ENTER %s, volt=%d(old=%d)\n", __func__, volt_new, vd_clk->cur_volt); + if (!IS_ERR_OR_NULL(vd_clk->regulator)) { + ret = dvfs_regulator_set_voltage_readback(vd_clk->regulator, volt_new, volt_new); + if (ret < 0) { + vd_clk->volt_set_flag = DVFS_SET_VOLT_FAILURE; + DVFS_ERR("%s %s set voltage up err ret = %d, Vnew = %d(was %d)mV\n", + __func__, vd_clk->name, ret, volt_new, vd_clk->cur_volt); + return -1; + } + + } else { + DVFS_ERR("%s up volt dvfs_clk->vd->regulator == NULL\n", __func__); + return -1; + } + + vd_clk->volt_set_flag = DVFS_SET_VOLT_SUCCESS; + vd_clk->cur_volt = volt_new; + + return 0; + +} + +int dvfs_scale_volt_bystep(struct vd_node *vd_clk, struct vd_node *vd_dep, int volt_new, int volt_dep_new, + int clk_biger_than_dep, int dep_biger_than_clk) + +{ + int ret = 0; + int volt_old = 0, volt_dep_old = 0; + + volt_old = vd_clk->cur_volt; + volt_dep_old = vd_dep->cur_volt; + + DVFS_DBG("ENTER %s, volt=%d(old=%d) vd_dep=%d(dep_old=%d)\n", __func__, + volt_new, volt_old, volt_dep_new, volt_dep_old); + + if (check_volt_correct(volt_old, &volt_new, volt_dep_old, &volt_dep_new, + clk_biger_than_dep, dep_biger_than_clk) < 0) { + DVFS_ERR("CURRENT VOLT INCORRECT, VD=%s, VD_DEP=%s\n", vd_clk->name, vd_dep->name); + return -1; + } + DVFS_DBG("ENTER %s, volt=%d(old=%d), volt_dep=%d(dep_old=%d)\n", __func__, + volt_new, volt_old, volt_dep_new, volt_dep_old); + ret = dvfs_scale_volt(vd_clk, vd_dep, volt_old, volt_new, volt_dep_old, volt_dep_new, + clk_biger_than_dep, dep_biger_than_clk); + if (ret < 0) { + vd_clk->volt_set_flag = DVFS_SET_VOLT_FAILURE; + DVFS_ERR("set volt error\n"); + return -1; + } + + return 0; +} + +int dvfs_reset_volt(struct vd_node *dvfs_vd) +{ + int flag_set_volt_correct = 0; + if (!IS_ERR_OR_NULL(dvfs_vd->regulator)) + flag_set_volt_correct = dvfs_regulator_get_voltage(dvfs_vd->regulator); + else { + DVFS_ERR("dvfs regulator is ERROR\n"); + return -1; + } + if (flag_set_volt_correct <= 0) { + DVFS_ERR("%s (clk:%s), try to reload arm_volt error %d!!! stop scaling\n", + __func__, dvfs_vd->name, flag_set_volt_correct); + return -1; + } + dvfs_vd->volt_set_flag = DVFS_SET_VOLT_SUCCESS; + DVFS_ERR("%s (clk:%s), try to reload arm_volt! arm_volt_correct = %d\n", + __func__, dvfs_vd->name, flag_set_volt_correct); + + /* Reset vd's voltage */ + dvfs_vd->cur_volt = flag_set_volt_correct; + + return dvfs_vd->cur_volt; +} + +int dvfs_get_depend_volt(struct clk_node *dvfs_clk, struct vd_node *dvfs_vd_dep, int rate_new) +{ + struct depend_list *depend; + struct cpufreq_frequency_table clk_fv_dep; + int ret = 0; + + DVFS_DBG("ENTER %s, rate_new=%d\n", __func__, rate_new); + list_for_each_entry(depend, &dvfs_clk->depend_list, node2clk) { + DVFS_DBG("--round depend clk:%s(depend:%s)\n", depend->dvfs_clk->name, depend->dep_vd->name); + // this place just consider ONE depend voltage domain, + // multi-depends must have some differece + clk_fv_dep.index = 0; + if (depend->dep_vd == dvfs_vd_dep) { + ret = dvfs_clk_get_ref_volt_depend(depend, rate_new / 1000, &clk_fv_dep); + if (ret < 0) { + DVFS_ERR("%s get dvfs_ref_volt_depend error\n", __func__); + return -1; + } + depend->req_volt = clk_fv_dep.index; + return depend->req_volt; + } + } + + DVFS_ERR("%s can not find vd node %s\n", __func__, dvfs_vd_dep->name); + return -1; +} +static struct clk_node *dvfs_clk_cpu; +static struct vd_node vd_core; +int dvfs_target_cpu(struct clk *clk, unsigned long rate_hz) +{ + struct clk_node *dvfs_clk; + int volt_new = 0, volt_dep_new = 0, clk_volt_store = 0; + struct cpufreq_frequency_table clk_fv; + int ret = 0; + unsigned long rate_new, rate_old; + + if (!clk) { + DVFS_ERR("%s is not a clk\n", __func__); + return -1; + } + dvfs_clk = clk_get_dvfs_info(clk); + DVFS_DBG("enter %s: clk(%s) rate = %lu Hz\n", __func__, dvfs_clk->name, rate_hz); + + if (!dvfs_clk || dvfs_clk->vd == NULL || IS_ERR_OR_NULL(dvfs_clk->vd->regulator)) { + DVFS_ERR("dvfs(%s) is not register regulator\n", dvfs_clk->name); + return -1; + } + + if (dvfs_clk->vd->volt_set_flag == DVFS_SET_VOLT_FAILURE) { + /* It means the last time set voltage error */ + ret = dvfs_reset_volt(dvfs_clk->vd); + if (ret < 0) { + return -1; + } + } + + /* Check limit rate */ + if (dvfs_clk->freq_limit_en) { + if (rate_hz < dvfs_clk->min_rate) { + rate_hz = dvfs_clk->min_rate; + } else if (rate_hz > dvfs_clk->max_rate) { + rate_hz = dvfs_clk->max_rate; + } + } + + /* need round rate */ + rate_old = clk_get_rate(clk); + rate_new = clk_round_rate_nolock(clk, rate_hz); + DVFS_DBG("dvfs(%s) round rate (%lu)(rount %lu) old (%lu)\n", + dvfs_clk->name, rate_hz, rate_new, rate_old); + + /* find the clk corresponding voltage */ + if (0 != dvfs_clk_get_ref_volt(dvfs_clk, rate_new / 1000, &clk_fv)) { + DVFS_ERR("dvfs(%s) rate %luhz is larger,not support\n", dvfs_clk->name, rate_hz); + return -1; + } + clk_volt_store = dvfs_clk->set_volt; + dvfs_clk->set_volt = clk_fv.index; + volt_new = dvfs_vd_get_newvolt_byclk(dvfs_clk); + + /* if up the rate */ + if (rate_new > rate_old) { + if (!list_empty(&dvfs_clk->depend_list)) { + // update depend's req_volt + ret = dvfs_get_depend_volt(dvfs_clk, &vd_core, rate_new); + if (ret <= 0) + goto fail_roll_back; + + volt_dep_new = dvfs_vd_get_newvolt_bypd(&vd_core); + if (volt_dep_new <= 0) + goto fail_roll_back; + ret = dvfs_scale_volt_direct(dvfs_clk->vd, volt_new); + + //ret = dvfs_scale_volt_bystep(dvfs_clk->vd, &vd_core, volt_new, volt_dep_new, + // ARM_HIGHER_LOGIC, LOGIC_HIGHER_ARM); + if (ret < 0) + goto fail_roll_back; + } else { + ret = dvfs_scale_volt_direct(dvfs_clk->vd, volt_new); + if (ret < 0) + goto fail_roll_back; + } + } + + /* scale rate */ + if (dvfs_clk->clk_dvfs_target) { + ret = dvfs_clk->clk_dvfs_target(clk, rate_new, clk_set_rate_locked); + } else { + ret = clk_set_rate_locked(clk, rate_new); + } + + if (ret < 0) { + DVFS_ERR("%s set rate err\n", __func__); + goto fail_roll_back; + } + dvfs_clk->set_freq = rate_new / 1000; + + DVFS_DBG("dvfs %s set rate %lu ok\n", dvfs_clk->name, clk_get_rate(clk)); + + /* if down the rate */ + if (rate_new < rate_old) { + if (!list_empty(&dvfs_clk->depend_list)) { + // update depend's req_volt + ret = dvfs_get_depend_volt(dvfs_clk, &vd_core, rate_new); + if (ret <= 0) + goto out; + + volt_dep_new = dvfs_vd_get_newvolt_bypd(&vd_core); + if (volt_dep_new <= 0) + goto out; + ret = dvfs_scale_volt_direct(dvfs_clk->vd, volt_new); + + //ret = dvfs_scale_volt_bystep(dvfs_clk->vd, &vd_core, volt_new, volt_dep_new, + // ARM_HIGHER_LOGIC, LOGIC_HIGHER_ARM); + if (ret < 0) + goto out; + } else { + ret = dvfs_scale_volt_direct(dvfs_clk->vd, volt_new); + if (ret < 0) + goto out; + } + } + + return ret; +fail_roll_back: + dvfs_clk->set_volt = clk_volt_store; + ret = dvfs_get_depend_volt(dvfs_clk, &vd_core, rate_old); + if (ret <= 0) { + DVFS_ERR("%s dvfs_get_depend_volt error when roll back!\n", __func__); + } +out: + return -1; +} + +int dvfs_target_core(struct clk *clk, unsigned long rate_hz) +{ + struct clk_node *dvfs_clk; + int volt_new = 0, volt_dep_new = 0, clk_volt_store = 0; + + struct cpufreq_frequency_table clk_fv; + + int ret = 0; + unsigned long rate_new, rate_old; + + if (!clk) { + DVFS_ERR("%s is not a clk\n", __func__); + return -1; + } + dvfs_clk = clk_get_dvfs_info(clk); + DVFS_DBG("enter %s: clk(%s) rate = %lu Hz\n", __func__, dvfs_clk->name, rate_hz); + + if (!dvfs_clk || dvfs_clk->vd == NULL || IS_ERR_OR_NULL(dvfs_clk->vd->regulator)) { + DVFS_ERR("dvfs(%s) is not register regulator\n", dvfs_clk->name); + return -1; + } + + if (dvfs_clk->vd->volt_set_flag == DVFS_SET_VOLT_FAILURE) { + /* It means the last time set voltage error */ + ret = dvfs_reset_volt(dvfs_clk->vd); + if (ret < 0) { + return -1; + } + } + + /* Check limit rate */ + if (dvfs_clk->freq_limit_en) { + if (rate_hz < dvfs_clk->min_rate) { + rate_hz = dvfs_clk->min_rate; + } else if (rate_hz > dvfs_clk->max_rate) { + rate_hz = dvfs_clk->max_rate; + } + } + + /* need round rate */ + rate_old = clk_get_rate(clk); + rate_new = clk_round_rate_nolock(clk, rate_hz); + DVFS_DBG("dvfs(%s) round rate (%lu)(rount %lu) old (%lu)\n", + dvfs_clk->name, rate_hz, rate_new, rate_old); + + /* find the clk corresponding voltage */ + if (0 != dvfs_clk_get_ref_volt(dvfs_clk, rate_new / 1000, &clk_fv)) { + DVFS_ERR("dvfs(%s) rate %luhz is larger,not support\n", dvfs_clk->name, rate_hz); + return -1; + } + clk_volt_store = dvfs_clk->set_volt; + dvfs_clk->set_volt = clk_fv.index; + volt_new = dvfs_vd_get_newvolt_byclk(dvfs_clk); + + /* if up the rate */ + if (rate_new > rate_old) { + DVFS_DBG("-----------------------------rate_new > rate_old\n"); + volt_dep_new = dvfs_vd_get_newvolt_byclk(dvfs_clk_cpu); + + if (volt_dep_new < 0) + goto fail_roll_back; + + ret = dvfs_scale_volt_bystep(dvfs_clk->vd, dvfs_clk_cpu->vd, volt_new, volt_dep_new, + LOGIC_HIGHER_ARM, ARM_HIGHER_LOGIC); + if (ret < 0) + goto fail_roll_back; + } + + /* scale rate */ + if (dvfs_clk->clk_dvfs_target) { + ret = dvfs_clk->clk_dvfs_target(clk, rate_new, clk_set_rate_locked); + } else { + + ret = clk_set_rate_locked(clk, rate_new); + } + + if (ret < 0) { + DVFS_ERR("%s set rate err\n", __func__); + goto fail_roll_back; + } + dvfs_clk->set_freq = rate_new / 1000; + + DVFS_DBG("dvfs %s set rate %lu ok\n", dvfs_clk->name, clk_get_rate(clk)); + + /* if down the rate */ + if (rate_new < rate_old) { + DVFS_DBG("-----------------------------rate_new < rate_old\n"); + volt_dep_new = dvfs_vd_get_newvolt_byclk(dvfs_clk_cpu); + + if (volt_dep_new < 0) + goto out; + + ret = dvfs_scale_volt_bystep(dvfs_clk->vd, dvfs_clk_cpu->vd, volt_new, volt_dep_new, + LOGIC_HIGHER_ARM, ARM_HIGHER_LOGIC); + if (ret < 0) + goto out; + } + + return ret; +fail_roll_back: + dvfs_clk->set_volt = clk_volt_store; + ret = dvfs_get_depend_volt(dvfs_clk, &vd_core, rate_old); + if (ret <= 0) { + DVFS_ERR("%s dvfs_get_depend_volt error when roll back!\n", __func__); + } + +out: + return -1; +} + +/*****************************init**************************/ +/** + * rate must be raising sequence + */ +static struct cpufreq_frequency_table cpu_dvfs_table[] = { + // {.frequency = 48 * DVFS_KHZ, .index = 920*DVFS_MV}, + // {.frequency = 126 * DVFS_KHZ, .index = 970 * DVFS_MV}, + // {.frequency = 252 * DVFS_KHZ, .index = 1040 * DVFS_MV}, + // {.frequency = 504 * DVFS_KHZ, .index = 1050 * DVFS_MV}, + {.frequency = 816 * DVFS_KHZ, .index = 1050 * DVFS_MV}, + // {.frequency = 1008 * DVFS_KHZ, .index = 1100 * DVFS_MV}, + {.frequency = CPUFREQ_TABLE_END}, +}; + +static struct cpufreq_frequency_table ddr_dvfs_table[] = { + // {.frequency = 100 * DVFS_KHZ, .index = 1100 * DVFS_MV}, + {.frequency = 200 * DVFS_KHZ, .index = 1000 * DVFS_MV}, + {.frequency = 300 * DVFS_KHZ, .index = 1050 * DVFS_MV}, + {.frequency = 400 * DVFS_KHZ, .index = 1100 * DVFS_MV}, + {.frequency = 500 * DVFS_KHZ, .index = 1150 * DVFS_MV}, + {.frequency = 600 * DVFS_KHZ, .index = 1200 * DVFS_MV}, + {.frequency = CPUFREQ_TABLE_END}, +}; + +static struct cpufreq_frequency_table gpu_dvfs_table[] = { + {.frequency = 90 * DVFS_KHZ, .index = 1100 * DVFS_MV}, + {.frequency = 180 * DVFS_KHZ, .index = 1150 * DVFS_MV}, + {.frequency = 300 * DVFS_KHZ, .index = 1100 * DVFS_MV}, + {.frequency = 400 * DVFS_KHZ, .index = 1150 * DVFS_MV}, + {.frequency = 500 * DVFS_KHZ, .index = 1200 * DVFS_MV}, + {.frequency = CPUFREQ_TABLE_END}, +}; + +static struct cpufreq_frequency_table peri_aclk_dvfs_table[] = { + {.frequency = 100 * DVFS_KHZ, .index = 1000 * DVFS_MV}, + {.frequency = 200 * DVFS_KHZ, .index = 1050 * DVFS_MV}, + {.frequency = 300 * DVFS_KHZ, .index = 1070 * DVFS_MV}, + {.frequency = 500 * DVFS_KHZ, .index = 1100 * DVFS_MV}, + {.frequency = CPUFREQ_TABLE_END}, +}; + +static struct cpufreq_frequency_table dep_cpu2core_table[] = { + // {.frequency = 252 * DVFS_KHZ, .index = 1025 * DVFS_MV}, + // {.frequency = 504 * DVFS_KHZ, .index = 1025 * DVFS_MV}, + {.frequency = 816 * DVFS_KHZ, .index = 1050 * DVFS_MV},//logic 1.050V + // {.frequency = 1008 * DVFS_KHZ,.index = 1050 * DVFS_MV}, + // {.frequency = 1200 * DVFS_KHZ,.index = 1050 * DVFS_MV}, + // {.frequency = 1272 * DVFS_KHZ,.index = 1050 * DVFS_MV},//logic 1.050V + // {.frequency = 1416 * DVFS_KHZ,.index = 1100 * DVFS_MV},//logic 1.100V + // {.frequency = 1512 * DVFS_KHZ,.index = 1125 * DVFS_MV},//logic 1.125V + // {.frequency = 1608 * DVFS_KHZ,.index = 1175 * DVFS_MV},//logic 1.175V + {.frequency = CPUFREQ_TABLE_END}, +}; + +static struct vd_node vd_cpu = { + .name = "vd_cpu", + .regulator_name = "vdd_cpu", + .vd_dvfs_target = dvfs_target_cpu, +}; + +static struct vd_node vd_core = { + .name = "vd_core", + .regulator_name = "vdd_core", + .vd_dvfs_target = dvfs_target_core, +}; + +static struct vd_node vd_rtc = { + .name = "vd_rtc", + .regulator_name = "vdd_rtc", + .vd_dvfs_target = NULL, +}; + +static struct vd_node *rk30_vds[] = {&vd_cpu, &vd_core, &vd_rtc}; + +static struct pd_node pd_a9_0 = { + .name = "pd_a9_0", + .vd = &vd_cpu, +}; +static struct pd_node pd_a9_1 = { + .name = "pd_a9_1", + .vd = &vd_cpu, +}; +static struct pd_node pd_debug = { + .name = "pd_debug", + .vd = &vd_cpu, +}; +static struct pd_node pd_scu = { + .name = "pd_scu", + .vd = &vd_cpu, +}; +static struct pd_node pd_video = { + .name = "pd_video", + .vd = &vd_core, +}; +static struct pd_node pd_vio = { + .name = "pd_vio", + .vd = &vd_core, +}; +static struct pd_node pd_gpu = { + .name = "pd_gpu", + .vd = &vd_core, +}; +static struct pd_node pd_peri = { + .name = "pd_peri", + .vd = &vd_core, +}; +static struct pd_node pd_cpu = { + .name = "pd_cpu", + .vd = &vd_core, +}; +static struct pd_node pd_alive = { + .name = "pd_alive", + .vd = &vd_core, +}; +static struct pd_node pd_rtc = { + .name = "pd_rtc", + .vd = &vd_rtc, +}; +#define LOOKUP_PD(_ppd) \ +{ \ + .pd = _ppd, \ +} +static struct pd_node_lookup rk30_pds[] = { + LOOKUP_PD(&pd_a9_0), + LOOKUP_PD(&pd_a9_1), + LOOKUP_PD(&pd_debug), + LOOKUP_PD(&pd_scu), + LOOKUP_PD(&pd_video), + LOOKUP_PD(&pd_vio), + LOOKUP_PD(&pd_gpu), + LOOKUP_PD(&pd_peri), + LOOKUP_PD(&pd_cpu), + LOOKUP_PD(&pd_alive), + LOOKUP_PD(&pd_rtc), +}; + +#define CLK_PDS(_ppd) \ +{ \ + .pd = _ppd, \ +} + +static struct pds_list cpu_pds[] = { + CLK_PDS(&pd_a9_0), + CLK_PDS(&pd_a9_1), + CLK_PDS(NULL), +}; + +static struct pds_list ddr_pds[] = { + CLK_PDS(&pd_cpu), + CLK_PDS(NULL), +}; + +static struct pds_list gpu_pds[] = { + CLK_PDS(&pd_gpu), + CLK_PDS(NULL), +}; + +static struct pds_list aclk_periph_pds[] = { + CLK_PDS(&pd_peri), + CLK_PDS(NULL), +}; + +#define RK_CLKS(_clk_name, _ppds, _dvfs_table, _dvfs_nb) \ +{ \ + .name = _clk_name, \ + .pds = _ppds,\ + .dvfs_table = _dvfs_table, \ + .dvfs_nb = _dvfs_nb, \ +} + +static struct clk_node rk30_clks[] = { + RK_CLKS("cpu", cpu_pds, cpu_dvfs_table, &rk_dvfs_clk_notifier), + //RK_CLKS("ddr", ddr_pds, ddr_dvfs_table, &rk_dvfs_clk_notifier), + RK_CLKS("gpu", gpu_pds, gpu_dvfs_table, &rk_dvfs_clk_notifier), + //RK_CLKS("aclk_periph", aclk_periph_pds, peri_aclk_dvfs_table, &rk_dvfs_clk_notifier), +}; + +#define RK_DEPPENDS(_clk_name, _pvd, _dep_table) \ +{ \ + .clk_name = _clk_name, \ + .dep_vd = _pvd,\ + .dep_table = _dep_table, \ +} + +static struct depend_lookup rk30_depends[] = { + RK_DEPPENDS("cpu", &vd_core, dep_cpu2core_table), + //RK_DEPPENDS("gpu", &vd_cpu, NULL), + //RK_DEPPENDS("gpu", &vd_cpu, NULL), +}; + +int rk30_dvfs_init(void) +{ + int i = 0; + for (i = 0; i < ARRAY_SIZE(rk30_vds); i++) { + rk_regist_vd(rk30_vds[i]); + } + for (i = 0; i < ARRAY_SIZE(rk30_pds); i++) { + rk_regist_pd(&rk30_pds[i]); + } + for (i = 0; i < ARRAY_SIZE(rk30_clks); i++) { + rk_regist_clk(&rk30_clks[i]); + } + for (i = 0; i < ARRAY_SIZE(rk30_depends); i++) { + rk_regist_depends(&rk30_depends[i]); + } + dvfs_clk_cpu = dvfs_get_dvfs_clk_byname("cpu"); + return 0; +} + +/*******************************AVS AREA****************************************/ +/* + * To use AVS function, you must call avs_init in machine_rk30_board_init(void)(board-rk30-sdk.c) + * And then call(vdd_log): + * regulator_set_voltage(dcdc, 1100000, 1100000); + * avs_init_val_get(1,1100000,"wm8326 init"); + * udelay(600); + * avs_set_scal_val(AVS_BASE); + * in wm831x_post_init(board-rk30-sdk-wm8326.c) + * AVS_BASE can use 172 + */ + +static int avs_scale_volt = 0; +static int avs_get_scal_val(int vol); + +int dvfs_avs_scale_table(struct clk *clk, char *depend_vd_name) +{ + /* if depend_vd_name == NULL scale clk table + * else scale clk's depend table, named depend_vd_name + * */ + struct vd_node *vd; + struct depend_list *depend; + struct clk_node *info = clk_get_dvfs_info(clk); + struct cpufreq_frequency_table *table = NULL; + int i; + + if (NULL == depend_vd_name) { + table = info->dvfs_table; + } else { + list_for_each_entry(vd, &rk_dvfs_tree, node) { + if (0 == strcmp(vd->name, depend_vd_name)) { + DVFS_DBG("%s FOUND A MATCH vd\n", __func__); + mutex_lock(&mutex); + list_for_each_entry(depend, &info->depend_list, node2clk) { + if (vd == depend->dep_vd && info == depend->dvfs_clk) { + DVFS_DBG("%s FOUND A MATCH table\n", __func__); + table = depend->dep_table; + break; + } + } + mutex_unlock(&mutex); + } + } + } + + if (table == NULL) { + DVFS_ERR("%s can not find match table\n", __func__); + return -1; + } + if (avs_scale_volt != 0) { + DVFS_DBG("AVS scale %s, depend name = %s, voltage = %d\n", + info->name, depend_vd_name, avs_scale_volt); + for (i = 0; table[i].frequency != CPUFREQ_TABLE_END; i++) { + table[i].index = avs_get_scal_val(table[i].index); + } + } + return 0; +} + +static void __iomem *rk30_nandc_base; + +#define nandc_readl(offset) readl_relaxed(rk30_nandc_base + offset) +#define nandc_writel(v, offset) do { writel_relaxed(v, rk30_nandc_base + offset); dsb(); } while (0) +static u8 rk30_get_avs_val(void) +{ + u32 nanc_save_reg[4]; + unsigned long flags; + u32 paramet = 0; + u32 count = 100; + preempt_disable(); + local_irq_save(flags); + + nanc_save_reg[0] = nandc_readl(0); + nanc_save_reg[1] = nandc_readl(0x130); + nanc_save_reg[2] = nandc_readl(0x134); + nanc_save_reg[3] = nandc_readl(0x158); + + nandc_writel(nanc_save_reg[0] | 0x1 << 14, 0); + nandc_writel(0x5, 0x130); + + nandc_writel(7, 0x158); + nandc_writel(1, 0x134); + + while(count--) { + paramet = nandc_readl(0x138); + if((paramet & 0x1)) + break; + udelay(1); + }; + paramet = (paramet >> 1) & 0xff; + nandc_writel(nanc_save_reg[0], 0); + nandc_writel(nanc_save_reg[1], 0x130); + nandc_writel(nanc_save_reg[2], 0x134); + nandc_writel(nanc_save_reg[3], 0x158); + + local_irq_restore(flags); + preempt_enable(); + return (u8)paramet; + +} +#define init_avs_times 10 +#define init_avs_st_num 5 + +struct init_avs_st { + int is_set; + u8 paramet[init_avs_times]; + int vol; + char *s; +}; + +static struct init_avs_st init_avs_paramet[init_avs_st_num]; + +void avs_init_val_get(int index, int vol, char *s) +{ + int i; + if(index >= init_avs_times) + return; + init_avs_paramet[index].vol = vol; + init_avs_paramet[index].s = s; + init_avs_paramet[index].is_set++; + for(i = 0; i < init_avs_times; i++) { + init_avs_paramet[index].paramet[i] = rk30_get_avs_val(); + mdelay(1); + } +} + +void avs_init(void) +{ + memset(&init_avs_paramet[0].is_set, 0, sizeof(init_avs_paramet)); + rk30_nandc_base = ioremap(RK2928_NANDC_PHYS, RK2928_NANDC_SIZE); + //avs_init_val_get(0,1150000,"board_init"); +} + +#define VOL_DYN_STEP (12500) //mv +#define AVS_VAL_PER_STEP (4) //mv + +static u8 avs_init_get_min_val(void) +{ + int i, j; + u8 min_avs = 0xff; + for(i = 0; i < init_avs_st_num; i++) { + if(init_avs_paramet[i].is_set && init_avs_paramet[i].vol == (1100 * 1000)) { + for(j = 0; j < init_avs_times; j++) + min_avs = (u8)min(min_avs, init_avs_paramet[i].paramet[j]); + } + + } + return min_avs; +} + +static int avs_get_scal_val(int vol) +{ + vol += avs_scale_volt; + if(vol < 1000 * 1000) + vol = 1000 * 1000; + if(vol > 1400 * 1000) + vol = 1400 * 1000; + return vol; +} +#if 0 +u8 avs_test_date[] = {172, 175, 176, 179, 171, 168, 165, 162, 199, 0}; +u8 avs_test_date_cunt = 0; +#endif +int avs_set_scal_val(u8 avs_base) +{ + u8 avs_test = avs_init_get_min_val(); + s8 step = 0; + + if (avs_base < avs_test) { + DVFS_DBG("AVS down voltage, ignore\n"); + return 0; + } + step = (avs_base - avs_test) / AVS_VAL_PER_STEP; + step = (avs_base > avs_test) ? (step + 1) : step; + if (step > 2) + step += 1; + avs_scale_volt = (step) * (VOL_DYN_STEP); + + DVFS_DBG("avs_set_scal_val test=%d,base=%d,step=%d,scale_vol=%d\n", + avs_test, avs_base, step, avs_scale_volt); + return 0; +} + +/*************************interface to get avs value and dvfs tree*************************/ +#define USE_NORMAL_TIME +#ifdef USE_NORMAL_TIME +static struct timer_list avs_timer; +#else +static struct hrtimer dvfs_hrtimer; +#endif + +static u32 avs_dyn_start = 0; +static u32 avs_dyn_data_cnt; +static u8 *avs_dyn_data = NULL; +static u32 show_line_cnt = 0; +static u8 dly_min; +static u8 dly_max; + +#define val_per_line (30) +#define line_pre_show (30) +#define avs_dyn_data_num (3*1000*1000) + +static u32 print_avs_init(char *buf) +{ + char *s = buf; + int i, j; + + for(j = 0; j < init_avs_st_num; j++) { + if(init_avs_paramet[j].vol <= 0) + continue; + s += sprintf(s, "%s ,vol=%d,paramet following\n", + init_avs_paramet[j].s, init_avs_paramet[j].vol); + for(i = 0; i < init_avs_times; i++) { + s += sprintf(s, "%d ", init_avs_paramet[j].paramet[i]); + } + + s += sprintf(s, "\n"); + } + return (s - buf); +} + +static ssize_t avs_init_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return print_avs_init(buf); +} + +static ssize_t avs_init_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t n) +{ + + return n; +} +static ssize_t avs_now_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", rk30_get_avs_val()); +} + +static ssize_t avs_now_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t n) +{ + return n; +} +static ssize_t avs_dyn_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + char *s = buf; + u32 i; + + if(avs_dyn_start) { + int start_cnt; + int end_cnt; + end_cnt = (avs_dyn_data_cnt ? (avs_dyn_data_cnt - 1) : 0); + if(end_cnt > (line_pre_show * val_per_line)) + start_cnt = end_cnt - (line_pre_show * val_per_line); + else + start_cnt = 0; + + dly_min = avs_dyn_data[start_cnt]; + dly_max = avs_dyn_data[start_cnt]; + + //s += sprintf(s,"data start=%d\n",i); + for(i = start_cnt; i <= end_cnt;) { + s += sprintf(s, "%d", avs_dyn_data[i]); + dly_min = min(dly_min, avs_dyn_data[i]); + dly_max = max(dly_max, avs_dyn_data[i]); + i++; + if(!(i % val_per_line)) { + s += sprintf(s, "\n"); + } else + s += sprintf(s, " "); + } + + s += sprintf(s, "\n"); + + s += sprintf(s, "new data is from=%d to %d\n", start_cnt, end_cnt); + //s += sprintf(s,"\n max=%d,min=%d,totolcnt=%d,line=%d\n",dly_max,dly_min,avs_dyn_data_cnt,show_line_cnt); + + + } else { + if(show_line_cnt == 0) { + dly_min = avs_dyn_data[0]; + dly_max = avs_dyn_data[0]; + } + + + for(i = show_line_cnt * (line_pre_show * val_per_line); i < avs_dyn_data_cnt;) { + s += sprintf(s, "%d", avs_dyn_data[i]); + dly_min = min(dly_min, avs_dyn_data[i]); + dly_max = max(dly_max, avs_dyn_data[i]); + i++; + if(!(i % val_per_line)) { + s += sprintf(s, "\n"); + } else + s += sprintf(s, " "); + if(i >= ((show_line_cnt + 1)*line_pre_show * val_per_line)) + break; + } + + s += sprintf(s, "\n"); + + s += sprintf(s, "max=%d,min=%d,totolcnt=%d,line=%d\n", + dly_max, dly_min, avs_dyn_data_cnt, show_line_cnt); + show_line_cnt++; + if(((show_line_cnt * line_pre_show)*val_per_line) >= avs_dyn_data_cnt) { + + show_line_cnt = 0; + + s += sprintf(s, "data is over\n"); + } + } + return (s - buf); +} + +static ssize_t avs_dyn_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t n) +{ + const char *pbuf; + + if((strncmp(buf, "start", strlen("start")) == 0)) { + pbuf = &buf[strlen("start")]; + avs_dyn_data_cnt = 0; + show_line_cnt = 0; + if(avs_dyn_data) { +#ifdef USE_NORMAL_TIME + mod_timer(&avs_timer, jiffies + msecs_to_jiffies(5)); +#else + hrtimer_start(&dvfs_hrtimer, ktime_set(0, 5 * 1000 * 1000), HRTIMER_MODE_REL); +#endif + avs_dyn_start = 1; + } + //sscanf(pbuf, "%d %d", &number, &voltage); + //DVFS_DBG("---------ldo %d %d\n", number, voltage); + + } else if((strncmp(buf, "stop", strlen("stop")) == 0)) { + pbuf = &buf[strlen("stop")]; + avs_dyn_start = 0; + show_line_cnt = 0; + //sscanf(pbuf, "%d %d", &number, &voltage); + //DVFS_DBG("---------dcdc %d %d\n", number, voltage); + } + + + + return n; +} + +static ssize_t dvfs_tree_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t n) +{ + return n; +} +static ssize_t dvfs_tree_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return dump_dbg_map(buf); + +} + +static void avs_timer_fn(unsigned long data) +{ + int i; + for(i = 0; i < 1; i++) { + if(avs_dyn_data_cnt >= avs_dyn_data_num) + return; + avs_dyn_data[avs_dyn_data_cnt] = rk30_get_avs_val(); + avs_dyn_data_cnt++; + } + if(avs_dyn_start) + mod_timer(&avs_timer, jiffies + msecs_to_jiffies(10)); +} +#if 0 +struct hrtimer dvfs_hrtimer; +static enum hrtimer_restart dvfs_hrtimer_timer_func(struct hrtimer *timer) +{ + int i; + for(i = 0; i < 1; i++) { + if(avs_dyn_data_cnt >= avs_dyn_data_num) + return HRTIMER_NORESTART; + avs_dyn_data[avs_dyn_data_cnt] = rk30_get_avs_val(); + avs_dyn_data_cnt++; + } + if(avs_dyn_start) + hrtimer_start(timer, ktime_set(0, 1 * 1000 * 1000), HRTIMER_MODE_REL); + +} +#endif +/*********************************************************************************/ +static struct kobject *dvfs_kobj; +struct dvfs_attribute { + struct attribute attr; + ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr, + char *buf); + ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t n); +}; + +static struct dvfs_attribute dvfs_attrs[] = { + /* node_name permision show_func store_func */ + __ATTR(dvfs_tree, S_IRUGO | S_IWUSR, dvfs_tree_show, dvfs_tree_store), + __ATTR(avs_init, S_IRUGO | S_IWUSR, avs_init_show, avs_init_store), + //__ATTR(avs_dyn, S_IRUGO | S_IWUSR, avs_dyn_show, avs_dyn_store), + __ATTR(avs_now, S_IRUGO | S_IWUSR, avs_now_show, avs_now_store), +}; + +static int __init dvfs_init(void) +{ + int i, ret = 0; +#ifdef USE_NORMAL_TIME + init_timer(&avs_timer); + //avs_timer.expires = jiffies+msecs_to_jiffies(1); + avs_timer.function = avs_timer_fn; + //mod_timer(&avs_timer,jiffies+msecs_to_jiffies(1)); +#else + hrtimer_init(&dvfs_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + dvfs_hrtimer.function = dvfs_hrtimer_timer_func; + //hrtimer_start(&dvfs_hrtimer,ktime_set(0, 5*1000*1000),HRTIMER_MODE_REL); +#endif + avs_dyn_data = kmalloc(avs_dyn_data_num, GFP_KERNEL); + + dvfs_kobj = kobject_create_and_add("dvfs", NULL); + if (!dvfs_kobj) + return -ENOMEM; + for (i = 0; i < ARRAY_SIZE(dvfs_attrs); i++) { + ret = sysfs_create_file(dvfs_kobj, &dvfs_attrs[i].attr); + if (ret != 0) { + DVFS_ERR("create index %d error\n", i); + return ret; + } + } + + return ret; +} +subsys_initcall(dvfs_init); + +/** + * dump_dbg_map() : Draw all informations of dvfs while debug + */ +static int dump_dbg_map(char *buf) +{ + int i; + struct vd_node *vd; + struct pd_node *pd, *clkparent; + struct clk_list *child; + struct clk_node *dvfs_clk; + struct depend_list *depend; + char* s = buf; + + s += sprintf(s, "-------------DVFS TREE-----------\n\n\n"); + s += sprintf(s, "RK30 DVFS TREE:\n"); + list_for_each_entry(vd, &rk_dvfs_tree, node) { + s += sprintf(s, "|\n|- voltage domain:%s\n", vd->name); + s += sprintf(s, "|- current voltage:%d\n", vd->cur_volt); + list_for_each_entry(depend, &vd->req_volt_list, node2vd) { + s += sprintf(s, "|- request voltage:%d, clk:%s\n", depend->req_volt, depend->dvfs_clk->name); + } + + list_for_each_entry(pd, &vd->pd_list, node) { + s += sprintf(s, "| |\n| |- power domain:%s, status = %s, current volt = %d\n", + pd->name, (pd->pd_status == PD_ON) ? "ON" : "OFF", pd->cur_volt); + + list_for_each_entry(child, &pd->clk_list, node) { + dvfs_clk = child->dvfs_clk; + s += sprintf(s, "| | |\n| | |- clock: %s current: rate %d, volt = %d, enable_dvfs = %s\n", + dvfs_clk->name, dvfs_clk->set_freq, dvfs_clk->set_volt, + dvfs_clk->enable_dvfs == 0 ? "DISABLE" : "ENABLE"); + for (i = 0; dvfs_clk->pds[i].pd != NULL; i++) { + clkparent = dvfs_clk->pds[i].pd; + s += sprintf(s, "| | | |- clock parents: %s, vd_parent = %s\n", + clkparent->name, clkparent->vd->name); + } + + for (i = 0; (dvfs_clk->dvfs_table[i].frequency != CPUFREQ_TABLE_END); i++) { + s += sprintf(s, "| | | |- freq = %d, volt = %d\n", + dvfs_clk->dvfs_table[i].frequency, + dvfs_clk->dvfs_table[i].index); + + } + + list_for_each_entry(depend, &dvfs_clk->depend_list, node2clk) { + s += sprintf(s, "| | | | |- DEPEND VD: %s\n", depend->dep_vd->name); + for (i = 0; (depend->dep_table[i].frequency != CPUFREQ_TABLE_END); i++) { + s += sprintf(s, "| | | | |- freq = %d, req_volt = %d\n", + depend->dep_table[i].frequency, + + depend->dep_table[i].index); + } + } + } + } + } + s += sprintf(s, "-------------DVFS TREE END------------\n"); + return s - buf; +} + + diff --git a/arch/arm/mach-rk2928/include/mach/dvfs.h b/arch/arm/mach-rk2928/include/mach/dvfs.h new file mode 100644 index 000000000000..59ec37d4704b --- /dev/null +++ b/arch/arm/mach-rk2928/include/mach/dvfs.h @@ -0,0 +1,183 @@ +/* arch/arm/mach-rk30/rk30_dvfs.h + * + * Copyright (C) 2012 ROCKCHIP, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _RK30_DVFS_H_ +#define _RK30_DVFS_H_ + +#include + +typedef int (*vd_dvfs_target_callback)(struct clk *clk, unsigned long rate); + +typedef int (*dvfs_set_rate_callback)(struct clk *clk, unsigned long rate); + +typedef int (*clk_dvfs_target_callback)(struct clk *clk, unsigned long rate, + dvfs_set_rate_callback set_rate); + +/** + * struct vd_node: To Store All Voltage Domains' info + * @name: Voltage Domain's Name + * @regulator_name: Voltage domain's regulator name + * @cur_volt: Voltage Domain's Current Voltage + * @regulator: Voltage Domain's regulator point + * @node: Point of he Voltage Domain List Node + * @pd_list: Head of Power Domain List Belongs to This Voltage Domain + * @req_volt_list: The list of clocks requests + * @dvfs_mutex: Lock + * @vd_dvfs_target: Callback function + */ + +struct vd_node { + char *name; + char *regulator_name; + int cur_volt; + int volt_set_flag; + struct regulator *regulator; + struct list_head node; + struct list_head pd_list; + struct list_head req_volt_list; + //struct mutex dvfs_mutex; + vd_dvfs_target_callback vd_dvfs_target; +}; + +/** + * struct pd_node: To Store All Power Domains' info + * @name: Power Domain's Name + * @cur_volt: Power Domain's Current Voltage + * @pd_status: Power Domain's status + * @vd: Voltage Domain the power domain belongs to + * @pd_clk: Look power domain as a clock + * @node: List node to Voltage Domain + * @clk_list: Head of Power Domain's Clocks List + */ +struct pd_node { + char *name; + int cur_volt; + unsigned char pd_status; + struct vd_node *vd; + //struct clk *pd_clk; + struct list_head node; + struct list_head clk_list; +}; + +struct pd_node_lookup { + struct pd_node *pd; +}; + +struct clk_list{ + struct clk_node *dvfs_clk; + struct list_head node; +}; + +struct pds_list { + struct clk_list clk_list; + struct pd_node *pd; +}; + +struct depend_list { + int req_volt; + struct clk_node *dvfs_clk; + struct vd_node *dep_vd; + struct list_head node2clk; + struct list_head node2vd; + struct cpufreq_frequency_table *dep_table; +}; + +struct depend_lookup { + char *clk_name; + struct clk_node *dvfs_clk; + struct vd_node *dep_vd; + struct depend_list dep_list; + struct cpufreq_frequency_table *dep_table; +}; + +/** + * struct clk_node: To Store All dvfs clocks' info + * @name: Dvfs clock's Name + * @set_freq: Dvfs clock's Current Frequency + * @set_volt: Dvfs clock's Current Voltage + * @enable_dvfs: Sign if DVFS clock enable + * @clk: System clk's point + * @pds: Power Domains dvfs clock belongs to + * @vd: Voltage Domains dvfs clock belongs to + * @depend_list: Dvfs Clock depend list + * @dvfs_nb: Notify list + * @dvfs_table: Frequency and voltage table for dvfs + * @clk_dvfs_target: Callback function + */ +struct clk_node { + char *name; + int set_freq; //KHZ + int set_volt; //MV + int enable_dvfs; + int freq_limit_en; //sign if use limit frequency + unsigned int min_rate; //limit min frequency + unsigned int max_rate; //limit max frequency + struct clk *clk; + struct pds_list *pds; + struct vd_node *vd; + struct list_head depend_list; + struct notifier_block *dvfs_nb; + struct cpufreq_frequency_table *dvfs_table; + clk_dvfs_target_callback clk_dvfs_target; +}; + +struct dvfs_arm_table { + unsigned int frequency; /* kHz - doesn't need to be in ascending + * order */ + unsigned int cpu_volt; /* any */ + + unsigned int logic_volt; +}; + +#ifdef CONFIG_DVFS +int rk30_dvfs_init(void); +int is_support_dvfs(struct clk_node *dvfs_info); +int dvfs_set_rate(struct clk *clk, unsigned long rate); +int clk_enable_dvfs(struct clk *clk); +int clk_disable_dvfs(struct clk *clk); +void dvfs_clk_register_set_rate_callback(struct clk *clk, clk_dvfs_target_callback clk_dvfs_target); +struct cpufreq_frequency_table *dvfs_get_freq_volt_table(struct clk *clk); +int dvfs_set_freq_volt_table(struct clk *clk, struct cpufreq_frequency_table *table); +int dvfs_set_depend_table(struct clk *clk, char *vd_name, struct cpufreq_frequency_table *table); +int dvfs_set_arm_logic_volt(struct dvfs_arm_table *dvfs_cpu_logic_table, struct cpufreq_frequency_table *cpu_dvfs_table, struct cpufreq_frequency_table *dep_cpu2core_table); +struct regulator* dvfs_get_regulator(char *regulator_name); +int dvfs_clk_enable_limit(struct clk *clk, unsigned int min_rate, unsigned max_rate); +int dvfs_clk_disable_limit(struct clk *clk); + +void avs_init(void); +void avs_init_val_get(int index,int vol,char *s); +int avs_set_scal_val(u8 avs_base); +int dvfs_avs_scale_table(struct clk* clk, char* depend_vd_name); +#else +static inline int rk30_dvfs_init(void) { return 0; } +static inline int is_support_dvfs(struct clk_node *dvfs_info) { return 0; } +static inline int dvfs_set_rate(struct clk *clk, unsigned long rate) { return 0; } +static inline int clk_enable_dvfs(struct clk *clk) { return 0; } +static inline int clk_disable_dvfs(struct clk *clk) { return 0; } +static inline void dvfs_clk_register_set_rate_callback(struct clk *clk, clk_dvfs_target_callback clk_dvfs_target) {} +static inline struct cpufreq_frequency_table *dvfs_get_freq_volt_table(struct clk *clk) { return NULL; } +static inline int dvfs_set_freq_volt_table(struct clk *clk, struct cpufreq_frequency_table *table) { return 0; } +static inline int dvfs_set_depend_table(struct clk *clk, char *vd_name, struct cpufreq_frequency_table *table) {return 0;} +static inline int dvfs_set_arm_logic_volt(struct dvfs_arm_table *dvfs_cpu_logic_table, struct cpufreq_frequency_table *cpu_dvfs_table, struct cpufreq_frequency_table *dep_cpu2core_table){ return 0; } +static inline struct regulator* dvfs_get_regulator(char *regulator_name){ return NULL; } +static inline int dvfs_clk_enable_limit(struct clk *clk, unsigned int min_rate, unsigned max_rate){ return 0; } +static inline int dvfs_clk_disable_limit(struct clk *clk){ return 0; }; + +static inline void avs_init(void){}; +static inline void avs_init_val_get(int index, int vol, char *s){}; +static inline int avs_set_scal_val(u8 avs_base){ return 0; }; +static inline int dvfs_avs_scale_table(struct clk* clk, char* depend_vd_name){ return 0; }; +#endif + +#endif