[RFC 3/3] S3C24xx: Add cpufreq driver for S3C2442

Rask Ingemann Lambertsen rask at sygehus.dk
Wed Apr 1 03:10:21 CEST 2009


   I adapted the S3C64xx cpufreq driver by Mark Brown for the S3C2442. The
frequencies are simply the ones I expect to be easy to work with on the
GTA02. However, much work on the kernel clock support remains to be able to
use all those frequencies. I also need to have another look at how the
S3C2442 clocks work.

   There's an ugly workaround for the GTA02 problem that the cpufreq driver
is initialised before the regulators are added. Suggestions are welcome.

   (This is also where my recent interest in the cpu_is_s3c2442() macro
comes from.)

diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 2dfd1c2..90cc2c6 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -1087,7 +1087,7 @@ endmenu
 menu "CPU Power Management"
 
 if (ARCH_SA1100 || ARCH_INTEGRATOR || ARCH_OMAP || ARCH_IMX || ARCH_PXA || \
-    ARCH_S3C64XX)
+    ARCH_S3C2410 || ARCH_S3C64XX)
 
 source "drivers/cpufreq/Kconfig"
 
@@ -1127,6 +1127,10 @@ config CPU_FREQ_PXA
 	default y
 	select CPU_FREQ_DEFAULT_GOV_USERSPACE
 
+config CPU_FREQ_S3C24XX
+	bool "CPUfreq support for S3C24xx CPUs"
+	depends on CPU_FREQ && CPU_S3C2442
+
 config CPU_FREQ_S3C64XX
 	bool "CPUfreq support for S3C64xx CPUs"
 	depends on CPU_FREQ && CPU_S3C6410
diff --git a/arch/arm/plat-s3c24xx/Makefile b/arch/arm/plat-s3c24xx/Makefile
index f135051..7567ee9 100644
--- a/arch/arm/plat-s3c24xx/Makefile
+++ b/arch/arm/plat-s3c24xx/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_HAVE_PWM)		+= pwm.o
 obj-$(CONFIG_S3C2410_CLOCK)	+= s3c2410-clock.o
 obj-$(CONFIG_S3C2410_DMA)	+= dma.o
 obj-$(CONFIG_S3C24XX_ADC)	+= adc.o
+obj-$(CONFIG_CPU_FREQ_S3C24XX)	+= cpufreq.o
 
 # device specific setup and/or initialisation
 obj-$(CONFIG_ARCH_S3C2410)	+= setup-i2c.o
