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