|  | /* | 
|  | * arch/arm/mach-tegra/cpu-tegra.c | 
|  | * | 
|  | * Copyright (C) 2010 Google, Inc. | 
|  | * | 
|  | * Author: | 
|  | *	Colin Cross <ccross@google.com> | 
|  | *	Based on arch/arm/plat-omap/cpu-omap.c, (C) 2005 Nokia Corporation | 
|  | * | 
|  | * 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 <linux/kernel.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/types.h> | 
|  | #include <linux/sched.h> | 
|  | #include <linux/cpufreq.h> | 
|  | #include <linux/delay.h> | 
|  | #include <linux/init.h> | 
|  | #include <linux/err.h> | 
|  | #include <linux/clk.h> | 
|  | #include <linux/io.h> | 
|  | #include <linux/suspend.h> | 
|  |  | 
|  | #include <asm/system.h> | 
|  |  | 
|  | #include <mach/hardware.h> | 
|  | #include <mach/clk.h> | 
|  |  | 
|  | /* Frequency table index must be sequential starting at 0 */ | 
|  | static struct cpufreq_frequency_table freq_table[] = { | 
|  | { 0, 216000 }, | 
|  | { 1, 312000 }, | 
|  | { 2, 456000 }, | 
|  | { 3, 608000 }, | 
|  | { 4, 760000 }, | 
|  | { 5, 816000 }, | 
|  | { 6, 912000 }, | 
|  | { 7, 1000000 }, | 
|  | { 8, CPUFREQ_TABLE_END }, | 
|  | }; | 
|  |  | 
|  | #define NUM_CPUS	2 | 
|  |  | 
|  | static struct clk *cpu_clk; | 
|  | static struct clk *emc_clk; | 
|  |  | 
|  | static unsigned long target_cpu_speed[NUM_CPUS]; | 
|  | static DEFINE_MUTEX(tegra_cpu_lock); | 
|  | static bool is_suspended; | 
|  |  | 
|  | int tegra_verify_speed(struct cpufreq_policy *policy) | 
|  | { | 
|  | return cpufreq_frequency_table_verify(policy, freq_table); | 
|  | } | 
|  |  | 
|  | unsigned int tegra_getspeed(unsigned int cpu) | 
|  | { | 
|  | unsigned long rate; | 
|  |  | 
|  | if (cpu >= NUM_CPUS) | 
|  | return 0; | 
|  |  | 
|  | rate = clk_get_rate(cpu_clk) / 1000; | 
|  | return rate; | 
|  | } | 
|  |  | 
|  | static int tegra_update_cpu_speed(unsigned long rate) | 
|  | { | 
|  | int ret = 0; | 
|  | struct cpufreq_freqs freqs; | 
|  |  | 
|  | freqs.old = tegra_getspeed(0); | 
|  | freqs.new = rate; | 
|  |  | 
|  | if (freqs.old == freqs.new) | 
|  | return ret; | 
|  |  | 
|  | /* | 
|  | * Vote on memory bus frequency based on cpu frequency | 
|  | * This sets the minimum frequency, display or avp may request higher | 
|  | */ | 
|  | if (rate >= 816000) | 
|  | clk_set_rate(emc_clk, 600000000); /* cpu 816 MHz, emc max */ | 
|  | else if (rate >= 456000) | 
|  | clk_set_rate(emc_clk, 300000000); /* cpu 456 MHz, emc 150Mhz */ | 
|  | else | 
|  | clk_set_rate(emc_clk, 100000000);  /* emc 50Mhz */ | 
|  |  | 
|  | for_each_online_cpu(freqs.cpu) | 
|  | cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); | 
|  |  | 
|  | #ifdef CONFIG_CPU_FREQ_DEBUG | 
|  | printk(KERN_DEBUG "cpufreq-tegra: transition: %u --> %u\n", | 
|  | freqs.old, freqs.new); | 
|  | #endif | 
|  |  | 
|  | ret = clk_set_rate(cpu_clk, freqs.new * 1000); | 
|  | if (ret) { | 
|  | pr_err("cpu-tegra: Failed to set cpu frequency to %d kHz\n", | 
|  | freqs.new); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | for_each_online_cpu(freqs.cpu) | 
|  | cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static unsigned long tegra_cpu_highest_speed(void) | 
|  | { | 
|  | unsigned long rate = 0; | 
|  | int i; | 
|  |  | 
|  | for_each_online_cpu(i) | 
|  | rate = max(rate, target_cpu_speed[i]); | 
|  | return rate; | 
|  | } | 
|  |  | 
|  | static int tegra_target(struct cpufreq_policy *policy, | 
|  | unsigned int target_freq, | 
|  | unsigned int relation) | 
|  | { | 
|  | int idx; | 
|  | unsigned int freq; | 
|  | int ret = 0; | 
|  |  | 
|  | mutex_lock(&tegra_cpu_lock); | 
|  |  | 
|  | if (is_suspended) { | 
|  | ret = -EBUSY; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | cpufreq_frequency_table_target(policy, freq_table, target_freq, | 
|  | relation, &idx); | 
|  |  | 
|  | freq = freq_table[idx].frequency; | 
|  |  | 
|  | target_cpu_speed[policy->cpu] = freq; | 
|  |  | 
|  | ret = tegra_update_cpu_speed(tegra_cpu_highest_speed()); | 
|  |  | 
|  | out: | 
|  | mutex_unlock(&tegra_cpu_lock); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int tegra_pm_notify(struct notifier_block *nb, unsigned long event, | 
|  | void *dummy) | 
|  | { | 
|  | mutex_lock(&tegra_cpu_lock); | 
|  | if (event == PM_SUSPEND_PREPARE) { | 
|  | is_suspended = true; | 
|  | pr_info("Tegra cpufreq suspend: setting frequency to %d kHz\n", | 
|  | freq_table[0].frequency); | 
|  | tegra_update_cpu_speed(freq_table[0].frequency); | 
|  | } else if (event == PM_POST_SUSPEND) { | 
|  | is_suspended = false; | 
|  | } | 
|  | mutex_unlock(&tegra_cpu_lock); | 
|  |  | 
|  | return NOTIFY_OK; | 
|  | } | 
|  |  | 
|  | static struct notifier_block tegra_cpu_pm_notifier = { | 
|  | .notifier_call = tegra_pm_notify, | 
|  | }; | 
|  |  | 
|  | static int tegra_cpu_init(struct cpufreq_policy *policy) | 
|  | { | 
|  | if (policy->cpu >= NUM_CPUS) | 
|  | return -EINVAL; | 
|  |  | 
|  | cpu_clk = clk_get_sys(NULL, "cpu"); | 
|  | if (IS_ERR(cpu_clk)) | 
|  | return PTR_ERR(cpu_clk); | 
|  |  | 
|  | emc_clk = clk_get_sys("cpu", "emc"); | 
|  | if (IS_ERR(emc_clk)) { | 
|  | clk_put(cpu_clk); | 
|  | return PTR_ERR(emc_clk); | 
|  | } | 
|  |  | 
|  | clk_enable(emc_clk); | 
|  | clk_enable(cpu_clk); | 
|  |  | 
|  | cpufreq_frequency_table_cpuinfo(policy, freq_table); | 
|  | cpufreq_frequency_table_get_attr(freq_table, policy->cpu); | 
|  | policy->cur = tegra_getspeed(policy->cpu); | 
|  | target_cpu_speed[policy->cpu] = policy->cur; | 
|  |  | 
|  | /* FIXME: what's the actual transition time? */ | 
|  | policy->cpuinfo.transition_latency = 300 * 1000; | 
|  |  | 
|  | policy->shared_type = CPUFREQ_SHARED_TYPE_ALL; | 
|  | cpumask_copy(policy->related_cpus, cpu_possible_mask); | 
|  |  | 
|  | if (policy->cpu == 0) | 
|  | register_pm_notifier(&tegra_cpu_pm_notifier); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int tegra_cpu_exit(struct cpufreq_policy *policy) | 
|  | { | 
|  | cpufreq_frequency_table_cpuinfo(policy, freq_table); | 
|  | clk_disable(emc_clk); | 
|  | clk_put(emc_clk); | 
|  | clk_put(cpu_clk); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static struct freq_attr *tegra_cpufreq_attr[] = { | 
|  | &cpufreq_freq_attr_scaling_available_freqs, | 
|  | NULL, | 
|  | }; | 
|  |  | 
|  | static struct cpufreq_driver tegra_cpufreq_driver = { | 
|  | .verify		= tegra_verify_speed, | 
|  | .target		= tegra_target, | 
|  | .get		= tegra_getspeed, | 
|  | .init		= tegra_cpu_init, | 
|  | .exit		= tegra_cpu_exit, | 
|  | .name		= "tegra", | 
|  | .attr		= tegra_cpufreq_attr, | 
|  | }; | 
|  |  | 
|  | static int __init tegra_cpufreq_init(void) | 
|  | { | 
|  | return cpufreq_register_driver(&tegra_cpufreq_driver); | 
|  | } | 
|  |  | 
|  | static void __exit tegra_cpufreq_exit(void) | 
|  | { | 
|  | cpufreq_unregister_driver(&tegra_cpufreq_driver); | 
|  | } | 
|  |  | 
|  |  | 
|  | MODULE_AUTHOR("Colin Cross <ccross@android.com>"); | 
|  | MODULE_DESCRIPTION("cpufreq driver for Nvidia Tegra2"); | 
|  | MODULE_LICENSE("GPL"); | 
|  | module_init(tegra_cpufreq_init); | 
|  | module_exit(tegra_cpufreq_exit); |