diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
commit | 76cb841cb886eef6b3bee341a2266c76578724ad (patch) | |
tree | f5892e5ba6cc11949952a6ce4ecbe6d516d6ce58 /drivers/cpufreq/tegra20-cpufreq.c | |
parent | Initial commit. (diff) | |
download | linux-upstream.tar.xz linux-upstream.zip |
Adding upstream version 4.19.249.upstream/4.19.249upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/cpufreq/tegra20-cpufreq.c')
-rw-r--r-- | drivers/cpufreq/tegra20-cpufreq.c | 236 |
1 files changed, 236 insertions, 0 deletions
diff --git a/drivers/cpufreq/tegra20-cpufreq.c b/drivers/cpufreq/tegra20-cpufreq.c new file mode 100644 index 000000000..05f57dcd5 --- /dev/null +++ b/drivers/cpufreq/tegra20-cpufreq.c @@ -0,0 +1,236 @@ +/* + * 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/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"); |