On Wed, Nov 2, 2011 at 9:43 PM, Kukjin Kim <kgene.kim@xxxxxxxxxxx> wrote: > From: Jongpill Lee <boyko.lee@xxxxxxxxxxx> > > This patch adds DVS lock feature for other driver and pm/ > reboot notifier to enhance stability. > > Signed-off-by: Jongpill Lee <boyko.lee@xxxxxxxxxxx> > Signed-off-by: SangWook Ju <sw.ju@xxxxxxxxxxx> > Signed-off-by: Jonghwan Choi <jhbird.choi@xxxxxxxxxxx> > Signed-off-by: Jaecheol Lee <jc.lee@xxxxxxxxxxx> > --- > arch/arm/mach-exynos4/include/mach/cpufreq.h | 39 ++++++ > drivers/cpufreq/exynos4210-cpufreq.c | 174 > +++++++++++++++++++++++++- > 2 files changed, 207 insertions(+), 6 deletions(-) > create mode 100644 arch/arm/mach-exynos4/include/mach/cpufreq.h > > diff --git a/arch/arm/mach-exynos4/include/mach/cpufreq.h > b/arch/arm/mach-exynos4/include/mach/cpufreq.h > new file mode 100644 > index 0000000..7e00931 > --- /dev/null > +++ b/arch/arm/mach-exynos4/include/mach/cpufreq.h > @@ -0,0 +1,39 @@ > +/* linux/arch/arm/mach-exynos4/include/mach/cpufreq.h > + * > + * Copyright (c) 2011 Samsung Electronics Co., Ltd. > + * http://www.samsung.com > + * > + * EXYNOS4 - CPUFreq support > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + */ > + > +/* > + * CPU frequency level index for using cpufreq lock API > + * This should be same with cpufreq_frequency_table > + */ > +enum cpufreq_level_request { > + CPU_L0, /* 1200MHz */ > + CPU_L1, /* 1000MHz */ > + CPU_L2, /* 800MHz */ > + CPU_L3, /* 500MHz */ > + CPU_L4, /* 200MHz */ > + CPU_LEVEL_END, > +}; > + > +enum cpufreq_lock_ID { > + DVFS_LOCK_ID_G2D, /* G2D */ > + DVFS_LOCK_ID_TV, /* TV */ > + DVFS_LOCK_ID_MFC, /* MFC */ > + DVFS_LOCK_ID_USB, /* USB */ > + DVFS_LOCK_ID_CAM, /* CAM */ > + DVFS_LOCK_ID_PM, /* PM */ > + DVFS_LOCK_ID_USER, /* USER */ > + DVFS_LOCK_ID_END, > +}; > + > +int exynos4_cpufreq_lock(unsigned int nId, > + enum cpufreq_level_request cpufreq_level); > +void exynos4_cpufreq_lock_free(unsigned int nId); > diff --git a/drivers/cpufreq/exynos4210-cpufreq.c > b/drivers/cpufreq/exynos4210-cpufreq.c > index 246f9e2..30e1949 100644 > --- a/drivers/cpufreq/exynos4210-cpufreq.c > +++ b/drivers/cpufreq/exynos4210-cpufreq.c > @@ -17,14 +17,21 @@ > #include <linux/slab.h> > #include <linux/regulator/consumer.h> > #include <linux/cpufreq.h> > +#include <linux/suspend.h> > +#include <linux/reboot.h> > > #include <mach/map.h> > #include <mach/regs-clock.h> > #include <mach/regs-mem.h> > +#include <mach/cpufreq.h> > > #include <plat/clock.h> > #include <plat/pm.h> > > +static bool exynos4_cpufreq_init_done; > +static DEFINE_MUTEX(set_freq_lock); > +static DEFINE_MUTEX(set_cpu_freq_lock); > + > static struct clk *cpu_clk; > static struct clk *moutcore; > static struct clk *mout_mpll; > @@ -53,6 +60,12 @@ static struct cpufreq_frequency_table > exynos4_freq_table[] = { > {0, CPUFREQ_TABLE_END}, > }; > > +/* This defines are for cpufreq lock */ > +#define CPUFREQ_MIN_LEVEL (CPUFREQ_LEVEL_END - 1) > +unsigned int cpufreq_lock_id; > +unsigned int cpufreq_lock_val[DVFS_LOCK_ID_END]; > +unsigned int cpufreq_lock_level = CPUFREQ_MIN_LEVEL; > + > static unsigned int clkdiv_cpu0[CPUFREQ_LEVEL_END][7] = { > /* > * Clock divider value for following > @@ -272,22 +285,31 @@ static int exynos4_target(struct cpufreq_policy > *policy, > { > unsigned int index, old_index; > unsigned int arm_volt; > + int ret = 0; > + > + mutex_lock(&set_freq_lock); > > freqs.old = exynos4_getspeed(policy->cpu); > > if (cpufreq_frequency_table_target(policy, exynos4_freq_table, > - freqs.old, relation, &old_index)) > - return -EINVAL; > + freqs.old, relation, &old_index)) > { > + ret = -EINVAL; > + goto out; > + } > > if (cpufreq_frequency_table_target(policy, exynos4_freq_table, > - target_freq, relation, &index)) > - return -EINVAL; > + target_freq, relation, &index)) { > + ret = -EINVAL; > + goto out; > + } > > freqs.new = exynos4_freq_table[index].frequency; > freqs.cpu = policy->cpu; > > - if (freqs.new == freqs.old) > - return 0; > + if (freqs.new == freqs.old) { > + ret = -EINVAL; > + goto out; > + } > > /* get the voltage value */ > arm_volt = exynos4_volt_table[index].arm_volt; > @@ -311,8 +333,98 @@ static int exynos4_target(struct cpufreq_policy > *policy, > regulator_set_voltage(arm_regulator, arm_volt, arm_volt); > } > > +out: > + mutex_unlock(&set_freq_lock); > + > + return ret; > +} > + > +atomic_t exynos4_cpufreq_lock_count; > + > +int exynos4_cpufreq_lock(unsigned int id, > + enum cpufreq_level_request cpufreq_level) > +{ > + int i, old_idx = 0; > + unsigned int freq_old, freq_new, arm_volt; > + > + if (!exynos4_cpufreq_init_done) > + return 0; > + > + if (cpufreq_lock_id & (1 << id)) { > + printk(KERN_ERR "%s:Device [%d] already locked cpufreq\n", > + __func__, id); > + return 0; > + } > + mutex_lock(&set_cpu_freq_lock); > + cpufreq_lock_id |= (1 << id); > + cpufreq_lock_val[id] = cpufreq_level; > + > + /* If the requested cpufreq is higher than current min frequency */ > + if (cpufreq_level < cpufreq_lock_level) > + cpufreq_lock_level = cpufreq_level; > + > + mutex_unlock(&set_cpu_freq_lock); > + > + /* > + * If current frequency is lower than requested freq, > + * it needs to update > + */ > + mutex_lock(&set_freq_lock); > + freq_old = exynos4_getspeed(0); > + freq_new = exynos4_freq_table[cpufreq_level].frequency; > + if (freq_old < freq_new) { > + /* Find out current level index */ > + for (i = 0 ; i < CPUFREQ_LEVEL_END ; i++) { > + if (freq_old == exynos4_freq_table[i].frequency) { > + old_idx = exynos4_freq_table[i].index; > + break; > + } else if (i == (CPUFREQ_LEVEL_END - 1)) { > + printk(KERN_ERR "%s: Level not found\n", > + __func__); > + mutex_unlock(&set_freq_lock); > + return -EINVAL; > + } else { > + continue; > + } > + } > + freqs.old = freq_old; > + freqs.new = freq_new; > + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); > + > + /* get the voltage value */ > + arm_volt = exynos4_volt_table[cpufreq_level].arm_volt; > + regulator_set_voltage(arm_regulator, arm_volt, > + arm_volt); > + > + exynos4_set_frequency(old_idx, cpufreq_level); > + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); > + } > + mutex_unlock(&set_freq_lock); > + > return 0; > } > +EXPORT_SYMBOL_GPL(exynos4_cpufreq_lock); > + > +void exynos4_cpufreq_lock_free(unsigned int id) > +{ > + int i; > + > + if (!exynos4_cpufreq_init_done) > + return; > + > + mutex_lock(&set_cpu_freq_lock); > + cpufreq_lock_id &= ~(1 << id); > + cpufreq_lock_val[id] = CPUFREQ_MIN_LEVEL; > + cpufreq_lock_level = CPUFREQ_MIN_LEVEL; > + if (cpufreq_lock_id) { > + for (i = 0; i < DVFS_LOCK_ID_END; i++) { > + if (cpufreq_lock_val[i] < cpufreq_lock_level) > + cpufreq_lock_level = cpufreq_lock_val[i]; > + } > + } > + mutex_unlock(&set_cpu_freq_lock); > +} > +EXPORT_SYMBOL_GPL(exynos4_cpufreq_lock_free); > > #ifdef CONFIG_PM > static int exynos4_cpufreq_suspend(struct cpufreq_policy *policy) > @@ -326,6 +438,31 @@ static int exynos4_cpufreq_resume(struct cpufreq_policy > *policy) > } > #endif > > +static int exynos4_cpufreq_notifier_event(struct notifier_block *this, > + unsigned long event, void *ptr) > +{ > + int ret; > + > + switch (event) { > + case PM_SUSPEND_PREPARE: > + ret = exynos4_cpufreq_lock(DVFS_LOCK_ID_PM, CPU_L0); > + if (ret < 0) > + return NOTIFY_BAD; > + pr_debug("PM_SUSPEND_PREPARE for CPUFREQ\n"); > + return NOTIFY_OK; > + case PM_POST_RESTORE: > + case PM_POST_SUSPEND: > + pr_debug("PM_POST_SUSPEND for CPUFREQ\n"); > + exynos4_cpufreq_lock_free(DVFS_LOCK_ID_PM); > + return NOTIFY_OK; > + } > + return NOTIFY_DONE; > +} > + > +static struct notifier_block exynos4_cpufreq_notifier = { > + .notifier_call = exynos4_cpufreq_notifier_event, > +}; > + > static int exynos4_cpufreq_cpu_init(struct cpufreq_policy *policy) > { > policy->cur = policy->min = policy->max = > exynos4_getspeed(policy->cpu); > @@ -351,6 +488,23 @@ static int exynos4_cpufreq_cpu_init(struct > cpufreq_policy *policy) > return cpufreq_frequency_table_cpuinfo(policy, exynos4_freq_table); > } > > +static int exynos4_cpufreq_reboot_notifier_call(struct notifier_block > *this, > + unsigned long code, void *_cmd) > +{ > + int ret; > + > + ret = exynos4_cpufreq_lock(DVFS_LOCK_ID_PM, CPU_L0); > + if (ret < 0) > + return NOTIFY_BAD; > + > + printk(KERN_INFO "REBOOT Notifier for CPUFREQ\n"); > + return NOTIFY_DONE; > +} > + > +static struct notifier_block exynos4_cpufreq_reboot_notifier = { > + .notifier_call = exynos4_cpufreq_reboot_notifier_call, > +}; > + > static struct cpufreq_driver exynos4_driver = { > .flags = CPUFREQ_STICKY, > .verify = exynos4_verify_speed, > @@ -391,6 +545,11 @@ static int __init exynos4_cpufreq_init(void) > goto err_vdd_arm; > } > > + register_pm_notifier(&exynos4_cpufreq_notifier); > + register_reboot_notifier(&exynos4_cpufreq_reboot_notifier); > + > + exynos4_cpufreq_init_done = true; > + > tmp = __raw_readl(S5P_CLKDIV_CPU); > > for (i = L0; i < CPUFREQ_LEVEL_END; i++) { > @@ -420,6 +579,9 @@ static int __init exynos4_cpufreq_init(void) > > return 0; > err_cpufreq: > + unregister_reboot_notifier(&exynos4_cpufreq_reboot_notifier); > + unregister_pm_notifier(&exynos4_cpufreq_notifier); > + > if (!IS_ERR(arm_regulator)) > regulator_put(arm_regulator); > > -- > 1.7.1 > > -- > To unsubscribe from this list: send the line "unsubscribe linux-samsung-soc" in > the body of a message to majordomo@xxxxxxxxxxxxxxx > More majordomo info at http://vger.kernel.org/majordomo-info.html > Except for the case of PM (suspend/reset), it appears that this feature is something that is meant to be supported by qos (<linux/pm_qos.h>). Couldn't we simply use the QoS feature to support locking frequency above some specific levels? With QoS, I guess the implementation becomes more general and straightforward. Although we will probably need to use "CPU_DMA_LATENCY" for this case, I think it isn't awefully difficult to use DMA-Latency as the metric. One concern about this is that we are hereby enforcing all the related device drivers stuck with Exynos4210 only unless others also use "cpufreq_lock_ID" and its friends. Cheers! MyungJoo. -- MyungJoo Ham, Ph.D. Mobile Software Platform Lab, DMC Business, Samsung Electronics -- To unsubscribe from this list: send the line "unsubscribe linux-samsung-soc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html