linux/drivers/cpufreq/tegra20-cpufreq.c
<<
>>
Prefs
   1/*
   2 * Copyright (C) 2010 Google, Inc.
   3 *
   4 * Author:
   5 *      Colin Cross <ccross@google.com>
   6 *      Based on arch/arm/plat-omap/cpu-omap.c, (C) 2005 Nokia Corporation
   7 *
   8 * This software is licensed under the terms of the GNU General Public
   9 * License version 2, as published by the Free Software Foundation, and
  10 * may be copied, distributed, and modified under those terms.
  11 *
  12 * This program is distributed in the hope that it will be useful,
  13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15 * GNU General Public License for more details.
  16 *
  17 */
  18
  19#include <linux/clk.h>
  20#include <linux/cpufreq.h>
  21#include <linux/err.h>
  22#include <linux/init.h>
  23#include <linux/module.h>
  24#include <linux/platform_device.h>
  25#include <linux/types.h>
  26
  27static struct cpufreq_frequency_table freq_table[] = {
  28        { .frequency = 216000 },
  29        { .frequency = 312000 },
  30        { .frequency = 456000 },
  31        { .frequency = 608000 },
  32        { .frequency = 760000 },
  33        { .frequency = 816000 },
  34        { .frequency = 912000 },
  35        { .frequency = 1000000 },
  36        { .frequency = CPUFREQ_TABLE_END },
  37};
  38
  39struct tegra20_cpufreq {
  40        struct device *dev;
  41        struct cpufreq_driver driver;
  42        struct clk *cpu_clk;
  43        struct clk *pll_x_clk;
  44        struct clk *pll_p_clk;
  45        bool pll_x_prepared;
  46};
  47
  48static unsigned int tegra_get_intermediate(struct cpufreq_policy *policy,
  49                                           unsigned int index)
  50{
  51        struct tegra20_cpufreq *cpufreq = cpufreq_get_driver_data();
  52        unsigned int ifreq = clk_get_rate(cpufreq->pll_p_clk) / 1000;
  53
  54        /*
  55         * Don't switch to intermediate freq if:
  56         * - we are already at it, i.e. policy->cur == ifreq
  57         * - index corresponds to ifreq
  58         */
  59        if (freq_table[index].frequency == ifreq || policy->cur == ifreq)
  60                return 0;
  61
  62        return ifreq;
  63}
  64
  65static int tegra_target_intermediate(struct cpufreq_policy *policy,
  66                                     unsigned int index)
  67{
  68        struct tegra20_cpufreq *cpufreq = cpufreq_get_driver_data();
  69        int ret;
  70
  71        /*
  72         * Take an extra reference to the main pll so it doesn't turn
  73         * off when we move the cpu off of it as enabling it again while we
  74         * switch to it from tegra_target() would take additional time.
  75         *
  76         * When target-freq is equal to intermediate freq we don't need to
  77         * switch to an intermediate freq and so this routine isn't called.
  78         * Also, we wouldn't be using pll_x anymore and must not take extra
  79         * reference to it, as it can be disabled now to save some power.
  80         */
  81        clk_prepare_enable(cpufreq->pll_x_clk);
  82
  83        ret = clk_set_parent(cpufreq->cpu_clk, cpufreq->pll_p_clk);
  84        if (ret)
  85                clk_disable_unprepare(cpufreq->pll_x_clk);
  86        else
  87                cpufreq->pll_x_prepared = true;
  88
  89        return ret;
  90}
  91
  92static int tegra_target(struct cpufreq_policy *policy, unsigned int index)
  93{
  94        struct tegra20_cpufreq *cpufreq = cpufreq_get_driver_data();
  95        unsigned long rate = freq_table[index].frequency;
  96        unsigned int ifreq = clk_get_rate(cpufreq->pll_p_clk) / 1000;
  97        int ret;
  98
  99        /*
 100         * target freq == pll_p, don't need to take extra reference to pll_x_clk
 101         * as it isn't used anymore.
 102         */
 103        if (rate == ifreq)
 104                return clk_set_parent(cpufreq->cpu_clk, cpufreq->pll_p_clk);
 105
 106        ret = clk_set_rate(cpufreq->pll_x_clk, rate * 1000);
 107        /* Restore to earlier frequency on error, i.e. pll_x */
 108        if (ret)
 109                dev_err(cpufreq->dev, "Failed to change pll_x to %lu\n", rate);
 110
 111        ret = clk_set_parent(cpufreq->cpu_clk, cpufreq->pll_x_clk);
 112        /* This shouldn't fail while changing or restoring */
 113        WARN_ON(ret);
 114
 115        /*
 116         * Drop count to pll_x clock only if we switched to intermediate freq
 117         * earlier while transitioning to a target frequency.
 118         */
 119        if (cpufreq->pll_x_prepared) {
 120                clk_disable_unprepare(cpufreq->pll_x_clk);
 121                cpufreq->pll_x_prepared = false;
 122        }
 123
 124        return ret;
 125}
 126
 127static int tegra_cpu_init(struct cpufreq_policy *policy)
 128{
 129        struct tegra20_cpufreq *cpufreq = cpufreq_get_driver_data();
 130        int ret;
 131
 132        clk_prepare_enable(cpufreq->cpu_clk);
 133
 134        /* FIXME: what's the actual transition time? */
 135        ret = cpufreq_generic_init(policy, freq_table, 300 * 1000);
 136        if (ret) {
 137                clk_disable_unprepare(cpufreq->cpu_clk);
 138                return ret;
 139        }
 140
 141        policy->clk = cpufreq->cpu_clk;
 142        policy->suspend_freq = freq_table[0].frequency;
 143        return 0;
 144}
 145
 146static int tegra_cpu_exit(struct cpufreq_policy *policy)
 147{
 148        struct tegra20_cpufreq *cpufreq = cpufreq_get_driver_data();
 149
 150        clk_disable_unprepare(cpufreq->cpu_clk);
 151        return 0;
 152}
 153
 154static int tegra20_cpufreq_probe(struct platform_device *pdev)
 155{
 156        struct tegra20_cpufreq *cpufreq;
 157        int err;
 158
 159        cpufreq = devm_kzalloc(&pdev->dev, sizeof(*cpufreq), GFP_KERNEL);
 160        if (!cpufreq)
 161                return -ENOMEM;
 162
 163        cpufreq->cpu_clk = clk_get_sys(NULL, "cclk");
 164        if (IS_ERR(cpufreq->cpu_clk))
 165                return PTR_ERR(cpufreq->cpu_clk);
 166
 167        cpufreq->pll_x_clk = clk_get_sys(NULL, "pll_x");
 168        if (IS_ERR(cpufreq->pll_x_clk)) {
 169                err = PTR_ERR(cpufreq->pll_x_clk);
 170                goto put_cpu;
 171        }
 172
 173        cpufreq->pll_p_clk = clk_get_sys(NULL, "pll_p");
 174        if (IS_ERR(cpufreq->pll_p_clk)) {
 175                err = PTR_ERR(cpufreq->pll_p_clk);
 176                goto put_pll_x;
 177        }
 178
 179        cpufreq->dev = &pdev->dev;
 180        cpufreq->driver.get = cpufreq_generic_get;
 181        cpufreq->driver.attr = cpufreq_generic_attr;
 182        cpufreq->driver.init = tegra_cpu_init;
 183        cpufreq->driver.exit = tegra_cpu_exit;
 184        cpufreq->driver.flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK;
 185        cpufreq->driver.verify = cpufreq_generic_frequency_table_verify;
 186        cpufreq->driver.suspend = cpufreq_generic_suspend;
 187        cpufreq->driver.driver_data = cpufreq;
 188        cpufreq->driver.target_index = tegra_target;
 189        cpufreq->driver.get_intermediate = tegra_get_intermediate;
 190        cpufreq->driver.target_intermediate = tegra_target_intermediate;
 191        snprintf(cpufreq->driver.name, CPUFREQ_NAME_LEN, "tegra");
 192
 193        err = cpufreq_register_driver(&cpufreq->driver);
 194        if (err)
 195                goto put_pll_p;
 196
 197        platform_set_drvdata(pdev, cpufreq);
 198
 199        return 0;
 200
 201put_pll_p:
 202        clk_put(cpufreq->pll_p_clk);
 203put_pll_x:
 204        clk_put(cpufreq->pll_x_clk);
 205put_cpu:
 206        clk_put(cpufreq->cpu_clk);
 207
 208        return err;
 209}
 210
 211static int tegra20_cpufreq_remove(struct platform_device *pdev)
 212{
 213        struct tegra20_cpufreq *cpufreq = platform_get_drvdata(pdev);
 214
 215        cpufreq_unregister_driver(&cpufreq->driver);
 216
 217        clk_put(cpufreq->pll_p_clk);
 218        clk_put(cpufreq->pll_x_clk);
 219        clk_put(cpufreq->cpu_clk);
 220
 221        return 0;
 222}
 223
 224static struct platform_driver tegra20_cpufreq_driver = {
 225        .probe          = tegra20_cpufreq_probe,
 226        .remove         = tegra20_cpufreq_remove,
 227        .driver         = {
 228                .name   = "tegra20-cpufreq",
 229        },
 230};
 231module_platform_driver(tegra20_cpufreq_driver);
 232
 233MODULE_ALIAS("platform:tegra20-cpufreq");
 234MODULE_AUTHOR("Colin Cross <ccross@android.com>");
 235MODULE_DESCRIPTION("NVIDIA Tegra20 cpufreq driver");
 236MODULE_LICENSE("GPL");
 237