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/regulator/consumer.h>
  29
  30#include <asm/smp_plat.h>
  31#include <asm/cpu.h>
  32
  33/* OPP tolerance in percentage */
  34#define OPP_TOLERANCE   4
  35
  36static struct cpufreq_frequency_table *freq_table;
  37static atomic_t freq_table_users = ATOMIC_INIT(0);
  38static struct clk *mpu_clk;
  39static struct device *mpu_dev;
  40static struct regulator *mpu_reg;
  41
  42static int omap_verify_speed(struct cpufreq_policy *policy)
  43{
  44        if (!freq_table)
  45                return -EINVAL;
  46        return cpufreq_frequency_table_verify(policy, freq_table);
  47}
  48
  49static unsigned int omap_getspeed(unsigned int cpu)
  50{
  51        unsigned long rate;
  52
  53        if (cpu >= NR_CPUS)
  54                return 0;
  55
  56        rate = clk_get_rate(mpu_clk) / 1000;
  57        return rate;
  58}
  59
  60static int omap_target(struct cpufreq_policy *policy,
  61                       unsigned int target_freq,
  62                       unsigned int relation)
  63{
  64        unsigned int i;
  65        int r, ret = 0;
  66        struct cpufreq_freqs freqs;
  67        struct opp *opp;
  68        unsigned long freq, volt = 0, volt_old = 0, tol = 0;
  69
  70        if (!freq_table) {
  71                dev_err(mpu_dev, "%s: cpu%d: no freq table!\n", __func__,
  72                                policy->cpu);
  73                return -EINVAL;
  74        }
  75
  76        ret = cpufreq_frequency_table_target(policy, freq_table, target_freq,
  77                        relation, &i);
  78        if (ret) {
  79                dev_dbg(mpu_dev, "%s: cpu%d: no freq match for %d(ret=%d)\n",
  80                        __func__, policy->cpu, target_freq, ret);
  81                return ret;
  82        }
  83        freqs.new = freq_table[i].frequency;
  84        if (!freqs.new) {
  85                dev_err(mpu_dev, "%s: cpu%d: no match for freq %d\n", __func__,
  86                        policy->cpu, target_freq);
  87                return -EINVAL;
  88        }
  89
  90        freqs.old = omap_getspeed(policy->cpu);
  91        freqs.cpu = policy->cpu;
  92
  93        if (freqs.old == freqs.new && policy->cur == freqs.new)
  94                return ret;
  95
  96        /* notifiers */
  97        for_each_cpu(i, policy->cpus) {
  98                freqs.cpu = i;
  99                cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
 100        }
 101
 102        freq = freqs.new * 1000;
 103        ret = clk_round_rate(mpu_clk, freq);
 104        if (IS_ERR_VALUE(ret)) {
 105                dev_warn(mpu_dev,
 106                         "CPUfreq: Cannot find matching frequency for %lu\n",
 107                         freq);
 108                return ret;
 109        }
 110        freq = ret;
 111
 112        if (mpu_reg) {
 113                opp = opp_find_freq_ceil(mpu_dev, &freq);
 114                if (IS_ERR(opp)) {
 115                        dev_err(mpu_dev, "%s: unable to find MPU OPP for %d\n",
 116                                __func__, freqs.new);
 117                        return -EINVAL;
 118                }
 119                volt = opp_get_voltage(opp);
 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        for_each_cpu(i, policy->cpus) {
 158                freqs.cpu = i;
 159                cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
 160        }
 161
 162        return ret;
 163}
 164
 165static inline void freq_table_free(void)
 166{
 167        if (atomic_dec_and_test(&freq_table_users))
 168                opp_free_cpufreq_table(mpu_dev, &freq_table);
 169}
 170
 171static int __cpuinit omap_cpu_init(struct cpufreq_policy *policy)
 172{
 173        int result = 0;
 174
 175        mpu_clk = clk_get(NULL, "cpufreq_ck");
 176        if (IS_ERR(mpu_clk))
 177                return PTR_ERR(mpu_clk);
 178
 179        if (policy->cpu >= NR_CPUS) {
 180                result = -EINVAL;
 181                goto fail_ck;
 182        }
 183
 184        policy->cur = policy->min = policy->max = omap_getspeed(policy->cpu);
 185
 186        if (!freq_table)
 187                result = opp_init_cpufreq_table(mpu_dev, &freq_table);
 188
 189        if (result) {
 190                dev_err(mpu_dev, "%s: cpu%d: failed creating freq table[%d]\n",
 191                                __func__, policy->cpu, result);
 192                goto fail_ck;
 193        }
 194
 195        atomic_inc_return(&freq_table_users);
 196
 197        result = cpufreq_frequency_table_cpuinfo(policy, freq_table);
 198        if (result)
 199                goto fail_table;
 200
 201        cpufreq_frequency_table_get_attr(freq_table, policy->cpu);
 202
 203        policy->min = policy->cpuinfo.min_freq;
 204        policy->max = policy->cpuinfo.max_freq;
 205        policy->cur = omap_getspeed(policy->cpu);
 206
 207        /*
 208         * On OMAP SMP configuartion, both processors share the voltage
 209         * and clock. So both CPUs needs to be scaled together and hence
 210         * needs software co-ordination. Use cpufreq affected_cpus
 211         * interface to handle this scenario. Additional is_smp() check
 212         * is to keep SMP_ON_UP build working.
 213         */
 214        if (is_smp()) {
 215                policy->shared_type = CPUFREQ_SHARED_TYPE_ANY;
 216                cpumask_setall(policy->cpus);
 217        }
 218
 219        /* FIXME: what's the actual transition time? */
 220        policy->cpuinfo.transition_latency = 300 * 1000;
 221
 222        return 0;
 223
 224fail_table:
 225        freq_table_free();
 226fail_ck:
 227        clk_put(mpu_clk);
 228        return result;
 229}
 230
 231static int omap_cpu_exit(struct cpufreq_policy *policy)
 232{
 233        freq_table_free();
 234        clk_put(mpu_clk);
 235        return 0;
 236}
 237
 238static struct freq_attr *omap_cpufreq_attr[] = {
 239        &cpufreq_freq_attr_scaling_available_freqs,
 240        NULL,
 241};
 242
 243static struct cpufreq_driver omap_driver = {
 244        .flags          = CPUFREQ_STICKY,
 245        .verify         = omap_verify_speed,
 246        .target         = omap_target,
 247        .get            = omap_getspeed,
 248        .init           = omap_cpu_init,
 249        .exit           = omap_cpu_exit,
 250        .name           = "omap",
 251        .attr           = omap_cpufreq_attr,
 252};
 253
 254static int __init omap_cpufreq_init(void)
 255{
 256        mpu_dev = get_cpu_device(0);
 257        if (!mpu_dev) {
 258                pr_warning("%s: unable to get the mpu device\n", __func__);
 259                return -EINVAL;
 260        }
 261
 262        mpu_reg = regulator_get(mpu_dev, "vcc");
 263        if (IS_ERR(mpu_reg)) {
 264                pr_warning("%s: unable to get MPU regulator\n", __func__);
 265                mpu_reg = NULL;
 266        } else {
 267                /* 
 268                 * Ensure physical regulator is present.
 269                 * (e.g. could be dummy regulator.)
 270                 */
 271                if (regulator_get_voltage(mpu_reg) < 0) {
 272                        pr_warn("%s: physical regulator not present for MPU\n",
 273                                __func__);
 274                        regulator_put(mpu_reg);
 275                        mpu_reg = NULL;
 276                }
 277        }
 278
 279        return cpufreq_register_driver(&omap_driver);
 280}
 281
 282static void __exit omap_cpufreq_exit(void)
 283{
 284        cpufreq_unregister_driver(&omap_driver);
 285}
 286
 287MODULE_DESCRIPTION("cpufreq driver for OMAP SoCs");
 288MODULE_LICENSE("GPL");
 289module_init(omap_cpufreq_init);
 290module_exit(omap_cpufreq_exit);
 291
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.