root/drivers/cpufreq/s3c2416-cpufreq.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. s3c2416_cpufreq_get_speed
  2. s3c2416_cpufreq_set_armdiv
  3. s3c2416_cpufreq_enter_dvs
  4. s3c2416_cpufreq_leave_dvs
  5. s3c2416_cpufreq_set_target
  6. s3c2416_cpufreq_cfg_regulator
  7. s3c2416_cpufreq_reboot_notifier_evt
  8. s3c2416_cpufreq_driver_init
  9. s3c2416_cpufreq_init

   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 
  22 static DEFINE_MUTEX(cpufreq_lock);
  23 
  24 struct 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 
  40 static struct s3c2416_data s3c2416_cpufreq;
  41 
  42 struct 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  */
  65 static struct s3c2416_dvfs s3c2416_dvfs_table[] = {
  66         [SOURCE_HCLK] = {  950000, 1250000 },
  67         [SOURCE_ARMDIV] = { 1250000, 1350000 },
  68 };
  69 #endif
  70 
  71 static 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 
  79 static 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 
  87 static 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 
 101 static 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 
 118 static 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 
 160 static 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 
 216 static 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 
 256 out:
 257         mutex_unlock(&cpufreq_lock);
 258 
 259         return ret;
 260 }
 261 
 262 #ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
 263 static 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 
 296 out:
 297         /* Guessed */
 298         s3c_freq->regulator_latency = 1 * 1000 * 1000;
 299 }
 300 #endif
 301 
 302 static 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 
 329 static struct notifier_block s3c2416_cpufreq_reboot_notifier = {
 330         .notifier_call = s3c2416_cpufreq_reboot_notifier_evt,
 331 };
 332 
 333 static 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
 457 err_vddarm:
 458         clk_put(s3c_freq->armclk);
 459 #endif
 460 err_armclk:
 461         clk_put(s3c_freq->hclk);
 462 err_hclk:
 463         clk_put(s3c_freq->armdiv);
 464 
 465         return ret;
 466 }
 467 
 468 static 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 
 478 static int __init s3c2416_cpufreq_init(void)
 479 {
 480         return cpufreq_register_driver(&s3c2416_cpufreq_driver);
 481 }
 482 module_init(s3c2416_cpufreq_init);

/* [<][>][^][v][top][bottom][index][help] */