linux/drivers/cpufreq/s3c64xx-cpufreq.c
<<
>>
Prefs
   1/*
   2 * Copyright 2009 Wolfson Microelectronics plc
   3 *
   4 * S3C64xx CPUfreq Support
   5 *
   6 * This program is free software; you can redistribute it and/or modify
   7 * it under the terms of the GNU General Public License version 2 as
   8 * published by the Free Software Foundation.
   9 */
  10
  11#define pr_fmt(fmt) "cpufreq: " fmt
  12
  13#include <linux/kernel.h>
  14#include <linux/types.h>
  15#include <linux/init.h>
  16#include <linux/cpufreq.h>
  17#include <linux/clk.h>
  18#include <linux/err.h>
  19#include <linux/regulator/consumer.h>
  20#include <linux/module.h>
  21
  22static struct clk *armclk;
  23static struct regulator *vddarm;
  24static unsigned long regulator_latency;
  25
  26#ifdef CONFIG_CPU_S3C6410
  27struct s3c64xx_dvfs {
  28        unsigned int vddarm_min;
  29        unsigned int vddarm_max;
  30};
  31
  32static struct s3c64xx_dvfs s3c64xx_dvfs_table[] = {
  33        [0] = { 1000000, 1150000 },
  34        [1] = { 1050000, 1150000 },
  35        [2] = { 1100000, 1150000 },
  36        [3] = { 1200000, 1350000 },
  37        [4] = { 1300000, 1350000 },
  38};
  39
  40static struct cpufreq_frequency_table s3c64xx_freq_table[] = {
  41        { 0,  66000 },
  42        { 0, 100000 },
  43        { 0, 133000 },
  44        { 1, 200000 },
  45        { 1, 222000 },
  46        { 1, 266000 },
  47        { 2, 333000 },
  48        { 2, 400000 },
  49        { 2, 532000 },
  50        { 2, 533000 },
  51        { 3, 667000 },
  52        { 4, 800000 },
  53        { 0, CPUFREQ_TABLE_END },
  54};
  55#endif
  56
  57static int s3c64xx_cpufreq_verify_speed(struct cpufreq_policy *policy)
  58{
  59        if (policy->cpu != 0)
  60                return -EINVAL;
  61
  62        return cpufreq_frequency_table_verify(policy, s3c64xx_freq_table);
  63}
  64
  65static unsigned int s3c64xx_cpufreq_get_speed(unsigned int cpu)
  66{
  67        if (cpu != 0)
  68                return 0;
  69
  70        return clk_get_rate(armclk) / 1000;
  71}
  72
  73static int s3c64xx_cpufreq_set_target(struct cpufreq_policy *policy,
  74                                      unsigned int target_freq,
  75                                      unsigned int relation)
  76{
  77        int ret;
  78        unsigned int i;
  79        struct cpufreq_freqs freqs;
  80        struct s3c64xx_dvfs *dvfs;
  81
  82        ret = cpufreq_frequency_table_target(policy, s3c64xx_freq_table,
  83                                             target_freq, relation, &i);
  84        if (ret != 0)
  85                return ret;
  86
  87        freqs.old = clk_get_rate(armclk) / 1000;
  88        freqs.new = s3c64xx_freq_table[i].frequency;
  89        freqs.flags = 0;
  90        dvfs = &s3c64xx_dvfs_table[s3c64xx_freq_table[i].driver_data];
  91
  92        if (freqs.old == freqs.new)
  93                return 0;
  94
  95        pr_debug("Transition %d-%dkHz\n", freqs.old, freqs.new);
  96
  97        cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE);
  98
  99#ifdef CONFIG_REGULATOR
 100        if (vddarm && freqs.new > freqs.old) {
 101                ret = regulator_set_voltage(vddarm,
 102                                            dvfs->vddarm_min,
 103                                            dvfs->vddarm_max);
 104                if (ret != 0) {
 105                        pr_err("Failed to set VDDARM for %dkHz: %d\n",
 106                               freqs.new, ret);
 107                        freqs.new = freqs.old;
 108                        goto post_notify;
 109                }
 110        }
 111#endif
 112
 113        ret = clk_set_rate(armclk, freqs.new * 1000);
 114        if (ret < 0) {
 115                pr_err("Failed to set rate %dkHz: %d\n",
 116                       freqs.new, ret);
 117                freqs.new = freqs.old;
 118        }
 119
 120post_notify:
 121        cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE);
 122        if (ret)
 123                goto err;
 124
 125#ifdef CONFIG_REGULATOR
 126        if (vddarm && freqs.new < freqs.old) {
 127                ret = regulator_set_voltage(vddarm,
 128                                            dvfs->vddarm_min,
 129                                            dvfs->vddarm_max);
 130                if (ret != 0) {
 131                        pr_err("Failed to set VDDARM for %dkHz: %d\n",
 132                               freqs.new, ret);
 133                        goto err_clk;
 134                }
 135        }
 136#endif
 137
 138        pr_debug("Set actual frequency %lukHz\n",
 139                 clk_get_rate(armclk) / 1000);
 140
 141        return 0;
 142
 143err_clk:
 144        if (clk_set_rate(armclk, freqs.old * 1000) < 0)
 145                pr_err("Failed to restore original clock rate\n");
 146err:
 147        cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE);
 148
 149        return ret;
 150}
 151
 152#ifdef CONFIG_REGULATOR
 153static void __init s3c64xx_cpufreq_config_regulator(void)
 154{
 155        int count, v, i, found;
 156        struct cpufreq_frequency_table *freq;
 157        struct s3c64xx_dvfs *dvfs;
 158
 159        count = regulator_count_voltages(vddarm);
 160        if (count < 0) {
 161                pr_err("Unable to check supported voltages\n");
 162        }
 163
 164        freq = s3c64xx_freq_table;
 165        while (count > 0 && freq->frequency != CPUFREQ_TABLE_END) {
 166                if (freq->frequency == CPUFREQ_ENTRY_INVALID)
 167                        continue;
 168
 169                dvfs = &s3c64xx_dvfs_table[freq->driver_data];
 170                found = 0;
 171
 172                for (i = 0; i < count; i++) {
 173                        v = regulator_list_voltage(vddarm, i);
 174                        if (v >= dvfs->vddarm_min && v <= dvfs->vddarm_max)
 175                                found = 1;
 176                }
 177
 178                if (!found) {
 179                        pr_debug("%dkHz unsupported by regulator\n",
 180                                 freq->frequency);
 181                        freq->frequency = CPUFREQ_ENTRY_INVALID;
 182                }
 183
 184                freq++;
 185        }
 186
 187        /* Guess based on having to do an I2C/SPI write; in future we
 188         * will be able to query the regulator performance here. */
 189        regulator_latency = 1 * 1000 * 1000;
 190}
 191#endif
 192
 193static int s3c64xx_cpufreq_driver_init(struct cpufreq_policy *policy)
 194{
 195        int ret;
 196        struct cpufreq_frequency_table *freq;
 197
 198        if (policy->cpu != 0)
 199                return -EINVAL;
 200
 201        if (s3c64xx_freq_table == NULL) {
 202                pr_err("No frequency information for this CPU\n");
 203                return -ENODEV;
 204        }
 205
 206        armclk = clk_get(NULL, "armclk");
 207        if (IS_ERR(armclk)) {
 208                pr_err("Unable to obtain ARMCLK: %ld\n",
 209                       PTR_ERR(armclk));
 210                return PTR_ERR(armclk);
 211        }
 212
 213#ifdef CONFIG_REGULATOR
 214        vddarm = regulator_get(NULL, "vddarm");
 215        if (IS_ERR(vddarm)) {
 216                ret = PTR_ERR(vddarm);
 217                pr_err("Failed to obtain VDDARM: %d\n", ret);
 218                pr_err("Only frequency scaling available\n");
 219                vddarm = NULL;
 220        } else {
 221                s3c64xx_cpufreq_config_regulator();
 222        }
 223#endif
 224
 225        freq = s3c64xx_freq_table;
 226        while (freq->frequency != CPUFREQ_TABLE_END) {
 227                unsigned long r;
 228
 229                /* Check for frequencies we can generate */
 230                r = clk_round_rate(armclk, freq->frequency * 1000);
 231                r /= 1000;
 232                if (r != freq->frequency) {
 233                        pr_debug("%dkHz unsupported by clock\n",
 234                                 freq->frequency);
 235                        freq->frequency = CPUFREQ_ENTRY_INVALID;
 236                }
 237
 238                /* If we have no regulator then assume startup
 239                 * frequency is the maximum we can support. */
 240                if (!vddarm && freq->frequency > s3c64xx_cpufreq_get_speed(0))
 241                        freq->frequency = CPUFREQ_ENTRY_INVALID;
 242
 243                freq++;
 244        }
 245
 246        policy->cur = clk_get_rate(armclk) / 1000;
 247
 248        /* Datasheet says PLL stabalisation time (if we were to use
 249         * the PLLs, which we don't currently) is ~300us worst case,
 250         * but add some fudge.
 251         */
 252        policy->cpuinfo.transition_latency = (500 * 1000) + regulator_latency;
 253
 254        ret = cpufreq_frequency_table_cpuinfo(policy, s3c64xx_freq_table);
 255        if (ret != 0) {
 256                pr_err("Failed to configure frequency table: %d\n",
 257                       ret);
 258                regulator_put(vddarm);
 259                clk_put(armclk);
 260        }
 261
 262        return ret;
 263}
 264
 265static struct cpufreq_driver s3c64xx_cpufreq_driver = {
 266        .flags          = 0,
 267        .verify         = s3c64xx_cpufreq_verify_speed,
 268        .target         = s3c64xx_cpufreq_set_target,
 269        .get            = s3c64xx_cpufreq_get_speed,
 270        .init           = s3c64xx_cpufreq_driver_init,
 271        .name           = "s3c",
 272};
 273
 274static int __init s3c64xx_cpufreq_init(void)
 275{
 276        return cpufreq_register_driver(&s3c64xx_cpufreq_driver);
 277}
 278module_init(s3c64xx_cpufreq_init);
 279
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.