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                rcu_read_lock();
 114                opp = opp_find_freq_ceil(mpu_dev, &freq);
 115                if (IS_ERR(opp)) {
 116                        rcu_read_unlock();
 117                        dev_err(mpu_dev, "%s: unable to find MPU OPP for %d\n",
 118                                __func__, freqs.new);
 119                        return -EINVAL;
 120                }
 121                volt = opp_get_voltage(opp);
 122                rcu_read_unlock();
 123                tol = volt * OPP_TOLERANCE / 100;
 124                volt_old = regulator_get_voltage(mpu_reg);
 125        }
 126
 127        dev_dbg(mpu_dev, "cpufreq-omap: %u MHz, %ld mV --> %u MHz, %ld mV\n", 
 128                freqs.old / 1000, volt_old ? volt_old / 1000 : -1,
 129                freqs.new / 1000, volt ? volt / 1000 : -1);
 130
 131        /* scaling up?  scale voltage before frequency */
 132        if (mpu_reg && (freqs.new > freqs.old)) {
 133                r = regulator_set_voltage(mpu_reg, volt - tol, volt + tol);
 134                if (r < 0) {
 135                        dev_warn(mpu_dev, "%s: unable to scale voltage up.\n",
 136                                 __func__);
 137                        freqs.new = freqs.old;
 138                        goto done;
 139                }
 140        }
 141
 142        ret = clk_set_rate(mpu_clk, freqs.new * 1000);
 143
 144        /* scaling down?  scale voltage after frequency */
 145        if (mpu_reg && (freqs.new < freqs.old)) {
 146                r = regulator_set_voltage(mpu_reg, volt - tol, volt + tol);
 147                if (r < 0) {
 148                        dev_warn(mpu_dev, "%s: unable to scale voltage down.\n",
 149                                 __func__);
 150                        ret = clk_set_rate(mpu_clk, freqs.old * 1000);
 151                        freqs.new = freqs.old;
 152                        goto done;
 153                }
 154        }
 155
 156        freqs.new = omap_getspeed(policy->cpu);
 157
 158done:
 159        /* notifiers */
 160        for_each_cpu(i, policy->cpus) {
 161                freqs.cpu = i;
 162                cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
 163        }
 164
 165        return ret;
 166}
 167
 168static inline void freq_table_free(void)
 169{
 170        if (atomic_dec_and_test(&freq_table_users))
 171                opp_free_cpufreq_table(mpu_dev, &freq_table);
 172}
 173
 174static int __cpuinit omap_cpu_init(struct cpufreq_policy *policy)
 175{
 176        int result = 0;
 177
 178        mpu_clk = clk_get(NULL, "cpufreq_ck");
 179        if (IS_ERR(mpu_clk))
 180                return PTR_ERR(mpu_clk);
 181
 182        if (policy->cpu >= NR_CPUS) {
 183                result = -EINVAL;
 184                goto fail_ck;
 185        }
 186
 187        policy->cur = policy->min = policy->max = omap_getspeed(policy->cpu);
 188
 189        if (!freq_table)
 190                result = opp_init_cpufreq_table(mpu_dev, &freq_table);
 191
 192        if (result) {
 193                dev_err(mpu_dev, "%s: cpu%d: failed creating freq table[%d]\n",
 194                                __func__, policy->cpu, result);
 195                goto fail_ck;
 196        }
 197
 198        atomic_inc_return(&freq_table_users);
 199
 200        result = cpufreq_frequency_table_cpuinfo(policy, freq_table);
 201        if (result)
 202                goto fail_table;
 203
 204        cpufreq_frequency_table_get_attr(freq_table, policy->cpu);
 205
 206        policy->min = policy->cpuinfo.min_freq;
 207        policy->max = policy->cpuinfo.max_freq;
 208        policy->cur = omap_getspeed(policy->cpu);
 209
 210        /*
 211         * On OMAP SMP configuartion, both processors share the voltage
 212         * and clock. So both CPUs needs to be scaled together and hence
 213         * needs software co-ordination. Use cpufreq affected_cpus
 214         * interface to handle this scenario. Additional is_smp() check
 215         * is to keep SMP_ON_UP build working.
 216         */
 217        if (is_smp()) {
 218                policy->shared_type = CPUFREQ_SHARED_TYPE_ANY;
 219                cpumask_setall(policy->cpus);
 220        }
 221
 222        /* FIXME: what's the actual transition time? */
 223        policy->cpuinfo.transition_latency = 300 * 1000;
 224
 225        return 0;
 226
 227fail_table:
 228        freq_table_free();
 229fail_ck:
 230        clk_put(mpu_clk);
 231        return result;
 232}
 233
 234static int omap_cpu_exit(struct cpufreq_policy *policy)
 235{
 236        freq_table_free();
 237        clk_put(mpu_clk);
 238        return 0;
 239}
 240
 241static struct freq_attr *omap_cpufreq_attr[] = {
 242        &cpufreq_freq_attr_scaling_available_freqs,
 243        NULL,
 244};
 245
 246static struct cpufreq_driver omap_driver = {
 247        .flags          = CPUFREQ_STICKY,
 248        .verify         = omap_verify_speed,
 249        .target         = omap_target,
 250        .get            = omap_getspeed,
 251        .init           = omap_cpu_init,
 252        .exit           = omap_cpu_exit,
 253        .name           = "omap",
 254        .attr           = omap_cpufreq_attr,
 255};
 256
 257static int __init omap_cpufreq_init(void)
 258{
 259        mpu_dev = get_cpu_device(0);
 260        if (!mpu_dev) {
 261                pr_warning("%s: unable to get the mpu device\n", __func__);
 262                return -EINVAL;
 263        }
 264
 265        mpu_reg = regulator_get(mpu_dev, "vcc");
 266        if (IS_ERR(mpu_reg)) {
 267                pr_warning("%s: unable to get MPU regulator\n", __func__);
 268                mpu_reg = NULL;
 269        } else {
 270                /* 
 271                 * Ensure physical regulator is present.
 272                 * (e.g. could be dummy regulator.)
 273                 */
 274                if (regulator_get_voltage(mpu_reg) < 0) {
 275                        pr_warn("%s: physical regulator not present for MPU\n",
 276                                __func__);
 277                        regulator_put(mpu_reg);
 278                        mpu_reg = NULL;
 279                }
 280        }
 281
 282        return cpufreq_register_driver(&omap_driver);
 283}
 284
 285static void __exit omap_cpufreq_exit(void)
 286{
 287        cpufreq_unregister_driver(&omap_driver);
 288}
 289
 290MODULE_DESCRIPTION("cpufreq driver for OMAP SoCs");
 291MODULE_LICENSE("GPL");
 292module_init(omap_cpufreq_init);
 293module_exit(omap_cpufreq_exit);
 294