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].index];
  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                        goto err;
 108                }
 109        }
 110#endif
 111
 112        ret = clk_set_rate(armclk, freqs.new * 1000);
 113        if (ret < 0) {
 114                pr_err("Failed to set rate %dkHz: %d\n",
 115                       freqs.new, ret);
 116                goto err;
 117        }
 118
 119        cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE);
 120
 121#ifdef CONFIG_REGULATOR
 122        if (vddarm && freqs.new < freqs.old) {
 123                ret = regulator_set_voltage(vddarm,
 124                                            dvfs->vddarm_min,
 125                                            dvfs->vddarm_max);
 126                if (ret != 0) {
 127                        pr_err("Failed to set VDDARM for %dkHz: %d\n",
 128                               freqs.new, ret);
 129                        goto err_clk;
 130                }
 131        }
 132#endif
 133
 134        pr_debug("Set actual frequency %lukHz\n",
 135                 clk_get_rate(armclk) / 1000);
 136
 137        return 0;
 138
 139err_clk:
 140        if (clk_set_rate(armclk, freqs.old * 1000) < 0)
 141                pr_err("Failed to restore original clock rate\n");
 142err:
 143        cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE);
 144
 145        return ret;
 146}
 147
 148#ifdef CONFIG_REGULATOR
 149static void __init s3c64xx_cpufreq_config_regulator(void)
 150{
 151        int count, v, i, found;
 152        struct cpufreq_frequency_table *freq;
 153        struct s3c64xx_dvfs *dvfs;
 154
 155        count = regulator_count_voltages(vddarm);
 156        if (count < 0) {
 157                pr_err("Unable to check supported voltages\n");
 158        }
 159
 160        freq = s3c64xx_freq_table;
 161        while (count > 0 && freq->frequency != CPUFREQ_TABLE_END) {
 162                if (freq->frequency == CPUFREQ_ENTRY_INVALID)
 163                        continue;
 164
 165                dvfs = &s3c64xx_dvfs_table[freq->index];
 166                found = 0;
 167
 168                for (i = 0; i < count; i++) {
 169                        v = regulator_list_voltage(vddarm, i);
 170                        if (v >= dvfs->vddarm_min && v <= dvfs->vddarm_max)
 171                                found = 1;
 172                }
 173
 174                if (!found) {
 175                        pr_debug("%dkHz unsupported by regulator\n",
 176                                 freq->frequency);
 177                        freq->frequency = CPUFREQ_ENTRY_INVALID;
 178                }
 179
 180                freq++;
 181        }
 182
 183        /* Guess based on having to do an I2C/SPI write; in future we
 184         * will be able to query the regulator performance here. */
 185        regulator_latency = 1 * 1000 * 1000;
 186}
 187#endif
 188
 189static int s3c64xx_cpufreq_driver_init(struct cpufreq_policy *policy)
 190{
 191        int ret;
 192        struct cpufreq_frequency_table *freq;
 193
 194        if (policy->cpu != 0)
 195                return -EINVAL;
 196
 197        if (s3c64xx_freq_table == NULL) {
 198                pr_err("No frequency information for this CPU\n");
 199                return -ENODEV;
 200        }
 201
 202        armclk = clk_get(NULL, "armclk");
 203        if (IS_ERR(armclk)) {
 204                pr_err("Unable to obtain ARMCLK: %ld\n",
 205                       PTR_ERR(armclk));
 206                return PTR_ERR(armclk);
 207        }
 208
 209#ifdef CONFIG_REGULATOR
 210        vddarm = regulator_get(NULL, "vddarm");
 211        if (IS_ERR(vddarm)) {
 212                ret = PTR_ERR(vddarm);
 213                pr_err("Failed to obtain VDDARM: %d\n", ret);
 214                pr_err("Only frequency scaling available\n");
 215                vddarm = NULL;
 216        } else {
 217                s3c64xx_cpufreq_config_regulator();
 218        }
 219#endif
 220
 221        freq = s3c64xx_freq_table;
 222        while (freq->frequency != CPUFREQ_TABLE_END) {
 223                unsigned long r;
 224
 225                /* Check for frequencies we can generate */
 226                r = clk_round_rate(armclk, freq->frequency * 1000);
 227                r /= 1000;
 228                if (r != freq->frequency) {
 229                        pr_debug("%dkHz unsupported by clock\n",
 230                                 freq->frequency);
 231                        freq->frequency = CPUFREQ_ENTRY_INVALID;
 232                }
 233
 234                /* If we have no regulator then assume startup
 235                 * frequency is the maximum we can support. */
 236                if (!vddarm && freq->frequency > s3c64xx_cpufreq_get_speed(0))
 237                        freq->frequency = CPUFREQ_ENTRY_INVALID;
 238
 239                freq++;
 240        }
 241
 242        policy->cur = clk_get_rate(armclk) / 1000;
 243
 244        /* Datasheet says PLL stabalisation time (if we were to use
 245         * the PLLs, which we don't currently) is ~300us worst case,
 246         * but add some fudge.
 247         */
 248        policy->cpuinfo.transition_latency = (500 * 1000) + regulator_latency;
 249
 250        ret = cpufreq_frequency_table_cpuinfo(policy, s3c64xx_freq_table);
 251        if (ret != 0) {
 252                pr_err("Failed to configure frequency table: %d\n",
 253                       ret);
 254                regulator_put(vddarm);
 255                clk_put(armclk);
 256        }
 257
 258        return ret;
 259}
 260
 261static struct cpufreq_driver s3c64xx_cpufreq_driver = {
 262        .owner          = THIS_MODULE,
 263        .flags          = 0,
 264        .verify         = s3c64xx_cpufreq_verify_speed,
 265        .target         = s3c64xx_cpufreq_set_target,
 266        .get            = s3c64xx_cpufreq_get_speed,
 267        .init           = s3c64xx_cpufreq_driver_init,
 268        .name           = "s3c",
 269};
 270
 271static int __init s3c64xx_cpufreq_init(void)
 272{
 273        return cpufreq_register_driver(&s3c64xx_cpufreq_driver);
 274}
 275module_init(s3c64xx_cpufreq_init);
 276
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.