linux/drivers/cpufreq/s3c2416-cpufreq.c
<<
>>
Prefs
   1/*
   2 * S3C2416/2450 CPUfreq Support
   3 *
   4 * Copyright 2011 Heiko Stuebner <heiko@sntech.de>
   5 *
   6 * based on s3c64xx_cpufreq.c
   7 *
   8 * Copyright 2009 Wolfson Microelectronics plc
   9 *
  10 * This program is free software; you can redistribute it and/or modify
  11 * it under the terms of the GNU General Public License version 2 as
  12 * published by the Free Software Foundation.
  13 */
  14
  15#include <linux/kernel.h>
  16#include <linux/types.h>
  17#include <linux/init.h>
  18#include <linux/cpufreq.h>
  19#include <linux/clk.h>
  20#include <linux/err.h>
  21#include <linux/regulator/consumer.h>
  22#include <linux/reboot.h>
  23#include <linux/module.h>
  24
  25static DEFINE_MUTEX(cpufreq_lock);
  26
  27struct s3c2416_data {
  28        struct clk *armdiv;
  29        struct clk *armclk;
  30        struct clk *hclk;
  31
  32        unsigned long regulator_latency;
  33#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
  34        struct regulator *vddarm;
  35#endif
  36
  37        struct cpufreq_frequency_table *freq_table;
  38
  39        bool is_dvs;
  40        bool disable_dvs;
  41};
  42
  43static struct s3c2416_data s3c2416_cpufreq;
  44
  45struct s3c2416_dvfs {
  46        unsigned int vddarm_min;
  47        unsigned int vddarm_max;
  48};
  49
  50/* pseudo-frequency for dvs mode */
  51#define FREQ_DVS        132333
  52
  53/* frequency to sleep and reboot in
  54 * it's essential to leave dvs, as some boards do not reconfigure the
  55 * regulator on reboot
  56 */
  57#define FREQ_SLEEP      133333
  58
  59/* Sources for the ARMCLK */
  60#define SOURCE_HCLK     0
  61#define SOURCE_ARMDIV   1
  62
  63#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
  64/* S3C2416 only supports changing the voltage in the dvs-mode.
  65 * Voltages down to 1.0V seem to work, so we take what the regulator
  66 * can get us.
  67 */
  68static struct s3c2416_dvfs s3c2416_dvfs_table[] = {
  69        [SOURCE_HCLK] = {  950000, 1250000 },
  70        [SOURCE_ARMDIV] = { 1250000, 1350000 },
  71};
  72#endif
  73
  74static struct cpufreq_frequency_table s3c2416_freq_table[] = {
  75        { 0, SOURCE_HCLK, FREQ_DVS },
  76        { 0, SOURCE_ARMDIV, 133333 },
  77        { 0, SOURCE_ARMDIV, 266666 },
  78        { 0, SOURCE_ARMDIV, 400000 },
  79        { 0, 0, CPUFREQ_TABLE_END },
  80};
  81
  82static struct cpufreq_frequency_table s3c2450_freq_table[] = {
  83        { 0, SOURCE_HCLK, FREQ_DVS },
  84        { 0, SOURCE_ARMDIV, 133500 },
  85        { 0, SOURCE_ARMDIV, 267000 },
  86        { 0, SOURCE_ARMDIV, 534000 },
  87        { 0, 0, CPUFREQ_TABLE_END },
  88};
  89
  90static unsigned int s3c2416_cpufreq_get_speed(unsigned int cpu)
  91{
  92        struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
  93
  94        if (cpu != 0)
  95                return 0;
  96
  97        /* return our pseudo-frequency when in dvs mode */
  98        if (s3c_freq->is_dvs)
  99                return FREQ_DVS;
 100
 101        return clk_get_rate(s3c_freq->armclk) / 1000;
 102}
 103
 104static int s3c2416_cpufreq_set_armdiv(struct s3c2416_data *s3c_freq,
 105                                      unsigned int freq)
 106{
 107        int ret;
 108
 109        if (clk_get_rate(s3c_freq->armdiv) / 1000 != freq) {
 110                ret = clk_set_rate(s3c_freq->armdiv, freq * 1000);
 111                if (ret < 0) {
 112                        pr_err("cpufreq: Failed to set armdiv rate %dkHz: %d\n",
 113                               freq, ret);
 114                        return ret;
 115                }
 116        }
 117
 118        return 0;
 119}
 120
 121static int s3c2416_cpufreq_enter_dvs(struct s3c2416_data *s3c_freq, int idx)
 122{
 123#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
 124        struct s3c2416_dvfs *dvfs;
 125#endif
 126        int ret;
 127
 128        if (s3c_freq->is_dvs) {
 129                pr_debug("cpufreq: already in dvs mode, nothing to do\n");
 130                return 0;
 131        }
 132
 133        pr_debug("cpufreq: switching armclk to hclk (%lukHz)\n",
 134                 clk_get_rate(s3c_freq->hclk) / 1000);
 135        ret = clk_set_parent(s3c_freq->armclk, s3c_freq->hclk);
 136        if (ret < 0) {
 137                pr_err("cpufreq: Failed to switch armclk to hclk: %d\n", ret);
 138                return ret;
 139        }
 140
 141#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
 142        /* changing the core voltage is only allowed when in dvs mode */
 143        if (s3c_freq->vddarm) {
 144                dvfs = &s3c2416_dvfs_table[idx];
 145
 146                pr_debug("cpufreq: setting regulator to %d-%d\n",
 147                         dvfs->vddarm_min, dvfs->vddarm_max);
 148                ret = regulator_set_voltage(s3c_freq->vddarm,
 149                                            dvfs->vddarm_min,
 150                                            dvfs->vddarm_max);
 151
 152                /* when lowering the voltage failed, there is nothing to do */
 153                if (ret != 0)
 154                        pr_err("cpufreq: Failed to set VDDARM: %d\n", ret);
 155        }
 156#endif
 157
 158        s3c_freq->is_dvs = 1;
 159
 160        return 0;
 161}
 162
 163static int s3c2416_cpufreq_leave_dvs(struct s3c2416_data *s3c_freq, int idx)
 164{
 165#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
 166        struct s3c2416_dvfs *dvfs;
 167#endif
 168        int ret;
 169
 170        if (!s3c_freq->is_dvs) {
 171                pr_debug("cpufreq: not in dvs mode, so can't leave\n");
 172                return 0;
 173        }
 174
 175#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
 176        if (s3c_freq->vddarm) {
 177                dvfs = &s3c2416_dvfs_table[idx];
 178
 179                pr_debug("cpufreq: setting regulator to %d-%d\n",
 180                         dvfs->vddarm_min, dvfs->vddarm_max);
 181                ret = regulator_set_voltage(s3c_freq->vddarm,
 182                                            dvfs->vddarm_min,
 183                                            dvfs->vddarm_max);
 184                if (ret != 0) {
 185                        pr_err("cpufreq: Failed to set VDDARM: %d\n", ret);
 186                        return ret;
 187                }
 188        }
 189#endif
 190
 191        /* force armdiv to hclk frequency for transition from dvs*/
 192        if (clk_get_rate(s3c_freq->armdiv) > clk_get_rate(s3c_freq->hclk)) {
 193                pr_debug("cpufreq: force armdiv to hclk frequency (%lukHz)\n",
 194                         clk_get_rate(s3c_freq->hclk) / 1000);
 195                ret = s3c2416_cpufreq_set_armdiv(s3c_freq,
 196                                        clk_get_rate(s3c_freq->hclk) / 1000);
 197                if (ret < 0) {
 198                        pr_err("cpufreq: Failed to set the armdiv to %lukHz: %d\n",
 199                               clk_get_rate(s3c_freq->hclk) / 1000, ret);
 200                        return ret;
 201                }
 202        }
 203
 204        pr_debug("cpufreq: switching armclk parent to armdiv (%lukHz)\n",
 205                        clk_get_rate(s3c_freq->armdiv) / 1000);
 206
 207        ret = clk_set_parent(s3c_freq->armclk, s3c_freq->armdiv);
 208        if (ret < 0) {
 209                pr_err("cpufreq: Failed to switch armclk clock parent to armdiv: %d\n",
 210                       ret);
 211                return ret;
 212        }
 213
 214        s3c_freq->is_dvs = 0;
 215
 216        return 0;
 217}
 218
 219static int s3c2416_cpufreq_set_target(struct cpufreq_policy *policy,
 220                                      unsigned int index)
 221{
 222        struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
 223        unsigned int new_freq;
 224        int idx, ret, to_dvs = 0;
 225
 226        mutex_lock(&cpufreq_lock);
 227
 228        idx = s3c_freq->freq_table[index].driver_data;
 229
 230        if (idx == SOURCE_HCLK)
 231                to_dvs = 1;
 232
 233        /* switching to dvs when it's not allowed */
 234        if (to_dvs && s3c_freq->disable_dvs) {
 235                pr_debug("cpufreq: entering dvs mode not allowed\n");
 236                ret = -EINVAL;
 237                goto out;
 238        }
 239
 240        /* When leavin dvs mode, always switch the armdiv to the hclk rate
 241         * The S3C2416 has stability issues when switching directly to
 242         * higher frequencies.
 243         */
 244        new_freq = (s3c_freq->is_dvs && !to_dvs)
 245                                ? clk_get_rate(s3c_freq->hclk) / 1000
 246                                : s3c_freq->freq_table[index].frequency;
 247
 248        if (to_dvs) {
 249                pr_debug("cpufreq: enter dvs\n");
 250                ret = s3c2416_cpufreq_enter_dvs(s3c_freq, idx);
 251        } else if (s3c_freq->is_dvs) {
 252                pr_debug("cpufreq: leave dvs\n");
 253                ret = s3c2416_cpufreq_leave_dvs(s3c_freq, idx);
 254        } else {
 255                pr_debug("cpufreq: change armdiv to %dkHz\n", new_freq);
 256                ret = s3c2416_cpufreq_set_armdiv(s3c_freq, new_freq);
 257        }
 258
 259out:
 260        mutex_unlock(&cpufreq_lock);
 261
 262        return ret;
 263}
 264
 265#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
 266static void __init s3c2416_cpufreq_cfg_regulator(struct s3c2416_data *s3c_freq)
 267{
 268        int count, v, i, found;
 269        struct cpufreq_frequency_table *pos;
 270        struct s3c2416_dvfs *dvfs;
 271
 272        count = regulator_count_voltages(s3c_freq->vddarm);
 273        if (count < 0) {
 274                pr_err("cpufreq: Unable to check supported voltages\n");
 275                return;
 276        }
 277
 278        if (!count)
 279                goto out;
 280
 281        cpufreq_for_each_valid_entry(pos, s3c_freq->freq_table) {
 282                dvfs = &s3c2416_dvfs_table[pos->driver_data];
 283                found = 0;
 284
 285                /* Check only the min-voltage, more is always ok on S3C2416 */
 286                for (i = 0; i < count; i++) {
 287                        v = regulator_list_voltage(s3c_freq->vddarm, i);
 288                        if (v >= dvfs->vddarm_min)
 289                                found = 1;
 290                }
 291
 292                if (!found) {
 293                        pr_debug("cpufreq: %dkHz unsupported by regulator\n",
 294                                 pos->frequency);
 295                        pos->frequency = CPUFREQ_ENTRY_INVALID;
 296                }
 297        }
 298
 299out:
 300        /* Guessed */
 301        s3c_freq->regulator_latency = 1 * 1000 * 1000;
 302}
 303#endif
 304
 305static int s3c2416_cpufreq_reboot_notifier_evt(struct notifier_block *this,
 306                                               unsigned long event, void *ptr)
 307{
 308        struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
 309        int ret;
 310
 311        mutex_lock(&cpufreq_lock);
 312
 313        /* disable further changes */
 314        s3c_freq->disable_dvs = 1;
 315
 316        mutex_unlock(&cpufreq_lock);
 317
 318        /* some boards don't reconfigure the regulator on reboot, which
 319         * could lead to undervolting the cpu when the clock is reset.
 320         * Therefore we always leave the DVS mode on reboot.
 321         */
 322        if (s3c_freq->is_dvs) {
 323                pr_debug("cpufreq: leave dvs on reboot\n");
 324                ret = cpufreq_driver_target(cpufreq_cpu_get(0), FREQ_SLEEP, 0);
 325                if (ret < 0)
 326                        return NOTIFY_BAD;
 327        }
 328
 329        return NOTIFY_DONE;
 330}
 331
 332static struct notifier_block s3c2416_cpufreq_reboot_notifier = {
 333        .notifier_call = s3c2416_cpufreq_reboot_notifier_evt,
 334};
 335
 336static int __init s3c2416_cpufreq_driver_init(struct cpufreq_policy *policy)
 337{
 338        struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
 339        struct cpufreq_frequency_table *pos;
 340        struct clk *msysclk;
 341        unsigned long rate;
 342        int ret;
 343
 344        if (policy->cpu != 0)
 345                return -EINVAL;
 346
 347        msysclk = clk_get(NULL, "msysclk");
 348        if (IS_ERR(msysclk)) {
 349                ret = PTR_ERR(msysclk);
 350                pr_err("cpufreq: Unable to obtain msysclk: %d\n", ret);
 351                return ret;
 352        }
 353
 354        /*
 355         * S3C2416 and S3C2450 share the same processor-ID and also provide no
 356         * other means to distinguish them other than through the rate of
 357         * msysclk. On S3C2416 msysclk runs at 800MHz and on S3C2450 at 533MHz.
 358         */
 359        rate = clk_get_rate(msysclk);
 360        if (rate == 800 * 1000 * 1000) {
 361                pr_info("cpufreq: msysclk running at %lukHz, using S3C2416 frequency table\n",
 362                        rate / 1000);
 363                s3c_freq->freq_table = s3c2416_freq_table;
 364                policy->cpuinfo.max_freq = 400000;
 365        } else if (rate / 1000 == 534000) {
 366                pr_info("cpufreq: msysclk running at %lukHz, using S3C2450 frequency table\n",
 367                        rate / 1000);
 368                s3c_freq->freq_table = s3c2450_freq_table;
 369                policy->cpuinfo.max_freq = 534000;
 370        }
 371
 372        /* not needed anymore */
 373        clk_put(msysclk);
 374
 375        if (s3c_freq->freq_table == NULL) {
 376                pr_err("cpufreq: No frequency information for this CPU, msysclk at %lukHz\n",
 377                       rate / 1000);
 378                return -ENODEV;
 379        }
 380
 381        s3c_freq->is_dvs = 0;
 382
 383        s3c_freq->armdiv = clk_get(NULL, "armdiv");
 384        if (IS_ERR(s3c_freq->armdiv)) {
 385                ret = PTR_ERR(s3c_freq->armdiv);
 386                pr_err("cpufreq: Unable to obtain ARMDIV: %d\n", ret);
 387                return ret;
 388        }
 389
 390        s3c_freq->hclk = clk_get(NULL, "hclk");
 391        if (IS_ERR(s3c_freq->hclk)) {
 392                ret = PTR_ERR(s3c_freq->hclk);
 393                pr_err("cpufreq: Unable to obtain HCLK: %d\n", ret);
 394                goto err_hclk;
 395        }
 396
 397        /* chech hclk rate, we only support the common 133MHz for now
 398         * hclk could also run at 66MHz, but this not often used
 399         */
 400        rate = clk_get_rate(s3c_freq->hclk);
 401        if (rate < 133 * 1000 * 1000) {
 402                pr_err("cpufreq: HCLK not at 133MHz\n");
 403                clk_put(s3c_freq->hclk);
 404                ret = -EINVAL;
 405                goto err_armclk;
 406        }
 407
 408        s3c_freq->armclk = clk_get(NULL, "armclk");
 409        if (IS_ERR(s3c_freq->armclk)) {
 410                ret = PTR_ERR(s3c_freq->armclk);
 411                pr_err("cpufreq: Unable to obtain ARMCLK: %d\n", ret);
 412                goto err_armclk;
 413        }
 414
 415#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
 416        s3c_freq->vddarm = regulator_get(NULL, "vddarm");
 417        if (IS_ERR(s3c_freq->vddarm)) {
 418                ret = PTR_ERR(s3c_freq->vddarm);
 419                pr_err("cpufreq: Failed to obtain VDDARM: %d\n", ret);
 420                goto err_vddarm;
 421        }
 422
 423        s3c2416_cpufreq_cfg_regulator(s3c_freq);
 424#else
 425        s3c_freq->regulator_latency = 0;
 426#endif
 427
 428        cpufreq_for_each_entry(pos, s3c_freq->freq_table) {
 429                /* special handling for dvs mode */
 430                if (pos->driver_data == 0) {
 431                        if (!s3c_freq->hclk) {
 432                                pr_debug("cpufreq: %dkHz unsupported as it would need unavailable dvs mode\n",
 433                                         pos->frequency);
 434                                pos->frequency = CPUFREQ_ENTRY_INVALID;
 435                        } else {
 436                                continue;
 437                        }
 438                }
 439
 440                /* Check for frequencies we can generate */
 441                rate = clk_round_rate(s3c_freq->armdiv,
 442                                      pos->frequency * 1000);
 443                rate /= 1000;
 444                if (rate != pos->frequency) {
 445                        pr_debug("cpufreq: %dkHz unsupported by clock (clk_round_rate return %lu)\n",
 446                                pos->frequency, rate);
 447                        pos->frequency = CPUFREQ_ENTRY_INVALID;
 448                }
 449        }
 450
 451        /* Datasheet says PLL stabalisation time must be at least 300us,
 452         * so but add some fudge. (reference in LOCKCON0 register description)
 453         */
 454        ret = cpufreq_generic_init(policy, s3c_freq->freq_table,
 455                        (500 * 1000) + s3c_freq->regulator_latency);
 456        if (ret)
 457                goto err_freq_table;
 458
 459        register_reboot_notifier(&s3c2416_cpufreq_reboot_notifier);
 460
 461        return 0;
 462
 463err_freq_table:
 464#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
 465        regulator_put(s3c_freq->vddarm);
 466err_vddarm:
 467#endif
 468        clk_put(s3c_freq->armclk);
 469err_armclk:
 470        clk_put(s3c_freq->hclk);
 471err_hclk:
 472        clk_put(s3c_freq->armdiv);
 473
 474        return ret;
 475}
 476
 477static struct cpufreq_driver s3c2416_cpufreq_driver = {
 478        .flags          = CPUFREQ_NEED_INITIAL_FREQ_CHECK,
 479        .verify         = cpufreq_generic_frequency_table_verify,
 480        .target_index   = s3c2416_cpufreq_set_target,
 481        .get            = s3c2416_cpufreq_get_speed,
 482        .init           = s3c2416_cpufreq_driver_init,
 483        .name           = "s3c2416",
 484        .attr           = cpufreq_generic_attr,
 485};
 486
 487static int __init s3c2416_cpufreq_init(void)
 488{
 489        return cpufreq_register_driver(&s3c2416_cpufreq_driver);
 490}
 491module_init(s3c2416_cpufreq_init);
 492