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