| // 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"); |