[PATCHv4 08/13] picoxcell: add cpufreq support

Jamie Iles jamie at jamieiles.com
Wed Feb 2 07:03:27 EST 2011


PC3X3 devices have a PLL to control the ARM core speed. Add a cpufreq
driver so that we can run at optimum speed and power.

v2:
	- move to a module initcall

Signed-off-by: Jamie Iles <jamie at jamieiles.com>
---
 arch/arm/mach-picoxcell/Makefile  |    1 +
 arch/arm/mach-picoxcell/cpufreq.c |  146 +++++++++++++++++++++++++++++++++++++
 2 files changed, 147 insertions(+), 0 deletions(-)
 create mode 100644 arch/arm/mach-picoxcell/cpufreq.c

diff --git a/arch/arm/mach-picoxcell/Makefile b/arch/arm/mach-picoxcell/Makefile
index f71a33e..c0bbb25 100644
--- a/arch/arm/mach-picoxcell/Makefile
+++ b/arch/arm/mach-picoxcell/Makefile
@@ -4,3 +4,4 @@ obj-y				:= picoxcell_core.o io.o axi2cfg.o \
 				   gpio.o \
 				   clk.o \
 				   devices.o
+obj-$(CONFIG_CPU_FREQ)		+= cpufreq.o
diff --git a/arch/arm/mach-picoxcell/cpufreq.c b/arch/arm/mach-picoxcell/cpufreq.c
new file mode 100644
index 0000000..ecb5d96
--- /dev/null
+++ b/arch/arm/mach-picoxcell/cpufreq.c
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2010 picoChip Designs Ltd., Jamie Iles
+ *
+ * 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.
+ *
+ * All enquiries to support at picochip.com
+ */
+#define pr_fmt(fmt) "picoxcell_cpufreq: " fmt
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/cpufreq.h>
+
+#include <mach/hardware.h>
+
+#include "picoxcell_core.h"
+#include "soc.h"
+
+static struct {
+	struct clk  *arm_clk;
+} cpufreq;
+
+/* The minimum frequency that we'll allow the ARM to run at (in KHz). */
+#define PC3X3_PLL_MIN_KHZ			    140000
+/* The maximum frequency that we'll allow the ARM to run at (in KHz). */
+#define PC3X3_PLL_MAX_KHZ			    700000
+/* The step between frequencies that the PLL can generate. */
+#define PC3X3_PLL_STEP				    5000
+
+/*
+ * The fuse block contains an 8 bit number which is the maximum clkf value
+ * that we can program. If this isn't programmed then allow 700Mhz operation.
+ * If not, limit the maximum speed to whatever this value corresponds to.
+ */
+static unsigned int picoxcell_cpufreq_max_clkf(void)
+{
+#define MAX_CLKF_FUSE	904
+#define MAX_CLKF_REG	IO_ADDRESS(PICOXCELL_FUSE_BASE + 904 / 8)
+	u8 max_clkf = readb(MAX_CLKF_REG);
+
+	return max_clkf ? ((max_clkf + 1) * 5) / 1000 : PC3X3_PLL_MAX_KHZ;
+}
+
+/*
+ * Initialise the new policy. We allow the PLL to go to the minimum speed but
+ * limit it to either 700Mhz or the frequency that corresponds to the clkf
+ * value in ARM_PLL_M_NUMBER fuses in the fuse block (if nonzero), whichever
+ * is smallest.
+ *
+ * A change of 20% should take ~2uS so we specify the transition latency as
+ * 50uS. This should allow jumps from 400MHz->700MHz within this period.
+ */
+static int picoxcell_cpufreq_init_policy(struct cpufreq_policy *policy)
+{
+	policy->cpuinfo.min_freq		= PC3X3_PLL_MIN_KHZ;
+	policy->cpuinfo.max_freq		= picoxcell_cpufreq_max_clkf();
+	policy->cpuinfo.transition_latency	= 50000;
+	policy->cur				= clk_get_rate(cpufreq.arm_clk);
+	policy->min				= PC3X3_PLL_MIN_KHZ;
+	policy->max				= picoxcell_cpufreq_max_clkf();
+
+	return 0;
+}
+
+static int picoxcell_cpufreq_verify(struct cpufreq_policy *policy)
+{
+	cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq,
+				     policy->cpuinfo.max_freq);
+
+	policy->min = clk_round_rate(cpufreq.arm_clk, policy->min);
+	policy->max = clk_round_rate(cpufreq.arm_clk, policy->max);
+
+	cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq,
+				     policy->cpuinfo.max_freq);
+
+	return 0;
+}
+
+static int picoxcell_cpufreq_target(struct cpufreq_policy *policy,
+				    unsigned int target_freq,
+				    unsigned int relation)
+{
+	int ret = 0;
+	struct cpufreq_freqs freqs;
+
+	if (target_freq > policy->max)
+		target_freq = policy->max;
+	if (target_freq < policy->min)
+		target_freq = policy->min;
+
+	target_freq = clk_round_rate(cpufreq.arm_clk, target_freq);
+
+	freqs.old = clk_get_rate(cpufreq.arm_clk);
+	freqs.new = target_freq;
+	freqs.cpu = policy->cpu;
+
+	if (freqs.new == freqs.old)
+		return 0;
+
+	cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
+
+	ret = clk_set_rate(cpufreq.arm_clk, target_freq);
+	if (ret) {
+		pr_err("unable to set cpufreq rate to %u\n", target_freq);
+		freqs.new = freqs.old;
+	}
+
+	cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
+
+	return ret;
+}
+
+static unsigned int picoxcell_cpufreq_get(unsigned int cpu)
+{
+	return (unsigned int)clk_get_rate(cpufreq.arm_clk);
+}
+
+static struct cpufreq_driver picoxcell_cpufreq_driver = {
+	.owner		= THIS_MODULE,
+	.flags		= CPUFREQ_STICKY,
+	.name		= "picoxcell",
+	.init		= picoxcell_cpufreq_init_policy,
+	.verify		= picoxcell_cpufreq_verify,
+	.target		= picoxcell_cpufreq_target,
+	.get		= picoxcell_cpufreq_get,
+};
+
+static int __init picoxcell_cpufreq_init(void)
+{
+	if (!picoxcell_has_feature(PICOXCELL_FEATURE_CPUFREQ))
+		return 0;
+
+	cpufreq.arm_clk = clk_get(NULL, "arm");
+
+	if (IS_ERR(cpufreq.arm_clk)) {
+		pr_info("cpufreq: no arm clock available - disabling scaling\n");
+		return PTR_ERR(cpufreq.arm_clk);
+	}
+
+	return cpufreq_register_driver(&picoxcell_cpufreq_driver);
+}
+module_init(picoxcell_cpufreq_init);
-- 
1.7.3.4




More information about the linux-arm-kernel mailing list