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

Rask Ingemann Lambertsen rask at sygehus.dk
Thu Apr 16 16:14:24 CEST 2009


   Here's an update to the s3c2442 cpufreq driver. The changes:

1) I got it to work with the 'ondemand' and 'conservative' governors. You
need to set the transition latency low enough. I put in 100 us but I'm
wondering if that is enough since we have to access the voltage regulators
over the I2C bus. TODO: Set different transition latency for voltage
regulator available vs. voltage regulator not available.

   The 'conservative' governor uses a default 'freq_step' value of 5 and
ramping up the frequence takes ages. 75 seems to be just right currently. I
set it from /etc/rc.local:

echo >/sys/devices/system/cpu/cpu0/cpufreq/conservative/freq_step 75

(I've compiled the kernel with 'converative' as the default governor.)

   When the transition happens, user space is blocked for long enough that
you can see when it happens just by letting a key autorepeat when logged in
over ssh.

2) I updated the voltage table to match what little up-to-date information I
have on it. It would be helpful to have the correct values.

3) I took out the frequencies that are not a multiple of 50 MHz since a lot
of work remains before they will work. Next on the list is 50 MHz.

--- /dev/null	2009-04-16 01:50:46.459463000 +0200
+++ b/arch/arm/plat-s3c24xx/cpufreq.c	2009-04-06 03:25:56.000000000 +0200
@@ -0,0 +1,255 @@
+/* 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;
+static unsigned int conservative_freq_step;
+
+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] = { 1150000, 1300000 }, /* 200 MHz (guesswork) */
+	[3] = { 1225000, 1400000 }, /* 300 MHz (guesswork) */
+	[4] = { 1300000, 1550000 }, /* 400 MHz */
+	[5] = { 1700000, 1800000 }, /* 500 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) */
+	{ 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;
+	unsigned long int last_freq, max_freq_step, max_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;
+	max_freq_step = 0;
+	max_freq = 0;
+	last_freq = 0;
+	while (freq->frequency != CPUFREQ_TABLE_END) {
+		unsigned long r;
+
+		r = clk_round_rate(armclk, freq->frequency * 1000);
+		r /= 1000;
+
+		if (r != freq->frequency) {
+			pr_info("cpufreq: Can't use frequency %u kHz.\n",
+				freq->frequency);
+			freq->frequency = CPUFREQ_ENTRY_INVALID;
+		} else {
+			/* This assumes the table is sorted by frequency. */
+			if (last_freq)
+				max_freq_step = max(max_freq_step, r - last_freq);
+			max_freq = max(max_freq, r);
+			last_freq = r;
+		}
+		freq++;
+	}
+
+	policy->cur = clk_get_rate(armclk) / 1000;
+
+	/* 100 us - hopefully we do better than that. */
+	policy->cpuinfo.transition_latency = 100000;
+	
+	/* Getting this right is critical to the performance of the
+	 * conservative governor.  Too high and we never change down.  Too
+	 * low and it takes ages to change up. */
+	conservative_freq_step = 100 * max_freq_step / max_freq;
+	pr_info("cpufreq: Suggested freq_step for conservative governor: %u\n",
+		conservative_freq_step);
+
+	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)
+{
+	return cpufreq_register_driver(&s3c24xx_cpufreq_driver);
+}
+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