/* * Copyright (C) 2010 Google, Inc. * Copyright (c) 2013, NVIDIA Corporation. * * Author: * Colin Cross * 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 #include #include #include #include #include #include #include #include #include "tegra-cpufreq.h" #define MAX_CPUS 4 static struct tegra_cpufreq_data *tegra_data; static const struct tegra_cpufreq_config *soc_config; static unsigned long target_cpu_speed[MAX_CPUS]; static DEFINE_MUTEX(tegra_cpu_lock); static bool is_suspended; static int tegra_verify_speed(struct cpufreq_policy *policy) { return cpufreq_frequency_table_verify(policy, tegra_data->freq_table); } static unsigned int tegra_getspeed(unsigned int cpu) { unsigned long rate; int i; rate = clk_get_rate(tegra_data->cpu_clk) / 1000; /* * Round to the neareset frequency. The actual frequency generated * by the CPU clock may differ slightly from the rate in the table. */ for (i = 0; tegra_data->freq_table[i + 1].frequency != CPUFREQ_TABLE_END; i++) { if (rate < (tegra_data->freq_table[i].frequency + tegra_data->freq_table[i + 1].frequency) / 2) return tegra_data->freq_table[i].frequency; } return tegra_data->freq_table[i].frequency; } static int tegra_update_cpu_speed(struct cpufreq_policy *policy, 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 * If cpu frequency is increasing, boost memory bandwidth first */ if (soc_config->emc_clk_set_rate && freqs.new > freqs.old) soc_config->emc_clk_set_rate(rate); cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); #ifdef CONFIG_CPU_FREQ_DEBUG printk(KERN_DEBUG "cpufreq-tegra: transition: %u --> %u\n", freqs.old, freqs.new); #endif ret = soc_config->cpu_clk_set_rate(freqs.new * 1000); if (ret) { pr_err("cpu-tegra: Failed to set cpu frequency to %d kHz\n", freqs.new); freqs.new = freqs.old; } /* * Vote on memory bus frequency based on cpu frequency * If cpu frequency is decreasing, lower memory bandwidth later */ if (soc_config->emc_clk_set_rate && freqs.new < freqs.old) soc_config->emc_clk_set_rate(rate); cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); return ret; } 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) { struct cpufreq_frequency_table *freq_table = tegra_data->freq_table; unsigned 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(policy, 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) { struct cpufreq_frequency_table *freq_table = tegra_data->freq_table; struct cpufreq_policy *policy = cpufreq_cpu_get(0); 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[tegra_data->suspend_index].frequency); tegra_update_cpu_speed(policy, freq_table[tegra_data->suspend_index].frequency); cpufreq_cpu_put(policy); } else if (event == PM_POST_SUSPEND) { is_suspended = false; tegra_update_cpu_speed(policy, policy->max); pr_info("Tegra cpufreq resume: restore frequency to %u kHz\n", policy->max); cpufreq_cpu_put(policy); } 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 (soc_config->cpufreq_clk_init) soc_config->cpufreq_clk_init(); cpufreq_frequency_table_cpuinfo(policy, tegra_data->freq_table); cpufreq_frequency_table_get_attr(tegra_data->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; cpumask_copy(policy->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, tegra_data->freq_table); if (soc_config->cpufreq_clk_exit) soc_config->cpufreq_clk_exit(); 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 struct { char *compat; int (*init)(struct tegra_cpufreq_data *, const struct tegra_cpufreq_config **); } tegra_init_funcs[] = { { "nvidia,tegra20", tegra20_cpufreq_init }, { "nvidia,tegra124", tegra124_cpufreq_init }, }; static int tegra_cpufreq_probe(struct platform_device *pdev) { int i; int ret = -EINVAL; tegra_data = devm_kzalloc(&pdev->dev, sizeof(*tegra_data), GFP_KERNEL); if (!tegra_data) { ret = -ENOMEM; goto out; } tegra_data->dev = &pdev->dev; for (i = 0; i < ARRAY_SIZE(tegra_init_funcs); i++) { if (of_machine_is_compatible(tegra_init_funcs[i].compat)) { ret = tegra_init_funcs[i].init(tegra_data, &soc_config); if (!ret) break; else goto out; } } if (i == ARRAY_SIZE(tegra_init_funcs)) goto out; ret = cpufreq_register_driver(&tegra_cpufreq_driver); if (ret) { dev_err(tegra_data->dev, "%s: failed to register cpufreq driver\n", __func__); goto out; } return 0; out: return ret; } static int tegra_cpufreq_remove(struct platform_device *pdev) { cpufreq_unregister_driver(&tegra_cpufreq_driver); return 0; } static struct platform_driver tegra_cpufreq_platdrv = { .driver = { .name = "tegra-cpufreq", .owner = THIS_MODULE, }, .probe = tegra_cpufreq_probe, .remove = tegra_cpufreq_remove, }; module_platform_driver(tegra_cpufreq_platdrv); int __init tegra_cpufreq_init(void) { struct platform_device_info devinfo = { .name = "tegra-cpufreq", }; platform_device_register_full(&devinfo); return 0; } EXPORT_SYMBOL(tegra_cpufreq_init); MODULE_AUTHOR("Colin Cross "); MODULE_DESCRIPTION("cpufreq driver for Nvidia Tegra2"); MODULE_LICENSE("GPL");