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