rendered paste bodyPreliminary cpufreq patch for the GTA01. Completely untested. Might notcompile.The cpufreq notifiers for the drivers are still missing.DO NOT RUN ON REAL HARDWARE. THE PWM OUTPUT WILL BE COMPLETELY WRONG.COMPLETELY UNTESTED.diff --git a/arch/arm/mach-s3c2410/Kconfig b/arch/arm/mach-s3c2410/Kconfigindex e2079cf..6f2f0fd 100644--- a/arch/arm/mach-s3c2410/Kconfig+++ b/arch/arm/mach-s3c2410/Kconfig@@ -112,3 +112,22 @@ config MACH_QT2410 endmenu +menu "CPU Frequency scaling"++source "drivers/cpufreq/Kconfig"++config S3C2410_CPUFREQ+ tristate "S3C2410A CPU Frequence scaling"+ depends on CPU_S3C2410 && CPU_FREQ && !SMP && EXPERIMENTAL+ select CPU_FREQ_TABLE+ help+ This is a cpufreq driver for the S3C CPU. Currently it knows only+ about the S3C2410A.+ + Since all on-chip peripherals use a clock derived from the CPU clock,+ using this can cause a few problems, like timing jitter and dropped+ bytes on the serial ports.+ + If in doubt, say N.++endmenudiff --git a/arch/arm/mach-s3c2410/Makefile b/arch/arm/mach-s3c2410/Makefileindex 3e7a855..d2b0aa0 100644--- a/arch/arm/mach-s3c2410/Makefile+++ b/arch/arm/mach-s3c2410/Makefile@@ -29,3 +29,6 @@ obj-$(CONFIG_MACH_AML_M5900) += mach-amlm5900.o obj-$(CONFIG_BAST_PC104_IRQ) += bast-irq.o obj-$(CONFIG_MACH_VR1000) += mach-vr1000.o usb-simtec.o obj-$(CONFIG_MACH_QT2410) += mach-qt2410.o++# cpufreq+obj-$(CONFIG_S3C2410_CPUFREQ) += s3c2410-cpufreq.odiff --git a/arch/arm/mach-s3c2410/s3c2410-cpufreq.c b/arch/arm/mach-s3c2410/s3c2410-cpufreq.cnew file mode 100644index 0000000..7618937--- /dev/null+++ b/arch/arm/mach-s3c2410/s3c2410-cpufreq.c@@ -0,0 +1,346 @@+/* S3C2410 cpufreq driver */++#include <asm/arch/regs-clock.h>+#include <asm/arch/regs-gpio.h>+#include <asm/arch/regs-mem.h>+#include <asm/io.h>+#include <asm/plat-s3c24xx/clock.h>+#include <asm/system.h>+#include <linux/bug.h>+#include <linux/cpufreq.h>+#include <linux/errno.h>+#include <linux/init.h>+#include <linux/kernel.h>+#include <linux/module.h>++#define S3C2410_CPUFREQ_NAME "s3c2410-cpufreq"++/*+ * Locking rules:+ *+ * This driver assumes it's the only one changing MPLLCON, CLKSLOW and CLKDIVN.+ * All reads are unlocked, and might happen in other drivers.+ *+ * The only function which sets any of these registers is s3c2410_cpufreq_set,+ * which disables all interrupts during the changes.+ *+ * The same rules are assumed to hold for the clk_mpll, clk_fclk, clk_hclk, and+ * clk_pclk rate values.+ */++#ifdef CONFIG_SMP+#error This driver is not SMP safe.+#endif++/* TODO: think of what to do with different xtal values */+#define S3C2410_XTAL_KHZ 12000++/* CLKDIVN used for SLOW mode. */+#define S3C2410_CLKDIVN_SLOW 0+/* CLKDIVN used for NORMAL mode. */+#define S3C2410_CLKDIVN_NORMAL (S3C2410_CLKDIVN_HDIVN | S3C2410_CLKDIVN_PDIVN)++/* Mask for the bits to be changed in CLKSLOW. */+#define S3C2410_CLKSLOW_SLOWVAL_MASK 0x7+#define S3C2410_CLKSLOW_SLOW_MASK (S3C2410_CLKSLOW_MPLL_OFF | \+ S3C2410_CLKSLOW_SLOW | S3C2410_CLKSLOW_SLOWVAL_MASK)++/* Generate a value to be set in CLKSLOW. */+#define S3C2410_SLOW(v) (S3C2410_CLKSLOW_MPLL_OFF | S3C2410_CLKSLOW_SLOW | \+ S3C2410_CLKSLOW_SLOWVAL(v))++/* Table of hardware register values for each frequency. */+static struct s3c2410_freq_info {+ unsigned int frequency;+ u32 clkslow;+ u32 pllcon;+ u32 clkdivn;+ u32 refresh; /* calculated value */+} s3c2410_freq_table[] = {+ /* SLOW modes */+ { S3C2410_XTAL_KHZ/1, S3C2410_SLOW(0), 0, S3C2410_CLKDIVN_SLOW, },+ { S3C2410_XTAL_KHZ/2, S3C2410_SLOW(1), 0, S3C2410_CLKDIVN_SLOW, },+ { S3C2410_XTAL_KHZ/4, S3C2410_SLOW(2), 0, S3C2410_CLKDIVN_SLOW, },+ { S3C2410_XTAL_KHZ/6, S3C2410_SLOW(3), 0, S3C2410_CLKDIVN_SLOW, },+ { S3C2410_XTAL_KHZ/8, S3C2410_SLOW(4), 0, S3C2410_CLKDIVN_SLOW, },+ { S3C2410_XTAL_KHZ/10, S3C2410_SLOW(5), 0, S3C2410_CLKDIVN_SLOW, },+ { S3C2410_XTAL_KHZ/12, S3C2410_SLOW(6), 0, S3C2410_CLKDIVN_SLOW, },+ /* not a nice round number for S3C2410_XTAL_KHZ == 12000 */+ /*{ S3C2410_XTAL_KHZ/14, S3C2410_SLOW(7), 0, S3C2410_CLKDIVN_SLOW, },*/++ /* PLL modes */+#if S3C2410_XTAL_KHZ != 12000+#error Hardcoded values for 12 MHz xtal+#endif+ /* FCLK must be more than 3x XTIpll */+ /*{ 33750, 0, S3C2410_PLLVAL(82, 2, 3), S3C2410_CLKDIVN_NORMAL, },*/+ { 45000, 0, S3C2410_PLLVAL(82, 1, 3), S3C2410_CLKDIVN_NORMAL, },+ { 50700, 0, S3C2410_PLLVAL(161, 3, 3), S3C2410_CLKDIVN_NORMAL, },+ { 56250, 0, S3C2410_PLLVAL(142, 2, 3), S3C2410_CLKDIVN_NORMAL, },+ { 67500, 0, S3C2410_PLLVAL(82, 2, 2), S3C2410_CLKDIVN_NORMAL, },+ { 79000, 0, S3C2410_PLLVAL(71, 1, 2), S3C2410_CLKDIVN_NORMAL, },+ { 84750, 0, S3C2410_PLLVAL(105, 2, 2), S3C2410_CLKDIVN_NORMAL, },+ { 90000, 0, S3C2410_PLLVAL(112, 2, 2), S3C2410_CLKDIVN_NORMAL, },+ { 101250, 0, S3C2410_PLLVAL(127, 2, 2), S3C2410_CLKDIVN_NORMAL, },+ { 113000, 0, S3C2410_PLLVAL(105, 1, 2), S3C2410_CLKDIVN_NORMAL, },+ { 118500, 0, S3C2410_PLLVAL(150, 2, 2), S3C2410_CLKDIVN_NORMAL, },+ { 124000, 0, S3C2410_PLLVAL(116, 1, 2), S3C2410_CLKDIVN_NORMAL, },+ { 135000, 0, S3C2410_PLLVAL(82, 2, 1), S3C2410_CLKDIVN_NORMAL, },+ { 147000, 0, S3C2410_PLLVAL(90, 2, 1), S3C2410_CLKDIVN_NORMAL, },+ { 152000, 0, S3C2410_PLLVAL(68, 1, 1), S3C2410_CLKDIVN_NORMAL, },+ { 158000, 0, S3C2410_PLLVAL(71, 1, 1), S3C2410_CLKDIVN_NORMAL, },+ { 170000, 0, S3C2410_PLLVAL(77, 1, 1), S3C2410_CLKDIVN_NORMAL, },+ { 180000, 0, S3C2410_PLLVAL(82, 1, 1), S3C2410_CLKDIVN_NORMAL, },+ { 186000, 0, S3C2410_PLLVAL(85, 1, 1), S3C2410_CLKDIVN_NORMAL, },+ { 192000, 0, S3C2410_PLLVAL(88, 1, 1), S3C2410_CLKDIVN_NORMAL, },+ { 202800, 0, S3C2410_PLLVAL(161, 3, 1), S3C2410_CLKDIVN_NORMAL, },+ { 266000, 0, S3C2410_PLLVAL(125, 1, 1), S3C2410_CLKDIVN_NORMAL, },+ /* no overclocking */+};++/* Auxiliary table for the cpufreq frequency table helpers. */+static struct cpufreq_frequency_table+s3c2410_frequency_table[ARRAY_SIZE(s3c2410_freq_table) + 1];++/* Original frequency from before loading this driver. */+static s3c2410_freq_info original_freq;++/* Get the current frequency. */+static unsigned int s3c2410_cpufreq_get(unsigned int cpu __maybe_unused)+{+ u32 clkslow = __raw_readl(S3C2410_CLKSLOW);+ if ((clkslow & S3C2410_CLKSLOW_SLOW)) {+ unsigned slowval = S3C2410_CLKSLOW_GET_SLOWVAL(clkslow);+ if (!slowval)+ return S3C2410_XTAL_KHZ;+ else+ return S3C2410_XTAL_KHZ / (slowval * 2);+ } else+ /* FIXME: if in fast bus mode and HDIV=1, the CPU will be using+ * HCLK instead of FCLK. */+ return s3c2410_get_pll(__raw_readl(S3C2410_MPLLCON),+ S3C2410_XTAL_KHZ * 1000) / 1000;+}++/* Updates the frequencies in the clock subsystem. */+static inline void s3c2410_cpufreq_update_clk(unsigned long mpll,+ unsigned long fclk, unsigned long hclk, unsigned long pclk)+{+ /* FIXME: This might not be enough, if some child clock has set the+ * rate manually. */+ clk_mpll.rate = mpll;+ clk_f.rate = fclk;+ clk_h.rate = hclk;+ clk_p.rate = pclk;+}++/* Do all the changes needed to switch to another frequency. */+static void s3c2410_cpufreq_set(const struct s3c2410_freq_info *freq_info)+{+ u32 clkslow, refresh;+ unsigned long mpll, fclk, hclk, pclk;+ unsigned long flags;++ local_irq_save(flags);++ /* FIXME: I'm not sure if this is the ideal order for writing to the+ * registers. */++ if ((freq_info->clkslow & S3C2410_CLKSLOW_SLOW)) {+ /* We're entering slow mode from slow mode or PLL mode. */+ clkslow = __raw_readl(S3C2410_CLKSLOW);+ clkslow &= ~S3C2410_CLKSLOW_SLOW_MASK;+ clkslow |= freq_info->clkslow;+ __raw_writel(clkslow, S3C2410_CLKSLOW);++ /* Set the clock dividers. */+ __raw_writel(freq_info->clkdivn, S3C2410_CLKDIVN);+ } else {+ __raw_writel(freq_info->pllcon, S3C2410_MPLLCON);++ /* Set the clock dividers. */+ __raw_writel(freq_info->clkdivn, S3C2410_CLKDIVN);++ /* If we are in slow mode, turn it off and enable the PLL. */+ clkslow = __raw_readl(S3C2410_CLKSLOW);+ if ((clkslow & S3C2410_CLKSLOW_SLOW))+ __raw_writel(clkslow & ~S3C2410_CLKSLOW_MASK,+ S3C2410_CLKSLOW);+ }++ /* Set the SDRAM refresh counter. */+ refresh = __raw_readl(S3C2410_REFRESH);+ refresh &= ~S3C2410_REFRESH_REFCOUNTER;+ refresh |= freq_info->refresh;+ __raw_writel(refresh, S3C2410_REFRESH);++ /* Update the clocks subsystem. */+ mpll = (freq_info->clkslow & S3C2410_CLKSLOW_MPLL_OFF)+ ? 0 : freq_info->frequency * 1000;+ fclk = freq_info->frequency * 1000;+ /* We know we aren't using HDIVN1. */+ hclk = (freq_info->clkdivn & S3C2410_CLKDIVN_HDIVN)+ ? fclk / 2 : fclk;+ pclk = (freq_info->clkdivn & S3C2410_CLKDIVN_PDIVN)+ ? hclk / 2 : hclk;+ s3c2410_cpufreq_update_clk(mpll, fclk, hclk, pclk);++ local_irq_restore(flags);+}++static int s3c2410_cpufreq_init(struct cpufreq_policy *policy)+{+ unsigned int original_hclk_khz;+ unsigned index;+ int err;++ /* Save original parameters. */+ original_freq.frequency = s3c2410_cpufreq_get(policy->cpu);+ original_freq.clkslow = __raw_readl(S3C2410_CLKSLOW)+ & S3C2410_CLKSLOW_SLOW_MASK;+ original_freq.pllcon = __raw_readl(S3C2410_MPLLCON);+ original_freq.clkdivn = __raw_readl(S3C2410_CLKDIVN);+ original_freq.refresh = __raw_readl(S3C2410_REFRESH)+ & S3C2410_REFRESH_REFCOUNTER;++ /* FIXME: Should also consider HDIVN1. */+ original_hclk_khz = (original_freq.clkdivn & S3C2410_CLKDIVN_HDIVN)+ ? original_freq.frequency / 2+ : original_freq.frequency;++ /* Calculate the value for the SDRAM refresh counter, based on the+ * original value. */+ for (index = 0; index < ARRAY_SIZE(s3c2410_freq_table); ++index) {+ unsigned int frequency, hclk_khz;+ u32 refresh;++ frequency = s3c2410_freq_table[index].frequency;+ /* FIXME: Should also consider HDIVN1. */+ hclk_khz = (s3c2410_freq_table[index].clkdivn+ & S3C2410_CLKDIVN_HDIVN)+ ? frequency / 2 : frequency;+ refresh = (1<<11) + 1 - (((1<<11) - original_freq.refresh + 1)+ * hclk_khz / original_hclk_khz);++ cpufreq_debug_printk(CPUFREQ_DEBUG_DRIVER,+ S3C2410_CPUFREQ_NAME,+ "frequency: %u kHz, refresh: %u\n",+ frequency, refresh);++ /* Shouldn't happen. */+ if (unlikely(refresh > S3C2410_REFRESH_REFCOUNTER)) {+ printk(KERN_WARN S3C2410_CPUFREQ_NAME+ ": invalid refresh counter (%u)"+ " for frequency %u kHz.\n",+ refresh, frequency);+ s3c2410_frequency_table[index].frequency+ = CPUFREQ_ENTRY_INVALID;+ continue;+ }++ s3c2410_freq_table[index].refresh = refresh;+ }++ policy->cpuinfo.transition_latency = 150000; /* 150us PLL lock time*/++ /* Sets policy->cpuinfo.min_freq, policy->cpuinfo.max_freq,+ * policy->min, and policy->max. */+ err = cpufreq_frequency_table_cpuinfo(policy);+ if (unlikely(err))+ return err;++ policy->cur = original_freq.frequency;+ policy->policy = CPUFREQ_POLICY_PERFORMANCE;+ policy->governor = CPUFREQ_DEFAULT_GOVERNOR;++ return 0;+}++static int s3c2410_cpufreq_verify(struct cpufreq_policy *policy)+{+ return cpufreq_frequency_table_verify(policy, s3c2410_frequency_table);+}++static int s3c2410_cpufreq_target(struct cpufreq_policy *policy,+ unsigned int target_freq, unsigned int relation)+{+ unsigned int index;++ err = cpufreq_frequency_table_target(policy, s3c2410_frequency_table,+ target_freq, relation, &index);+ if (unlikely(err))+ return err;++ s3c2410_cpufreq_set(&s3c2410_freq_table[index]);+ return 0;+}++static int s3c2410_cpufreq_exit(struct cpufreq_policy *policy __maybe_unused)+{+ /* Restore original parameters. */+ s3c2410_cpufreq_set(&original_freq);+ return 0;+}++static int s3c2410_cpufreq_suspend(struct cpufreq_policy *policy __maybe_unused,+ pm_message_t pmsg __maybe_unused)+{+ /* Restore original parameters. */+ s3c2410_cpufreq_set(&original_freq);+ return 0;+}++static struct cpufreq_driver s3c2410_cpufreq_driver = {+ .name = S3C2410_CPUFREQ_NAME,+ .owner = THIS_MODULE,+ .flags = CPUFREQ_PM_NO_WARN,+ .init = s3c2410_cpufreq_init,+ .verify = s3c2410_cpufreq_verify,+ .target = s3c2410_cpufreq_target,+ .get = s3c2410_cpufreq_get,+ .exit = s3c2410_cpufreq_exit,+ .suspend = s3c2410_cpufreq_suspend,+};++static int __init s3c2410_cpufreq_module_init(void)+{+ unsigned index;++ /* We only know about the S3C2410A so far. */+ /* CPU checking based on arch/arm/plat-s3c24xx/cpu.c */+ if (unlikely(cpu_architecture() >= CPU_ARCH_ARMv5))+ return -ENODEV;+ if (unlikely(__raw_readl(S3C2410_GSTATUS1) != S3C2410_GSTATUS1_2410A))+ return -ENODEV;++ /* A lot of values are hardcoded for a 12 MHz xtal. */+ if (unlikely(clk_xtal.rate != S3C2410_XTAL_KHZ * 1000))+ return -EINVAL;++ /* Fill the table used by the frequency helpers. */+ for (index = 0; index < ARRAY_SIZE(s3c2410_freq_table); ++index) {+ s3c2410_frequency_table[index].index = index;+ s3c2410_frequency_table[index].frequency =+ s3c2410_freq_table[index].frequency;+ }+ s3c2410_frequency_table[index].frequency = CPUFREQ_TABLE_END;++ return cpufreq_register_driver(&s3c2410_cpufreq_driver);+}++static void __exit s3c2410_cpufreq_module_exit(void)+{+ cpufreq_unregister_driver(&s3c2410_cpufreq_driver);+}++/*+ * FIXME: The documentation contradicts itself. It says level 7 (which is+ * late_initcall()) or later but in a parenthesis says module_init (which is+ * level 6). Some drivers use one, some drivers use the other.+ */+late_initcall(s3c2410_cpufreq_module_init);+module_exit(s3c2410_cpufreq_module_exit);++MODULE_LICENSE("GPL");+MODULE_AUTHOR("Cesar Eduardo Barros <cesarb@cesarb.net>");+MODULE_DESCRIPTION("S3C2410 cpufreq driver");diff --git a/include/asm-arm/arch-s3c2410/regs-gpio.h b/include/asm-arm/arch-s3c2410/regs-gpio.hindex b693158..787be5b 100644--- a/include/asm-arm/arch-s3c2410/regs-gpio.h+++ b/include/asm-arm/arch-s3c2410/regs-gpio.h@@ -1104,6 +1104,7 @@ #define S3C2410_GSTATUS1_IDMASK (0xffff0000) #define S3C2410_GSTATUS1_2410 (0x32410000)+#define S3C2410_GSTATUS1_2410A (0x32410002) #define S3C2410_GSTATUS1_2412 (0x32412001) #define S3C2410_GSTATUS1_2440 (0x32440000) #define S3C2410_GSTATUS1_2442 (0x32440aaa)diff --git a/include/asm-arm/arch-s3c2410/system.h b/include/asm-arm/arch-s3c2410/system.hindex 6389178..aa8cc4d 100644--- a/include/asm-arm/arch-s3c2410/system.h+++ b/include/asm-arm/arch-s3c2410/system.h@@ -28,6 +28,10 @@ void s3c24xx_default_idle(void) unsigned long tmp; int i; + /* Do nothing if in slow mode. */+ if ((__raw_readl(S3C2410_CLKSLOW) & S3C2410_CLKSLOW_SLOW))+ return;+ /* idle the system by using the idle mode which will wait for an * interrupt to happen before restarting the system. */