468 lines
12 KiB
C
468 lines
12 KiB
C
/*
|
|
* Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/cpufreq.h>
|
|
#include <linux/err.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/suspend.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/of.h>
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/tegra-soc.h>
|
|
#include <linux/tegra-dvfs.h>
|
|
#include <linux/clk/tegra124-dfll.h>
|
|
|
|
#include "tegra-cpufreq.h"
|
|
|
|
#define MAX_DVFS_FREQS 40
|
|
#define LP_BACKUP_FREQ 204000
|
|
#define CPU_FREQ_STEP 102000 /* 102MHz cpu_g table step */
|
|
#define CPU_FREQ_TABLE_MAX_SIZE (2 * MAX_DVFS_FREQS + 1)
|
|
#define CLUSTER_SWITCH_THRESHOLD 696000000
|
|
|
|
static struct clk *cpu_clk;
|
|
static struct clk *fcpu_clk;
|
|
static struct clk *lpcpu_clk;
|
|
static struct clk *pll_main_clk;
|
|
static struct clk *pll_backup_clk;
|
|
static struct clk *cpu_dfll_clk;
|
|
static struct clk *emc_clk;
|
|
|
|
static unsigned long dfll_mode_threshold;
|
|
static struct cpufreq_frequency_table
|
|
tegra124_cpufreq_tables[CPU_FREQ_TABLE_MAX_SIZE];
|
|
static cpumask_t online_cpus;
|
|
|
|
static DEFINE_MUTEX(tegra124_cpu_lock);
|
|
|
|
static int tegra124_switch_cluster(unsigned int cluster)
|
|
{
|
|
int ret;
|
|
|
|
pr_debug("Switching to cluster %u\n", cluster);
|
|
|
|
ret = tegra_switch_cluster(cluster);
|
|
if (is_lp_cluster())
|
|
cpu_clk = lpcpu_clk;
|
|
else
|
|
cpu_clk = fcpu_clk;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tegra124_cpu_clk_dfll_on(struct clk *cpu_clk,
|
|
unsigned long new_rate, unsigned long old_rate)
|
|
{
|
|
int ret;
|
|
|
|
ret = clk_set_rate(cpu_dfll_clk, new_rate);
|
|
if (ret) {
|
|
pr_err("Failed to set cpu rate %lu on source %s\n",
|
|
new_rate, __clk_get_name(cpu_dfll_clk));
|
|
goto out;
|
|
}
|
|
|
|
if (clk_get_parent(cpu_clk) == cpu_dfll_clk)
|
|
return 0;
|
|
|
|
ret = clk_set_parent(cpu_clk, cpu_dfll_clk);
|
|
if (ret) {
|
|
pr_err("Failed to switch cpu to %s with error: %d\n",
|
|
__clk_get_name(cpu_dfll_clk), ret);
|
|
goto out;
|
|
}
|
|
ret = tegra124_dfll_lock_loop();
|
|
WARN(ret, "Failed to lock %s at rate %lu\n",
|
|
__clk_get_name(cpu_dfll_clk), new_rate);
|
|
|
|
tegra_dvfs_dfll_mode_set(cpu_clk, new_rate);
|
|
|
|
pr_debug("CPU DFLL Mode ----> ON\n");
|
|
|
|
return 0;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
int tegra124_cpu_clk_dfll_off(struct clk *cpu_clk,
|
|
unsigned long new_rate, unsigned long old_rate)
|
|
{
|
|
int ret;
|
|
struct clk *pll;
|
|
|
|
pll = (new_rate <= LP_BACKUP_FREQ * 1000) ?
|
|
pll_backup_clk : pll_main_clk;
|
|
|
|
if (old_rate != dfll_mode_threshold) {
|
|
ret = tegra_dvfs_set_rate(cpu_clk, dfll_mode_threshold);
|
|
if (!ret)
|
|
ret = clk_set_rate(cpu_dfll_clk, dfll_mode_threshold);
|
|
|
|
if (ret) {
|
|
pr_err("Failed to set cpu rate %lu on source %s\n",
|
|
dfll_mode_threshold,
|
|
__clk_get_name(cpu_dfll_clk));
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ret = tegra124_dfll_unlock_loop();
|
|
if (ret) {
|
|
pr_err("Failed to unlock %s\n", __clk_get_name(cpu_dfll_clk));
|
|
goto back_to_dfll;
|
|
}
|
|
|
|
ret = tegra_dvfs_dfll_mode_clear(cpu_clk, new_rate);
|
|
if (ret) {
|
|
pr_err("Failed to set cpu rail for rate %lu\n", new_rate);
|
|
goto back_to_dfll;
|
|
}
|
|
|
|
ret = clk_set_rate(pll, new_rate);
|
|
if (ret) {
|
|
pr_err("Failed to set cpu rate %lu on source %s\n",
|
|
new_rate, __clk_get_name(pll_main_clk));
|
|
goto back_to_dfll;
|
|
}
|
|
|
|
ret = clk_set_parent(cpu_clk, pll);
|
|
if (ret) {
|
|
pr_err("Failed to switch cpu to %s\n", __clk_get_name(pll));
|
|
goto back_to_dfll;
|
|
}
|
|
|
|
tegra_dvfs_set_rate(cpu_clk, new_rate);
|
|
|
|
pr_debug("CPU DFLL Mode ----> OFF\n");
|
|
|
|
return 0;
|
|
|
|
back_to_dfll:
|
|
tegra124_dfll_lock_loop();
|
|
tegra_dvfs_dfll_mode_set(cpu_clk, new_rate);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void tegra124_emc_clk_set_rate(unsigned long cpu_rate)
|
|
{
|
|
unsigned long emc_max_rate = 0;
|
|
|
|
if (IS_ERR(emc_clk))
|
|
return;
|
|
|
|
emc_max_rate = clk_round_rate(emc_clk, ULONG_MAX);
|
|
|
|
/* Vote on memory bus frequency based on cpu frequency */
|
|
if (cpu_rate >= 1400000)
|
|
clk_set_rate(emc_clk, emc_max_rate);
|
|
else if (cpu_rate >= 1200000)
|
|
clk_set_rate(emc_clk, 750000000);
|
|
else if (cpu_rate >= 1100000)
|
|
clk_set_rate(emc_clk, 600000000);
|
|
else if (cpu_rate >= 1000000)
|
|
clk_set_rate(emc_clk, 500000000);
|
|
else if (cpu_rate >= 800000)
|
|
clk_set_rate(emc_clk, 375000000);
|
|
else if (cpu_rate >= 500000)
|
|
clk_set_rate(emc_clk, 200000000);
|
|
else if (cpu_rate >= 250000)
|
|
clk_set_rate(emc_clk, 100000000);
|
|
else
|
|
clk_set_rate(emc_clk, 0);
|
|
}
|
|
|
|
static int tegra124_set_rate_pll(unsigned long new_rate, unsigned long old_rate)
|
|
{
|
|
int ret;
|
|
|
|
clk_prepare_enable(pll_main_clk);
|
|
|
|
if (cpu_clk == fcpu_clk && new_rate > old_rate)
|
|
tegra_dvfs_set_rate(cpu_clk, new_rate);
|
|
|
|
ret = clk_set_parent(cpu_clk, pll_backup_clk);
|
|
if (ret) {
|
|
pr_err("Tegra124 cpufreq: Failed to reparent cpu to pll_p\n");
|
|
goto out;
|
|
}
|
|
|
|
if (new_rate == clk_get_rate(pll_backup_clk))
|
|
goto out;
|
|
|
|
ret = clk_set_rate(pll_main_clk, new_rate);
|
|
if (ret) {
|
|
pr_err("Tegra124 cpufreq: Failed to change pll_x to %lu\n",
|
|
new_rate);
|
|
goto out;
|
|
}
|
|
|
|
ret = clk_set_parent(cpu_clk, pll_main_clk);
|
|
if (ret) {
|
|
pr_err("Tegra124 cpufreq: Failed to reparent cpu to pll_x\n");
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
if (cpu_clk == fcpu_clk && new_rate <= old_rate)
|
|
tegra_dvfs_set_rate(cpu_clk, new_rate);
|
|
|
|
clk_disable_unprepare(pll_main_clk);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tegra124_cpu_clk_set_rate(unsigned long new_rate)
|
|
{
|
|
int *dfll_mv;
|
|
unsigned long *freqs;
|
|
int num_dfll_freqs = 0;
|
|
unsigned long old_rate = clk_get_rate(cpu_clk);
|
|
bool has_dfll = IS_ERR(cpu_dfll_clk) ? false : true;
|
|
int ret = 0;
|
|
|
|
if (tegra124_dfll_get_fv_table(&num_dfll_freqs, &freqs, &dfll_mv))
|
|
pr_err("Tegra124 cpufreq: Failed to get dfll fv_table\n");
|
|
|
|
mutex_lock(&tegra124_cpu_lock);
|
|
|
|
/* Switch back to G cluster if we're in G range. */
|
|
if (is_lp_cluster() && new_rate >= CLUSTER_SWITCH_THRESHOLD) {
|
|
ret = tegra124_switch_cluster(TEGRA_CLUSTER_G);
|
|
if (ret)
|
|
goto out;
|
|
}
|
|
|
|
if (!has_dfll || !num_dfll_freqs || is_lp_cluster()) {
|
|
/* DFLL is not supported so we set CPU rate on PLL. */
|
|
ret = tegra124_set_rate_pll(new_rate, old_rate);
|
|
} else {
|
|
/* DFLL is supported. */
|
|
if (new_rate >= dfll_mode_threshold)
|
|
ret = tegra124_cpu_clk_dfll_on(cpu_clk, new_rate,
|
|
old_rate);
|
|
else if (clk_get_parent(cpu_clk) == cpu_dfll_clk)
|
|
ret = tegra124_cpu_clk_dfll_off(cpu_clk, new_rate,
|
|
old_rate);
|
|
else
|
|
ret = tegra124_set_rate_pll(new_rate, old_rate);
|
|
}
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
/* If only CPU0 is up and we're lower than G range, switch. */
|
|
if (cpumask_weight(&online_cpus) == 1 &&
|
|
cpumask_first(&online_cpus) == 0 &&
|
|
new_rate < CLUSTER_SWITCH_THRESHOLD)
|
|
ret = tegra124_switch_cluster(TEGRA_CLUSTER_LP);
|
|
|
|
out:
|
|
mutex_unlock(&tegra124_cpu_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void tegra124_cpufreq_clk_init(void)
|
|
{
|
|
if (!IS_ERR(emc_clk))
|
|
clk_prepare_enable(emc_clk);
|
|
BUG_ON(!__clk_is_enabled(cpu_clk) && !__clk_is_prepared(cpu_clk));
|
|
}
|
|
|
|
static void tegra124_cpufreq_clk_exit(void)
|
|
{
|
|
if (!IS_ERR(emc_clk))
|
|
clk_disable_unprepare(emc_clk);
|
|
}
|
|
|
|
static const struct tegra_cpufreq_config tegra124_cpufreq_config = {
|
|
.emc_clk_set_rate = tegra124_emc_clk_set_rate,
|
|
.cpu_clk_set_rate = tegra124_cpu_clk_set_rate,
|
|
.cpufreq_clk_init = tegra124_cpufreq_clk_init,
|
|
.cpufreq_clk_exit = tegra124_cpufreq_clk_exit,
|
|
};
|
|
|
|
static int tegra124_prepare_tables(struct tegra_cpufreq_data *data)
|
|
{
|
|
int i, j, ret, num_lp_freqs, num_g_freqs;
|
|
int *g_millivolts;
|
|
bool g_vmin_done = false;
|
|
unsigned long *freqs_lp, *freqs_g;
|
|
unsigned long g_vmin_freq, g_start_freq, max_freq, freq;
|
|
|
|
ret = tegra_dvfs_get_freqs(lpcpu_clk, &freqs_lp, &num_lp_freqs);
|
|
if (ret || !num_lp_freqs) {
|
|
pr_err("Tegra124 cpufreq: Failed to get LP CPU freq-table\n");
|
|
goto out;
|
|
}
|
|
|
|
ret = tegra124_dfll_get_fv_table(&num_g_freqs, &freqs_g, &g_millivolts);
|
|
if (ret || !num_g_freqs) {
|
|
pr_err("Tegra124 cpufreq: Failed to get G CPU freq-table\n");
|
|
goto out;
|
|
}
|
|
|
|
g_vmin_freq = freqs_g[0];
|
|
if (g_vmin_freq < LP_BACKUP_FREQ * 1000) {
|
|
WARN(1, "%s: LP CPU backup rate exceeds G CPU rate at Vmin\n",
|
|
__func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Start with singular frequency */
|
|
i = 0;
|
|
tegra124_cpufreq_tables[i++].frequency = LP_BACKUP_FREQ;
|
|
|
|
/*
|
|
* Next, set table steps along LP CPU dvfs ladder, but make sure G CPU
|
|
* dvfs rate at minimum voltage is not missed (if it happens to be below
|
|
* LP maximum rate)
|
|
*/
|
|
max_freq = freqs_lp[num_lp_freqs - 1];
|
|
for (j = 0; j < num_lp_freqs; j++) {
|
|
freq = freqs_lp[j];
|
|
|
|
if (freq <= LP_BACKUP_FREQ)
|
|
continue;
|
|
|
|
if (!g_vmin_done && freq >= g_vmin_freq) {
|
|
g_vmin_done = true;
|
|
if (freq > g_vmin_freq && g_vmin_freq / 1000 >
|
|
tegra124_cpufreq_tables[i - 1].frequency)
|
|
tegra124_cpufreq_tables[i++].frequency
|
|
= g_vmin_freq / 1000;
|
|
}
|
|
tegra124_cpufreq_tables[i++].frequency = freq / 1000;
|
|
|
|
if (freq == max_freq)
|
|
break;
|
|
}
|
|
|
|
/* Fill in the non-DFLL CPU G frequencies above LP CPU maximum rate */
|
|
if (freq < g_vmin_freq) {
|
|
int n = (g_vmin_freq - freq) / CPU_FREQ_STEP;
|
|
for (j = 0; j <= n; j++) {
|
|
freq = g_vmin_freq - CPU_FREQ_STEP * (n - j);
|
|
tegra124_cpufreq_tables[i++].frequency = freq / 1000;
|
|
}
|
|
}
|
|
|
|
/* Now, step along the rest of G CPU dvfs ladder */
|
|
g_start_freq = freq;
|
|
|
|
for (j = 0; j < num_g_freqs; j++) {
|
|
freq = freqs_g[j];
|
|
if (freq > g_start_freq)
|
|
tegra124_cpufreq_tables[i++].frequency = freq / 1000;
|
|
}
|
|
tegra124_cpufreq_tables[i].frequency = CPUFREQ_TABLE_END;
|
|
|
|
data->suspend_index = i - 1;
|
|
|
|
return 0;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int tegra124_cpu_notifier(struct notifier_block *nb,
|
|
unsigned long action, void *cpu)
|
|
{
|
|
int ret = NOTIFY_OK;
|
|
|
|
if (action != CPU_UP_PREPARE && action != CPU_DEAD)
|
|
return NOTIFY_OK;
|
|
|
|
mutex_lock(&tegra124_cpu_lock);
|
|
if (action == CPU_DEAD)
|
|
cpumask_clear_cpu((unsigned int)cpu, &online_cpus);
|
|
else {
|
|
cpumask_set_cpu((unsigned int)cpu, &online_cpus);
|
|
if (is_lp_cluster() &&
|
|
tegra124_switch_cluster(TEGRA_CLUSTER_G) != 0)
|
|
ret = NOTIFY_BAD;
|
|
}
|
|
mutex_unlock(&tegra124_cpu_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct notifier_block tegra124_cpu_nb = {
|
|
.notifier_call = tegra124_cpu_notifier,
|
|
};
|
|
|
|
int tegra124_cpufreq_init(struct tegra_cpufreq_data *data,
|
|
const struct tegra_cpufreq_config **soc_config)
|
|
{
|
|
int ret;
|
|
|
|
fcpu_clk = devm_clk_get(data->dev, "cclk_g");
|
|
if (IS_ERR(fcpu_clk))
|
|
return PTR_ERR(fcpu_clk);
|
|
|
|
lpcpu_clk = devm_clk_get(data->dev, "cclk_lp");
|
|
if (IS_ERR(lpcpu_clk))
|
|
return PTR_ERR(lpcpu_clk);
|
|
|
|
pll_main_clk = devm_clk_get(data->dev, "pll_x");
|
|
if (IS_ERR(pll_main_clk))
|
|
return PTR_ERR(pll_main_clk);
|
|
|
|
pll_backup_clk = devm_clk_get(data->dev, "pll_p_out4");
|
|
if (IS_ERR(pll_backup_clk))
|
|
return PTR_ERR(pll_backup_clk);
|
|
|
|
emc_clk = devm_clk_get(data->dev, "cpu.emc");
|
|
if (IS_ERR(emc_clk))
|
|
pr_info("Tegra124 cpufreq: no cpu.emc shared clock found\n");
|
|
|
|
cpu_dfll_clk = devm_clk_get(data->dev, "dfllCPU_out");
|
|
if (IS_ERR(cpu_dfll_clk))
|
|
pr_info("Tegra124 cpufreq: no DFLL clock found\n");
|
|
|
|
/* Start with fast CPU */
|
|
cpu_clk = fcpu_clk;
|
|
|
|
ret = tegra_dvfs_get_dfll_threshold(cpu_clk, &dfll_mode_threshold);
|
|
if (ret) {
|
|
ret = -EPROBE_DEFER;
|
|
pr_err("Tegra124 cpufreq: failed to get dfll_mode_threshold\n");
|
|
goto out;
|
|
}
|
|
|
|
ret = tegra124_prepare_tables(data);
|
|
if (ret)
|
|
goto out;
|
|
|
|
ret = tegra_cluster_control_init();
|
|
if (ret)
|
|
goto out;
|
|
|
|
cpumask_copy(&online_cpus, cpu_online_mask);
|
|
register_cpu_notifier(&tegra124_cpu_nb);
|
|
|
|
data->cpu_clk = cpu_clk;
|
|
data->freq_table = tegra124_cpufreq_tables;
|
|
*soc_config = &tegra124_cpufreq_config;
|
|
|
|
return 0;
|
|
out:
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(tegra124_cpufreq_init);
|