--- /dev/null	2009-03-31 17:58:48.384148000 +0200
+++ b/arch/arm/plat-s3c24xx/cpufreq.c	2009-04-01 00:10:12.000000000 +0200
@@ -0,0 +1,239 @@
+/* linux/arch/arm/plat-s3c24xx/cpufreq.c
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ *
+ * S3C24XX CPUfreq Support
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/cpufreq.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/regulator/consumer.h>
+
+#include <mach/cpu.h>
+
+static struct clk *armclk;
+static struct regulator *vddarm;
+
+struct s3c24xx_voltage {
+	unsigned int vddarm_min;
+	unsigned int vddarm_max;
+};
+
+static struct s3c24xx_voltage s3c2442_voltage_table[] = {
+	[0] = { 1000000, 1300000 }, /* 50 MHz, 66 MHz */
+	[1] = { 1100000, 1300000 }, /* 75 MHz, 100 MHz */
+	[2] = { 1250000, 1300000 }, /* 200 MHz (guesswork) */
+	[3] = { 1350000, 1400000 }, /* 300 MHz */
+	[4] = { 1500000, 1550000 }, /* 400 MHz */
+};
+
+static struct cpufreq_frequency_table s3c2442_freq_table[] = {
+/*	volt   freq	  FCLK ACLK HCLK PCLK (dividers) */
+	{ 0,  50000 },	/* 200   50   50   50 (1:4:4:4) */
+	{ 0,  66000 },	/* 200   66   66   50 (1:3:3:4) */
+	{ 1,  75000 },	/* 300   75   75   50 (1:4:4:6) */
+	{ 1, 100000 },	/* 200  100  100   50 (1:2:2:4) */
+	{ 2, 200000 },	/* 200  200  100   50 (1:1:2:4) */
+	{ 3, 300000 },	/* 300  300  100   50 (1:1:3:6) */
+	{ 4, 400000 },	/* 400  400  100   50 (1:1:4:8) */
+	{ 0, CPUFREQ_TABLE_END },
+};
+
+/* Data tables for current CPU and maximum index into it */
+static struct cpufreq_frequency_table *s3c24xx_freq_table;
+static struct s3c24xx_voltage *s3c24xx_voltage_table;
+
+static int s3c24xx_cpufreq_verify_speed(struct cpufreq_policy *policy)
+{
+	if (policy->cpu != 0)
+		return -EINVAL;
+
+	return cpufreq_frequency_table_verify(policy, s3c24xx_freq_table);
+}
+
+static unsigned int s3c24xx_cpufreq_get_speed(unsigned int cpu)
+{
+	if (cpu != 0)
+		return 0;
+
+	return clk_get_rate(armclk) / 1000;
+}
+
+static int s3c24xx_cpufreq_set_target(struct cpufreq_policy *policy,
+				      unsigned int target_freq,
+				      unsigned int relation)
+{
+	int ret = 0;
+	unsigned int i;
+	struct cpufreq_freqs freqs;
+	struct s3c24xx_voltage *voltage;
+
+	ret = cpufreq_frequency_table_target(policy, s3c24xx_freq_table,
+					     target_freq, relation, &i);
+	if (ret != 0)
+		return ret;
+
+	freqs.cpu = 0;
+	freqs.old = clk_get_rate(armclk) / 1000;
+	freqs.new = s3c24xx_freq_table[i].frequency;
+	freqs.flags = 0;
+	voltage = &s3c24xx_voltage_table[s3c24xx_freq_table[i].index];
+
+	if (freqs.old == freqs.new)
+		return 0;
+
+#ifdef CONFIG_REGULATOR
+        /* Workaround for regulator not existing during init.  */
+        if (!vddarm) {
+        	vddarm = regulator_get(NULL, "vddarm");
+        	if (IS_ERR(vddarm))
+        		vddarm = NULL;
+	}
+#endif
+
+	pr_debug("cpufreq: Transition %u-%u kHz\n", freqs.old, freqs.new);
+
+	cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
+
+#ifdef CONFIG_REGULATOR
+	if (vddarm && freqs.new > freqs.old) {
+		ret = regulator_set_voltage(vddarm,
+					    voltage->vddarm_min,
+					    voltage->vddarm_max);
+		if (ret != 0) {
+			pr_err("cpufreq: Failed to set VDDARM for %dkHz: %d\n",
+			       freqs.new, ret);
+			goto err;
+		}
+	}
+#endif
+
+	ret = clk_set_rate(armclk, freqs.new * 1000);
+	if (ret < 0) {
+		pr_err("cpufreq: Failed to set rate %u kHz: %d\n",
+		       freqs.new, ret);
+		goto err;
+	}
+
+#ifdef CONFIG_REGULATOR
+	if (vddarm && freqs.new < freqs.old) {
+		ret = regulator_set_voltage(vddarm,
+					    voltage->vddarm_min,
+					    voltage->vddarm_max);
+		if (ret != 0) {
+			pr_err("cpufreq: Failed to set VDDARM for %u kHz: %d\n",
+			       freqs.new, ret);
+			goto err_clk;
+		}
+	}
+#endif
+
+	cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
+
+	pr_debug("cpufreq: Set actual frequency %lu kHz\n",
+		 clk_get_rate(armclk) / 1000);
+
+	return 0;
+
+err_clk:
+	if (clk_set_rate(armclk, freqs.old * 1000) < 0)
+		pr_err("Failed to restore original clock rate\n");
+err:
+	cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
+
+	return ret;
+}
+
+
+static int __init s3c24xx_cpufreq_driver_init(struct cpufreq_policy *policy)
+{
+	int ret;
+	struct cpufreq_frequency_table *freq;;
+
+	if (policy->cpu != 0)
+		return -EINVAL;
+
+	if (cpu_is_s3c2442()) {
+		s3c24xx_freq_table = s3c2442_freq_table;
+		s3c24xx_voltage_table = s3c2442_voltage_table;
+	}
+
+	if (s3c24xx_freq_table == NULL) {
+		pr_err("cpufreq: No frequency information for this CPU\n");
+		return -ENODEV;
+	}
+
+	armclk = clk_get(NULL, "armclk");
+	if (IS_ERR(armclk)) {
+		pr_err("cpufreq: Unable to obtain ARMCLK: %ld\n",
+		       PTR_ERR(armclk));
+		return PTR_ERR(armclk);
+	}
+
+#ifdef CONFIG_REGULATOR
+        /* FIXME The regulator doesn't exist yet when we get here.  */
+	vddarm = regulator_get(NULL, "vddarm");
+	if (IS_ERR(vddarm)) {
+		ret = PTR_ERR(vddarm);
+		pr_err("cpufreq: Failed to obtain VDDARM: %d\n", ret);
+		pr_err("cpufreq: Only frequency scaling available\n");
+		vddarm = NULL;
+	}
+#endif
+
+	/* Check for frequencies we can generate */
+	freq = s3c24xx_freq_table;
+	while (freq->frequency != CPUFREQ_TABLE_END) {
+		unsigned long r;
+
+		r = clk_round_rate(armclk, freq->frequency * 1000);
+		r /= 1000;
+
+		if (r != freq->frequency) {
+			pr_debug("cpufreq: Can't use frequency %u kHz.\n",
+				 freq->frequency);
+			freq->frequency = CPUFREQ_ENTRY_INVALID;
+		}
+		freq++;
+	}
+
+	policy->cur = clk_get_rate(armclk) / 1000;
+	policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL;
+
+	ret = cpufreq_frequency_table_cpuinfo(policy, s3c24xx_freq_table);
+	if (ret == 0)
+		return ret;
+
+	pr_err("cpufreq: Failed to configure frequency table: %d\n", ret);
+
+	regulator_put(vddarm);
+	clk_put(armclk);
+	return ret;
+}
+
+static struct cpufreq_driver s3c24xx_cpufreq_driver = {
+	.owner		= THIS_MODULE,
+	.flags          = 0,
+	.verify		= s3c24xx_cpufreq_verify_speed,
+	.target		= s3c24xx_cpufreq_set_target,
+	.get		= s3c24xx_cpufreq_get_speed,
+	.init		= s3c24xx_cpufreq_driver_init,
+	.name		= "s3c24xx",
+};
+
+static int __init s3c24xx_cpufreq_init(void)
+{
+	int err;
+	err = cpufreq_register_driver(&s3c24xx_cpufreq_driver);
+	pr_err("cpufreq: Registering driver returned %d.\n", err);
+	return err;
+}
+module_init(s3c24xx_cpufreq_init);


-- 
Rask Ingemann Lambertsen
Danish law requires addresses in e-mail to be logged and stored for a year



More information about the openmoko-kernel mailing list