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