18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * cpu-sa1100.c: clock scaling for the SA1100 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2000 2001, The Delft University of Technology 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Authors: 88c2ecf20Sopenharmony_ci * - Johan Pouwelse (J.A.Pouwelse@its.tudelft.nl): initial version 98c2ecf20Sopenharmony_ci * - Erik Mouw (J.A.K.Mouw@its.tudelft.nl): 108c2ecf20Sopenharmony_ci * - major rewrite for linux-2.3.99 118c2ecf20Sopenharmony_ci * - rewritten for the more generic power management scheme in 128c2ecf20Sopenharmony_ci * linux-2.4.5-rmk1 138c2ecf20Sopenharmony_ci * 148c2ecf20Sopenharmony_ci * This software has been developed while working on the LART 158c2ecf20Sopenharmony_ci * computing board (http://www.lartmaker.nl/), which is 168c2ecf20Sopenharmony_ci * sponsored by the Mobile Multi-media Communications 178c2ecf20Sopenharmony_ci * (http://www.mobimedia.org/) and Ubiquitous Communications 188c2ecf20Sopenharmony_ci * (http://www.ubicom.tudelft.nl/) projects. 198c2ecf20Sopenharmony_ci * 208c2ecf20Sopenharmony_ci * The authors can be reached at: 218c2ecf20Sopenharmony_ci * 228c2ecf20Sopenharmony_ci * Erik Mouw 238c2ecf20Sopenharmony_ci * Information and Communication Theory Group 248c2ecf20Sopenharmony_ci * Faculty of Information Technology and Systems 258c2ecf20Sopenharmony_ci * Delft University of Technology 268c2ecf20Sopenharmony_ci * P.O. Box 5031 278c2ecf20Sopenharmony_ci * 2600 GA Delft 288c2ecf20Sopenharmony_ci * The Netherlands 298c2ecf20Sopenharmony_ci * 308c2ecf20Sopenharmony_ci * Theory of operations 318c2ecf20Sopenharmony_ci * ==================== 328c2ecf20Sopenharmony_ci * 338c2ecf20Sopenharmony_ci * Clock scaling can be used to lower the power consumption of the CPU 348c2ecf20Sopenharmony_ci * core. This will give you a somewhat longer running time. 358c2ecf20Sopenharmony_ci * 368c2ecf20Sopenharmony_ci * The SA-1100 has a single register to change the core clock speed: 378c2ecf20Sopenharmony_ci * 388c2ecf20Sopenharmony_ci * PPCR 0x90020014 PLL config 398c2ecf20Sopenharmony_ci * 408c2ecf20Sopenharmony_ci * However, the DRAM timings are closely related to the core clock 418c2ecf20Sopenharmony_ci * speed, so we need to change these, too. The used registers are: 428c2ecf20Sopenharmony_ci * 438c2ecf20Sopenharmony_ci * MDCNFG 0xA0000000 DRAM config 448c2ecf20Sopenharmony_ci * MDCAS0 0xA0000004 Access waveform 458c2ecf20Sopenharmony_ci * MDCAS1 0xA0000008 Access waveform 468c2ecf20Sopenharmony_ci * MDCAS2 0xA000000C Access waveform 478c2ecf20Sopenharmony_ci * 488c2ecf20Sopenharmony_ci * Care must be taken to change the DRAM parameters the correct way, 498c2ecf20Sopenharmony_ci * because otherwise the DRAM becomes unusable and the kernel will 508c2ecf20Sopenharmony_ci * crash. 518c2ecf20Sopenharmony_ci * 528c2ecf20Sopenharmony_ci * The simple solution to avoid a kernel crash is to put the actual 538c2ecf20Sopenharmony_ci * clock change in ROM and jump to that code from the kernel. The main 548c2ecf20Sopenharmony_ci * disadvantage is that the ROM has to be modified, which is not 558c2ecf20Sopenharmony_ci * possible on all SA-1100 platforms. Another disadvantage is that 568c2ecf20Sopenharmony_ci * jumping to ROM makes clock switching unnecessary complicated. 578c2ecf20Sopenharmony_ci * 588c2ecf20Sopenharmony_ci * The idea behind this driver is that the memory configuration can be 598c2ecf20Sopenharmony_ci * changed while running from DRAM (even with interrupts turned on!) 608c2ecf20Sopenharmony_ci * as long as all re-configuration steps yield a valid DRAM 618c2ecf20Sopenharmony_ci * configuration. The advantages are clear: it will run on all SA-1100 628c2ecf20Sopenharmony_ci * platforms, and the code is very simple. 638c2ecf20Sopenharmony_ci * 648c2ecf20Sopenharmony_ci * If you really want to understand what is going on in 658c2ecf20Sopenharmony_ci * sa1100_update_dram_timings(), you'll have to read sections 8.2, 668c2ecf20Sopenharmony_ci * 9.5.7.3, and 10.2 from the "Intel StrongARM SA-1100 Microprocessor 678c2ecf20Sopenharmony_ci * Developers Manual" (available for free from Intel). 688c2ecf20Sopenharmony_ci */ 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci#include <linux/kernel.h> 718c2ecf20Sopenharmony_ci#include <linux/types.h> 728c2ecf20Sopenharmony_ci#include <linux/init.h> 738c2ecf20Sopenharmony_ci#include <linux/cpufreq.h> 748c2ecf20Sopenharmony_ci#include <linux/io.h> 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci#include <asm/cputype.h> 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci#include <mach/generic.h> 798c2ecf20Sopenharmony_ci#include <mach/hardware.h> 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_cistruct sa1100_dram_regs { 828c2ecf20Sopenharmony_ci int speed; 838c2ecf20Sopenharmony_ci u32 mdcnfg; 848c2ecf20Sopenharmony_ci u32 mdcas0; 858c2ecf20Sopenharmony_ci u32 mdcas1; 868c2ecf20Sopenharmony_ci u32 mdcas2; 878c2ecf20Sopenharmony_ci}; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_cistatic struct cpufreq_driver sa1100_driver; 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_cistatic struct sa1100_dram_regs sa1100_dram_settings[] = { 938c2ecf20Sopenharmony_ci /*speed, mdcnfg, mdcas0, mdcas1, mdcas2, clock freq */ 948c2ecf20Sopenharmony_ci { 59000, 0x00dc88a3, 0xcccccccf, 0xfffffffc, 0xffffffff},/* 59.0 MHz */ 958c2ecf20Sopenharmony_ci { 73700, 0x011490a3, 0xcccccccf, 0xfffffffc, 0xffffffff},/* 73.7 MHz */ 968c2ecf20Sopenharmony_ci { 88500, 0x014e90a3, 0xcccccccf, 0xfffffffc, 0xffffffff},/* 88.5 MHz */ 978c2ecf20Sopenharmony_ci {103200, 0x01889923, 0xcccccccf, 0xfffffffc, 0xffffffff},/* 103.2 MHz */ 988c2ecf20Sopenharmony_ci {118000, 0x01c29923, 0x9999998f, 0xfffffff9, 0xffffffff},/* 118.0 MHz */ 998c2ecf20Sopenharmony_ci {132700, 0x01fb2123, 0x9999998f, 0xfffffff9, 0xffffffff},/* 132.7 MHz */ 1008c2ecf20Sopenharmony_ci {147500, 0x02352123, 0x3333330f, 0xfffffff3, 0xffffffff},/* 147.5 MHz */ 1018c2ecf20Sopenharmony_ci {162200, 0x026b29a3, 0x38e38e1f, 0xfff8e38e, 0xffffffff},/* 162.2 MHz */ 1028c2ecf20Sopenharmony_ci {176900, 0x02a329a3, 0x71c71c1f, 0xfff1c71c, 0xffffffff},/* 176.9 MHz */ 1038c2ecf20Sopenharmony_ci {191700, 0x02dd31a3, 0xe38e383f, 0xffe38e38, 0xffffffff},/* 191.7 MHz */ 1048c2ecf20Sopenharmony_ci {206400, 0x03153223, 0xc71c703f, 0xffc71c71, 0xffffffff},/* 206.4 MHz */ 1058c2ecf20Sopenharmony_ci {221200, 0x034fba23, 0xc71c703f, 0xffc71c71, 0xffffffff},/* 221.2 MHz */ 1068c2ecf20Sopenharmony_ci {235900, 0x03853a23, 0xe1e1e07f, 0xe1e1e1e1, 0xffffffe1},/* 235.9 MHz */ 1078c2ecf20Sopenharmony_ci {250700, 0x03bf3aa3, 0xc3c3c07f, 0xc3c3c3c3, 0xffffffc3},/* 250.7 MHz */ 1088c2ecf20Sopenharmony_ci {265400, 0x03f7c2a3, 0xc3c3c07f, 0xc3c3c3c3, 0xffffffc3},/* 265.4 MHz */ 1098c2ecf20Sopenharmony_ci {280200, 0x0431c2a3, 0x878780ff, 0x87878787, 0xffffff87},/* 280.2 MHz */ 1108c2ecf20Sopenharmony_ci { 0, 0, 0, 0, 0 } /* last entry */ 1118c2ecf20Sopenharmony_ci}; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_cistatic void sa1100_update_dram_timings(int current_speed, int new_speed) 1148c2ecf20Sopenharmony_ci{ 1158c2ecf20Sopenharmony_ci struct sa1100_dram_regs *settings = sa1100_dram_settings; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci /* find speed */ 1188c2ecf20Sopenharmony_ci while (settings->speed != 0) { 1198c2ecf20Sopenharmony_ci if (new_speed == settings->speed) 1208c2ecf20Sopenharmony_ci break; 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci settings++; 1238c2ecf20Sopenharmony_ci } 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci if (settings->speed == 0) { 1268c2ecf20Sopenharmony_ci panic("%s: couldn't find dram setting for speed %d\n", 1278c2ecf20Sopenharmony_ci __func__, new_speed); 1288c2ecf20Sopenharmony_ci } 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci /* No risk, no fun: run with interrupts on! */ 1318c2ecf20Sopenharmony_ci if (new_speed > current_speed) { 1328c2ecf20Sopenharmony_ci /* We're going FASTER, so first relax the memory 1338c2ecf20Sopenharmony_ci * timings before changing the core frequency 1348c2ecf20Sopenharmony_ci */ 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci /* Half the memory access clock */ 1378c2ecf20Sopenharmony_ci MDCNFG |= MDCNFG_CDB2; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci /* The order of these statements IS important, keep 8 1408c2ecf20Sopenharmony_ci * pulses!! 1418c2ecf20Sopenharmony_ci */ 1428c2ecf20Sopenharmony_ci MDCAS2 = settings->mdcas2; 1438c2ecf20Sopenharmony_ci MDCAS1 = settings->mdcas1; 1448c2ecf20Sopenharmony_ci MDCAS0 = settings->mdcas0; 1458c2ecf20Sopenharmony_ci MDCNFG = settings->mdcnfg; 1468c2ecf20Sopenharmony_ci } else { 1478c2ecf20Sopenharmony_ci /* We're going SLOWER: first decrease the core 1488c2ecf20Sopenharmony_ci * frequency and then tighten the memory settings. 1498c2ecf20Sopenharmony_ci */ 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci /* Half the memory access clock */ 1528c2ecf20Sopenharmony_ci MDCNFG |= MDCNFG_CDB2; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci /* The order of these statements IS important, keep 8 1558c2ecf20Sopenharmony_ci * pulses!! 1568c2ecf20Sopenharmony_ci */ 1578c2ecf20Sopenharmony_ci MDCAS0 = settings->mdcas0; 1588c2ecf20Sopenharmony_ci MDCAS1 = settings->mdcas1; 1598c2ecf20Sopenharmony_ci MDCAS2 = settings->mdcas2; 1608c2ecf20Sopenharmony_ci MDCNFG = settings->mdcnfg; 1618c2ecf20Sopenharmony_ci } 1628c2ecf20Sopenharmony_ci} 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_cistatic int sa1100_target(struct cpufreq_policy *policy, unsigned int ppcr) 1658c2ecf20Sopenharmony_ci{ 1668c2ecf20Sopenharmony_ci unsigned int cur = sa11x0_getspeed(0); 1678c2ecf20Sopenharmony_ci unsigned int new_freq; 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci new_freq = sa11x0_freq_table[ppcr].frequency; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci if (new_freq > cur) 1728c2ecf20Sopenharmony_ci sa1100_update_dram_timings(cur, new_freq); 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci PPCR = ppcr; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci if (new_freq < cur) 1778c2ecf20Sopenharmony_ci sa1100_update_dram_timings(cur, new_freq); 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci return 0; 1808c2ecf20Sopenharmony_ci} 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_cistatic int __init sa1100_cpu_init(struct cpufreq_policy *policy) 1838c2ecf20Sopenharmony_ci{ 1848c2ecf20Sopenharmony_ci cpufreq_generic_init(policy, sa11x0_freq_table, 0); 1858c2ecf20Sopenharmony_ci return 0; 1868c2ecf20Sopenharmony_ci} 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_cistatic struct cpufreq_driver sa1100_driver __refdata = { 1898c2ecf20Sopenharmony_ci .flags = CPUFREQ_STICKY | CPUFREQ_NEED_INITIAL_FREQ_CHECK | 1908c2ecf20Sopenharmony_ci CPUFREQ_NO_AUTO_DYNAMIC_SWITCHING, 1918c2ecf20Sopenharmony_ci .verify = cpufreq_generic_frequency_table_verify, 1928c2ecf20Sopenharmony_ci .target_index = sa1100_target, 1938c2ecf20Sopenharmony_ci .get = sa11x0_getspeed, 1948c2ecf20Sopenharmony_ci .init = sa1100_cpu_init, 1958c2ecf20Sopenharmony_ci .name = "sa1100", 1968c2ecf20Sopenharmony_ci}; 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_cistatic int __init sa1100_dram_init(void) 1998c2ecf20Sopenharmony_ci{ 2008c2ecf20Sopenharmony_ci if (cpu_is_sa1100()) 2018c2ecf20Sopenharmony_ci return cpufreq_register_driver(&sa1100_driver); 2028c2ecf20Sopenharmony_ci else 2038c2ecf20Sopenharmony_ci return -ENODEV; 2048c2ecf20Sopenharmony_ci} 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ciarch_initcall(sa1100_dram_init); 207