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