[PATCH 4/5] [CPUFREQ] EXYNOS4210: Add DVS lock feature for other driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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


[Index of Archives]     [Linux SoC Development]     [Linux Rockchip Development]     [Linux USB Development]     [Video for Linux]     [Linux Audio Users]     [Linux SCSI]     [Yosemite News]

  Powered by Linux