[PATCH 2/9] Experimental S3C2410A cpufreq driver (core)

Cesar Eduardo Barros cesarb at cesarb.net
Wed Feb 13 02:24:30 CET 2008


This is a cpufreq driver for the S3C2410A. It deals only with the main
frequency switching part and with the SDRAM refresh counter. The rest of the
hardware should be dealt with via cpufreq notifiers on each of the drivers.

It also has experimental support for the S3C2442.

Signed-off-by: Cesar Eduardo Barros <cesarb at cesarb.net>
---
 arch/arm/Kconfig                               |   16 +-
 arch/arm/mach-s3c2410/Makefile                 |    3 +
 arch/arm/mach-s3c2410/s3c2410-cpufreq.c        | 1465 ++++++++++++++++++++++++
 include/asm-arm/arch-s3c2410/regs-clock.h      |    1 +
 include/asm-arm/arch-s3c2410/regs-gpio.h       |    1 +
 include/asm-arm/arch-s3c2410/s3c2410-cpufreq.h |   88 ++
 include/asm-arm/arch-s3c2410/system.h          |    4 +
 include/asm-arm/plat-s3c24xx/clock.h           |    5 +
 8 files changed, 1582 insertions(+), 1 deletions(-)
 create mode 100644 arch/arm/mach-s3c2410/s3c2410-cpufreq.c
 create mode 100644 include/asm-arm/arch-s3c2410/s3c2410-cpufreq.h

diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index ef7b9ff..39c8b2b 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -874,7 +874,7 @@ config ATAGS_PROC
 
 endmenu
 
-if (ARCH_SA1100 || ARCH_INTEGRATOR || ARCH_OMAP || ARCH_IMX )
+if (ARCH_SA1100 || ARCH_INTEGRATOR || ARCH_OMAP || ARCH_IMX || ARCH_S3C2410 )
 
 menu "CPU Frequency scaling"
 
@@ -910,6 +910,20 @@ config CPU_FREQ_IMX
 
 	  If in doubt, say N.
 
+config CPU_FREQ_S3C2410
+	tristate "S3C2410 CPU Frequence scaling"
+	depends on (CPU_S3C2410 || CPU_S3C2442) && 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 and the S3C2442.
+	  
+	  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.
+
 endmenu
 
 endif
diff --git a/arch/arm/mach-s3c2410/Makefile b/arch/arm/mach-s3c2410/Makefile
index bb577f4..d75a292 100644
--- a/arch/arm/mach-s3c2410/Makefile
+++ b/arch/arm/mach-s3c2410/Makefile
@@ -31,3 +31,6 @@ 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
 obj-$(CONFIG_MACH_NEO1973_GTA01)+= mach-gta01.o
