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