linux/drivers/cpufreq/s3c2440-cpufreq.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Copyright (c) 2006-2009 Simtec Electronics
   4 *      http://armlinux.simtec.co.uk/
   5 *      Ben Dooks <ben@simtec.co.uk>
   6 *      Vincent Sanders <vince@simtec.co.uk>
   7 *
   8 * S3C2440/S3C2442 CPU Frequency scaling
   9*/
  10
  11#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  12
  13#include <linux/init.h>
  14#include <linux/module.h>
  15#include <linux/interrupt.h>
  16#include <linux/ioport.h>
  17#include <linux/cpufreq.h>
  18#include <linux/device.h>
  19#include <linux/delay.h>
  20#include <linux/clk.h>
  21#include <linux/err.h>
  22#include <linux/io.h>
  23
  24#include <asm/mach/arch.h>
  25#include <asm/mach/map.h>
  26
  27#include <mach/regs-clock.h>
  28
  29#include <plat/cpu.h>
  30#include <plat/cpu-freq-core.h>
  31
  32static struct clk *xtal;
  33static struct clk *fclk;
  34static struct clk *hclk;
  35static struct clk *armclk;
  36
  37/* HDIV: 1, 2, 3, 4, 6, 8 */
  38
  39static inline int within_khz(unsigned long a, unsigned long b)
  40{
  41        long diff = a - b;
  42
  43        return (diff >= -1000 && diff <= 1000);
  44}
  45
  46/**
  47 * s3c2440_cpufreq_calcdivs - calculate divider settings
  48 * @cfg: The cpu frequency settings.
  49 *
  50 * Calcualte the divider values for the given frequency settings
  51 * specified in @cfg. The values are stored in @cfg for later use
  52 * by the relevant set routine if the request settings can be reached.
  53 */
  54static int s3c2440_cpufreq_calcdivs(struct s3c_cpufreq_config *cfg)
  55{
  56        unsigned int hdiv, pdiv;
  57        unsigned long hclk, fclk, armclk;
  58        unsigned long hclk_max;
  59
  60        fclk = cfg->freq.fclk;
  61        armclk = cfg->freq.armclk;
  62        hclk_max = cfg->max.hclk;
  63
  64        s3c_freq_dbg("%s: fclk is %lu, armclk %lu, max hclk %lu\n",
  65                     __func__, fclk, armclk, hclk_max);
  66
  67        if (armclk > fclk) {
  68                pr_warn("%s: armclk > fclk\n", __func__);
  69                armclk = fclk;
  70        }
  71
  72        /* if we are in DVS, we need HCLK to be <= ARMCLK */
  73        if (armclk < fclk && armclk < hclk_max)
  74                hclk_max = armclk;
  75
  76        for (hdiv = 1; hdiv < 9; hdiv++) {
  77                if (hdiv == 5 || hdiv == 7)
  78                        hdiv++;
  79
  80                hclk = (fclk / hdiv);
  81                if (hclk <= hclk_max || within_khz(hclk, hclk_max))
  82                        break;
  83        }
  84
  85        s3c_freq_dbg("%s: hclk %lu, div %d\n", __func__, hclk, hdiv);
  86
  87        if (hdiv > 8)
  88                goto invalid;
  89
  90        pdiv = (hclk > cfg->max.pclk) ? 2 : 1;
  91
  92        if ((hclk / pdiv) > cfg->max.pclk)
  93                pdiv++;
  94
  95        s3c_freq_dbg("%s: pdiv %d\n", __func__, pdiv);
  96
  97        if (pdiv > 2)
  98                goto invalid;
  99
 100        pdiv *= hdiv;
 101
 102        /* calculate a valid armclk */
 103
 104        if (armclk < hclk)
 105                armclk = hclk;
 106
 107        /* if we're running armclk lower than fclk, this really means
 108         * that the system should go into dvs mode, which means that
 109         * armclk is connected to hclk. */
 110        if (armclk < fclk) {
 111                cfg->divs.dvs = 1;
 112                armclk = hclk;
 113        } else
 114                cfg->divs.dvs = 0;
 115
 116        cfg->freq.armclk = armclk;
 117
 118        /* store the result, and then return */
 119
 120        cfg->divs.h_divisor = hdiv;
 121        cfg->divs.p_divisor = pdiv;
 122
 123        return 0;
 124
 125 invalid:
 126        return -EINVAL;
 127}
 128
 129#define CAMDIVN_HCLK_HALF (S3C2440_CAMDIVN_HCLK3_HALF | \
 130                           S3C2440_CAMDIVN_HCLK4_HALF)
 131
 132/**
 133 * s3c2440_cpufreq_setdivs - set the cpu frequency divider settings
 134 * @cfg: The cpu frequency settings.
 135 *
 136 * Set the divisors from the settings in @cfg, which where generated
 137 * during the calculation phase by s3c2440_cpufreq_calcdivs().
 138 */
 139static void s3c2440_cpufreq_setdivs(struct s3c_cpufreq_config *cfg)
 140{
 141        unsigned long clkdiv, camdiv;
 142
 143        s3c_freq_dbg("%s: divisors: h=%d, p=%d\n", __func__,
 144                     cfg->divs.h_divisor, cfg->divs.p_divisor);
 145
 146        clkdiv = __raw_readl(S3C2410_CLKDIVN);
 147        camdiv = __raw_readl(S3C2440_CAMDIVN);
 148
 149        clkdiv &= ~(S3C2440_CLKDIVN_HDIVN_MASK | S3C2440_CLKDIVN_PDIVN);
 150        camdiv &= ~CAMDIVN_HCLK_HALF;
 151
 152        switch (cfg->divs.h_divisor) {
 153        case 1:
 154                clkdiv |= S3C2440_CLKDIVN_HDIVN_1;
 155                break;
 156
 157        case 2:
 158                clkdiv |= S3C2440_CLKDIVN_HDIVN_2;
 159                break;
 160
 161        case 6:
 162                camdiv |= S3C2440_CAMDIVN_HCLK3_HALF;
 163        case 3:
 164                clkdiv |= S3C2440_CLKDIVN_HDIVN_3_6;
 165                break;
 166
 167        case 8:
 168                camdiv |= S3C2440_CAMDIVN_HCLK4_HALF;
 169        case 4:
 170                clkdiv |= S3C2440_CLKDIVN_HDIVN_4_8;
 171                break;
 172
 173        default:
 174                BUG();  /* we don't expect to get here. */
 175        }
 176
 177        if (cfg->divs.p_divisor != cfg->divs.h_divisor)
 178                clkdiv |= S3C2440_CLKDIVN_PDIVN;
 179
 180        /* todo - set pclk. */
 181
 182        /* Write the divisors first with hclk intentionally halved so that
 183         * when we write clkdiv we will under-frequency instead of over. We
 184         * then make a short delay and remove the hclk halving if necessary.
 185         */
 186
 187        __raw_writel(camdiv | CAMDIVN_HCLK_HALF, S3C2440_CAMDIVN);
 188        __raw_writel(clkdiv, S3C2410_CLKDIVN);
 189
 190        ndelay(20);
 191        __raw_writel(camdiv, S3C2440_CAMDIVN);
 192
 193        clk_set_parent(armclk, cfg->divs.dvs ? hclk : fclk);
 194}
 195
 196static int run_freq_for(unsigned long max_hclk, unsigned long fclk,
 197                        int *divs,
 198                        struct cpufreq_frequency_table *table,
 199                        size_t table_size)
 200{
 201        unsigned long freq;
 202        int index = 0;
 203        int div;
 204
 205        for (div = *divs; div > 0; div = *divs++) {
 206                freq = fclk / div;
 207
 208                if (freq > max_hclk && div != 1)
 209                        continue;
 210
 211                freq /= 1000; /* table is in kHz */
 212                index = s3c_cpufreq_addfreq(table, index, table_size, freq);
 213                if (index < 0)
 214                        break;
 215        }
 216
 217        return index;
 218}
 219
 220static int hclk_divs[] = { 1, 2, 3, 4, 6, 8, -1 };
 221
 222static int s3c2440_cpufreq_calctable(struct s3c_cpufreq_config *cfg,
 223                                     struct cpufreq_frequency_table *table,
 224                                     size_t table_size)
 225{
 226        int ret;
 227
 228        WARN_ON(cfg->info == NULL);
 229        WARN_ON(cfg->board == NULL);
 230
 231        ret = run_freq_for(cfg->info->max.hclk,
 232                           cfg->info->max.fclk,
 233                           hclk_divs,
 234                           table, table_size);
 235
 236        s3c_freq_dbg("%s: returning %d\n", __func__, ret);
 237
 238        return ret;
 239}
 240
 241static struct s3c_cpufreq_info s3c2440_cpufreq_info = {
 242        .max            = {
 243                .fclk   = 400000000,
 244                .hclk   = 133333333,
 245                .pclk   =  66666666,
 246        },
 247
 248        .locktime_m     = 300,
 249        .locktime_u     = 300,
 250        .locktime_bits  = 16,
 251
 252        .name           = "s3c244x",
 253        .calc_iotiming  = s3c2410_iotiming_calc,
 254        .set_iotiming   = s3c2410_iotiming_set,
 255        .get_iotiming   = s3c2410_iotiming_get,
 256        .set_fvco       = s3c2410_set_fvco,
 257
 258        .set_refresh    = s3c2410_cpufreq_setrefresh,
 259        .set_divs       = s3c2440_cpufreq_setdivs,
 260        .calc_divs      = s3c2440_cpufreq_calcdivs,
 261        .calc_freqtable = s3c2440_cpufreq_calctable,
 262
 263        .debug_io_show  = s3c_cpufreq_debugfs_call(s3c2410_iotiming_debugfs),
 264};
 265
 266static int s3c2440_cpufreq_add(struct device *dev,
 267                               struct subsys_interface *sif)
 268{
 269        xtal = s3c_cpufreq_clk_get(NULL, "xtal");
 270        hclk = s3c_cpufreq_clk_get(NULL, "hclk");
 271        fclk = s3c_cpufreq_clk_get(NULL, "fclk");
 272        armclk = s3c_cpufreq_clk_get(NULL, "armclk");
 273
 274        if (IS_ERR(xtal) || IS_ERR(hclk) || IS_ERR(fclk) || IS_ERR(armclk)) {
 275                pr_err("%s: failed to get clocks\n", __func__);
 276                return -ENOENT;
 277        }
 278
 279        return s3c_cpufreq_register(&s3c2440_cpufreq_info);
 280}
 281
 282static struct subsys_interface s3c2440_cpufreq_interface = {
 283        .name           = "s3c2440_cpufreq",
 284        .subsys         = &s3c2440_subsys,
 285        .add_dev        = s3c2440_cpufreq_add,
 286};
 287
 288static int s3c2440_cpufreq_init(void)
 289{
 290        return subsys_interface_register(&s3c2440_cpufreq_interface);
 291}
 292
 293/* arch_initcall adds the clocks we need, so use subsys_initcall. */
 294subsys_initcall(s3c2440_cpufreq_init);
 295
 296static struct subsys_interface s3c2442_cpufreq_interface = {
 297        .name           = "s3c2442_cpufreq",
 298        .subsys         = &s3c2442_subsys,
 299        .add_dev        = s3c2440_cpufreq_add,
 300};
 301
 302static int s3c2442_cpufreq_init(void)
 303{
 304        return subsys_interface_register(&s3c2442_cpufreq_interface);
 305}
 306subsys_initcall(s3c2442_cpufreq_init);
 307