+
+# cpufreq
+obj-$(CONFIG_CPU_FREQ_S3C2410)	+= s3c2410-cpufreq.o
diff --git a/arch/arm/mach-s3c2410/s3c2410-cpufreq.c b/arch/arm/mach-s3c2410/s3c2410-cpufreq.c
new file mode 100644
index 0000000..e86ece4
--- /dev/null
+++ b/arch/arm/mach-s3c2410/s3c2410-cpufreq.c
@@ -0,0 +1,1465 @@
+/*
+ *  S3C2410 cpufreq driver
+ *  Copyright (C) 2008  Cesar Eduardo Barros <cesarb at cesarb.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <asm/arch/regs-clock.h>
+#include <asm/arch/regs-gpio.h>
+#include <asm/arch/regs-mem.h>
+#include <asm/arch/s3c2410-cpufreq.h>
+#include <asm/div64.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/delay.h>
+#include <linux/errno.h>
+#include <linux/hardirq.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/preempt.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#define S3C2410_CPUFREQ_NAME "s3c2410-cpufreq"
+
+#ifdef CONFIG_SMP
+#error This driver is not SMP safe.
+#endif
+
+struct s3c2410_freq_info_dividers {
+	u32	clkdivn;
+	u32	camdivn;
+};
+
+struct s3c2410_freq_info {
+	unsigned int				frequency;
+	u32					clkslow;
+	u32					pllcon;
+	struct s3c2410_freq_info_dividers	divn;	/* maximum value */
+};
+
+struct s3c2410_cpufreq_ops {
+	/* Get the current frequency, without locking. */
+	unsigned int (*get)(void);
+
+	/* Calculate the frequencies to be updated in the clock subsystem. */
+	void (*calc_clk)(const struct s3c2410_freq_info *freq_info,
+		const struct s3c2410_freq_info_dividers *freq_info_dividers,
+		struct s3c2410_cpufreq_clocks *clocks);
+
+	/* Prepare to do all the changes needed to switch to another frequency.
+	 * This function runs before the drivers are notified, and before any
+	 * locks are taken. Interrupts are enabled. */
+	void (*pre_set)(const struct s3c2410_freq_info *freq_info,
+		const struct s3c2410_freq_info_dividers *freq_info_dividers,
+		const struct s3c2410_cpufreq_freqs *freqs);
+
+	/* Do all the changes needed to switch to another frequency. */
+	void (*set)(const struct s3c2410_freq_info *freq_info,
+		const struct s3c2410_freq_info_dividers *freq_info_dividers,
+		const struct s3c2410_cpufreq_freqs *freqs);
+
+	/* Compares a divider setting for equality with the maximum value. */
+	bool (*freq_info_dividers_equal)(
+		const struct s3c2410_freq_info *freq_info,
+		const struct s3c2410_freq_info_dividers *freq_info_dividers);
+
+	/* Verifies if a divider setting is valid for this frequency. */
+	bool (*freq_info_dividers_valid)(
+		const struct s3c2410_freq_info *freq_info,
+		const struct s3c2410_freq_info_dividers *freq_info_dividers);
+
+	/* Save original parameters, without locking. */
+	void (*save_original_freqs)(struct s3c2410_freq_info *freq_info);
+
+	/* Calculate PLL lock time. */
+	unsigned int (*get_transition_latency)(void);
+
+	/* To help debug power usage. */
+	ssize_t (*enabled_clocks_show)(char *buf);
+};
+
+static const struct s3c2410_cpufreq_info {
+	const struct s3c2410_cpufreq_ops *ops;
+
+	/* Table of hardware register values for each frequency. */
+	const struct s3c2410_freq_info *freq_table;
+	size_t freq_table_size;
+
+	/* Index within freq_table which should always use the max divider. */
+	unsigned int performance_index;
+
+	/* Table of hardware register values for each divider setting. */
+	const struct s3c2410_freq_info_dividers *freq_table_dividers;
+	size_t freq_table_dividers_size;
+
+	/* PLL lock time. */
+	unsigned int transition_latency;
+
+	/* Crystal frequency. */
+	unsigned long xtal_rate;
+} *s3c2410_cpufreq_info;
+
+/* Auxiliary table for the cpufreq frequency table helpers. */
+static struct cpufreq_frequency_table *s3c2410_frequency_table;
+/* Auxiliary table for the cpufreq frequency table target helper. */
+static struct cpufreq_frequency_table *s3c2410_frequency_table_target;
+
+/* Table of divider settings allowed by the policy helpers. */
+static bool *s3c2410_frequency_table_target_dividers;
+
+/* Original frequency from before loading this driver. */
+static struct s3c2410_freq_info original_freq;
+/* Original refresh counter from before loading this driver. */
+static u32 original_refresh;
+/* Cached value of s3c2410_cpufreq_calc_clk(). */
+static struct s3c2410_cpufreq_clocks original_clocks;
+
+/* Current frequency. */
+static const struct s3c2410_freq_info *current_freq;
+/* Current divider settings. */
+static const struct s3c2410_freq_info_dividers *current_freq_dividers;
+
+/* Get the current frequency. */
+static unsigned int s3c2410_cpufreq_get(unsigned int cpu __maybe_unused)
+{
+	const struct s3c2410_cpufreq_info *info = s3c2410_cpufreq_info;
+	unsigned int ret;
+
+	mutex_lock(&clocks_mutex);
+	ret = info->ops->get();
+	mutex_unlock(&clocks_mutex);
+
+	return ret;
+}
+
+/* Updates the frequencies in the clock subsystem. */
+static void _s3c2410_cpufreq_update_clk(
+		const struct s3c2410_cpufreq_clocks *clocks)
+{
+	/* The caller holds the mutex. */
+
+	/* FIXME: This might not be enough, if some child clock has set the
+	 * rate manually. */
+	clk_mpll.rate = clocks->mpll;
+	clk_f.rate = clocks->fclk;
+	clk_h.rate = clocks->hclk;
+	clk_p.rate = clocks->pclk;
+}
+
+/* Saved pointer to the s3c2410_cpufreq_freqs struct on the stack. */
+static const struct s3c2410_cpufreq_freqs *s3c2410_cpufreq_set_target_freqs;
+
+/*
+ * Gets the s3c2410_cpufreq_freqs struct associated with the current notifier
+ * call, if possible.
+ *
+ * This function should only be called from within a cpufreq transition
+ * notifier. If the notifier was called from this driver, the cpufreq_freqs
+ * struct passed is in fact a s3c2410_cpufreq_freqs struct, which has extra
+ * information; this function retrieves it.
+ *
+ * If the notifier was called from the cpufreq core (which should happen only
+ * in unusual conditions where it gets out of sync), there is no extra
+ * information; in that case, this function returns NULL.
+ */
+const struct s3c2410_cpufreq_freqs *s3c2410_cpufreq_target_freqs(
+		struct cpufreq_freqs *freqs __maybe_unused)
+{
+	return s3c2410_cpufreq_set_target_freqs;
+}
+EXPORT_SYMBOL_GPL(s3c2410_cpufreq_target_freqs);
+
+/* Do all the changes needed to switch to another frequency. */
+static void s3c2410_cpufreq_set(struct cpufreq_policy *policy,
+		const struct s3c2410_freq_info *freq_info,
+		const struct s3c2410_freq_info_dividers *freq_info_dividers)
+{
+	const struct s3c2410_cpufreq_info *info = s3c2410_cpufreq_info;
+	struct s3c2410_cpufreq_freqs freqs;
+	u32 refresh, refresh_val;
+	unsigned long flags;
+
+	freqs.freqs.cpu = policy->cpu;
+	freqs.freqs.old = s3c2410_cpufreq_get(policy->cpu);
+	freqs.freqs.new = freq_info->frequency;
+
+	/* Refuse to do useless transitions. */
+	if (freqs.freqs.old == freqs.freqs.new)
+		return;
+
+	/* Calculate the old and new clock values. */
+	info->ops->calc_clk(current_freq, current_freq_dividers, &freqs.old);
+	info->ops->calc_clk(freq_info, freq_info_dividers, &freqs.new);
+
+	/* Calculate the new refresh counter. */
+	refresh_val = (1<<11) + 1 -
+		cpufreq_scale((1<<11) - original_refresh + 1,
+				original_clocks.hclk, freqs.new.hclk);
+	if (unlikely(refresh_val > S3C2410_REFRESH_REFCOUNTER))
+		refresh_val = S3C2410_REFRESH_REFCOUNTER;
+
+	/* Save freqs for s3c2410_cpufreq_target_freqs which might be called
+	 * from the transition notifier. */
+	s3c2410_cpufreq_set_target_freqs = &freqs;
+
+	if (info->ops->pre_set)
+		info->ops->pre_set(freq_info, freq_info_dividers, &freqs);
+
+	/* Notify the drivers before the transition. */
+	cpufreq_notify_transition(&freqs.freqs, CPUFREQ_PRECHANGE);
+
+	/* Disable preemption to avoid delaying the CPUFREQ_POSTCHANGE
+	 * notify. */
+	preempt_disable();
+
+	/* Not only lock the mutex, but also disable interrupts during the
+	 * transition. */
+	mutex_lock(&clocks_mutex);
+	local_irq_save(flags);
+	local_fiq_disable();	/* Will be restored by local_irq_restore(). */
+
+	info->ops->set(freq_info, freq_info_dividers, &freqs);
+
+	/* Set the SDRAM refresh counter. */
+	refresh = __raw_readl(S3C2410_REFRESH);
+	refresh &= ~S3C2410_REFRESH_REFCOUNTER;
+	refresh |= refresh_val;
+	__raw_writel(refresh, S3C2410_REFRESH);
+
+	/* Update the clocks subsystem. */
+	_s3c2410_cpufreq_update_clk(&freqs.new);
+
+	/* We must enable interrupts (and preempt) only after updating the
+	 * clocks subsystem, since clk_get_rate() is unlocked. */
+	local_irq_restore(flags);
+	mutex_unlock(&clocks_mutex);
+
+	cpufreq_debug_printk(CPUFREQ_DEBUG_DRIVER, S3C2410_CPUFREQ_NAME,
+		"mpll: %u Hz, fclk: %u Hz, hclk: %u Hz, pclk: %u Hz, "
+		"refresh: %u\n",
+		freqs.new.mpll, freqs.new.fclk, freqs.new.hclk, freqs.new.pclk,
+		refresh_val);
+
+	/* Notify the drivers after the transition. */
+	cpufreq_notify_transition(&freqs.freqs, CPUFREQ_POSTCHANGE);
+
+	/* Should be enabled only after CPUFREQ_POSTCHANGE; see comment above
+	 * on preempt_enable(). */
+	preempt_enable();
+
+	/* Avoid leaving a dangling pointer to the stack. The notifiers can
+	 * also be called directly from within the cpufreq core. */
+	s3c2410_cpufreq_set_target_freqs = NULL;
+
+	/* Save freq_info for the next call to this function. */
+	current_freq = freq_info;
+	current_freq_dividers = freq_info_dividers;
+}
+
+/* Helper for drivers who want to implement CPUFREQ_ADJUST. */
+int s3c2410_cpufreq_adjust_table(struct cpufreq_policy *policy,
+		s3c2410_cpufreq_adjust_table_cb cb, void *data)
+{
+	const struct s3c2410_cpufreq_info *info = s3c2410_cpufreq_info;
+	unsigned int index;
+	unsigned int min = UINT_MAX, max = 0;
+
+	/*
+	 * This code abuses slightly the core cpufreq interfaces. It not only
+	 * finds the range of valid frequencies, it also invalidates the
+	 * invalid frequencies on the table used by ->target. This is needed
+	 * due to the serial driver, which can work on some frequencies but
+	 * not others.
+	 *
+	 * The invalid frequencies are cleared at the beginning of
+	 * CPUFREQ_ADJUST via the s3c2410_cpufreq_pre_adjust() notifier call.
+	 */
+	for (index = 0; index < info->freq_table_size; ++index) {
+		unsigned int frequency = info->freq_table[index].frequency;
+		struct s3c2410_cpufreq_clocks clocks;
+		bool valid = false;
+		unsigned int index2, tmp;
+
+		/* Skip if outside the policy range. */
+		if (frequency < policy->min || frequency > policy->max)
+			continue;
+
+		/* Skip if already marked as invalid. */
+		if (s3c2410_frequency_table_target[index].frequency
+				== CPUFREQ_ENTRY_INVALID)
+			continue;
+
+		/* Try to find a valid set of dividers. */
+		for (index2 = 0;
+				index2 < info->freq_table_dividers_size;
+				++index2) {
+			/* Skip if already marked as invalid. */
+			if (!s3c2410_frequency_table_target_dividers
+					[index * info->freq_table_dividers_size
+						+ index2])
+				continue;
+
+			/* Skip if not a valid combination. */
+			if (!info->ops->freq_info_dividers_valid(
+					&info->freq_table[index],
+					&info->freq_table_dividers[index2]))
+				continue;
+
+			/* Ask the callback if it's valid. */
+			info->ops->calc_clk(&info->freq_table[index],
+				&info->freq_table_dividers[index2],
+				&clocks);
+			valid = cb(data, &clocks);
+			if (valid)
+				cpufreq_debug_printk(CPUFREQ_DEBUG_DRIVER,
+					S3C2410_CPUFREQ_NAME,
+					"adjust_table: valid index %u(%u)\n",
+					index, index2);
+
+			/*
+			 * This is subtle. If the driver does not work on any
+			 * of the divider settings, we do not want to overwrite
+			 * all divider settings. Instead, we wait until it
+			 * allows at least one divider setting.
+			 */
+			if (valid)
+				break;
+
+			/*
+			 * Stop if we hit the fastest allowed divider settings
+			 * (which are the ones on s3c2410_freq_table).
+			 *
+			 * This depends on s3c2410_freq_table_dividers being
+			 * correctly sorted.
+			 */
+			if (info->ops->freq_info_dividers_equal(
+					&info->freq_table[index],
+					&info->freq_table_dividers[index2]))
+				break;
+
+		}
+
+		/*
+		 * Mark all skipped divider settings, if valid.
+		 *
+		 * If not valid, either it will be marked as
+		 * CPUFREQ_ENTRY_INVALID, or no change should happen.
+		 */
+		if (valid) {
+			for (tmp = 0; tmp < index2; ++tmp)
+				s3c2410_frequency_table_target_dividers
+					[index * info->freq_table_dividers_size
+						+ tmp] = false;
+		}
+
+		/*
+		 * This is subtle. If the driver does not work on any of the
+		 * frequencies, we do not want to overwrite all frequencies
+		 * with CPUFREQ_ENTRY_INVALID. Instead, we wait until it allows
+		 * at least one frequency.
+		 *
+		 * The driver will know it lost during the target notifier.
+		 */
+		if (!valid && !max)
+			continue;
+
+		/* If max is not set yet, this is the first valid frequency. */
+		if (valid && !max) {
+			/*
+			 * Mark all the skipped values as invalid, since they
+			 * might be within the policy range if the table is not
+			 * sorted.
+			 */
+			for (tmp = 0; tmp < index; ++tmp)
+				s3c2410_frequency_table_target[tmp].frequency
+						= CPUFREQ_ENTRY_INVALID;
+		}
+
+		/* Not valid, so mark as invalid and continue. */
+		if (!valid) {
+			s3c2410_frequency_table_target[index].frequency
+					= CPUFREQ_ENTRY_INVALID;
+			continue;
+		}
+
+		/* Save the max/min values. */
+		min = min(min, frequency);
+		max = max(max, frequency);
+	}
+
+	/* Update the policy if a valid range was found. */
+	if (max) {
+		policy->min = max(policy->min, min);
+		policy->max = min(policy->max, max);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(s3c2410_cpufreq_adjust_table);
+
+/* Resets s3c2410_frequency_table_target at the beginning of CPUFREQ_ADJUST. */
+static int s3c2410_cpufreq_pre_adjust(struct notifier_block *nb __maybe_unused,
+		unsigned long val, void *data __maybe_unused)
+{
+	const struct s3c2410_cpufreq_info *info = s3c2410_cpufreq_info;
+	unsigned index, index2;
+
+	if (val != CPUFREQ_ADJUST)
+		return 0;
+
+	/* Copy from the other table instead of the original table, since it
+	 * might already have some CPUFREQ_ENTRY_INVALID entries. */
+	for (index = 0; index < info->freq_table_size; ++index) {
+		s3c2410_frequency_table_target[index].frequency =
+				s3c2410_frequency_table[index].frequency;
+
+		if (index != info->performance_index) {
+			/* All dividers are allowed by default. */
+			for (index2 = 0;
+					index2 < info->freq_table_dividers_size;
+					++index2) {
+				s3c2410_frequency_table_target_dividers
+					[index * info->freq_table_dividers_size
+						+ index2] = true;
+			}
+		} else {
+			/* Special case for the performance index. */
+			for (index2 = 0;
+					index2 < info->freq_table_dividers_size;
+					++index2) {
+				s3c2410_frequency_table_target_dividers
+					[index * info->freq_table_dividers_size
+						+ index2] = info->ops
+						->freq_info_dividers_equal(
+						&info->freq_table[index],
+						&info->freq_table_dividers
+								[index2]);
+			}
+		}
+	}
+
+	return 0;
+}
+
+static struct notifier_block s3c2410_cpufreq_pre_adjust_nb = {
+	.notifier_call	= s3c2410_cpufreq_pre_adjust,
+	.priority	= 1000,	/* must run before anything else */
+};
+
+static bool s3c2410_cpufreq_pin;
+
+/* Helper for drivers who want to make CPUFREQ_ADJUST pin the policy to a
+ * fixed frequency, but don't care which frequency it is. */
+int s3c2410_cpufreq_adjust_pin(struct cpufreq_policy *policy __maybe_unused)
+{
+	/*
+	 * This is in fact implemented by waiting until the end of the
+	 * CPUFREQ_INCOMPATIBLE phase (which runs immediately after
+	 * CPUFREQ_ADJUST) and then simply setting policy->min to whatever
+	 * policy->max ended up being.
+	 */
+	s3c2410_cpufreq_pin = true;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(s3c2410_cpufreq_adjust_pin);
+
+/* Forces a fixed frequency at the end of CPUFREQ_INCOMPATIBLE. */
+static int s3c2410_cpufreq_post_incompatible(
+		struct notifier_block *nb __maybe_unused,
+		unsigned long val, void *data)
+{
+	struct cpufreq_policy *policy = data;
+
+	if (val != CPUFREQ_INCOMPATIBLE)
+		return 0;
+
+	if (s3c2410_cpufreq_pin)
+		policy->min = policy->max;
+
+	s3c2410_cpufreq_pin = false;
+
+	return 0;
+}
+
+static struct notifier_block s3c2410_cpufreq_post_incompatible_nb = {
+	.notifier_call	= s3c2410_cpufreq_post_incompatible,
+	.priority	= -1000,	/* must run after anything else */
+};
+
+static void s3c2410_cpufreq_update_policy_work_func(
+		struct work_struct *work __maybe_unused)
+{
+	unsigned int cpu;
+
+	for_each_online_cpu(cpu)
+		cpufreq_update_policy(cpu);
+}
+
+static DECLARE_WORK(s3c2410_cpufreq_update_policy_work,
+		s3c2410_cpufreq_update_policy_work_func);
+
+/* Asynchronous version of s3c2410_cpufreq_update_policy(). Can be called in
+ * contexts where s3c2410_cpufreq_update_policy() cannot be called, but will
+ * return before the policy has been updated. */
+void s3c2410_cpufreq_schedule_update_policy(void)
+{
+	schedule_work(&s3c2410_cpufreq_update_policy_work);
+}
+EXPORT_SYMBOL_GPL(s3c2410_cpufreq_schedule_update_policy);
+
+static int s3c2410_cpufreq_init(struct cpufreq_policy *policy)
+{
+	const struct s3c2410_cpufreq_info *info = s3c2410_cpufreq_info;
+	int err;
+
+	/* Save original parameters. */
+	mutex_lock(&clocks_mutex);
+	info->ops->save_original_freqs(&original_freq);
+	original_refresh = __raw_readl(S3C2410_REFRESH)
+			& S3C2410_REFRESH_REFCOUNTER;
+	mutex_unlock(&clocks_mutex);
+
+	info->ops->calc_clk(&original_freq, &original_freq.divn,
+			&original_clocks);
+
+	current_freq = &original_freq;
+	current_freq_dividers = &original_freq.divn;
+
+	policy->cpuinfo.transition_latency = info->transition_latency;
+
+	cpufreq_debug_printk(CPUFREQ_DEBUG_DRIVER, S3C2410_CPUFREQ_NAME,
+			"transition latency: %u ns\n",
+			info->transition_latency);
+
+	/* Sets policy->cpuinfo.min_freq, policy->cpuinfo.max_freq,
+	 * policy->min, and policy->max. */
+	err = cpufreq_frequency_table_cpuinfo(policy, s3c2410_frequency_table);
+	if (unlikely(err))
+		return err;
+
+	policy->cur = original_freq.frequency;
+	policy->policy = CPUFREQ_POLICY_PERFORMANCE;
+	policy->governor = CPUFREQ_DEFAULT_GOVERNOR;
+
+	cpufreq_frequency_table_get_attr(s3c2410_frequency_table, policy->cpu);
+
+	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)
+{
+	const struct s3c2410_cpufreq_info *info = s3c2410_cpufreq_info;
+	unsigned int index, index2;
+	int err;
+
+	err = cpufreq_frequency_table_target(policy,
+			s3c2410_frequency_table_target,
+			target_freq, relation, &index);
+	if (unlikely(err))
+		return err;
+
+	for (index2 = 0; index2 < info->freq_table_dividers_size; ++index2) {
+		if (s3c2410_frequency_table_target_dividers
+				[index * info->freq_table_dividers_size
+						+ index2])
+			goto found;
+	}
+	return -EINVAL;
+
+found:
+	s3c2410_cpufreq_set(policy, &info->freq_table[index],
+			&info->freq_table_dividers[index2]);
+	return 0;
+}
+
+static int s3c2410_cpufreq_exit(struct cpufreq_policy *policy)
+{
+	/* Restore original parameters. */
+	s3c2410_cpufreq_set(policy, &original_freq, &original_freq.divn);
+
+	cpufreq_frequency_table_put_attr(policy->cpu);
+
+	return 0;
+}
+
+static int s3c2410_cpufreq_suspend(struct cpufreq_policy *policy,
+		pm_message_t pmsg __maybe_unused)
+{
+	/* Restore original parameters. */
+	s3c2410_cpufreq_set(policy, &original_freq, &original_freq.divn);
+	return 0;
+}
+
+static ssize_t s3c2410_cpufreq_scaling_cur_clocks_show(
+		struct cpufreq_policy *policy __maybe_unused, char *buf)
+{
+	const struct s3c2410_cpufreq_info *info = s3c2410_cpufreq_info;
+	struct s3c2410_cpufreq_clocks clocks;
+
+	info->ops->calc_clk(current_freq, current_freq_dividers, &clocks);
+
+	return sprintf(buf, "%lu %lu %lu %lu\n", clocks.mpll, clocks.fclk,
+			clocks.hclk, clocks.pclk);
+}
+
+static struct freq_attr s3c2410_cpufreq_scaling_cur_clocks_attr = {
+	.attr = {
+		.name = "scaling_cur_clocks",
+		.mode = 0444,
+		.owner = THIS_MODULE,
+	},
+	.show = s3c2410_cpufreq_scaling_cur_clocks_show,
+};
+
+/* To help debug power usage. */
+static ssize_t s3c2410_cpufreq_enabled_clocks_show(
+		struct cpufreq_policy *policy __maybe_unused, char *buf)
+{
+	return s3c2410_cpufreq_info->ops->enabled_clocks_show(buf);
+}
+
+static struct freq_attr s3c2410_cpufreq_enabled_clocks_attr = {
+	.attr = {
+		.name = "enabled_clocks",
+		.mode = 0444,
+		.owner = THIS_MODULE,
+	},
+	.show = s3c2410_cpufreq_enabled_clocks_show,
+};
+
+static struct freq_attr *s3c2410_cpufreq_attr[] = {
+	&cpufreq_freq_attr_scaling_available_freqs,
+	&s3c2410_cpufreq_scaling_cur_clocks_attr,
+	&s3c2410_cpufreq_enabled_clocks_attr,
+	NULL
+};
+
+static struct cpufreq_driver s3c2410_cpufreq_driver = {
+	.name		= S3C2410_CPUFREQ_NAME,
+	.owner		= THIS_MODULE,
+	.init		= s3c2410_cpufreq_init,
+	.verify		= s3c2410_cpufreq_verify,
+	.target		= s3c2410_cpufreq_target,
+	.get		= s3c2410_cpufreq_get,
+	.exit		= s3c2410_cpufreq_exit,
+	.suspend	= s3c2410_cpufreq_suspend,
+	.attr		= s3c2410_cpufreq_attr,
+};
+
+#if defined (CONFIG_CPU_S3C2410) || defined(CONFIG_CPU_S3C2442)
+static ssize_t s3c2410_cpufreq_2410_enabled_clocks_show(char *buf)
+{
+	static const char * const clock_names[32 - 4] = {
+		"nand", "lcdc", "usb_host", "usb_device",
+		"pwmtimer", "sdi", "uart0", "uart1",
+		"uart2", "gpio", "rtc", "adc",
+		"iic", "iis", "spi", "camera",
+	};
+
+	const char * const *clock_name = clock_names;
+	ssize_t count = 0;
+
+	/* No need to lock the mutex here. */
+	u32 clkcon = __raw_readl(S3C2410_CLKCON);
+
+	/* Skip the first four bits. */
+	clkcon >>= 4;
+
+	while (clkcon) {
+		if ((clkcon & 0x1) && *clock_name)
+			count += sprintf(buf + count, "%s ", *clock_name);
+
+		clkcon >>= 1;
+		clock_name++;
+	}
+
+	/* Replace final space with newline. */
+	if (count)
+		buf[count - 1] = '\n';
+
+	return count;
+}
+#endif
+
+#ifdef CONFIG_CPU_S3C2410
+
+/* Hardcoded 12MHz XTAL */
+#define S3C2410_XTAL_KHZ 12000
+
+/* CLKDIVN values. */
+#define S3C2410_CLKDIVN_1_4_4 S3C2410_CLKDIVN_HDIVN1
+#define S3C2410_CLKDIVN_1_2_4 (S3C2410_CLKDIVN_HDIVN | S3C2410_CLKDIVN_PDIVN)
+#define S3C2410_CLKDIVN_1_2_2 S3C2410_CLKDIVN_HDIVN
+#define S3C2410_CLKDIVN_1_1_2 S3C2410_CLKDIVN_PDIVN
+#define S3C2410_CLKDIVN_1_1_1 0
+
+/* Table of hardware register values for each divider setting. */
+static const struct s3c2410_freq_info_dividers
+s3c2410_freq_table_dividers_2410[] = {
+	/* This table must be lexicographically sorted. */
+	{ S3C2410_CLKDIVN_1_4_4, },
+	{ S3C2410_CLKDIVN_1_2_4, },
+	{ S3C2410_CLKDIVN_1_2_2, },
+	{ S3C2410_CLKDIVN_1_1_2, },
+	{ S3C2410_CLKDIVN_1_1_1, },
+};
+
+#define S3C2410_DIVIDERS_1_2_4 { S3C2410_CLKDIVN_1_2_4, }
+#define S3C2410_DIVIDERS_1_1_2 { S3C2410_CLKDIVN_1_1_2, }
+#define S3C2410_DIVIDERS_1_1_1 { S3C2410_CLKDIVN_1_1_1, }
+
+/* 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 const struct s3c2410_freq_info s3c2410_freq_table_2410[] = {
+	/* SLOW modes */
+	/* not a nice round number for S3C2410_XTAL_KHZ == 12000 */
+	/*{ S3C2410_XTAL_KHZ/14, S3C2410_SLOW(7), 0, S3C2410_DIVIDERS_1_1_1 },*/
+	{ S3C2410_XTAL_KHZ/12, S3C2410_SLOW(6), 0, S3C2410_DIVIDERS_1_1_1, },
+	{ S3C2410_XTAL_KHZ/10, S3C2410_SLOW(5), 0, S3C2410_DIVIDERS_1_1_1, },
+	{ S3C2410_XTAL_KHZ/8, S3C2410_SLOW(4), 0, S3C2410_DIVIDERS_1_1_1, },
+	{ S3C2410_XTAL_KHZ/6, S3C2410_SLOW(3), 0, S3C2410_DIVIDERS_1_1_1, },
+	{ S3C2410_XTAL_KHZ/4, S3C2410_SLOW(2), 0, S3C2410_DIVIDERS_1_1_1, },
+	{ S3C2410_XTAL_KHZ/2, S3C2410_SLOW(1), 0, S3C2410_DIVIDERS_1_1_1, },
+	{ S3C2410_XTAL_KHZ/1, S3C2410_SLOW(0), 0, S3C2410_DIVIDERS_1_1_1, },
+
+	/* 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_DIVIDERS_1_1_1, },*/
+	{ 45000, 0, S3C2410_PLLVAL(82, 1, 3), S3C2410_DIVIDERS_1_1_1, },
+	{ 50700, 0, S3C2410_PLLVAL(161, 3, 3), S3C2410_DIVIDERS_1_1_1, },
+	{ 56250, 0, S3C2410_PLLVAL(142, 2, 3), S3C2410_DIVIDERS_1_1_1, },
+	{ 67500, 0, S3C2410_PLLVAL(82, 2, 2), S3C2410_DIVIDERS_1_1_2, },
+	{ 79000, 0, S3C2410_PLLVAL(71, 1, 2), S3C2410_DIVIDERS_1_1_2, },
+	{ 84750, 0, S3C2410_PLLVAL(105, 2, 2), S3C2410_DIVIDERS_1_1_2, },
+	{ 90000, 0, S3C2410_PLLVAL(112, 2, 2), S3C2410_DIVIDERS_1_1_2, },
+	{ 101250, 0, S3C2410_PLLVAL(127, 2, 2), S3C2410_DIVIDERS_1_1_2, },
+	{ 113000, 0, S3C2410_PLLVAL(105, 1, 2), S3C2410_DIVIDERS_1_1_2, },
+	{ 118500, 0, S3C2410_PLLVAL(150, 2, 2), S3C2410_DIVIDERS_1_1_2, },
+	{ 124000, 0, S3C2410_PLLVAL(116, 1, 2), S3C2410_DIVIDERS_1_1_2, },
+	{ 135000, 0, S3C2410_PLLVAL(82, 2, 1), S3C2410_DIVIDERS_1_2_4, },
+	{ 147000, 0, S3C2410_PLLVAL(90, 2, 1), S3C2410_DIVIDERS_1_2_4, },
+	{ 152000, 0, S3C2410_PLLVAL(68, 1, 1), S3C2410_DIVIDERS_1_2_4, },
+	{ 158000, 0, S3C2410_PLLVAL(71, 1, 1), S3C2410_DIVIDERS_1_2_4, },
+	{ 170000, 0, S3C2410_PLLVAL(77, 1, 1), S3C2410_DIVIDERS_1_2_4, },
+	{ 180000, 0, S3C2410_PLLVAL(82, 1, 1), S3C2410_DIVIDERS_1_2_4, },
+	{ 186000, 0, S3C2410_PLLVAL(85, 1, 1), S3C2410_DIVIDERS_1_2_4, },
+	{ 192000, 0, S3C2410_PLLVAL(88, 1, 1), S3C2410_DIVIDERS_1_2_4, },
+	{ 202800, 0, S3C2410_PLLVAL(161, 3, 1), S3C2410_DIVIDERS_1_2_4, },
+	{ 266000, 0, S3C2410_PLLVAL(125, 1, 1), S3C2410_DIVIDERS_1_2_4, },
+	/* no overclocking */
+};
+
+/* Special performance frequency: when chosen, always use the max divider. */
+#define S3C2410_FREQ_INFO_PERFORMANCE_INDEX \
+		(ARRAY_SIZE(s3c2410_freq_table_2410) - 1)
+
+static unsigned int s3c2410_cpufreq_2410_get(void)
+{
+	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;
+}
+
+static void s3c2410_cpufreq_2410_calc_clk(
+		const struct s3c2410_freq_info *freq_info,
+		const struct s3c2410_freq_info_dividers *freq_info_dividers,
+		struct s3c2410_cpufreq_clocks *clocks)
+{
+	clocks->mpll = (freq_info->clkslow & S3C2410_CLKSLOW_MPLL_OFF)
+			? 0 : freq_info->frequency * 1000;
+	clocks->fclk = freq_info->frequency * 1000;
+
+	if ((freq_info_dividers->clkdivn & S3C2410_CLKDIVN_HDIVN1)) {
+		BUG_ON((freq_info_dividers->clkdivn & S3C2410_CLKDIVN_HDIVN) ||
+			(freq_info_dividers->clkdivn & S3C2410_CLKDIVN_PDIVN));
+
+		clocks->hclk = clocks->fclk / 4;
+		clocks->pclk = clocks->fclk / 4;
+	} else {
+		clocks->hclk = (freq_info_dividers->clkdivn
+						& S3C2410_CLKDIVN_HDIVN)
+				? clocks->fclk / 2 : clocks->fclk;
+		clocks->pclk = (freq_info_dividers->clkdivn
+						& S3C2410_CLKDIVN_PDIVN)
+				? clocks->hclk / 2 : clocks->hclk;
+	}
+}
+
+static void s3c2410_cpufreq_2410_set(
+		const struct s3c2410_freq_info *freq_info,
+		const struct s3c2410_freq_info_dividers *freq_info_dividers,
+		const struct s3c2410_cpufreq_freqs *freqs)
+{
+	u32 clkslow;
+
+	/* 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_dividers->clkdivn, S3C2410_CLKDIVN);
+	} else {
+		/* If increasing FCLK, set the clock dividers before setting
+		 * the PLL to avoid going out of spec. */
+		if (freqs->new.fclk > freqs->old.fclk)
+			__raw_writel(freq_info_dividers->clkdivn,
+					S3C2410_CLKDIVN);
+
+		__raw_writel(freq_info->pllcon, S3C2410_MPLLCON);
+
+		/* If decreasing FCLK, set the clock dividers after setting
+		 * the PLL to avoid going out of spec. */
+		if (freqs->new.fclk <= freqs->old.fclk)
+			__raw_writel(freq_info_dividers->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_SLOW_MASK,
+					S3C2410_CLKSLOW);
+	}
+}
+
+static bool s3c2410_freq_info_dividers_equal(
+		const struct s3c2410_freq_info *freq_info,
+		const struct s3c2410_freq_info_dividers *freq_info_dividers)
+{
+	return freq_info->divn.clkdivn == freq_info_dividers->clkdivn;
+}
+
+static bool s3c2410_freq_info_dividers_valid(
+		const struct s3c2410_freq_info *freq_info __maybe_unused,
+		const struct s3c2410_freq_info_dividers *freq_info_dividers
+				__maybe_unused)
+{
+	return true;
+}
+
+static void s3c2410_save_original_freqs(struct s3c2410_freq_info *freq_info)
+{
+	original_freq.frequency		= s3c2410_cpufreq_2410_get();
+	original_freq.clkslow		= __raw_readl(S3C2410_CLKSLOW)
+			& S3C2410_CLKSLOW_SLOW_MASK;
+	original_freq.pllcon		= __raw_readl(S3C2410_MPLLCON);
+	original_freq.divn.clkdivn	= __raw_readl(S3C2410_CLKDIVN);
+}
+
+static unsigned int s3c2410_get_transition_latency(void)
+{
+	/* This formula was guessed. FIXME: find the correct formula. */
+	unsigned long long locktime = __raw_readl(S3C2410_LOCKTIME) & 0xFFF;
+	locktime += 1;
+	locktime *= 1000 * 1000;
+	do_div(locktime, S3C2410_XTAL_KHZ);
+	return locktime;
+}
+
+static const struct s3c2410_cpufreq_ops s3c2410_cpufreq_2410_ops = {
+	.get				= s3c2410_cpufreq_2410_get,
+	.calc_clk			= s3c2410_cpufreq_2410_calc_clk,
+	.set				= s3c2410_cpufreq_2410_set,
+	.freq_info_dividers_equal	= s3c2410_freq_info_dividers_equal,
+	.freq_info_dividers_valid	= s3c2410_freq_info_dividers_valid,
+	.save_original_freqs		= s3c2410_save_original_freqs,
+	.get_transition_latency	= s3c2410_get_transition_latency,
+	.enabled_clocks_show	= s3c2410_cpufreq_2410_enabled_clocks_show,
+};
+
+static struct s3c2410_cpufreq_info s3c2410_cpufreq_2410_info = {
+	.ops			= &s3c2410_cpufreq_2410_ops,
+
+	.freq_table		= s3c2410_freq_table_2410,
+	.freq_table_size	= ARRAY_SIZE(s3c2410_freq_table_2410),
+	.performance_index	= S3C2410_FREQ_INFO_PERFORMANCE_INDEX,
+
+	.freq_table_dividers	= s3c2410_freq_table_dividers_2410,
+	.freq_table_dividers_size
+				= ARRAY_SIZE(s3c2410_freq_table_dividers_2410),
+
+	.xtal_rate		= S3C2410_XTAL_KHZ * 1000,
+};
+#define s3c2410_cpufreq_2410_info_ptr &s3c2410_cpufreq_2410_info
+
+#else
+#define s3c2410_cpufreq_2410_info_ptr NULL
+#endif
+
+#ifdef CONFIG_CPU_S3C2442
+
+/* Hardcoded 12MHz XTAL */
+#define S3C2442_XTAL_KHZ 12000
+
+/* CLKDIVN values. */
+#define S3C2442_CLKDIVN_1_8_16 (S3C2440_CLKDIVN_HDIVN_4_8 | S3C2440_CLKDIVN_PDIVN)
+#define S3C2442_CLKDIVN_1_8_8 S3C2440_CLKDIVN_HDIVN_4_8
+#define S3C2442_CLKDIVN_1_6_12 (S3C2440_CLKDIVN_HDIVN_3_6 | S3C2440_CLKDIVN_PDIVN)
+#define S3C2442_CLKDIVN_1_6_6 S3C2440_CLKDIVN_HDIVN_3_6
+#define S3C2442_CLKDIVN_1_4_8 (S3C2440_CLKDIVN_HDIVN_4_8 | S3C2440_CLKDIVN_PDIVN)
+#define S3C2442_CLKDIVN_1_4_4 S3C2440_CLKDIVN_HDIVN_4_8
+#define S3C2442_CLKDIVN_1_3_6 (S3C2440_CLKDIVN_HDIVN_3_6 | S3C2440_CLKDIVN_PDIVN)
+#define S3C2442_CLKDIVN_1_3_3 S3C2440_CLKDIVN_HDIVN_3_6
+#define S3C2442_CLKDIVN_1_2_4 (S3C2440_CLKDIVN_HDIVN_2 | S3C2440_CLKDIVN_PDIVN)
+#define S3C2442_CLKDIVN_1_2_2 S3C2440_CLKDIVN_HDIVN_2
+#define S3C2442_CLKDIVN_1_1_2 (S3C2440_CLKDIVN_HDIVN_1 |S3C2440_CLKDIVN_PDIVN)
+#define S3C2442_CLKDIVN_1_1_1 S3C2440_CLKDIVN_HDIVN_1
+
+/* CAMDIVN values. */
+#define S3C2442_CAMDIVN_1_8_16 S3C2440_CAMDIVN_HCLK4_HALF
+#define S3C2442_CAMDIVN_1_8_8 S3C2440_CAMDIVN_HCLK4_HALF
+#define S3C2442_CAMDIVN_1_6_12 S3C2440_CAMDIVN_HCLK3_HALF
+#define S3C2442_CAMDIVN_1_6_6 S3C2440_CAMDIVN_HCLK3_HALF
+#define S3C2442_CAMDIVN_1_4_8 0
+#define S3C2442_CAMDIVN_1_4_4 0
+#define S3C2442_CAMDIVN_1_3_6 0
+#define S3C2442_CAMDIVN_1_3_3 0
+#define S3C2442_CAMDIVN_1_2_4 0
+#define S3C2442_CAMDIVN_1_2_2 0
+#define S3C2442_CAMDIVN_1_1_2 0
+#define S3C2442_CAMDIVN_1_1_1 0
+
+/* Table of hardware register values for each divider setting. */
+static const struct s3c2410_freq_info_dividers
+s3c2410_freq_table_dividers_2442[] = {
+	/* This table must be lexicographically sorted. */
+	{ S3C2442_CLKDIVN_1_8_16, S3C2442_CAMDIVN_1_8_16, },
+	{ S3C2442_CLKDIVN_1_8_8, S3C2442_CAMDIVN_1_8_8, },
+	{ S3C2442_CLKDIVN_1_6_12, S3C2442_CAMDIVN_1_6_12, },
+	{ S3C2442_CLKDIVN_1_6_6, S3C2442_CAMDIVN_1_6_6, },
+	{ S3C2442_CLKDIVN_1_4_8, S3C2442_CAMDIVN_1_4_8, },
+	{ S3C2442_CLKDIVN_1_4_4, S3C2442_CAMDIVN_1_4_4, },
+	{ S3C2442_CLKDIVN_1_3_6, S3C2442_CAMDIVN_1_3_6, },
+	{ S3C2442_CLKDIVN_1_3_3, S3C2442_CAMDIVN_1_3_3, },
+	{ S3C2442_CLKDIVN_1_2_4, S3C2442_CAMDIVN_1_2_4, },
+	{ S3C2442_CLKDIVN_1_2_2, S3C2442_CAMDIVN_1_2_2, },
+	{ S3C2442_CLKDIVN_1_1_2, S3C2442_CAMDIVN_1_1_2, },
+	{ S3C2442_CLKDIVN_1_1_1, S3C2442_CAMDIVN_1_1_1, },
+};
+
+/* MPLLCON values. */
+#define S3C2442_MPLLCON_300 S3C2410_PLLVAL(67, 1, 1)
+#define S3C2442_MPLLCON_400 S3C2410_PLLVAL(42, 1, 0)	/* from GTA02 u-boot */
+
+#define S3C2442_DIVIDERS_1_8_8_DVSEN \
+	{ S3C2442_CLKDIVN_1_8_8, \
+		S3C2442_CAMDIVN_1_8_8 | S3C2440_CAMDIVN_DVSEN, }
+#define S3C2442_DIVIDERS_1_6_6_DVSEN \
+	{ S3C2442_CLKDIVN_1_6_6, \
+		S3C2442_CAMDIVN_1_6_6 | S3C2440_CAMDIVN_DVSEN, }
+#define S3C2442_DIVIDERS_1_4_8_DVSEN \
+	{ S3C2442_CLKDIVN_1_4_8, \
+		S3C2442_CAMDIVN_1_4_8 | S3C2440_CAMDIVN_DVSEN, }
+#define S3C2442_DIVIDERS_1_4_8 { S3C2442_CLKDIVN_1_4_8, S3C2442_CAMDIVN_1_4_8, }
+#define S3C2442_DIVIDERS_1_1_1 { S3C2442_CLKDIVN_1_1_1, S3C2442_CAMDIVN_1_1_1, }
+
+/* Mask for the bits to be changed in CLKSLOW. */
+#define S3C2442_CLKSLOW_SLOWVAL_MASK 0x7
+#define S3C2442_CLKSLOW_SLOW_MASK (S3C2410_CLKSLOW_MPLL_OFF | \
+		S3C2410_CLKSLOW_SLOW | S3C2442_CLKSLOW_SLOWVAL_MASK)
+
+/* Generate a value to be set in CLKSLOW. */
+#define S3C2442_SLOW(v) (S3C2410_CLKSLOW_MPLL_OFF | S3C2410_CLKSLOW_SLOW | \
+	S3C2410_CLKSLOW_SLOWVAL(v))
+
+/* Mask for the bits to be changed in CLKDIVN and CAMDIVN. */
+#define S3C2442_CLKDIVN_MASK (S3C2440_CLKDIVN_HDIVN_MASK | \
+	S3C2440_CLKDIVN_PDIVN)
+#define S3C2442_CAMDIVN_MASK (S3C2440_CAMDIVN_HCLK3_HALF | \
+	S3C2440_CAMDIVN_HCLK4_HALF | S3C2440_CAMDIVN_DVSEN)
+
+/* Table of hardware register values for each frequency. */
+static const struct s3c2410_freq_info s3c2410_freq_table_2442[] = {
+	/* SLOW modes */
+	/* not a nice round number for S3C2442_XTAL_KHZ == 12000 */
+	/*{ S3C2442_XTAL_KHZ/14, S3C2442_SLOW(7), 0, S3C2442_DIVIDERS_1_1_1 },*/
+	{ S3C2442_XTAL_KHZ/12, S3C2442_SLOW(6), 0, S3C2442_DIVIDERS_1_1_1, },
+	{ S3C2442_XTAL_KHZ/10, S3C2442_SLOW(5), 0, S3C2442_DIVIDERS_1_1_1, },
+	{ S3C2442_XTAL_KHZ/8, S3C2442_SLOW(4), 0, S3C2442_DIVIDERS_1_1_1, },
+	{ S3C2442_XTAL_KHZ/6, S3C2442_SLOW(3), 0, S3C2442_DIVIDERS_1_1_1, },
+	{ S3C2442_XTAL_KHZ/4, S3C2442_SLOW(2), 0, S3C2442_DIVIDERS_1_1_1, },
+	{ S3C2442_XTAL_KHZ/2, S3C2442_SLOW(1), 0, S3C2442_DIVIDERS_1_1_1, },
+	{ S3C2442_XTAL_KHZ/1, S3C2442_SLOW(0), 0, S3C2442_DIVIDERS_1_1_1, },
+
+	/* PLL modes */
+#if S3C2442_XTAL_KHZ != 12000
+#error Hardcoded values for 12 MHz xtal
+#endif
+	{ 37500, 0, S3C2442_MPLLCON_300, S3C2442_DIVIDERS_1_8_8_DVSEN, },
+	/*{ 50000, 0, S3C2441_MPLLCON_400, S3C2442_DIVIDERS_1_8_8_DVSEN, },*/
+	{ 50000, 0, S3C2442_MPLLCON_300, S3C2442_DIVIDERS_1_6_6_DVSEN, },
+	{ 75000, 0, S3C2442_MPLLCON_300, S3C2442_DIVIDERS_1_4_8_DVSEN, },
+	{ 100000, 0, S3C2442_MPLLCON_400, S3C2442_DIVIDERS_1_4_8_DVSEN, },
+	{ 300000, 0, S3C2442_MPLLCON_300, S3C2442_DIVIDERS_1_4_8, },
+	{ 400000, 0, S3C2442_MPLLCON_400, S3C2442_DIVIDERS_1_4_8, },
+};
+
+/* Special performance frequency: when chosen, always use the max divider. */
+#define S3C2442_FREQ_INFO_PERFORMANCE_INDEX \
+		(ARRAY_SIZE(s3c2410_freq_table_2442) - 1)
+
+static unsigned int s3c2410_cpufreq_2442_get(void)
+{
+	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 {
+		unsigned int mclk_khz = s3c2410_get_pll(
+				__raw_readl(S3C2410_MPLLCON),
+				S3C2410_XTAL_KHZ * 1000) / 1000;
+		u32 camdivn = __raw_readl(S3C2440_CAMDIVN);
+
+		if ((camdivn & S3C2440_CAMDIVN_DVSEN)) {
+			u32 clkdivn = __raw_readl(S3C2410_CLKDIVN);
+			switch (clkdivn & S3C2440_CLKDIVN_HDIVN_MASK) {
+			case S3C2440_CLKDIVN_HDIVN_1:
+				return mclk_khz;
+			case S3C2440_CLKDIVN_HDIVN_2:
+				return mclk_khz / 2;
+			case S3C2440_CLKDIVN_HDIVN_4_8:
+				return (camdivn & S3C2440_CAMDIVN_HCLK4_HALF)
+					? mclk_khz / 8
+					: mclk_khz / 4;
+			case S3C2440_CLKDIVN_HDIVN_3_6:
+				return (camdivn & S3C2440_CAMDIVN_HCLK3_HALF)
+					? mclk_khz / 6
+					: mclk_khz / 3;
+			default:
+				BUG();	/* can't happen */
+			}
+		} else
+			return mclk_khz;
+	}
+}
+
+static void s3c2410_cpufreq_2442_calc_clk(
+		const struct s3c2410_freq_info *freq_info,
+		const struct s3c2410_freq_info_dividers *freq_info_dividers,
+		struct s3c2410_cpufreq_clocks *clocks)
+{
+	unsigned hclk_div = 0, fclk_mul;
+
+	switch (freq_info_dividers->clkdivn & S3C2440_CLKDIVN_HDIVN_MASK) {
+	case S3C2440_CLKDIVN_HDIVN_1:
+		hclk_div = 1;
+		break;
+	case S3C2440_CLKDIVN_HDIVN_2:
+		hclk_div = 2;
+		break;
+	case S3C2440_CLKDIVN_HDIVN_4_8:
+		hclk_div = (freq_info_dividers->camdivn
+				& S3C2440_CAMDIVN_HCLK4_HALF)
+			?  8 : 4;
+		break;
+	case S3C2440_CLKDIVN_HDIVN_3_6:
+		hclk_div = (freq_info_dividers->camdivn
+				& S3C2440_CAMDIVN_HCLK3_HALF)
+			? 6 : 3;
+		break;
+	}
+
+	/* DVS_EN is always only on freq_info->divn */
+	fclk_mul = (freq_info->divn.camdivn & S3C2440_CAMDIVN_DVSEN)
+			? hclk_div : 1;
+
+	clocks->mpll = (freq_info->clkslow & S3C2410_CLKSLOW_MPLL_OFF)
+			? 0 : freq_info->frequency * fclk_mul * 1000;
+	clocks->fclk = freq_info->frequency * fclk_mul * 1000;
+	clocks->hclk = (freq_info->divn.camdivn & S3C2440_CAMDIVN_DVSEN)
+			? freq_info->frequency * 1000 : clocks->fclk / hclk_div;
+	clocks->pclk = (freq_info_dividers->clkdivn & S3C2440_CLKDIVN_PDIVN)
+			? clocks->hclk / 2 : clocks->hclk;
+}
+
+static void s3c2410_cpufreq_2442_pre_set(
+		const struct s3c2410_freq_info *freq_info,
+		const struct s3c2410_freq_info_dividers *freq_info_dividers
+				__maybe_unused,
+		const struct s3c2410_cpufreq_freqs *freqs __maybe_unused)
+{
+	const struct s3c2410_cpufreq_info *info = s3c2410_cpufreq_info;
+	u32 clkslow;
+
+	/*
+	 * According to the datasheet, when coming from slow mode, the chip
+	 * stays in slow mode during the PLL lock time, instead of stopping
+	 * the clocks like on the other transisions.
+	 *
+	 * To avoid either having the frequency unpredictably change outside
+	 * the locked region or needing a long wait inside the locked region,
+	 * "warm up" the PLL in normal process context with interrupts enabled
+	 * and nothing locked except the cpufreq code. This way, it will almost
+	 * instantly switch to the new frequency.
+	 */
+
+	if ((current_freq->clkslow & S3C2410_CLKSLOW_SLOW) &&
+			!(freq_info->clkslow & S3C2410_CLKSLOW_SLOW)) {
+		/* Protect agains concurrent modification of CLKSLOW. */
+		mutex_lock(&clocks_mutex);
+
+		/*
+		 * Nothing but cpufreq should play with this register, so this
+		 * should be safe without any extra locking.
+		 */
+		__raw_writel(freq_info->pllcon, S3C2410_MPLLCON);
+
+		/*
+		 * Enable the PLL. Nothing else should change the cpufreq bits
+		 * on this register, so this is also safe without any extra
+		 * locking (clocks_mutex is needed only to protect against
+		 * race conditions with the code which changes the UCLK_OFF
+		 * bit).
+		 */
+		clkslow = __raw_readl(S3C2410_CLKSLOW);
+		clkslow &= ~S3C2410_CLKSLOW_MPLL_OFF;
+		__raw_writel(clkslow, S3C2410_CLKSLOW);
+
+		mutex_unlock(&clocks_mutex);
+
+		/* Wait for the PLL lock time to finish. */
+		if (likely(!in_atomic()))
+			msleep((info->transition_latency + 999999) / 1000000);
+		else
+			/*
+			 * When called from the suspend code, interrupts might
+			 * be disabled. In that case, we must not use msleep(),
+			 * since it would try to schedule while atomic.
+			 */
+			mdelay((info->transition_latency + 999999) / 1000000);
+	}
+}
+
+static inline void _s3c2410_cpufreq_2442_update_dividers(
+		const struct s3c2410_freq_info_dividers *freq_info_dividers)
+{
+	u32 clkdivn, camdivn;
+
+	/*
+	 * Carefully try to sequence the register settings so as to avoid ever
+	 * increasing the frequencies above their maximum.
+	 */
+
+	/* First, set (but do not reset) HCLK3_HALF and HCLK4_HALF. */
+	camdivn = __raw_readl(S3C2440_CAMDIVN);
+	camdivn |= freq_info_dividers->camdivn & (S3C2440_CAMDIVN_HCLK3_HALF
+						| S3C2440_CAMDIVN_HCLK4_HALF);
+	__raw_writel(camdivn, S3C2440_CAMDIVN);
+
+	/* Then, update CLKDIVN. */
+	clkdivn = __raw_readl(S3C2410_CLKDIVN);
+	clkdivn &= ~S3C2442_CLKDIVN_MASK;
+	clkdivn |= freq_info_dividers->clkdivn;
+	__raw_writel(clkdivn, S3C2410_CLKDIVN);
+
+	/* Finally, update HCLK3_HALF and HCLK4_HALF. */
+	camdivn &= ~(S3C2440_CAMDIVN_HCLK3_HALF | S3C2440_CAMDIVN_HCLK4_HALF);
+	camdivn |= freq_info_dividers->camdivn & (S3C2440_CAMDIVN_HCLK3_HALF
+						| S3C2440_CAMDIVN_HCLK4_HALF);
+	__raw_writel(camdivn, S3C2440_CAMDIVN);
+}
+
+static void s3c2410_cpufreq_2442_set(
+		const struct s3c2410_freq_info *freq_info,
+		const struct s3c2410_freq_info_dividers *freq_info_dividers,
+		const struct s3c2410_cpufreq_freqs *freqs)
+{
+	u32 clkslow, clkdivn, camdivn;
+
+	/* FIXME: I'm not sure if this is the ideal order for writing to the
+	 * registers. */
+
+	if ((freq_info->clkslow & S3C2410_CLKSLOW_SLOW)) {
+		/* Clear DVS_EN. */
+		camdivn = __raw_readl(S3C2440_CAMDIVN);
+		camdivn &= ~S3C2440_CAMDIVN_DVSEN;
+		__raw_writel(camdivn, S3C2440_CAMDIVN);
+
+		/* We're entering slow mode from slow mode or PLL mode. */
+		clkslow = __raw_readl(S3C2410_CLKSLOW);
+		clkslow &= ~S3C2442_CLKSLOW_SLOW_MASK;
+		clkslow |= freq_info->clkslow;
+		__raw_writel(clkslow, S3C2410_CLKSLOW);
+
+		/* Set the clock dividers. */
+		camdivn &= ~(S3C2442_CAMDIVN_MASK & ~S3C2440_CAMDIVN_DVSEN);
+		camdivn |= freq_info_dividers->camdivn & ~S3C2440_CAMDIVN_DVSEN;
+		__raw_writel(camdivn, S3C2440_CAMDIVN);
+
+		clkdivn = __raw_readl(S3C2410_CLKDIVN);
+		clkdivn &= ~S3C2442_CLKDIVN_MASK;
+		clkdivn |= freq_info_dividers->clkdivn;
+		__raw_writel(clkdivn, S3C2410_CLKDIVN);
+	} else {
+		/* If increasing MPLL, set the clock dividers before setting
+		 * the PLL to avoid going out of spec. */
+		if (freqs->new.mpll > freqs->old.mpll)
+			_s3c2410_cpufreq_2442_update_dividers(
+					freq_info_dividers);
+
+		/* Avoid reloading MPLLCON if not needed. */
+		if (__raw_readl(S3C2410_MPLLCON) != freq_info->pllcon)
+			__raw_writel(freq_info->pllcon, S3C2410_MPLLCON);
+
+		/* If decreasing FCLK, set the clock dividers after setting
+		 * the PLL to avoid going out of spec. */
+		if (freqs->new.mpll <= freqs->old.mpll)
+			_s3c2410_cpufreq_2442_update_dividers(
+					freq_info_dividers);
+
+		/* 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_SLOW_MASK,
+					S3C2410_CLKSLOW);
+
+		/* Update DVS_EN. */
+		camdivn = __raw_readl(S3C2440_CAMDIVN);
+		camdivn &= ~S3C2440_CAMDIVN_DVSEN;
+		camdivn |= freq_info->divn.camdivn & S3C2440_CAMDIVN_DVSEN;
+		__raw_writel(camdivn, S3C2440_CAMDIVN);
+	}
+}
+
+static bool s3c2442_freq_info_dividers_equal(
+		const struct s3c2410_freq_info *freq_info,
+		const struct s3c2410_freq_info_dividers *freq_info_dividers)
+{
+	return freq_info->divn.clkdivn == freq_info_dividers->clkdivn
+		&& (freq_info->divn.camdivn & ~S3C2440_CAMDIVN_DVSEN)
+				== freq_info_dividers->camdivn;
+}
+
+static bool s3c2442_freq_info_dividers_valid(
+		const struct s3c2410_freq_info *freq_info,
+		const struct s3c2410_freq_info_dividers *freq_info_dividers)
+{
+	if (!(freq_info->divn.camdivn & S3C2440_CAMDIVN_DVSEN))
+		return true;
+
+	/*
+	 * If we are using DVS_EN, HCLK must be the same (else the core
+	 * frequency would be wrong).
+	 */
+
+	return (freq_info->divn.clkdivn & S3C2440_CLKDIVN_HDIVN_MASK)
+		== (freq_info_dividers->clkdivn & S3C2440_CLKDIVN_HDIVN_MASK)
+		&& (freq_info->divn.camdivn & (S3C2440_CAMDIVN_HCLK3_HALF
+						| S3C2440_CAMDIVN_HCLK4_HALF))
+		== (freq_info_dividers->camdivn & (S3C2440_CAMDIVN_HCLK3_HALF
+						| S3C2440_CAMDIVN_HCLK4_HALF));
+}
+
+static void s3c2442_save_original_freqs(struct s3c2410_freq_info *freq_info)
+{
+	original_freq.frequency		= s3c2410_cpufreq_2442_get();
+	original_freq.clkslow		= __raw_readl(S3C2410_CLKSLOW)
+			& S3C2442_CLKSLOW_SLOW_MASK;
+	original_freq.pllcon		= __raw_readl(S3C2410_MPLLCON);
+	original_freq.divn.clkdivn	= __raw_readl(S3C2410_CLKDIVN)
+			& S3C2442_CLKDIVN_MASK;
+	original_freq.divn.camdivn	= __raw_readl(S3C2440_CAMDIVN)
+			& S3C2442_CAMDIVN_MASK;
+}
+
+static unsigned int s3c2442_get_transition_latency(void)
+{
+	/* This formula was guessed. FIXME: find the correct formula. */
+	unsigned long long locktime = __raw_readl(S3C2410_LOCKTIME) & 0xFFFF;
+	locktime += 1;
+	locktime *= 1000 * 1000;
+	do_div(locktime, S3C2410_XTAL_KHZ);
+	return locktime;
+}
+
+static const struct s3c2410_cpufreq_ops s3c2410_cpufreq_2442_ops = {
+	.get				= s3c2410_cpufreq_2442_get,
+	.calc_clk			= s3c2410_cpufreq_2442_calc_clk,
+	.pre_set			= s3c2410_cpufreq_2442_pre_set,
+	.set				= s3c2410_cpufreq_2442_set,
+	.freq_info_dividers_equal	= s3c2442_freq_info_dividers_equal,
+	.freq_info_dividers_valid	= s3c2442_freq_info_dividers_valid,
+	.save_original_freqs		= s3c2442_save_original_freqs,
+	.get_transition_latency	= s3c2442_get_transition_latency,
+	.enabled_clocks_show	= s3c2410_cpufreq_2410_enabled_clocks_show,
+};
+
+static struct s3c2410_cpufreq_info s3c2410_cpufreq_2442_info = {
+	.ops			= &s3c2410_cpufreq_2442_ops,
+
+	.freq_table		= s3c2410_freq_table_2442,
+	.freq_table_size	= ARRAY_SIZE(s3c2410_freq_table_2442),
+	.performance_index	= S3C2442_FREQ_INFO_PERFORMANCE_INDEX,
+
+	.freq_table_dividers	= s3c2410_freq_table_dividers_2442,
+	.freq_table_dividers_size
+				= ARRAY_SIZE(s3c2410_freq_table_dividers_2442),
+
+	.xtal_rate		= S3C2442_XTAL_KHZ * 1000,
+};
+#define s3c2410_cpufreq_2442_info_ptr &s3c2410_cpufreq_2442_info
+
+#else
+#define s3c2410_cpufreq_2442_info_ptr NULL
+#endif
+
+static int __init s3c2410_cpufreq_module_init(void)
+{
+	struct s3c2410_cpufreq_info *info = NULL;
+	u32 gstatus1;
+	unsigned index;
+	int ret;
+
+	/* CPU checking based on arch/arm/plat-s3c24xx/cpu.c */
+	if (unlikely(cpu_architecture() >= CPU_ARCH_ARMv5))
+		return -ENODEV;
+	gstatus1 = __raw_readl(S3C2410_GSTATUS1);
+	if (gstatus1 == S3C2410_GSTATUS1_2410A)
+		info = s3c2410_cpufreq_2410_info_ptr;
+	else if (gstatus1 == S3C2410_GSTATUS1_2442)
+		info = s3c2410_cpufreq_2442_info_ptr;
+
+	if (unlikely(!info))
+		return -ENODEV;
+
+	/* A lot of values are hardcoded for the xtal. */
+	if (unlikely(clk_xtal.rate != info->xtal_rate))
+		return -EINVAL;
+
+	/* Allocate the auxiliary tables. */
+	s3c2410_frequency_table = kcalloc(info->freq_table_size + 1,
+			sizeof(*s3c2410_frequency_table), GFP_KERNEL);
+	s3c2410_frequency_table_target = kcalloc(info->freq_table_size + 1,
+			sizeof(*s3c2410_frequency_table_target), GFP_KERNEL);
+	s3c2410_frequency_table_target_dividers = kcalloc(
+			info->freq_table_size * info->freq_table_dividers_size,
+			sizeof(*s3c2410_frequency_table_target_dividers),
+			GFP_KERNEL);
+
+	if (unlikely(!s3c2410_frequency_table
+			|| !s3c2410_frequency_table_target
+			|| !s3c2410_frequency_table_target_dividers)) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	info->transition_latency = info->ops->get_transition_latency();
+	s3c2410_cpufreq_info = info;
+
+	/* Fill the tables used by the frequency helpers. */
+	for (index = 0; index < info->freq_table_size; ++index) {
+		s3c2410_frequency_table[index].index = index;
+		s3c2410_frequency_table[index].frequency =
+				info->freq_table[index].frequency;
+
+		s3c2410_frequency_table_target[index].index = index;
+		s3c2410_frequency_table_target[index].frequency =
+				info->freq_table[index].frequency;
+	}
+	s3c2410_frequency_table[index].frequency = CPUFREQ_TABLE_END;
+	s3c2410_frequency_table_target[index].frequency = CPUFREQ_TABLE_END;
+
+	/* Register the policy helper notifiers. */
+	cpufreq_register_notifier(&s3c2410_cpufreq_pre_adjust_nb,
+			CPUFREQ_POLICY_NOTIFIER);
+	cpufreq_register_notifier(&s3c2410_cpufreq_post_incompatible_nb,
+			CPUFREQ_POLICY_NOTIFIER);
+
+	ret = cpufreq_register_driver(&s3c2410_cpufreq_driver);
+	if (unlikely(ret))
+		goto out;
+
+	return 0;
+
+out:
+	kfree(s3c2410_frequency_table);
+	kfree(s3c2410_frequency_table_target);
+	kfree(s3c2410_frequency_table_target_dividers);
+	s3c2410_cpufreq_info = NULL;
+	return ret;
+}
+
+static void __exit s3c2410_cpufreq_module_exit(void)
+{
+	/* Unregister the policy helper notifiers. */
+	cpufreq_unregister_notifier(&s3c2410_cpufreq_pre_adjust_nb,
+			CPUFREQ_POLICY_NOTIFIER);
+	cpufreq_unregister_notifier(&s3c2410_cpufreq_post_incompatible_nb,
+			CPUFREQ_POLICY_NOTIFIER);
+
+	cpufreq_unregister_driver(&s3c2410_cpufreq_driver);
+
+	cancel_work_sync(&s3c2410_cpufreq_update_policy_work);
+
+	kfree(s3c2410_frequency_table);
+	kfree(s3c2410_frequency_table_target);
+	kfree(s3c2410_frequency_table_target_dividers);
+	s3c2410_cpufreq_info = NULL;
+}
+
+/*
+ * 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 at cesarb.net>");
+MODULE_DESCRIPTION("S3C2410 cpufreq driver");
diff --git a/include/asm-arm/arch-s3c2410/regs-clock.h b/include/asm-arm/arch-s3c2410/regs-clock.h
index e39656b..fa77582 100644
--- a/include/asm-arm/arch-s3c2410/regs-clock.h
+++ b/include/asm-arm/arch-s3c2410/regs-clock.h
@@ -69,6 +69,7 @@
 
 #define S3C2410_CLKDIVN_PDIVN	     (1<<0)
 #define S3C2410_CLKDIVN_HDIVN	     (1<<1)
+#define S3C2410_CLKDIVN_HDIVN1	     (1<<2)
 
 #define S3C2410_CLKSLOW_UCLK_OFF	(1<<7)
 #define S3C2410_CLKSLOW_MPLL_OFF	(1<<5)
diff --git a/include/asm-arm/arch-s3c2410/regs-gpio.h b/include/asm-arm/arch-s3c2410/regs-gpio.h
index 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/s3c2410-cpufreq.h b/include/asm-arm/arch-s3c2410/s3c2410-cpufreq.h
new file mode 100644
index 0000000..f845499
--- /dev/null
+++ b/include/asm-arm/arch-s3c2410/s3c2410-cpufreq.h
@@ -0,0 +1,88 @@
+/*
+ *  S3C2410 cpufreq driver
+ *  Copyright (C) 2008  Cesar Eduardo Barros <cesarb at cesarb.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef _ASM_ARCH_S3C2410_CPUFREQ_H
+#define _ASM_ARCH_S3C2410_CPUFREQ_H
+
+#include <linux/cpufreq.h>
+
+struct s3c2410_cpufreq_clocks {
+	unsigned long mpll;
+	unsigned long fclk;
+	unsigned long hclk;
+	unsigned long pclk;
+};
+
+/* transition helpers */
+
+struct s3c2410_cpufreq_freqs {
+	struct cpufreq_freqs freqs;
+	struct s3c2410_cpufreq_clocks old;
+	struct s3c2410_cpufreq_clocks new;
+};
+
+extern const struct s3c2410_cpufreq_freqs *s3c2410_cpufreq_target_freqs(
+		struct cpufreq_freqs *freqs);
+
+/* policy helpers */
+
+typedef bool (*s3c2410_cpufreq_adjust_table_cb)(void *data,
+		const struct s3c2410_cpufreq_clocks *clocks);
+extern int s3c2410_cpufreq_adjust_table(struct cpufreq_policy *policy,
+		s3c2410_cpufreq_adjust_table_cb cb, void *data);
+
+extern int s3c2410_cpufreq_adjust_pin(struct cpufreq_policy *policy);
+
+/* general helpers */
+
+#ifdef CONFIG_CPU_FREQ
+
+#include <linux/cpumask.h>
+#include <linux/kernel.h>
+
+static inline void s3c2410_cpufreq_update_policy(void)
+{
+	unsigned int cpu;
+
+	/*
+	 * This function should not be called with any locks which could be
+	 * needed by a notifier held. It also should not be called from atomic
+	 * context.
+	 */
+	might_sleep();
+
+	for_each_online_cpu(cpu)
+		cpufreq_update_policy(cpu);
+}
+
+extern void s3c2410_cpufreq_schedule_update_policy(void);
+
+#else
+
+static inline void s3c2410_cpufreq_update_policy(void)
+{
+}
+
+static inline void s3c2410_cpufreq_schedule_update_policy(void)
+{
+}
+
+#endif
+
+#endif
diff --git a/include/asm-arm/arch-s3c2410/system.h b/include/asm-arm/arch-s3c2410/system.h
index 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.
 	 */
diff --git a/include/asm-arm/plat-s3c24xx/clock.h b/include/asm-arm/plat-s3c24xx/clock.h
index 235b753..c813517 100644
--- a/include/asm-arm/plat-s3c24xx/clock.h
+++ b/include/asm-arm/plat-s3c24xx/clock.h
@@ -10,6 +10,11 @@
  * published by the Free Software Foundation.
 */
 
+#include <linux/list.h>
+
+struct module;
+struct clk;
+
 struct clk {
 	struct list_head      list;
 	struct module        *owner;
-- 
1.5.4





More information about the openmoko-kernel mailing list