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