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