Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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