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