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        { SOURCE_HCLK, FREQ_DVS },
  76        { SOURCE_ARMDIV, 133333 },
  77        { SOURCE_ARMDIV, 266666 },
  78        { SOURCE_ARMDIV, 400000 },
  79        { 0, CPUFREQ_TABLE_END },
  80};
  81
  82static struct cpufreq_frequency_table s3c2450_freq_table[] = {
  83        { SOURCE_HCLK, FREQ_DVS },
  84        { SOURCE_ARMDIV, 133500 },
  85        { SOURCE_ARMDIV, 267000 },
  86        { SOURCE_ARMDIV, 534000 },
  87        { 0, CPUFREQ_TABLE_END },
  88};
  89
  90static int s3c2416_cpufreq_verify_speed(struct cpufreq_policy *policy)
  91{
  92        struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
  93
  94        if (policy->cpu != 0)
  95                return -EINVAL;
  96
  97        return cpufreq_frequency_table_verify(policy, s3c_freq->freq_table);
  98}
  99
 100static unsigned int s3c2416_cpufreq_get_speed(unsigned int cpu)
 101{
 102        struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
 103
 104        if (cpu != 0)
 105                return 0;
 106
 107        /* return our pseudo-frequency when in dvs mode */
 108        if (s3c_freq->is_dvs)
 109                return FREQ_DVS;
 110
 111        return clk_get_rate(s3c_freq->armclk) / 1000;
 112}
 113
 114static int s3c2416_cpufreq_set_armdiv(struct s3c2416_data *s3c_freq,
 115                                      unsigned int freq)
 116{
 117        int ret;
 118
 119        if (clk_get_rate(s3c_freq->armdiv) / 1000 != freq) {
 120                ret = clk_set_rate(s3c_freq->armdiv, freq * 1000);
 121                if (ret < 0) {
 122                        pr_err("cpufreq: Failed to set armdiv rate %dkHz: %d\n",
 123                               freq, ret);
 124                        return ret;
 125                }
 126        }
 127
 128        return 0;
 129}
 130
 131static int s3c2416_cpufreq_enter_dvs(struct s3c2416_data *s3c_freq, int idx)
 132{
 133#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
 134        struct s3c2416_dvfs *dvfs;
 135#endif
 136        int ret;
 137
 138        if (s3c_freq->is_dvs) {
 139                pr_debug("cpufreq: already in dvs mode, nothing to do\n");
 140                return 0;
 141        }
 142
 143        pr_debug("cpufreq: switching armclk to hclk (%lukHz)\n",
 144                 clk_get_rate(s3c_freq->hclk) / 1000);
 145        ret = clk_set_parent(s3c_freq->armclk, s3c_freq->hclk);
 146        if (ret < 0) {
 147                pr_err("cpufreq: Failed to switch armclk to hclk: %d\n", ret);
 148                return ret;
 149        }
 150
 151#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
 152        /* changing the core voltage is only allowed when in dvs mode */
 153        if (s3c_freq->vddarm) {
 154                dvfs = &s3c2416_dvfs_table[idx];
 155
 156                pr_debug("cpufreq: setting regulator to %d-%d\n",
 157                         dvfs->vddarm_min, dvfs->vddarm_max);
 158                ret = regulator_set_voltage(s3c_freq->vddarm,
 159                                            dvfs->vddarm_min,
 160                                            dvfs->vddarm_max);
 161
 162                /* when lowering the voltage failed, there is nothing to do */
 163                if (ret != 0)
 164                        pr_err("cpufreq: Failed to set VDDARM: %d\n", ret);
 165        }
 166#endif
 167
 168        s3c_freq->is_dvs = 1;
 169
 170        return 0;
 171}
 172
 173static int s3c2416_cpufreq_leave_dvs(struct s3c2416_data *s3c_freq, int idx)
 174{
 175#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
 176        struct s3c2416_dvfs *dvfs;
 177#endif
 178        int ret;
 179
 180        if (!s3c_freq->is_dvs) {
 181                pr_debug("cpufreq: not in dvs mode, so can't leave\n");
 182                return 0;
 183        }
 184
 185#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
 186        if (s3c_freq->vddarm) {
 187                dvfs = &s3c2416_dvfs_table[idx];
 188
 189                pr_debug("cpufreq: setting regulator to %d-%d\n",
 190                         dvfs->vddarm_min, dvfs->vddarm_max);
 191                ret = regulator_set_voltage(s3c_freq->vddarm,
 192                                            dvfs->vddarm_min,
 193                                            dvfs->vddarm_max);
 194                if (ret != 0) {
 195                        pr_err("cpufreq: Failed to set VDDARM: %d\n", ret);
 196                        return ret;
 197                }
 198        }
 199#endif
 200
 201        /* force armdiv to hclk frequency for transition from dvs*/
 202        if (clk_get_rate(s3c_freq->armdiv) > clk_get_rate(s3c_freq->hclk)) {
 203                pr_debug("cpufreq: force armdiv to hclk frequency (%lukHz)\n",
 204                         clk_get_rate(s3c_freq->hclk) / 1000);
 205                ret = s3c2416_cpufreq_set_armdiv(s3c_freq,
 206                                        clk_get_rate(s3c_freq->hclk) / 1000);
 207                if (ret < 0) {
 208                        pr_err("cpufreq: Failed to set the armdiv to %lukHz: %d\n",
 209                               clk_get_rate(s3c_freq->hclk) / 1000, ret);
 210                        return ret;
 211                }
 212        }
 213
 214        pr_debug("cpufreq: switching armclk parent to armdiv (%lukHz)\n",
 215                        clk_get_rate(s3c_freq->armdiv) / 1000);
 216
 217        ret = clk_set_parent(s3c_freq->armclk, s3c_freq->armdiv);
 218        if (ret < 0) {
 219                pr_err("cpufreq: Failed to switch armclk clock parent to armdiv: %d\n",
 220                       ret);
 221                return ret;
 222        }
 223
 224        s3c_freq->is_dvs = 0;
 225
 226        return 0;
 227}
 228
 229static int s3c2416_cpufreq_set_target(struct cpufreq_policy *policy,
 230                                      unsigned int target_freq,
 231                                      unsigned int relation)
 232{
 233        struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
 234        struct cpufreq_freqs freqs;
 235        int idx, ret, to_dvs = 0;
 236        unsigned int i;
 237
 238        mutex_lock(&cpufreq_lock);
 239
 240        pr_debug("cpufreq: to %dKHz, relation %d\n", target_freq, relation);
 241
 242        ret = cpufreq_frequency_table_target(policy, s3c_freq->freq_table,
 243                                             target_freq, relation, &i);
 244        if (ret != 0)
 245                goto out;
 246
 247        idx = s3c_freq->freq_table[i].driver_data;
 248
 249        if (idx == SOURCE_HCLK)
 250                to_dvs = 1;
 251
 252        /* switching to dvs when it's not allowed */
 253        if (to_dvs && s3c_freq->disable_dvs) {
 254                pr_debug("cpufreq: entering dvs mode not allowed\n");
 255                ret = -EINVAL;
 256                goto out;
 257        }
 258
 259        freqs.flags = 0;
 260        freqs.old = s3c_freq->is_dvs ? FREQ_DVS
 261                                     : clk_get_rate(s3c_freq->armclk) / 1000;
 262
 263        /* When leavin dvs mode, always switch the armdiv to the hclk rate
 264         * The S3C2416 has stability issues when switching directly to
 265         * higher frequencies.
 266         */
 267        freqs.new = (s3c_freq->is_dvs && !to_dvs)
 268                                ? clk_get_rate(s3c_freq->hclk) / 1000
 269                                : s3c_freq->freq_table[i].frequency;
 270
 271        pr_debug("cpufreq: Transition %d-%dkHz\n", freqs.old, freqs.new);
 272
 273        if (!to_dvs && freqs.old == freqs.new)
 274                goto out;
 275
 276        cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE);
 277
 278        if (to_dvs) {
 279                pr_debug("cpufreq: enter dvs\n");
 280                ret = s3c2416_cpufreq_enter_dvs(s3c_freq, idx);
 281        } else if (s3c_freq->is_dvs) {
 282                pr_debug("cpufreq: leave dvs\n");
 283                ret = s3c2416_cpufreq_leave_dvs(s3c_freq, idx);
 284        } else {
 285                pr_debug("cpufreq: change armdiv to %dkHz\n", freqs.new);
 286                ret = s3c2416_cpufreq_set_armdiv(s3c_freq, freqs.new);
 287        }
 288
 289        cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE);
 290
 291out:
 292        mutex_unlock(&cpufreq_lock);
 293
 294        return ret;
 295}
 296
 297#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
 298static void __init s3c2416_cpufreq_cfg_regulator(struct s3c2416_data *s3c_freq)
 299{
 300        int count, v, i, found;
 301        struct cpufreq_frequency_table *freq;
 302        struct s3c2416_dvfs *dvfs;
 303
 304        count = regulator_count_voltages(s3c_freq->vddarm);
 305        if (count < 0) {
 306                pr_err("cpufreq: Unable to check supported voltages\n");
 307                return;
 308        }
 309
 310        freq = s3c_freq->freq_table;
 311        while (count > 0 && freq->frequency != CPUFREQ_TABLE_END) {
 312                if (freq->frequency == CPUFREQ_ENTRY_INVALID)
 313                        continue;
 314
 315                dvfs = &s3c2416_dvfs_table[freq->driver_data];
 316                found = 0;
 317
 318                /* Check only the min-voltage, more is always ok on S3C2416 */
 319                for (i = 0; i < count; i++) {
 320                        v = regulator_list_voltage(s3c_freq->vddarm, i);
 321                        if (v >= dvfs->vddarm_min)
 322                                found = 1;
 323                }
 324
 325                if (!found) {
 326                        pr_debug("cpufreq: %dkHz unsupported by regulator\n",
 327                                 freq->frequency);
 328                        freq->frequency = CPUFREQ_ENTRY_INVALID;
 329                }
 330
 331                freq++;
 332        }
 333
 334        /* Guessed */
 335        s3c_freq->regulator_latency = 1 * 1000 * 1000;
 336}
 337#endif
 338
 339static int s3c2416_cpufreq_reboot_notifier_evt(struct notifier_block *this,
 340                                               unsigned long event, void *ptr)
 341{
 342        struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
 343        int ret;
 344
 345        mutex_lock(&cpufreq_lock);
 346
 347        /* disable further changes */
 348        s3c_freq->disable_dvs = 1;
 349
 350        mutex_unlock(&cpufreq_lock);
 351
 352        /* some boards don't reconfigure the regulator on reboot, which
 353         * could lead to undervolting the cpu when the clock is reset.
 354         * Therefore we always leave the DVS mode on reboot.
 355         */
 356        if (s3c_freq->is_dvs) {
 357                pr_debug("cpufreq: leave dvs on reboot\n");
 358                ret = cpufreq_driver_target(cpufreq_cpu_get(0), FREQ_SLEEP, 0);
 359                if (ret < 0)
 360                        return NOTIFY_BAD;
 361        }
 362
 363        return NOTIFY_DONE;
 364}
 365
 366static struct notifier_block s3c2416_cpufreq_reboot_notifier = {
 367        .notifier_call = s3c2416_cpufreq_reboot_notifier_evt,
 368};
 369
 370static int __init s3c2416_cpufreq_driver_init(struct cpufreq_policy *policy)
 371{
 372        struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
 373        struct cpufreq_frequency_table *freq;
 374        struct clk *msysclk;
 375        unsigned long rate;
 376        int ret;
 377
 378        if (policy->cpu != 0)
 379                return -EINVAL;
 380
 381        msysclk = clk_get(NULL, "msysclk");
 382        if (IS_ERR(msysclk)) {
 383                ret = PTR_ERR(msysclk);
 384                pr_err("cpufreq: Unable to obtain msysclk: %d\n", ret);
 385                return ret;
 386        }
 387
 388        /*
 389         * S3C2416 and S3C2450 share the same processor-ID and also provide no
 390         * other means to distinguish them other than through the rate of
 391         * msysclk. On S3C2416 msysclk runs at 800MHz and on S3C2450 at 533MHz.
 392         */
 393        rate = clk_get_rate(msysclk);
 394        if (rate == 800 * 1000 * 1000) {
 395                pr_info("cpufreq: msysclk running at %lukHz, using S3C2416 frequency table\n",
 396                        rate / 1000);
 397                s3c_freq->freq_table = s3c2416_freq_table;
 398                policy->cpuinfo.max_freq = 400000;
 399        } else if (rate / 1000 == 534000) {
 400                pr_info("cpufreq: msysclk running at %lukHz, using S3C2450 frequency table\n",
 401                        rate / 1000);
 402                s3c_freq->freq_table = s3c2450_freq_table;
 403                policy->cpuinfo.max_freq = 534000;
 404        }
 405
 406        /* not needed anymore */
 407        clk_put(msysclk);
 408
 409        if (s3c_freq->freq_table == NULL) {
 410                pr_err("cpufreq: No frequency information for this CPU, msysclk at %lukHz\n",
 411                       rate / 1000);
 412                return -ENODEV;
 413        }
 414
 415        s3c_freq->is_dvs = 0;
 416
 417        s3c_freq->armdiv = clk_get(NULL, "armdiv");
 418        if (IS_ERR(s3c_freq->armdiv)) {
 419                ret = PTR_ERR(s3c_freq->armdiv);
 420                pr_err("cpufreq: Unable to obtain ARMDIV: %d\n", ret);
 421                return ret;
 422        }
 423
 424        s3c_freq->hclk = clk_get(NULL, "hclk");
 425        if (IS_ERR(s3c_freq->hclk)) {
 426                ret = PTR_ERR(s3c_freq->hclk);
 427                pr_err("cpufreq: Unable to obtain HCLK: %d\n", ret);
 428                goto err_hclk;
 429        }
 430
 431        /* chech hclk rate, we only support the common 133MHz for now
 432         * hclk could also run at 66MHz, but this not often used
 433         */
 434        rate = clk_get_rate(s3c_freq->hclk);
 435        if (rate < 133 * 1000 * 1000) {
 436                pr_err("cpufreq: HCLK not at 133MHz\n");
 437                clk_put(s3c_freq->hclk);
 438                ret = -EINVAL;
 439                goto err_armclk;
 440        }
 441
 442        s3c_freq->armclk = clk_get(NULL, "armclk");
 443        if (IS_ERR(s3c_freq->armclk)) {
 444                ret = PTR_ERR(s3c_freq->armclk);
 445                pr_err("cpufreq: Unable to obtain ARMCLK: %d\n", ret);
 446                goto err_armclk;
 447        }
 448
 449#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
 450        s3c_freq->vddarm = regulator_get(NULL, "vddarm");
 451        if (IS_ERR(s3c_freq->vddarm)) {
 452                ret = PTR_ERR(s3c_freq->vddarm);
 453                pr_err("cpufreq: Failed to obtain VDDARM: %d\n", ret);
 454                goto err_vddarm;
 455        }
 456
 457        s3c2416_cpufreq_cfg_regulator(s3c_freq);
 458#else
 459        s3c_freq->regulator_latency = 0;
 460#endif
 461
 462        freq = s3c_freq->freq_table;
 463        while (freq->frequency != CPUFREQ_TABLE_END) {
 464                /* special handling for dvs mode */
 465                if (freq->driver_data == 0) {
 466                        if (!s3c_freq->hclk) {
 467                                pr_debug("cpufreq: %dkHz unsupported as it would need unavailable dvs mode\n",
 468                                         freq->frequency);
 469                                freq->frequency = CPUFREQ_ENTRY_INVALID;
 470                        } else {
 471                                freq++;
 472                                continue;
 473                        }
 474                }
 475
 476                /* Check for frequencies we can generate */
 477                rate = clk_round_rate(s3c_freq->armdiv,
 478                                      freq->frequency * 1000);
 479                rate /= 1000;
 480                if (rate != freq->frequency) {
 481                        pr_debug("cpufreq: %dkHz unsupported by clock (clk_round_rate return %lu)\n",
 482                                 freq->frequency, rate);
 483                        freq->frequency = CPUFREQ_ENTRY_INVALID;
 484                }
 485
 486                freq++;
 487        }
 488
 489        policy->cur = clk_get_rate(s3c_freq->armclk) / 1000;
 490
 491        /* Datasheet says PLL stabalisation time must be at least 300us,
 492         * so but add some fudge. (reference in LOCKCON0 register description)
 493         */
 494        policy->cpuinfo.transition_latency = (500 * 1000) +
 495                                             s3c_freq->regulator_latency;
 496
 497        ret = cpufreq_frequency_table_cpuinfo(policy, s3c_freq->freq_table);
 498        if (ret)
 499                goto err_freq_table;
 500
 501        cpufreq_frequency_table_get_attr(s3c_freq->freq_table, 0);
 502
 503        register_reboot_notifier(&s3c2416_cpufreq_reboot_notifier);
 504
 505        return 0;
 506
 507err_freq_table:
 508#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
 509        regulator_put(s3c_freq->vddarm);
 510err_vddarm:
 511#endif
 512        clk_put(s3c_freq->armclk);
 513err_armclk:
 514        clk_put(s3c_freq->hclk);
 515err_hclk:
 516        clk_put(s3c_freq->armdiv);
 517
 518        return ret;
 519}
 520
 521static struct freq_attr *s3c2416_cpufreq_attr[] = {
 522        &cpufreq_freq_attr_scaling_available_freqs,
 523        NULL,
 524};
 525
 526static struct cpufreq_driver s3c2416_cpufreq_driver = {
 527        .owner          = THIS_MODULE,
 528        .flags          = 0,
 529        .verify         = s3c2416_cpufreq_verify_speed,
 530        .target         = s3c2416_cpufreq_set_target,
 531        .get            = s3c2416_cpufreq_get_speed,
 532        .init           = s3c2416_cpufreq_driver_init,
 533        .name           = "s3c2416",
 534        .attr           = s3c2416_cpufreq_attr,
 535};
 536
 537static int __init s3c2416_cpufreq_init(void)
 538{
 539        return cpufreq_register_driver(&s3c2416_cpufreq_driver);
 540}
 541module_init(s3c2416_cpufreq_init);
 542
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.