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#include <plat/clock.h>
  34#include <plat/omap-pm.h>
  35#include <plat/common.h>
  36#include <plat/omap_device.h>
  37
  38#include <mach/hardware.h>
  39
  40/* OPP tolerance in percentage */
  41#define OPP_TOLERANCE   4
  42
  43#ifdef CONFIG_SMP
  44struct lpj_info {
  45        unsigned long   ref;
  46        unsigned int    freq;
  47};
  48
  49static DEFINE_PER_CPU(struct lpj_info, lpj_ref);
  50static struct lpj_info global_lpj_ref;
  51#endif
  52
  53static struct cpufreq_frequency_table *freq_table;
  54static atomic_t freq_table_users = ATOMIC_INIT(0);
  55static struct clk *mpu_clk;
  56static char *mpu_clk_name;
  57static struct device *mpu_dev;
  58static struct regulator *mpu_reg;
  59
  60static int omap_verify_speed(struct cpufreq_policy *policy)
  61{
  62        if (!freq_table)
  63                return -EINVAL;
  64        return cpufreq_frequency_table_verify(policy, freq_table);
  65}
  66
  67static unsigned int omap_getspeed(unsigned int cpu)
  68{
  69        unsigned long rate;
  70
  71        if (cpu >= NR_CPUS)
  72                return 0;
  73
  74        rate = clk_get_rate(mpu_clk) / 1000;
  75        return rate;
  76}
  77
  78static int omap_target(struct cpufreq_policy *policy,
  79                       unsigned int target_freq,
  80                       unsigned int relation)
  81{
  82        unsigned int i;
  83        int r, ret = 0;
  84        struct cpufreq_freqs freqs;
  85        struct opp *opp;
  86        unsigned long freq, volt = 0, volt_old = 0, tol = 0;
  87
  88        if (!freq_table) {
  89                dev_err(mpu_dev, "%s: cpu%d: no freq table!\n", __func__,
  90                                policy->cpu);
  91                return -EINVAL;
  92        }
  93
  94        ret = cpufreq_frequency_table_target(policy, freq_table, target_freq,
  95                        relation, &i);
  96        if (ret) {
  97                dev_dbg(mpu_dev, "%s: cpu%d: no freq match for %d(ret=%d)\n",
  98                        __func__, policy->cpu, target_freq, ret);
  99                return ret;
 100        }
 101        freqs.new = freq_table[i].frequency;
 102        if (!freqs.new) {
 103                dev_err(mpu_dev, "%s: cpu%d: no match for freq %d\n", __func__,
 104                        policy->cpu, target_freq);
 105                return -EINVAL;
 106        }
 107
 108        freqs.old = omap_getspeed(policy->cpu);
 109        freqs.cpu = policy->cpu;
 110
 111        if (freqs.old == freqs.new && policy->cur == freqs.new)
 112                return ret;
 113
 114        /* notifiers */
 115        for_each_cpu(i, policy->cpus) {
 116                freqs.cpu = i;
 117                cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
 118        }
 119
 120        freq = freqs.new * 1000;
 121
 122        if (mpu_reg) {
 123                opp = opp_find_freq_ceil(mpu_dev, &freq);
 124                if (IS_ERR(opp)) {
 125                        dev_err(mpu_dev, "%s: unable to find MPU OPP for %d\n",
 126                                __func__, freqs.new);
 127                        return -EINVAL;
 128                }
 129                volt = opp_get_voltage(opp);
 130                tol = volt * OPP_TOLERANCE / 100;
 131                volt_old = regulator_get_voltage(mpu_reg);
 132        }
 133
 134        dev_dbg(mpu_dev, "cpufreq-omap: %u MHz, %ld mV --> %u MHz, %ld mV\n", 
 135                freqs.old / 1000, volt_old ? volt_old / 1000 : -1,
 136                freqs.new / 1000, volt ? volt / 1000 : -1);
 137
 138        /* scaling up?  scale voltage before frequency */
 139        if (mpu_reg && (freqs.new > freqs.old)) {
 140                r = regulator_set_voltage(mpu_reg, volt - tol, volt + tol);
 141                if (r < 0) {
 142                        dev_warn(mpu_dev, "%s: unable to scale voltage up.\n",
 143                                 __func__);
 144                        freqs.new = freqs.old;
 145                        goto done;
 146                }
 147        }
 148
 149        ret = clk_set_rate(mpu_clk, freqs.new * 1000);
 150
 151        /* scaling down?  scale voltage after frequency */
 152        if (mpu_reg && (freqs.new < freqs.old)) {
 153                r = regulator_set_voltage(mpu_reg, volt - tol, volt + tol);
 154                if (r < 0) {
 155                        dev_warn(mpu_dev, "%s: unable to scale voltage down.\n",
 156                                 __func__);
 157                        ret = clk_set_rate(mpu_clk, freqs.old * 1000);
 158                        freqs.new = freqs.old;
 159                        goto done;
 160                }
 161        }
 162
 163        freqs.new = omap_getspeed(policy->cpu);
 164#ifdef CONFIG_SMP
 165        /*
 166         * Note that loops_per_jiffy is not updated on SMP systems in
 167         * cpufreq driver. So, update the per-CPU loops_per_jiffy value
 168         * on frequency transition. We need to update all dependent CPUs.
 169         */
 170        for_each_cpu(i, policy->cpus) {
 171                struct lpj_info *lpj = &per_cpu(lpj_ref, i);
 172                if (!lpj->freq) {
 173                        lpj->ref = per_cpu(cpu_data, i).loops_per_jiffy;
 174                        lpj->freq = freqs.old;
 175                }
 176
 177                per_cpu(cpu_data, i).loops_per_jiffy =
 178                        cpufreq_scale(lpj->ref, lpj->freq, freqs.new);
 179        }
 180
 181        /* And don't forget to adjust the global one */
 182        if (!global_lpj_ref.freq) {
 183                global_lpj_ref.ref = loops_per_jiffy;
 184                global_lpj_ref.freq = freqs.old;
 185        }
 186        loops_per_jiffy = cpufreq_scale(global_lpj_ref.ref, global_lpj_ref.freq,
 187                                        freqs.new);
 188#endif
 189
 190done:
 191        /* notifiers */
 192        for_each_cpu(i, policy->cpus) {
 193                freqs.cpu = i;
 194                cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
 195        }
 196
 197        return ret;
 198}
 199
 200static inline void freq_table_free(void)
 201{
 202        if (atomic_dec_and_test(&freq_table_users))
 203                opp_free_cpufreq_table(mpu_dev, &freq_table);
 204}
 205
 206static int __cpuinit omap_cpu_init(struct cpufreq_policy *policy)
 207{
 208        int result = 0;
 209
 210        mpu_clk = clk_get(NULL, mpu_clk_name);
 211        if (IS_ERR(mpu_clk))
 212                return PTR_ERR(mpu_clk);
 213
 214        if (policy->cpu >= NR_CPUS) {
 215                result = -EINVAL;
 216                goto fail_ck;
 217        }
 218
 219        policy->cur = policy->min = policy->max = omap_getspeed(policy->cpu);
 220
 221        if (!freq_table)
 222                result = opp_init_cpufreq_table(mpu_dev, &freq_table);
 223
 224        if (result) {
 225                dev_err(mpu_dev, "%s: cpu%d: failed creating freq table[%d]\n",
 226                                __func__, policy->cpu, result);
 227                goto fail_ck;
 228        }
 229
 230        atomic_inc_return(&freq_table_users);
 231
 232        result = cpufreq_frequency_table_cpuinfo(policy, freq_table);
 233        if (result)
 234                goto fail_table;
 235
 236        cpufreq_frequency_table_get_attr(freq_table, policy->cpu);
 237
 238        policy->min = policy->cpuinfo.min_freq;
 239        policy->max = policy->cpuinfo.max_freq;
 240        policy->cur = omap_getspeed(policy->cpu);
 241
 242        /*
 243         * On OMAP SMP configuartion, both processors share the voltage
 244         * and clock. So both CPUs needs to be scaled together and hence
 245         * needs software co-ordination. Use cpufreq affected_cpus
 246         * interface to handle this scenario. Additional is_smp() check
 247         * is to keep SMP_ON_UP build working.
 248         */
 249        if (is_smp()) {
 250                policy->shared_type = CPUFREQ_SHARED_TYPE_ANY;
 251                cpumask_setall(policy->cpus);
 252        }
 253
 254        /* FIXME: what's the actual transition time? */
 255        policy->cpuinfo.transition_latency = 300 * 1000;
 256
 257        return 0;
 258
 259fail_table:
 260        freq_table_free();
 261fail_ck:
 262        clk_put(mpu_clk);
 263        return result;
 264}
 265
 266static int omap_cpu_exit(struct cpufreq_policy *policy)
 267{
 268        freq_table_free();
 269        clk_put(mpu_clk);
 270        return 0;
 271}
 272
 273static struct freq_attr *omap_cpufreq_attr[] = {
 274        &cpufreq_freq_attr_scaling_available_freqs,
 275        NULL,
 276};
 277
 278static struct cpufreq_driver omap_driver = {
 279        .flags          = CPUFREQ_STICKY,
 280        .verify         = omap_verify_speed,
 281        .target         = omap_target,
 282        .get            = omap_getspeed,
 283        .init           = omap_cpu_init,
 284        .exit           = omap_cpu_exit,
 285        .name           = "omap",
 286        .attr           = omap_cpufreq_attr,
 287};
 288
 289static int __init omap_cpufreq_init(void)
 290{
 291        if (cpu_is_omap24xx())
 292                mpu_clk_name = "virt_prcm_set";
 293        else if (cpu_is_omap34xx())
 294                mpu_clk_name = "dpll1_ck";
 295        else if (cpu_is_omap44xx())
 296                mpu_clk_name = "dpll_mpu_ck";
 297
 298        if (!mpu_clk_name) {
 299                pr_err("%s: unsupported Silicon?\n", __func__);
 300                return -EINVAL;
 301        }
 302
 303        mpu_dev = omap_device_get_by_hwmod_name("mpu");
 304        if (!mpu_dev) {
 305                pr_warning("%s: unable to get the mpu device\n", __func__);
 306                return -EINVAL;
 307        }
 308
 309        mpu_reg = regulator_get(mpu_dev, "vcc");
 310        if (IS_ERR(mpu_reg)) {
 311                pr_warning("%s: unable to get MPU regulator\n", __func__);
 312                mpu_reg = NULL;
 313        } else {
 314                /* 
 315                 * Ensure physical regulator is present.
 316                 * (e.g. could be dummy regulator.)
 317                 */
 318                if (regulator_get_voltage(mpu_reg) < 0) {
 319                        pr_warn("%s: physical regulator not present for MPU\n",
 320                                __func__);
 321                        regulator_put(mpu_reg);
 322                        mpu_reg = NULL;
 323                }
 324        }
 325
 326        return cpufreq_register_driver(&omap_driver);
 327}
 328
 329static void __exit omap_cpufreq_exit(void)
 330{
 331        cpufreq_unregister_driver(&omap_driver);
 332}
 333
 334MODULE_DESCRIPTION("cpufreq driver for OMAP SoCs");
 335MODULE_LICENSE("GPL");
 336module_init(omap_cpufreq_init);
 337module_exit(omap_cpufreq_exit);
 338
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.