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