![Thomas Gleixner](/assets/img/avatar_default.png)
Based on 1 normalized pattern(s): 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 extracted by the scancode license scanner the SPDX license identifier GPL-2.0-only has been chosen to replace the boilerplate/reference in 285 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Alexios Zavras <alexios.zavras@intel.com> Reviewed-by: Allison Randal <allison@lohutok.net> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190529141900.642774971@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
228 lines
5.9 KiB
C
228 lines
5.9 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2010 Google, Inc.
|
|
*
|
|
* Author:
|
|
* Colin Cross <ccross@google.com>
|
|
* Based on arch/arm/plat-omap/cpu-omap.c, (C) 2005 Nokia Corporation
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/cpufreq.h>
|
|
#include <linux/err.h>
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/types.h>
|
|
|
|
static struct cpufreq_frequency_table freq_table[] = {
|
|
{ .frequency = 216000 },
|
|
{ .frequency = 312000 },
|
|
{ .frequency = 456000 },
|
|
{ .frequency = 608000 },
|
|
{ .frequency = 760000 },
|
|
{ .frequency = 816000 },
|
|
{ .frequency = 912000 },
|
|
{ .frequency = 1000000 },
|
|
{ .frequency = CPUFREQ_TABLE_END },
|
|
};
|
|
|
|
struct tegra20_cpufreq {
|
|
struct device *dev;
|
|
struct cpufreq_driver driver;
|
|
struct clk *cpu_clk;
|
|
struct clk *pll_x_clk;
|
|
struct clk *pll_p_clk;
|
|
bool pll_x_prepared;
|
|
};
|
|
|
|
static unsigned int tegra_get_intermediate(struct cpufreq_policy *policy,
|
|
unsigned int index)
|
|
{
|
|
struct tegra20_cpufreq *cpufreq = cpufreq_get_driver_data();
|
|
unsigned int ifreq = clk_get_rate(cpufreq->pll_p_clk) / 1000;
|
|
|
|
/*
|
|
* Don't switch to intermediate freq if:
|
|
* - we are already at it, i.e. policy->cur == ifreq
|
|
* - index corresponds to ifreq
|
|
*/
|
|
if (freq_table[index].frequency == ifreq || policy->cur == ifreq)
|
|
return 0;
|
|
|
|
return ifreq;
|
|
}
|
|
|
|
static int tegra_target_intermediate(struct cpufreq_policy *policy,
|
|
unsigned int index)
|
|
{
|
|
struct tegra20_cpufreq *cpufreq = cpufreq_get_driver_data();
|
|
int ret;
|
|
|
|
/*
|
|
* Take an extra reference to the main pll so it doesn't turn
|
|
* off when we move the cpu off of it as enabling it again while we
|
|
* switch to it from tegra_target() would take additional time.
|
|
*
|
|
* When target-freq is equal to intermediate freq we don't need to
|
|
* switch to an intermediate freq and so this routine isn't called.
|
|
* Also, we wouldn't be using pll_x anymore and must not take extra
|
|
* reference to it, as it can be disabled now to save some power.
|
|
*/
|
|
clk_prepare_enable(cpufreq->pll_x_clk);
|
|
|
|
ret = clk_set_parent(cpufreq->cpu_clk, cpufreq->pll_p_clk);
|
|
if (ret)
|
|
clk_disable_unprepare(cpufreq->pll_x_clk);
|
|
else
|
|
cpufreq->pll_x_prepared = true;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_target(struct cpufreq_policy *policy, unsigned int index)
|
|
{
|
|
struct tegra20_cpufreq *cpufreq = cpufreq_get_driver_data();
|
|
unsigned long rate = freq_table[index].frequency;
|
|
unsigned int ifreq = clk_get_rate(cpufreq->pll_p_clk) / 1000;
|
|
int ret;
|
|
|
|
/*
|
|
* target freq == pll_p, don't need to take extra reference to pll_x_clk
|
|
* as it isn't used anymore.
|
|
*/
|
|
if (rate == ifreq)
|
|
return clk_set_parent(cpufreq->cpu_clk, cpufreq->pll_p_clk);
|
|
|
|
ret = clk_set_rate(cpufreq->pll_x_clk, rate * 1000);
|
|
/* Restore to earlier frequency on error, i.e. pll_x */
|
|
if (ret)
|
|
dev_err(cpufreq->dev, "Failed to change pll_x to %lu\n", rate);
|
|
|
|
ret = clk_set_parent(cpufreq->cpu_clk, cpufreq->pll_x_clk);
|
|
/* This shouldn't fail while changing or restoring */
|
|
WARN_ON(ret);
|
|
|
|
/*
|
|
* Drop count to pll_x clock only if we switched to intermediate freq
|
|
* earlier while transitioning to a target frequency.
|
|
*/
|
|
if (cpufreq->pll_x_prepared) {
|
|
clk_disable_unprepare(cpufreq->pll_x_clk);
|
|
cpufreq->pll_x_prepared = false;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_cpu_init(struct cpufreq_policy *policy)
|
|
{
|
|
struct tegra20_cpufreq *cpufreq = cpufreq_get_driver_data();
|
|
int ret;
|
|
|
|
clk_prepare_enable(cpufreq->cpu_clk);
|
|
|
|
/* FIXME: what's the actual transition time? */
|
|
ret = cpufreq_generic_init(policy, freq_table, 300 * 1000);
|
|
if (ret) {
|
|
clk_disable_unprepare(cpufreq->cpu_clk);
|
|
return ret;
|
|
}
|
|
|
|
policy->clk = cpufreq->cpu_clk;
|
|
policy->suspend_freq = freq_table[0].frequency;
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_cpu_exit(struct cpufreq_policy *policy)
|
|
{
|
|
struct tegra20_cpufreq *cpufreq = cpufreq_get_driver_data();
|
|
|
|
clk_disable_unprepare(cpufreq->cpu_clk);
|
|
return 0;
|
|
}
|
|
|
|
static int tegra20_cpufreq_probe(struct platform_device *pdev)
|
|
{
|
|
struct tegra20_cpufreq *cpufreq;
|
|
int err;
|
|
|
|
cpufreq = devm_kzalloc(&pdev->dev, sizeof(*cpufreq), GFP_KERNEL);
|
|
if (!cpufreq)
|
|
return -ENOMEM;
|
|
|
|
cpufreq->cpu_clk = clk_get_sys(NULL, "cclk");
|
|
if (IS_ERR(cpufreq->cpu_clk))
|
|
return PTR_ERR(cpufreq->cpu_clk);
|
|
|
|
cpufreq->pll_x_clk = clk_get_sys(NULL, "pll_x");
|
|
if (IS_ERR(cpufreq->pll_x_clk)) {
|
|
err = PTR_ERR(cpufreq->pll_x_clk);
|
|
goto put_cpu;
|
|
}
|
|
|
|
cpufreq->pll_p_clk = clk_get_sys(NULL, "pll_p");
|
|
if (IS_ERR(cpufreq->pll_p_clk)) {
|
|
err = PTR_ERR(cpufreq->pll_p_clk);
|
|
goto put_pll_x;
|
|
}
|
|
|
|
cpufreq->dev = &pdev->dev;
|
|
cpufreq->driver.get = cpufreq_generic_get;
|
|
cpufreq->driver.attr = cpufreq_generic_attr;
|
|
cpufreq->driver.init = tegra_cpu_init;
|
|
cpufreq->driver.exit = tegra_cpu_exit;
|
|
cpufreq->driver.flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK;
|
|
cpufreq->driver.verify = cpufreq_generic_frequency_table_verify;
|
|
cpufreq->driver.suspend = cpufreq_generic_suspend;
|
|
cpufreq->driver.driver_data = cpufreq;
|
|
cpufreq->driver.target_index = tegra_target;
|
|
cpufreq->driver.get_intermediate = tegra_get_intermediate;
|
|
cpufreq->driver.target_intermediate = tegra_target_intermediate;
|
|
snprintf(cpufreq->driver.name, CPUFREQ_NAME_LEN, "tegra");
|
|
|
|
err = cpufreq_register_driver(&cpufreq->driver);
|
|
if (err)
|
|
goto put_pll_p;
|
|
|
|
platform_set_drvdata(pdev, cpufreq);
|
|
|
|
return 0;
|
|
|
|
put_pll_p:
|
|
clk_put(cpufreq->pll_p_clk);
|
|
put_pll_x:
|
|
clk_put(cpufreq->pll_x_clk);
|
|
put_cpu:
|
|
clk_put(cpufreq->cpu_clk);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int tegra20_cpufreq_remove(struct platform_device *pdev)
|
|
{
|
|
struct tegra20_cpufreq *cpufreq = platform_get_drvdata(pdev);
|
|
|
|
cpufreq_unregister_driver(&cpufreq->driver);
|
|
|
|
clk_put(cpufreq->pll_p_clk);
|
|
clk_put(cpufreq->pll_x_clk);
|
|
clk_put(cpufreq->cpu_clk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver tegra20_cpufreq_driver = {
|
|
.probe = tegra20_cpufreq_probe,
|
|
.remove = tegra20_cpufreq_remove,
|
|
.driver = {
|
|
.name = "tegra20-cpufreq",
|
|
},
|
|
};
|
|
module_platform_driver(tegra20_cpufreq_driver);
|
|
|
|
MODULE_ALIAS("platform:tegra20-cpufreq");
|
|
MODULE_AUTHOR("Colin Cross <ccross@android.com>");
|
|
MODULE_DESCRIPTION("NVIDIA Tegra20 cpufreq driver");
|
|
MODULE_LICENSE("GPL");
|