| /* | 
 |  * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. | 
 |  *		http://www.samsung.com | 
 |  * | 
 |  * EXYNOS - CPU frequency scaling support for EXYNOS series | 
 |  * | 
 |  * 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. | 
 | */ | 
 |  | 
 | #include <linux/kernel.h> | 
 | #include <linux/err.h> | 
 | #include <linux/clk.h> | 
 | #include <linux/io.h> | 
 | #include <linux/slab.h> | 
 | #include <linux/regulator/consumer.h> | 
 | #include <linux/cpufreq.h> | 
 | #include <linux/suspend.h> | 
 |  | 
 | #include <mach/cpufreq.h> | 
 |  | 
 | #include <plat/cpu.h> | 
 |  | 
 | static struct exynos_dvfs_info *exynos_info; | 
 |  | 
 | static struct regulator *arm_regulator; | 
 | static struct cpufreq_freqs freqs; | 
 |  | 
 | static unsigned int locking_frequency; | 
 | static bool frequency_locked; | 
 | static DEFINE_MUTEX(cpufreq_lock); | 
 |  | 
 | int exynos_verify_speed(struct cpufreq_policy *policy) | 
 | { | 
 | 	return cpufreq_frequency_table_verify(policy, | 
 | 					      exynos_info->freq_table); | 
 | } | 
 |  | 
 | unsigned int exynos_getspeed(unsigned int cpu) | 
 | { | 
 | 	return clk_get_rate(exynos_info->cpu_clk) / 1000; | 
 | } | 
 |  | 
 | static int exynos_target(struct cpufreq_policy *policy, | 
 | 			  unsigned int target_freq, | 
 | 			  unsigned int relation) | 
 | { | 
 | 	unsigned int index, old_index; | 
 | 	unsigned int arm_volt, safe_arm_volt = 0; | 
 | 	int ret = 0; | 
 | 	struct cpufreq_frequency_table *freq_table = exynos_info->freq_table; | 
 | 	unsigned int *volt_table = exynos_info->volt_table; | 
 | 	unsigned int mpll_freq_khz = exynos_info->mpll_freq_khz; | 
 |  | 
 | 	mutex_lock(&cpufreq_lock); | 
 |  | 
 | 	freqs.old = policy->cur; | 
 |  | 
 | 	if (frequency_locked && target_freq != locking_frequency) { | 
 | 		ret = -EAGAIN; | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 * The policy max have been changed so that we cannot get proper | 
 | 	 * old_index with cpufreq_frequency_table_target(). Thus, ignore | 
 | 	 * policy and get the index from the raw freqeuncy table. | 
 | 	 */ | 
 | 	for (old_index = 0; | 
 | 		freq_table[old_index].frequency != CPUFREQ_TABLE_END; | 
 | 		old_index++) | 
 | 		if (freq_table[old_index].frequency == freqs.old) | 
 | 			break; | 
 |  | 
 | 	if (freq_table[old_index].frequency == CPUFREQ_TABLE_END) { | 
 | 		ret = -EINVAL; | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	if (cpufreq_frequency_table_target(policy, freq_table, | 
 | 					   target_freq, relation, &index)) { | 
 | 		ret = -EINVAL; | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	freqs.new = freq_table[index].frequency; | 
 | 	freqs.cpu = policy->cpu; | 
 |  | 
 | 	/* | 
 | 	 * ARM clock source will be changed APLL to MPLL temporary | 
 | 	 * To support this level, need to control regulator for | 
 | 	 * required voltage level | 
 | 	 */ | 
 | 	if (exynos_info->need_apll_change != NULL) { | 
 | 		if (exynos_info->need_apll_change(old_index, index) && | 
 | 		   (freq_table[index].frequency < mpll_freq_khz) && | 
 | 		   (freq_table[old_index].frequency < mpll_freq_khz)) | 
 | 			safe_arm_volt = volt_table[exynos_info->pll_safe_idx]; | 
 | 	} | 
 | 	arm_volt = volt_table[index]; | 
 |  | 
 | 	cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); | 
 |  | 
 | 	/* When the new frequency is higher than current frequency */ | 
 | 	if ((freqs.new > freqs.old) && !safe_arm_volt) { | 
 | 		/* Firstly, voltage up to increase frequency */ | 
 | 		regulator_set_voltage(arm_regulator, arm_volt, | 
 | 				arm_volt); | 
 | 	} | 
 |  | 
 | 	if (safe_arm_volt) | 
 | 		regulator_set_voltage(arm_regulator, safe_arm_volt, | 
 | 				      safe_arm_volt); | 
 | 	if (freqs.new != freqs.old) | 
 | 		exynos_info->set_freq(old_index, index); | 
 |  | 
 | 	cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); | 
 |  | 
 | 	/* When the new frequency is lower than current frequency */ | 
 | 	if ((freqs.new < freqs.old) || | 
 | 	   ((freqs.new > freqs.old) && safe_arm_volt)) { | 
 | 		/* down the voltage after frequency change */ | 
 | 		regulator_set_voltage(arm_regulator, arm_volt, | 
 | 				arm_volt); | 
 | 	} | 
 |  | 
 | out: | 
 | 	mutex_unlock(&cpufreq_lock); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | #ifdef CONFIG_PM | 
 | static int exynos_cpufreq_suspend(struct cpufreq_policy *policy) | 
 | { | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int exynos_cpufreq_resume(struct cpufreq_policy *policy) | 
 | { | 
 | 	return 0; | 
 | } | 
 | #endif | 
 |  | 
 | /** | 
 |  * exynos_cpufreq_pm_notifier - block CPUFREQ's activities in suspend-resume | 
 |  *			context | 
 |  * @notifier | 
 |  * @pm_event | 
 |  * @v | 
 |  * | 
 |  * While frequency_locked == true, target() ignores every frequency but | 
 |  * locking_frequency. The locking_frequency value is the initial frequency, | 
 |  * which is set by the bootloader. In order to eliminate possible | 
 |  * inconsistency in clock values, we save and restore frequencies during | 
 |  * suspend and resume and block CPUFREQ activities. Note that the standard | 
 |  * suspend/resume cannot be used as they are too deep (syscore_ops) for | 
 |  * regulator actions. | 
 |  */ | 
 | static int exynos_cpufreq_pm_notifier(struct notifier_block *notifier, | 
 | 				       unsigned long pm_event, void *v) | 
 | { | 
 | 	struct cpufreq_policy *policy = cpufreq_cpu_get(0); /* boot CPU */ | 
 | 	static unsigned int saved_frequency; | 
 | 	unsigned int temp; | 
 |  | 
 | 	mutex_lock(&cpufreq_lock); | 
 | 	switch (pm_event) { | 
 | 	case PM_SUSPEND_PREPARE: | 
 | 		if (frequency_locked) | 
 | 			goto out; | 
 |  | 
 | 		frequency_locked = true; | 
 |  | 
 | 		if (locking_frequency) { | 
 | 			saved_frequency = exynos_getspeed(0); | 
 |  | 
 | 			mutex_unlock(&cpufreq_lock); | 
 | 			exynos_target(policy, locking_frequency, | 
 | 				      CPUFREQ_RELATION_H); | 
 | 			mutex_lock(&cpufreq_lock); | 
 | 		} | 
 | 		break; | 
 |  | 
 | 	case PM_POST_SUSPEND: | 
 | 		if (saved_frequency) { | 
 | 			/* | 
 | 			 * While frequency_locked, only locking_frequency | 
 | 			 * is valid for target(). In order to use | 
 | 			 * saved_frequency while keeping frequency_locked, | 
 | 			 * we temporarly overwrite locking_frequency. | 
 | 			 */ | 
 | 			temp = locking_frequency; | 
 | 			locking_frequency = saved_frequency; | 
 |  | 
 | 			mutex_unlock(&cpufreq_lock); | 
 | 			exynos_target(policy, locking_frequency, | 
 | 				      CPUFREQ_RELATION_H); | 
 | 			mutex_lock(&cpufreq_lock); | 
 |  | 
 | 			locking_frequency = temp; | 
 | 		} | 
 | 		frequency_locked = false; | 
 | 		break; | 
 | 	} | 
 | out: | 
 | 	mutex_unlock(&cpufreq_lock); | 
 |  | 
 | 	return NOTIFY_OK; | 
 | } | 
 |  | 
 | static struct notifier_block exynos_cpufreq_nb = { | 
 | 	.notifier_call = exynos_cpufreq_pm_notifier, | 
 | }; | 
 |  | 
 | static int exynos_cpufreq_cpu_init(struct cpufreq_policy *policy) | 
 | { | 
 | 	policy->cur = policy->min = policy->max = exynos_getspeed(policy->cpu); | 
 |  | 
 | 	cpufreq_frequency_table_get_attr(exynos_info->freq_table, policy->cpu); | 
 |  | 
 | 	locking_frequency = exynos_getspeed(0); | 
 |  | 
 | 	/* set the transition latency value */ | 
 | 	policy->cpuinfo.transition_latency = 100000; | 
 |  | 
 | 	/* | 
 | 	 * EXYNOS4 multi-core processors has 2 cores | 
 | 	 * that the frequency cannot be set independently. | 
 | 	 * Each cpu is bound to the same speed. | 
 | 	 * So the affected cpu is all of the cpus. | 
 | 	 */ | 
 | 	if (num_online_cpus() == 1) { | 
 | 		cpumask_copy(policy->related_cpus, cpu_possible_mask); | 
 | 		cpumask_copy(policy->cpus, cpu_online_mask); | 
 | 	} else { | 
 | 		cpumask_setall(policy->cpus); | 
 | 	} | 
 |  | 
 | 	return cpufreq_frequency_table_cpuinfo(policy, exynos_info->freq_table); | 
 | } | 
 |  | 
 | static struct cpufreq_driver exynos_driver = { | 
 | 	.flags		= CPUFREQ_STICKY, | 
 | 	.verify		= exynos_verify_speed, | 
 | 	.target		= exynos_target, | 
 | 	.get		= exynos_getspeed, | 
 | 	.init		= exynos_cpufreq_cpu_init, | 
 | 	.name		= "exynos_cpufreq", | 
 | #ifdef CONFIG_PM | 
 | 	.suspend	= exynos_cpufreq_suspend, | 
 | 	.resume		= exynos_cpufreq_resume, | 
 | #endif | 
 | }; | 
 |  | 
 | static int __init exynos_cpufreq_init(void) | 
 | { | 
 | 	int ret = -EINVAL; | 
 |  | 
 | 	exynos_info = kzalloc(sizeof(struct exynos_dvfs_info), GFP_KERNEL); | 
 | 	if (!exynos_info) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	if (soc_is_exynos4210()) | 
 | 		ret = exynos4210_cpufreq_init(exynos_info); | 
 | 	else if (soc_is_exynos4212() || soc_is_exynos4412()) | 
 | 		ret = exynos4x12_cpufreq_init(exynos_info); | 
 | 	else if (soc_is_exynos5250()) | 
 | 		ret = exynos5250_cpufreq_init(exynos_info); | 
 | 	else | 
 | 		pr_err("%s: CPU type not found\n", __func__); | 
 |  | 
 | 	if (ret) | 
 | 		goto err_vdd_arm; | 
 |  | 
 | 	if (exynos_info->set_freq == NULL) { | 
 | 		pr_err("%s: No set_freq function (ERR)\n", __func__); | 
 | 		goto err_vdd_arm; | 
 | 	} | 
 |  | 
 | 	arm_regulator = regulator_get(NULL, "vdd_arm"); | 
 | 	if (IS_ERR(arm_regulator)) { | 
 | 		pr_err("%s: failed to get resource vdd_arm\n", __func__); | 
 | 		goto err_vdd_arm; | 
 | 	} | 
 |  | 
 | 	register_pm_notifier(&exynos_cpufreq_nb); | 
 |  | 
 | 	if (cpufreq_register_driver(&exynos_driver)) { | 
 | 		pr_err("%s: failed to register cpufreq driver\n", __func__); | 
 | 		goto err_cpufreq; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | err_cpufreq: | 
 | 	unregister_pm_notifier(&exynos_cpufreq_nb); | 
 |  | 
 | 	if (!IS_ERR(arm_regulator)) | 
 | 		regulator_put(arm_regulator); | 
 | err_vdd_arm: | 
 | 	kfree(exynos_info); | 
 | 	pr_debug("%s: failed initialization\n", __func__); | 
 | 	return -EINVAL; | 
 | } | 
 | late_initcall(exynos_cpufreq_init); |