linux/drivers/cpufreq/omap-cpufreq.c
<<
>>
Prefs
   1/*
   2 *  CPU frequency scaling for OMAP using OPP information
   3 *
   4 *  Copyright (C) 2005 Nokia Corporation
   5 *  Written by Tony Lindgren <tony@atomide.com>
   6 *
   7 *  Based on cpu-sa1110.c, Copyright (C) 2001 Russell King
   8 *
   9 * Copyright (C) 2007-2011 Texas Instruments, Inc.
  10 * - OMAP3/4 support by Rajendra Nayak, Santosh Shilimkar
  11 *
  12 * This program is free software; you can redistribute it and/or modify
  13 * it under the terms of the GNU General Public License version 2 as
  14 * published by the Free Software Foundation.
  15 */
  16#include <linux/types.h>
  17#include <linux/kernel.h>
  18#include <linux/sched.h>
  19#include <linux/cpufreq.h>
  20#include <linux/delay.h>
  21#include <linux/init.h>
  22#include <linux/err.h>
  23#include <linux/clk.h>
  24#include <linux/io.h>
  25#include <linux/opp.h>
  26#include <linux/cpu.h>
  27#include <linux/module.h>
  28#include <linux/platform_device.h>
  29#include <linux/regulator/consumer.h>
  30
  31#include <asm/smp_plat.h>
  32#include <asm/cpu.h>
  33
  34/* OPP tolerance in percentage */
  35#define OPP_TOLERANCE   4
  36
  37static struct cpufreq_frequency_table *freq_table;
  38static atomic_t freq_table_users = ATOMIC_INIT(0);
  39static struct clk *mpu_clk;
  40static struct device *mpu_dev;
  41static struct regulator *mpu_reg;
  42
  43static int omap_verify_speed(struct cpufreq_policy *policy)
  44{
  45        if (!freq_table)
  46                return -EINVAL;
  47        return cpufreq_frequency_table_verify(policy, freq_table);
  48}
  49
  50static unsigned int omap_getspeed(unsigned int cpu)
  51{
  52        unsigned long rate;
  53
  54        if (cpu >= NR_CPUS)
  55                return 0;
  56
  57        rate = clk_get_rate(mpu_clk) / 1000;
  58        return rate;
  59}
  60
  61static int omap_target(struct cpufreq_policy *policy,
  62                       unsigned int target_freq,
  63                       unsigned int relation)
  64{
  65        unsigned int i;
  66        int r, ret = 0;
  67        struct cpufreq_freqs freqs;
  68        struct opp *opp;
  69        unsigned long freq, volt = 0, volt_old = 0, tol = 0;
  70
  71        if (!freq_table) {
  72                dev_err(mpu_dev, "%s: cpu%d: no freq table!\n", __func__,
  73                                policy->cpu);
  74                return -EINVAL;
  75        }
  76
  77        ret = cpufreq_frequency_table_target(policy, freq_table, target_freq,
  78                        relation, &i);
  79        if (ret) {
  80                dev_dbg(mpu_dev, "%s: cpu%d: no freq match for %d(ret=%d)\n",
  81                        __func__, policy->cpu, target_freq, ret);
  82                return ret;
  83        }
  84        freqs.new = freq_table[i].frequency;
  85        if (!freqs.new) {
  86                dev_err(mpu_dev, "%s: cpu%d: no match for freq %d\n", __func__,
  87                        policy->cpu, target_freq);
  88                return -EINVAL;
  89        }
  90
  91        freqs.old = omap_getspeed(policy->cpu);
  92
  93        if (freqs.old == freqs.new && policy->cur == freqs.new)
  94                return ret;
  95
  96        /* notifiers */
  97        cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE);
  98
  99        freq = freqs.new * 1000;
 100        ret = clk_round_rate(mpu_clk, freq);
 101        if (IS_ERR_VALUE(ret)) {
 102                dev_warn(mpu_dev,
 103                         "CPUfreq: Cannot find matching frequency for %lu\n",
 104                         freq);
 105                return ret;
 106        }
 107        freq = ret;
 108
 109        if (mpu_reg) {
 110                rcu_read_lock();
 111                opp = opp_find_freq_ceil(mpu_dev, &freq);
 112                if (IS_ERR(opp)) {
 113                        rcu_read_unlock();
 114                        dev_err(mpu_dev, "%s: unable to find MPU OPP for %d\n",
 115                                __func__, freqs.new);
 116                        return -EINVAL;
 117                }
 118                volt = opp_get_voltage(opp);
 119                rcu_read_unlock();
 120                tol = volt * OPP_TOLERANCE / 100;
 121                volt_old = regulator_get_voltage(mpu_reg);
 122        }
 123
 124        dev_dbg(mpu_dev, "cpufreq-omap: %u MHz, %ld mV --> %u MHz, %ld mV\n", 
 125                freqs.old / 1000, volt_old ? volt_old / 1000 : -1,
 126                freqs.new / 1000, volt ? volt / 1000 : -1);
 127
 128        /* scaling up?  scale voltage before frequency */
 129        if (mpu_reg && (freqs.new > freqs.old)) {
 130                r = regulator_set_voltage(mpu_reg, volt - tol, volt + tol);
 131                if (r < 0) {
 132                        dev_warn(mpu_dev, "%s: unable to scale voltage up.\n",
 133                                 __func__);
 134                        freqs.new = freqs.old;
 135                        goto done;
 136                }
 137        }
 138
 139        ret = clk_set_rate(mpu_clk, freqs.new * 1000);
 140
 141        /* scaling down?  scale voltage after frequency */
 142        if (mpu_reg && (freqs.new < freqs.old)) {
 143                r = regulator_set_voltage(mpu_reg, volt - tol, volt + tol);
 144                if (r < 0) {
 145                        dev_warn(mpu_dev, "%s: unable to scale voltage down.\n",
 146                                 __func__);
 147                        ret = clk_set_rate(mpu_clk, freqs.old * 1000);
 148                        freqs.new = freqs.old;
 149                        goto done;
 150                }
 151        }
 152
 153        freqs.new = omap_getspeed(policy->cpu);
 154
 155done:
 156        /* notifiers */
 157        cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE);
 158
 159        return ret;
 160}
 161
 162static inline void freq_table_free(void)
 163{
 164        if (atomic_dec_and_test(&freq_table_users))
 165                opp_free_cpufreq_table(mpu_dev, &freq_table);
 166}
 167
 168static int omap_cpu_init(struct cpufreq_policy *policy)
 169{
 170        int result = 0;
 171
 172        mpu_clk = clk_get(NULL, "cpufreq_ck");
 173        if (IS_ERR(mpu_clk))
 174                return PTR_ERR(mpu_clk);
 175
 176        if (policy->cpu >= NR_CPUS) {
 177                result = -EINVAL;
 178                goto fail_ck;
 179        }
 180
 181        policy->cur = omap_getspeed(policy->cpu);
 182
 183        if (!freq_table)
 184                result = opp_init_cpufreq_table(mpu_dev, &freq_table);
 185
 186        if (result) {
 187                dev_err(mpu_dev, "%s: cpu%d: failed creating freq table[%d]\n",
 188                                __func__, policy->cpu, result);
 189                goto fail_ck;
 190        }
 191
 192        atomic_inc_return(&freq_table_users);
 193
 194        result = cpufreq_frequency_table_cpuinfo(policy, freq_table);
 195        if (result)
 196                goto fail_table;
 197
 198        cpufreq_frequency_table_get_attr(freq_table, policy->cpu);
 199
 200        policy->cur = omap_getspeed(policy->cpu);
 201
 202        /*
 203         * On OMAP SMP configuartion, both processors share the voltage
 204         * and clock. So both CPUs needs to be scaled together and hence
 205         * needs software co-ordination. Use cpufreq affected_cpus
 206         * interface to handle this scenario. Additional is_smp() check
 207         * is to keep SMP_ON_UP build working.
 208         */
 209        if (is_smp())
 210                cpumask_setall(policy->cpus);
 211
 212        /* FIXME: what's the actual transition time? */
 213        policy->cpuinfo.transition_latency = 300 * 1000;
 214
 215        return 0;
 216
 217fail_table:
 218        freq_table_free();
 219fail_ck:
 220        clk_put(mpu_clk);
 221        return result;
 222}
 223
 224static int omap_cpu_exit(struct cpufreq_policy *policy)
 225{
 226        freq_table_free();
 227        clk_put(mpu_clk);
 228        return 0;
 229}
 230
 231static struct freq_attr *omap_cpufreq_attr[] = {
 232        &cpufreq_freq_attr_scaling_available_freqs,
 233        NULL,
 234};
 235
 236static struct cpufreq_driver omap_driver = {
 237        .flags          = CPUFREQ_STICKY,
 238        .verify         = omap_verify_speed,
 239        .target         = omap_target,
 240        .get            = omap_getspeed,
 241        .init           = omap_cpu_init,
 242        .exit           = omap_cpu_exit,
 243        .name           = "omap",
 244        .attr           = omap_cpufreq_attr,
 245};
 246
 247static int omap_cpufreq_probe(struct platform_device *pdev)
 248{
 249        mpu_dev = get_cpu_device(0);
 250        if (!mpu_dev) {
 251                pr_warning("%s: unable to get the mpu device\n", __func__);
 252                return -EINVAL;
 253        }
 254
 255        mpu_reg = regulator_get(mpu_dev, "vcc");
 256        if (IS_ERR(mpu_reg)) {
 257                pr_warning("%s: unable to get MPU regulator\n", __func__);
 258                mpu_reg = NULL;
 259        } else {
 260                /* 
 261                 * Ensure physical regulator is present.
 262                 * (e.g. could be dummy regulator.)
 263                 */
 264                if (regulator_get_voltage(mpu_reg) < 0) {
 265                        pr_warn("%s: physical regulator not present for MPU\n",
 266                                __func__);
 267                        regulator_put(mpu_reg);
 268                        mpu_reg = NULL;
 269                }
 270        }
 271
 272        return cpufreq_register_driver(&omap_driver);
 273}
 274
 275static int omap_cpufreq_remove(struct platform_device *pdev)
 276{
 277        return cpufreq_unregister_driver(&omap_driver);
 278}
 279
 280static struct platform_driver omap_cpufreq_platdrv = {
 281        .driver = {
 282                .name   = "omap-cpufreq",
 283                .owner  = THIS_MODULE,
 284        },
 285        .probe          = omap_cpufreq_probe,
 286        .remove         = omap_cpufreq_remove,
 287};
 288module_platform_driver(omap_cpufreq_platdrv);
 289
 290MODULE_DESCRIPTION("cpufreq driver for OMAP SoCs");
 291MODULE_LICENSE("GPL");
 292