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 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        struct cpufreq_policy *policy;
 311
 312        mutex_lock(&cpufreq_lock);
 313
 314        /* disable further changes */
 315        s3c_freq->disable_dvs = 1;
 316
 317        mutex_unlock(&cpufreq_lock);
 318
 319        /* some boards don't reconfigure the regulator on reboot, which
 320         * could lead to undervolting the cpu when the clock is reset.
 321         * Therefore we always leave the DVS mode on reboot.
 322         */
 323        if (s3c_freq->is_dvs) {
 324                pr_debug("cpufreq: leave dvs on reboot\n");
 325
 326                policy = cpufreq_cpu_get(0);
 327                if (!policy) {
 328                        pr_debug("cpufreq: get no policy for cpu0\n");
 329                        return NOTIFY_BAD;
 330                }
 331
 332                ret = cpufreq_driver_target(policy, FREQ_SLEEP, 0);
 333                cpufreq_cpu_put(policy);
 334
 335                if (ret < 0)
 336                        return NOTIFY_BAD;
 337        }
 338
 339        return NOTIFY_DONE;
 340}
 341
 342static struct notifier_block s3c2416_cpufreq_reboot_notifier = {
 343        .notifier_call = s3c2416_cpufreq_reboot_notifier_evt,
 344};
 345
 346static int s3c2416_cpufreq_driver_init(struct cpufreq_policy *policy)
 347{
 348        struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
 349        struct cpufreq_frequency_table *pos;
 350        struct clk *msysclk;
 351        unsigned long rate;
 352        int ret;
 353
 354        if (policy->cpu != 0)
 355                return -EINVAL;
 356
 357        msysclk = clk_get(NULL, "msysclk");
 358        if (IS_ERR(msysclk)) {
 359                ret = PTR_ERR(msysclk);
 360                pr_err("cpufreq: Unable to obtain msysclk: %d\n", ret);
 361                return ret;
 362        }
 363
 364        /*
 365         * S3C2416 and S3C2450 share the same processor-ID and also provide no
 366         * other means to distinguish them other than through the rate of
 367         * msysclk. On S3C2416 msysclk runs at 800MHz and on S3C2450 at 533MHz.
 368         */
 369        rate = clk_get_rate(msysclk);
 370        if (rate == 800 * 1000 * 1000) {
 371                pr_info("cpufreq: msysclk running at %lukHz, using S3C2416 frequency table\n",
 372                        rate / 1000);
 373                s3c_freq->freq_table = s3c2416_freq_table;
 374                policy->cpuinfo.max_freq = 400000;
 375        } else if (rate / 1000 == 534000) {
 376                pr_info("cpufreq: msysclk running at %lukHz, using S3C2450 frequency table\n",
 377                        rate / 1000);
 378                s3c_freq->freq_table = s3c2450_freq_table;
 379                policy->cpuinfo.max_freq = 534000;
 380        }
 381
 382        /* not needed anymore */
 383        clk_put(msysclk);
 384
 385        if (s3c_freq->freq_table == NULL) {
 386                pr_err("cpufreq: No frequency information for this CPU, msysclk at %lukHz\n",
 387                       rate / 1000);
 388                return -ENODEV;
 389        }
 390
 391        s3c_freq->is_dvs = 0;
 392
 393        s3c_freq->armdiv = clk_get(NULL, "armdiv");
 394        if (IS_ERR(s3c_freq->armdiv)) {
 395                ret = PTR_ERR(s3c_freq->armdiv);
 396                pr_err("cpufreq: Unable to obtain ARMDIV: %d\n", ret);
 397                return ret;
 398        }
 399
 400        s3c_freq->hclk = clk_get(NULL, "hclk");
 401        if (IS_ERR(s3c_freq->hclk)) {
 402                ret = PTR_ERR(s3c_freq->hclk);
 403                pr_err("cpufreq: Unable to obtain HCLK: %d\n", ret);
 404                goto err_hclk;
 405        }
 406
 407        /* chech hclk rate, we only support the common 133MHz for now
 408         * hclk could also run at 66MHz, but this not often used
 409         */
 410        rate = clk_get_rate(s3c_freq->hclk);
 411        if (rate < 133 * 1000 * 1000) {
 412                pr_err("cpufreq: HCLK not at 133MHz\n");
 413                ret = -EINVAL;
 414                goto err_armclk;
 415        }
 416
 417        s3c_freq->armclk = clk_get(NULL, "armclk");
 418        if (IS_ERR(s3c_freq->armclk)) {
 419                ret = PTR_ERR(s3c_freq->armclk);
 420                pr_err("cpufreq: Unable to obtain ARMCLK: %d\n", ret);
 421                goto err_armclk;
 422        }
 423
 424#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
 425        s3c_freq->vddarm = regulator_get(NULL, "vddarm");
 426        if (IS_ERR(s3c_freq->vddarm)) {
 427                ret = PTR_ERR(s3c_freq->vddarm);
 428                pr_err("cpufreq: Failed to obtain VDDARM: %d\n", ret);
 429                goto err_vddarm;
 430        }
 431
 432        s3c2416_cpufreq_cfg_regulator(s3c_freq);
 433#else
 434        s3c_freq->regulator_latency = 0;
 435#endif
 436
 437        cpufreq_for_each_entry(pos, s3c_freq->freq_table) {
 438                /* special handling for dvs mode */
 439                if (pos->driver_data == 0) {
 440                        if (!s3c_freq->hclk) {
 441                                pr_debug("cpufreq: %dkHz unsupported as it would need unavailable dvs mode\n",
 442                                         pos->frequency);
 443                                pos->frequency = CPUFREQ_ENTRY_INVALID;
 444                        } else {
 445                                continue;
 446                        }
 447                }
 448
 449                /* Check for frequencies we can generate */
 450                rate = clk_round_rate(s3c_freq->armdiv,
 451                                      pos->frequency * 1000);
 452                rate /= 1000;
 453                if (rate != pos->frequency) {
 454                        pr_debug("cpufreq: %dkHz unsupported by clock (clk_round_rate return %lu)\n",
 455                                pos->frequency, rate);
 456                        pos->frequency = CPUFREQ_ENTRY_INVALID;
 457                }
 458        }
 459
 460        /* Datasheet says PLL stabalisation time must be at least 300us,
 461         * so but add some fudge. (reference in LOCKCON0 register description)
 462         */
 463        cpufreq_generic_init(policy, s3c_freq->freq_table,
 464                        (500 * 1000) + s3c_freq->regulator_latency);
 465        register_reboot_notifier(&s3c2416_cpufreq_reboot_notifier);
 466
 467        return 0;
 468
 469#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
 470err_vddarm:
 471        clk_put(s3c_freq->armclk);
 472#endif
 473err_armclk:
 474        clk_put(s3c_freq->hclk);
 475err_hclk:
 476        clk_put(s3c_freq->armdiv);
 477
 478        return ret;
 479}
 480
 481static struct cpufreq_driver s3c2416_cpufreq_driver = {
 482        .flags          = CPUFREQ_NEED_INITIAL_FREQ_CHECK,
 483        .verify         = cpufreq_generic_frequency_table_verify,
 484        .target_index   = s3c2416_cpufreq_set_target,
 485        .get            = s3c2416_cpufreq_get_speed,
 486        .init           = s3c2416_cpufreq_driver_init,
 487        .name           = "s3c2416",
 488        .attr           = cpufreq_generic_attr,
 489};
 490
 491static int __init s3c2416_cpufreq_init(void)
 492{
 493        return cpufreq_register_driver(&s3c2416_cpufreq_driver);
 494}
 495module_init(s3c2416_cpufreq_init);
 496