18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 2006-2009 Simtec Electronics 48c2ecf20Sopenharmony_ci * http://armlinux.simtec.co.uk/ 58c2ecf20Sopenharmony_ci * Ben Dooks <ben@simtec.co.uk> 68c2ecf20Sopenharmony_ci * Vincent Sanders <vince@simtec.co.uk> 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * S3C2440/S3C2442 CPU Frequency scaling 98c2ecf20Sopenharmony_ci*/ 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <linux/init.h> 148c2ecf20Sopenharmony_ci#include <linux/module.h> 158c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 168c2ecf20Sopenharmony_ci#include <linux/ioport.h> 178c2ecf20Sopenharmony_ci#include <linux/cpufreq.h> 188c2ecf20Sopenharmony_ci#include <linux/device.h> 198c2ecf20Sopenharmony_ci#include <linux/delay.h> 208c2ecf20Sopenharmony_ci#include <linux/clk.h> 218c2ecf20Sopenharmony_ci#include <linux/err.h> 228c2ecf20Sopenharmony_ci#include <linux/io.h> 238c2ecf20Sopenharmony_ci#include <linux/soc/samsung/s3c-cpufreq-core.h> 248c2ecf20Sopenharmony_ci#include <linux/soc/samsung/s3c-pm.h> 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci#include <asm/mach/arch.h> 278c2ecf20Sopenharmony_ci#include <asm/mach/map.h> 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci#define S3C2440_CLKDIVN_PDIVN (1<<0) 308c2ecf20Sopenharmony_ci#define S3C2440_CLKDIVN_HDIVN_MASK (3<<1) 318c2ecf20Sopenharmony_ci#define S3C2440_CLKDIVN_HDIVN_1 (0<<1) 328c2ecf20Sopenharmony_ci#define S3C2440_CLKDIVN_HDIVN_2 (1<<1) 338c2ecf20Sopenharmony_ci#define S3C2440_CLKDIVN_HDIVN_4_8 (2<<1) 348c2ecf20Sopenharmony_ci#define S3C2440_CLKDIVN_HDIVN_3_6 (3<<1) 358c2ecf20Sopenharmony_ci#define S3C2440_CLKDIVN_UCLK (1<<3) 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci#define S3C2440_CAMDIVN_CAMCLK_MASK (0xf<<0) 388c2ecf20Sopenharmony_ci#define S3C2440_CAMDIVN_CAMCLK_SEL (1<<4) 398c2ecf20Sopenharmony_ci#define S3C2440_CAMDIVN_HCLK3_HALF (1<<8) 408c2ecf20Sopenharmony_ci#define S3C2440_CAMDIVN_HCLK4_HALF (1<<9) 418c2ecf20Sopenharmony_ci#define S3C2440_CAMDIVN_DVSEN (1<<12) 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci#define S3C2442_CAMDIVN_CAMCLK_DIV3 (1<<5) 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_cistatic struct clk *xtal; 468c2ecf20Sopenharmony_cistatic struct clk *fclk; 478c2ecf20Sopenharmony_cistatic struct clk *hclk; 488c2ecf20Sopenharmony_cistatic struct clk *armclk; 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci/* HDIV: 1, 2, 3, 4, 6, 8 */ 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic inline int within_khz(unsigned long a, unsigned long b) 538c2ecf20Sopenharmony_ci{ 548c2ecf20Sopenharmony_ci long diff = a - b; 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci return (diff >= -1000 && diff <= 1000); 578c2ecf20Sopenharmony_ci} 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci/** 608c2ecf20Sopenharmony_ci * s3c2440_cpufreq_calcdivs - calculate divider settings 618c2ecf20Sopenharmony_ci * @cfg: The cpu frequency settings. 628c2ecf20Sopenharmony_ci * 638c2ecf20Sopenharmony_ci * Calcualte the divider values for the given frequency settings 648c2ecf20Sopenharmony_ci * specified in @cfg. The values are stored in @cfg for later use 658c2ecf20Sopenharmony_ci * by the relevant set routine if the request settings can be reached. 668c2ecf20Sopenharmony_ci */ 678c2ecf20Sopenharmony_cistatic int s3c2440_cpufreq_calcdivs(struct s3c_cpufreq_config *cfg) 688c2ecf20Sopenharmony_ci{ 698c2ecf20Sopenharmony_ci unsigned int hdiv, pdiv; 708c2ecf20Sopenharmony_ci unsigned long hclk, fclk, armclk; 718c2ecf20Sopenharmony_ci unsigned long hclk_max; 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci fclk = cfg->freq.fclk; 748c2ecf20Sopenharmony_ci armclk = cfg->freq.armclk; 758c2ecf20Sopenharmony_ci hclk_max = cfg->max.hclk; 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci s3c_freq_dbg("%s: fclk is %lu, armclk %lu, max hclk %lu\n", 788c2ecf20Sopenharmony_ci __func__, fclk, armclk, hclk_max); 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci if (armclk > fclk) { 818c2ecf20Sopenharmony_ci pr_warn("%s: armclk > fclk\n", __func__); 828c2ecf20Sopenharmony_ci armclk = fclk; 838c2ecf20Sopenharmony_ci } 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci /* if we are in DVS, we need HCLK to be <= ARMCLK */ 868c2ecf20Sopenharmony_ci if (armclk < fclk && armclk < hclk_max) 878c2ecf20Sopenharmony_ci hclk_max = armclk; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci for (hdiv = 1; hdiv < 9; hdiv++) { 908c2ecf20Sopenharmony_ci if (hdiv == 5 || hdiv == 7) 918c2ecf20Sopenharmony_ci hdiv++; 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci hclk = (fclk / hdiv); 948c2ecf20Sopenharmony_ci if (hclk <= hclk_max || within_khz(hclk, hclk_max)) 958c2ecf20Sopenharmony_ci break; 968c2ecf20Sopenharmony_ci } 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci s3c_freq_dbg("%s: hclk %lu, div %d\n", __func__, hclk, hdiv); 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci if (hdiv > 8) 1018c2ecf20Sopenharmony_ci goto invalid; 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci pdiv = (hclk > cfg->max.pclk) ? 2 : 1; 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci if ((hclk / pdiv) > cfg->max.pclk) 1068c2ecf20Sopenharmony_ci pdiv++; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci s3c_freq_dbg("%s: pdiv %d\n", __func__, pdiv); 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci if (pdiv > 2) 1118c2ecf20Sopenharmony_ci goto invalid; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci pdiv *= hdiv; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci /* calculate a valid armclk */ 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci if (armclk < hclk) 1188c2ecf20Sopenharmony_ci armclk = hclk; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci /* if we're running armclk lower than fclk, this really means 1218c2ecf20Sopenharmony_ci * that the system should go into dvs mode, which means that 1228c2ecf20Sopenharmony_ci * armclk is connected to hclk. */ 1238c2ecf20Sopenharmony_ci if (armclk < fclk) { 1248c2ecf20Sopenharmony_ci cfg->divs.dvs = 1; 1258c2ecf20Sopenharmony_ci armclk = hclk; 1268c2ecf20Sopenharmony_ci } else 1278c2ecf20Sopenharmony_ci cfg->divs.dvs = 0; 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci cfg->freq.armclk = armclk; 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci /* store the result, and then return */ 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci cfg->divs.h_divisor = hdiv; 1348c2ecf20Sopenharmony_ci cfg->divs.p_divisor = pdiv; 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci return 0; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci invalid: 1398c2ecf20Sopenharmony_ci return -EINVAL; 1408c2ecf20Sopenharmony_ci} 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci#define CAMDIVN_HCLK_HALF (S3C2440_CAMDIVN_HCLK3_HALF | \ 1438c2ecf20Sopenharmony_ci S3C2440_CAMDIVN_HCLK4_HALF) 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci/** 1468c2ecf20Sopenharmony_ci * s3c2440_cpufreq_setdivs - set the cpu frequency divider settings 1478c2ecf20Sopenharmony_ci * @cfg: The cpu frequency settings. 1488c2ecf20Sopenharmony_ci * 1498c2ecf20Sopenharmony_ci * Set the divisors from the settings in @cfg, which where generated 1508c2ecf20Sopenharmony_ci * during the calculation phase by s3c2440_cpufreq_calcdivs(). 1518c2ecf20Sopenharmony_ci */ 1528c2ecf20Sopenharmony_cistatic void s3c2440_cpufreq_setdivs(struct s3c_cpufreq_config *cfg) 1538c2ecf20Sopenharmony_ci{ 1548c2ecf20Sopenharmony_ci unsigned long clkdiv, camdiv; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci s3c_freq_dbg("%s: divisors: h=%d, p=%d\n", __func__, 1578c2ecf20Sopenharmony_ci cfg->divs.h_divisor, cfg->divs.p_divisor); 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci clkdiv = s3c24xx_read_clkdivn(); 1608c2ecf20Sopenharmony_ci camdiv = s3c2440_read_camdivn(); 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci clkdiv &= ~(S3C2440_CLKDIVN_HDIVN_MASK | S3C2440_CLKDIVN_PDIVN); 1638c2ecf20Sopenharmony_ci camdiv &= ~CAMDIVN_HCLK_HALF; 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci switch (cfg->divs.h_divisor) { 1668c2ecf20Sopenharmony_ci case 1: 1678c2ecf20Sopenharmony_ci clkdiv |= S3C2440_CLKDIVN_HDIVN_1; 1688c2ecf20Sopenharmony_ci break; 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci case 2: 1718c2ecf20Sopenharmony_ci clkdiv |= S3C2440_CLKDIVN_HDIVN_2; 1728c2ecf20Sopenharmony_ci break; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci case 6: 1758c2ecf20Sopenharmony_ci camdiv |= S3C2440_CAMDIVN_HCLK3_HALF; 1768c2ecf20Sopenharmony_ci case 3: 1778c2ecf20Sopenharmony_ci clkdiv |= S3C2440_CLKDIVN_HDIVN_3_6; 1788c2ecf20Sopenharmony_ci break; 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci case 8: 1818c2ecf20Sopenharmony_ci camdiv |= S3C2440_CAMDIVN_HCLK4_HALF; 1828c2ecf20Sopenharmony_ci case 4: 1838c2ecf20Sopenharmony_ci clkdiv |= S3C2440_CLKDIVN_HDIVN_4_8; 1848c2ecf20Sopenharmony_ci break; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci default: 1878c2ecf20Sopenharmony_ci BUG(); /* we don't expect to get here. */ 1888c2ecf20Sopenharmony_ci } 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci if (cfg->divs.p_divisor != cfg->divs.h_divisor) 1918c2ecf20Sopenharmony_ci clkdiv |= S3C2440_CLKDIVN_PDIVN; 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci /* todo - set pclk. */ 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci /* Write the divisors first with hclk intentionally halved so that 1968c2ecf20Sopenharmony_ci * when we write clkdiv we will under-frequency instead of over. We 1978c2ecf20Sopenharmony_ci * then make a short delay and remove the hclk halving if necessary. 1988c2ecf20Sopenharmony_ci */ 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci s3c2440_write_camdivn(camdiv | CAMDIVN_HCLK_HALF); 2018c2ecf20Sopenharmony_ci s3c24xx_write_clkdivn(clkdiv); 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci ndelay(20); 2048c2ecf20Sopenharmony_ci s3c2440_write_camdivn(camdiv); 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci clk_set_parent(armclk, cfg->divs.dvs ? hclk : fclk); 2078c2ecf20Sopenharmony_ci} 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_cistatic int run_freq_for(unsigned long max_hclk, unsigned long fclk, 2108c2ecf20Sopenharmony_ci int *divs, 2118c2ecf20Sopenharmony_ci struct cpufreq_frequency_table *table, 2128c2ecf20Sopenharmony_ci size_t table_size) 2138c2ecf20Sopenharmony_ci{ 2148c2ecf20Sopenharmony_ci unsigned long freq; 2158c2ecf20Sopenharmony_ci int index = 0; 2168c2ecf20Sopenharmony_ci int div; 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci for (div = *divs; div > 0; div = *divs++) { 2198c2ecf20Sopenharmony_ci freq = fclk / div; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci if (freq > max_hclk && div != 1) 2228c2ecf20Sopenharmony_ci continue; 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci freq /= 1000; /* table is in kHz */ 2258c2ecf20Sopenharmony_ci index = s3c_cpufreq_addfreq(table, index, table_size, freq); 2268c2ecf20Sopenharmony_ci if (index < 0) 2278c2ecf20Sopenharmony_ci break; 2288c2ecf20Sopenharmony_ci } 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci return index; 2318c2ecf20Sopenharmony_ci} 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_cistatic int hclk_divs[] = { 1, 2, 3, 4, 6, 8, -1 }; 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_cistatic int s3c2440_cpufreq_calctable(struct s3c_cpufreq_config *cfg, 2368c2ecf20Sopenharmony_ci struct cpufreq_frequency_table *table, 2378c2ecf20Sopenharmony_ci size_t table_size) 2388c2ecf20Sopenharmony_ci{ 2398c2ecf20Sopenharmony_ci int ret; 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci WARN_ON(cfg->info == NULL); 2428c2ecf20Sopenharmony_ci WARN_ON(cfg->board == NULL); 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci ret = run_freq_for(cfg->info->max.hclk, 2458c2ecf20Sopenharmony_ci cfg->info->max.fclk, 2468c2ecf20Sopenharmony_ci hclk_divs, 2478c2ecf20Sopenharmony_ci table, table_size); 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci s3c_freq_dbg("%s: returning %d\n", __func__, ret); 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci return ret; 2528c2ecf20Sopenharmony_ci} 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_cistatic struct s3c_cpufreq_info s3c2440_cpufreq_info = { 2558c2ecf20Sopenharmony_ci .max = { 2568c2ecf20Sopenharmony_ci .fclk = 400000000, 2578c2ecf20Sopenharmony_ci .hclk = 133333333, 2588c2ecf20Sopenharmony_ci .pclk = 66666666, 2598c2ecf20Sopenharmony_ci }, 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci .locktime_m = 300, 2628c2ecf20Sopenharmony_ci .locktime_u = 300, 2638c2ecf20Sopenharmony_ci .locktime_bits = 16, 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_ci .name = "s3c244x", 2668c2ecf20Sopenharmony_ci .calc_iotiming = s3c2410_iotiming_calc, 2678c2ecf20Sopenharmony_ci .set_iotiming = s3c2410_iotiming_set, 2688c2ecf20Sopenharmony_ci .get_iotiming = s3c2410_iotiming_get, 2698c2ecf20Sopenharmony_ci .set_fvco = s3c2410_set_fvco, 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci .set_refresh = s3c2410_cpufreq_setrefresh, 2728c2ecf20Sopenharmony_ci .set_divs = s3c2440_cpufreq_setdivs, 2738c2ecf20Sopenharmony_ci .calc_divs = s3c2440_cpufreq_calcdivs, 2748c2ecf20Sopenharmony_ci .calc_freqtable = s3c2440_cpufreq_calctable, 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_ci .debug_io_show = s3c_cpufreq_debugfs_call(s3c2410_iotiming_debugfs), 2778c2ecf20Sopenharmony_ci}; 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_cistatic int s3c2440_cpufreq_add(struct device *dev, 2808c2ecf20Sopenharmony_ci struct subsys_interface *sif) 2818c2ecf20Sopenharmony_ci{ 2828c2ecf20Sopenharmony_ci xtal = s3c_cpufreq_clk_get(NULL, "xtal"); 2838c2ecf20Sopenharmony_ci hclk = s3c_cpufreq_clk_get(NULL, "hclk"); 2848c2ecf20Sopenharmony_ci fclk = s3c_cpufreq_clk_get(NULL, "fclk"); 2858c2ecf20Sopenharmony_ci armclk = s3c_cpufreq_clk_get(NULL, "armclk"); 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ci if (IS_ERR(xtal) || IS_ERR(hclk) || IS_ERR(fclk) || IS_ERR(armclk)) { 2888c2ecf20Sopenharmony_ci pr_err("%s: failed to get clocks\n", __func__); 2898c2ecf20Sopenharmony_ci return -ENOENT; 2908c2ecf20Sopenharmony_ci } 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_ci return s3c_cpufreq_register(&s3c2440_cpufreq_info); 2938c2ecf20Sopenharmony_ci} 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_cistatic struct subsys_interface s3c2440_cpufreq_interface = { 2968c2ecf20Sopenharmony_ci .name = "s3c2440_cpufreq", 2978c2ecf20Sopenharmony_ci .subsys = &s3c2440_subsys, 2988c2ecf20Sopenharmony_ci .add_dev = s3c2440_cpufreq_add, 2998c2ecf20Sopenharmony_ci}; 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_cistatic int s3c2440_cpufreq_init(void) 3028c2ecf20Sopenharmony_ci{ 3038c2ecf20Sopenharmony_ci return subsys_interface_register(&s3c2440_cpufreq_interface); 3048c2ecf20Sopenharmony_ci} 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_ci/* arch_initcall adds the clocks we need, so use subsys_initcall. */ 3078c2ecf20Sopenharmony_cisubsys_initcall(s3c2440_cpufreq_init); 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_cistatic struct subsys_interface s3c2442_cpufreq_interface = { 3108c2ecf20Sopenharmony_ci .name = "s3c2442_cpufreq", 3118c2ecf20Sopenharmony_ci .subsys = &s3c2442_subsys, 3128c2ecf20Sopenharmony_ci .add_dev = s3c2440_cpufreq_add, 3138c2ecf20Sopenharmony_ci}; 3148c2ecf20Sopenharmony_ci 3158c2ecf20Sopenharmony_cistatic int s3c2442_cpufreq_init(void) 3168c2ecf20Sopenharmony_ci{ 3178c2ecf20Sopenharmony_ci return subsys_interface_register(&s3c2442_cpufreq_interface); 3188c2ecf20Sopenharmony_ci} 3198c2ecf20Sopenharmony_cisubsys_initcall(s3c2442_cpufreq_init); 320