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 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].index;
 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.cpu = 0;
 260        freqs.flags = 0;
 261        freqs.old = s3c_freq->is_dvs ? FREQ_DVS
 262                                     : clk_get_rate(s3c_freq->armclk) / 1000;
 263
 264        /* When leavin dvs mode, always switch the armdiv to the hclk rate
 265         * The S3C2416 has stability issues when switching directly to
 266         * higher frequencies.
 267         */
 268        freqs.new = (s3c_freq->is_dvs && !to_dvs)
 269                                ? clk_get_rate(s3c_freq->hclk) / 1000
 270                                : s3c_freq->freq_table[i].frequency;
 271
 272        pr_debug("cpufreq: Transition %d-%dkHz\n", freqs.old, freqs.new);
 273
 274        if (!to_dvs && freqs.old == freqs.new)
 275                goto out;
 276
 277        cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
 278
 279        if (to_dvs) {
 280                pr_debug("cpufreq: enter dvs\n");
 281                ret = s3c2416_cpufreq_enter_dvs(s3c_freq, idx);
 282        } else if (s3c_freq->is_dvs) {
 283                pr_debug("cpufreq: leave dvs\n");
 284                ret = s3c2416_cpufreq_leave_dvs(s3c_freq, idx);
 285        } else {
 286                pr_debug("cpufreq: change armdiv to %dkHz\n", freqs.new);
 287                ret = s3c2416_cpufreq_set_armdiv(s3c_freq, freqs.new);
 288        }
 289
 290        cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
 291
 292out:
 293        mutex_unlock(&cpufreq_lock);
 294
 295        return ret;
 296}
 297
 298#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
 299static void __init s3c2416_cpufreq_cfg_regulator(struct s3c2416_data *s3c_freq)
 300{
 301        int count, v, i, found;
 302        struct cpufreq_frequency_table *freq;
 303        struct s3c2416_dvfs *dvfs;
 304
 305        count = regulator_count_voltages(s3c_freq->vddarm);
 306        if (count < 0) {
 307                pr_err("cpufreq: Unable to check supported voltages\n");
 308                return;
 309        }
 310
 311        freq = s3c_freq->freq_table;
 312        while (count > 0 && freq->frequency != CPUFREQ_TABLE_END) {
 313                if (freq->frequency == CPUFREQ_ENTRY_INVALID)
 314                        continue;
 315
 316                dvfs = &s3c2416_dvfs_table[freq->index];
 317                found = 0;
 318
 319                /* Check only the min-voltage, more is always ok on S3C2416 */
 320                for (i = 0; i < count; i++) {
 321                        v = regulator_list_voltage(s3c_freq->vddarm, i);
 322                        if (v >= dvfs->vddarm_min)
 323                                found = 1;
 324                }
 325
 326                if (!found) {
 327                        pr_debug("cpufreq: %dkHz unsupported by regulator\n",
 328                                 freq->frequency);
 329                        freq->frequency = CPUFREQ_ENTRY_INVALID;
 330                }
 331
 332                freq++;
 333        }
 334
 335        /* Guessed */
 336        s3c_freq->regulator_latency = 1 * 1000 * 1000;
 337}
 338#endif
 339
 340static int s3c2416_cpufreq_reboot_notifier_evt(struct notifier_block *this,
 341                                               unsigned long event, void *ptr)
 342{
 343        struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
 344        int ret;
 345
 346        mutex_lock(&cpufreq_lock);
 347
 348        /* disable further changes */
 349        s3c_freq->disable_dvs = 1;
 350
 351        mutex_unlock(&cpufreq_lock);
 352
 353        /* some boards don't reconfigure the regulator on reboot, which
 354         * could lead to undervolting the cpu when the clock is reset.
 355         * Therefore we always leave the DVS mode on reboot.
 356         */
 357        if (s3c_freq->is_dvs) {
 358                pr_debug("cpufreq: leave dvs on reboot\n");
 359                ret = cpufreq_driver_target(cpufreq_cpu_get(0), FREQ_SLEEP, 0);
 360                if (ret < 0)
 361                        return NOTIFY_BAD;
 362        }
 363
 364        return NOTIFY_DONE;
 365}
 366
 367static struct notifier_block s3c2416_cpufreq_reboot_notifier = {
 368        .notifier_call = s3c2416_cpufreq_reboot_notifier_evt,
 369};
 370
 371static int __init s3c2416_cpufreq_driver_init(struct cpufreq_policy *policy)
 372{
 373        struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
 374        struct cpufreq_frequency_table *freq;
 375        struct clk *msysclk;
 376        unsigned long rate;
 377        int ret;
 378
 379        if (policy->cpu != 0)
 380                return -EINVAL;
 381
 382        msysclk = clk_get(NULL, "msysclk");
 383        if (IS_ERR(msysclk)) {
 384                ret = PTR_ERR(msysclk);
 385                pr_err("cpufreq: Unable to obtain msysclk: %d\n", ret);
 386                return ret;
 387        }
 388
 389        /*
 390         * S3C2416 and S3C2450 share the same processor-ID and also provide no
 391         * other means to distinguish them other than through the rate of
 392         * msysclk. On S3C2416 msysclk runs at 800MHz and on S3C2450 at 533MHz.
 393         */
 394        rate = clk_get_rate(msysclk);
 395        if (rate == 800 * 1000 * 1000) {
 396                pr_info("cpufreq: msysclk running at %lukHz, using S3C2416 frequency table\n",
 397                        rate / 1000);
 398                s3c_freq->freq_table = s3c2416_freq_table;
 399                policy->cpuinfo.max_freq = 400000;
 400        } else if (rate / 1000 == 534000) {
 401                pr_info("cpufreq: msysclk running at %lukHz, using S3C2450 frequency table\n",
 402                        rate / 1000);
 403                s3c_freq->freq_table = s3c2450_freq_table;
 404                policy->cpuinfo.max_freq = 534000;
 405        }
 406
 407        /* not needed anymore */
 408        clk_put(msysclk);
 409
 410        if (s3c_freq->freq_table == NULL) {
 411                pr_err("cpufreq: No frequency information for this CPU, msysclk at %lukHz\n",
 412                       rate / 1000);
 413                return -ENODEV;
 414        }
 415
 416        s3c_freq->is_dvs = 0;
 417
 418        s3c_freq->armdiv = clk_get(NULL, "armdiv");
 419        if (IS_ERR(s3c_freq->armdiv)) {
 420                ret = PTR_ERR(s3c_freq->armdiv);
 421                pr_err("cpufreq: Unable to obtain ARMDIV: %d\n", ret);
 422                return ret;
 423        }
 424
 425        s3c_freq->hclk = clk_get(NULL, "hclk");
 426        if (IS_ERR(s3c_freq->hclk)) {
 427                ret = PTR_ERR(s3c_freq->hclk);
 428                pr_err("cpufreq: Unable to obtain HCLK: %d\n", ret);
 429                goto err_hclk;
 430        }
 431
 432        /* chech hclk rate, we only support the common 133MHz for now
 433         * hclk could also run at 66MHz, but this not often used
 434         */
 435        rate = clk_get_rate(s3c_freq->hclk);
 436        if (rate < 133 * 1000 * 1000) {
 437                pr_err("cpufreq: HCLK not at 133MHz\n");
 438                clk_put(s3c_freq->hclk);
 439                ret = -EINVAL;
 440                goto err_armclk;
 441        }
 442
 443        s3c_freq->armclk = clk_get(NULL, "armclk");
 444        if (IS_ERR(s3c_freq->armclk)) {
 445                ret = PTR_ERR(s3c_freq->armclk);
 446                pr_err("cpufreq: Unable to obtain ARMCLK: %d\n", ret);
 447                goto err_armclk;
 448        }
 449
 450#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
 451        s3c_freq->vddarm = regulator_get(NULL, "vddarm");
 452        if (IS_ERR(s3c_freq->vddarm)) {
 453                ret = PTR_ERR(s3c_freq->vddarm);
 454                pr_err("cpufreq: Failed to obtain VDDARM: %d\n", ret);
 455                goto err_vddarm;
 456        }
 457
 458        s3c2416_cpufreq_cfg_regulator(s3c_freq);
 459#else
 460        s3c_freq->regulator_latency = 0;
 461#endif
 462
 463        freq = s3c_freq->freq_table;
 464        while (freq->frequency != CPUFREQ_TABLE_END) {
 465                /* special handling for dvs mode */
 466                if (freq->index == 0) {
 467                        if (!s3c_freq->hclk) {
 468                                pr_debug("cpufreq: %dkHz unsupported as it would need unavailable dvs mode\n",
 469                                         freq->frequency);
 470                                freq->frequency = CPUFREQ_ENTRY_INVALID;
 471                        } else {
 472                                freq++;
 473                                continue;
 474                        }
 475                }
 476
 477                /* Check for frequencies we can generate */
 478                rate = clk_round_rate(s3c_freq->armdiv,
 479                                      freq->frequency * 1000);
 480                rate /= 1000;
 481                if (rate != freq->frequency) {
 482                        pr_debug("cpufreq: %dkHz unsupported by clock (clk_round_rate return %lu)\n",
 483                                 freq->frequency, rate);
 484                        freq->frequency = CPUFREQ_ENTRY_INVALID;
 485                }
 486
 487                freq++;
 488        }
 489
 490        policy->cur = clk_get_rate(s3c_freq->armclk) / 1000;
 491
 492        /* Datasheet says PLL stabalisation time must be at least 300us,
 493         * so but add some fudge. (reference in LOCKCON0 register description)
 494         */
 495        policy->cpuinfo.transition_latency = (500 * 1000) +
 496                                             s3c_freq->regulator_latency;
 497
 498        ret = cpufreq_frequency_table_cpuinfo(policy, s3c_freq->freq_table);
 499        if (ret)
 500                goto err_freq_table;
 501
 502        cpufreq_frequency_table_get_attr(s3c_freq->freq_table, 0);
 503
 504        register_reboot_notifier(&s3c2416_cpufreq_reboot_notifier);
 505
 506        return 0;
 507
 508err_freq_table:
 509#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
 510        regulator_put(s3c_freq->vddarm);
 511err_vddarm:
 512#endif
 513        clk_put(s3c_freq->armclk);
 514err_armclk:
 515        clk_put(s3c_freq->hclk);
 516err_hclk:
 517        clk_put(s3c_freq->armdiv);
 518
 519        return ret;
 520}
 521
 522static struct freq_attr *s3c2416_cpufreq_attr[] = {
 523        &cpufreq_freq_attr_scaling_available_freqs,
 524        NULL,
 525};
 526
 527static struct cpufreq_driver s3c2416_cpufreq_driver = {
 528        .owner          = THIS_MODULE,
 529        .flags          = 0,
 530        .verify         = s3c2416_cpufreq_verify_speed,
 531        .target         = s3c2416_cpufreq_set_target,
 532        .get            = s3c2416_cpufreq_get_speed,
 533        .init           = s3c2416_cpufreq_driver_init,
 534        .name           = "s3c2416",
 535        .attr           = s3c2416_cpufreq_attr,
 536};
 537
 538static int __init s3c2416_cpufreq_init(void)
 539{
 540        return cpufreq_register_driver(&s3c2416_cpufreq_driver);
 541}
 542module_init(s3c2416_cpufreq_init);
 543
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.