diff mbox

[v2] cpufreq/arm-bl-cpufreq: Add simple cpufreq big.LITTLE switcher frontend

Message ID 1332258545-20223-1-git-send-email-dave.martin@linaro.org
State Not Applicable
Headers show

Commit Message

Dave Martin March 20, 2012, 3:49 p.m. UTC
This patch adds a very simple cpufreq-based frontend to the ARM
big.LITTLE switcher.

This driver simply simulates two performance points corresponding
to the big and little clusters.

Note that this driver requires the ARM switcher implementation to
be loaded in order to work.  The switcher must have been built with
ASYNC = FALSE in big-little/Makefile in order for the synchronous
switching interface to be exposed.

Bugs and limitations:

  * Switching twice in quick succession currently tends to cause
    a deadlock inside the switcher.  I still need to identify
    exactly what is going wrong here.

  * There is currently no tracing interface, and no interface for
    reporting what the dummy frequencies exposed by the driver
    actually mean in terms of real cluster / performance point
    combinations.  For the very simple case supported,
    cpuinfo_max_freq corresponds to big and cpuinfo_min_freq
    corresponds to LITTLE.

  * cpufreq will trigger spurious extra cluster switches.  This is
    a "feature" since I didn't tell cpufreq that this matters, but
    it may not be desirable.  Setting policy->cpus can probably fix
    this.

Signed-off-by: Dave Martin <dave.martin@linaro.org>
---
Changes since v1:

  * driver->get() method added to allow the current "frequency" to
    be queried via cpuinfo_cur_freq in sysfs.

  * Some minor documentation fixes and tidyups.

  * The default governor is no longer overridden in the driver.  It
    turns out that I misread the cpufreq documentation and this
    isn't needed.

  * This patch rewrites cluster_to_freq() to do its mapping using the
    bl_freqs[] table instead of relying on its own knowledge.

 Documentation/cpu-freq/cpufreq-arm-bl.txt |   47 ++++++
 arch/arm/Kconfig                          |    1 +
 drivers/cpufreq/Kconfig.arm               |   18 +++
 drivers/cpufreq/Makefile                  |    4 +
 drivers/cpufreq/arm-bl-cpufreq.h          |   13 ++
 drivers/cpufreq/arm-bl-cpufreq_driver.c   |  217 +++++++++++++++++++++++++++++
 drivers/cpufreq/arm-bl-cpufreq_hvc.S      |   15 ++
 7 files changed, 315 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/cpu-freq/cpufreq-arm-bl.txt
 create mode 100644 drivers/cpufreq/arm-bl-cpufreq.h
 create mode 100644 drivers/cpufreq/arm-bl-cpufreq_driver.c
 create mode 100644 drivers/cpufreq/arm-bl-cpufreq_hvc.S

Comments

Avik Sil March 21, 2012, 10:11 a.m. UTC | #1
With this patch, the default governor is userspace but after one hour of
booting it's switching to performance governor!

# tail -n 20 /var/log/syslog
Jan  1 00:00:06 linaro-developer kernel: init: ureadahead main process
(614) terminated with status 5
Jan  1 00:00:06 linaro-developer kernel: EXT4-fs (mmcblk0p2):
re-mounted. Opts: errors=remount-ro
Jan  1 00:00:06 linaro-developer kernel: init: udev-fallback-graphics
main process (789) terminated with status 1
Jan  1 00:00:06 linaro-developer kernel: init: failsafe main process
(779) killed by TERM signal
Jan  1 00:00:06 linaro-developer cron[826]: (CRON) INFO (pidfile fd = 3)
Jan  1 00:00:06 linaro-developer cron[840]: (CRON) STARTUP (fork ok)
Jan  1 00:00:06 linaro-developer cron[840]: (CRON) INFO (Running @reboot
jobs)
Jan  1 00:00:06 linaro-developer kernel: init: tty1 main process (859)
killed by TERM signal
Jan  1 00:01:06 linaro-developer kernel: ondemand governor failed, too
long transition latency of HW, fallback to performance governor
Jan  1 00:01:06 linaro-developer kernel: arm-bl-cpufreq: Switching to
cluster 0
Jan  1 00:01:06 linaro-developer kernel: arm-bl-cpufreq: Switching to
cluster 0
Jan  1 00:01:06 linaro-developer kernel: ondemand governor failed, too
long transition latency of HW, fallback to performance governor
Jan  1 00:01:06 linaro-developer kernel: arm-bl-cpufreq: Switching to
cluster 0
Jan  1 00:01:06 linaro-developer kernel: arm-bl-cpufreq: Switching to
cluster 0
Jan  1 00:01:06 linaro-developer kernel: ondemand governor failed, too
long transition latency of HW, fallback to performance governor
Jan  1 00:01:06 linaro-developer kernel: arm-bl-cpufreq: Switching to
cluster 0
Jan  1 00:01:06 linaro-developer kernel: arm-bl-cpufreq: Switching to
cluster 0
Jan  1 00:01:06 linaro-developer kernel: ondemand governor failed, too
long transition latency of HW, fallback to performance governor
Jan  1 00:01:06 linaro-developer kernel: arm-bl-cpufreq: Switching to
cluster 0
Jan  1 00:01:06 linaro-developer kernel: arm-bl-cpufreq: Switching to
cluster 0

Regards,
Avik

On Tuesday 20 March 2012 09:19 PM, Dave Martin wrote:

> This patch adds a very simple cpufreq-based frontend to the ARM
> big.LITTLE switcher.
> 
> This driver simply simulates two performance points corresponding
> to the big and little clusters.
> 
> Note that this driver requires the ARM switcher implementation to
> be loaded in order to work.  The switcher must have been built with
> ASYNC = FALSE in big-little/Makefile in order for the synchronous
> switching interface to be exposed.
> 
> Bugs and limitations:
> 
>   * Switching twice in quick succession currently tends to cause
>     a deadlock inside the switcher.  I still need to identify
>     exactly what is going wrong here.
> 
>   * There is currently no tracing interface, and no interface for
>     reporting what the dummy frequencies exposed by the driver
>     actually mean in terms of real cluster / performance point
>     combinations.  For the very simple case supported,
>     cpuinfo_max_freq corresponds to big and cpuinfo_min_freq
>     corresponds to LITTLE.
> 
>   * cpufreq will trigger spurious extra cluster switches.  This is
>     a "feature" since I didn't tell cpufreq that this matters, but
>     it may not be desirable.  Setting policy->cpus can probably fix
>     this.
> 
> Signed-off-by: Dave Martin <dave.martin@linaro.org>
> ---
> Changes since v1:
> 
>   * driver->get() method added to allow the current "frequency" to
>     be queried via cpuinfo_cur_freq in sysfs.
> 
>   * Some minor documentation fixes and tidyups.
> 
>   * The default governor is no longer overridden in the driver.  It
>     turns out that I misread the cpufreq documentation and this
>     isn't needed.
> 
>   * This patch rewrites cluster_to_freq() to do its mapping using the
>     bl_freqs[] table instead of relying on its own knowledge.
>
Omar Ramirez Luna March 21, 2012, 11:45 a.m. UTC | #2
On 21 March 2012 05:11, Avik Sil <avik.sil@linaro.org> wrote:
> With this patch, the default governor is userspace but after one hour of
> booting it's switching to performance governor!

Same here, but mine went for 25 min before switching from userspace to
performance.

Also, I'm not seeing echo $freq > scaling_setspeed update the
frequency when switching to LITTLE, however the cluster switch is
occurring because in the model I can see the transitions between
cluster0 and cluster1.

Regards,

Omar
Omar Ramirez Luna March 21, 2012, 12:04 p.m. UTC | #3
On 20 March 2012 10:49, Dave Martin <dave.martin@linaro.org> wrote:
...
> +static int bl_cpufreq_init(struct cpufreq_policy *policy)
> +{
> +       int err;
> +
> +       /*
> +        * Set CPU and policy min and max frequencies based on bl_freqs:
> +        */
> +       err = cpufreq_frequency_table_cpuinfo(policy, bl_freqs);
> +       if (err)
> +               goto error;
> +
> +       /*
> +        * No need for locking here:
> +        * cpufreq is not active until initialisation has finished.

s/initialisation/initialization/

> +        * Ideally, transition_latency should be calibrated here.
> +        */
> +       policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL;
> +       policy->cur = get_current_freq();
> +
> +       /*
> +        * A b.L switch can be triggered from any CPU, but will affect them all.
> +        * The set of related CPUs should perhaps be determined from the
> +        * system CPU topology, rather than just the set of CPUs present...
> +        */
> +       policy->shared_type = CPUFREQ_SHARED_TYPE_ANY;
> +       cpumask_copy(policy->related_cpus, cpu_present_mask);
> +       /*
> +        * We do not set ->cpus here, because it doesn't actually matter if
> +        * we try to switch on two CPUs at the same time.  Setting ->cpus
> +        * to cpu_present_mask might provide a way to avoid the need to take
> +        * switcher_lock when switching, though.
> +        */
> +
> +       info("cpufreq initialised successfully\n");
> +       return 0;
> +
> +error:
> +       warn("%s: cpufreq initialisation failed (%d)\n", __func__, err);
> +       return err;
> +}
> +
> +static int bl_cpufreq_verify(struct cpufreq_policy *policy)
> +{
> +       return cpufreq_frequency_table_verify(policy, bl_freqs);
> +}
> +
> +static int bl_cpufreq_target(struct cpufreq_policy *policy,
> +                            unsigned int target_freq,
> +                            unsigned int relation)
> +{
> +       int err;
> +       int index;
> +
> +       if(cpufreq_frequency_table_target(policy, bl_freqs, target_freq,
> +                                         relation, &index))
> +               return err;

Seems like err is never assigned.

Regards,

Omar
Dave Martin March 21, 2012, 12:33 p.m. UTC | #4
On Wed, Mar 21, 2012 at 07:04:25AM -0500, Omar Ramirez Luna wrote:
> On 20 March 2012 10:49, Dave Martin <dave.martin@linaro.org> wrote:
> ...
> > +static int bl_cpufreq_init(struct cpufreq_policy *policy)
> > +{
> > +       int err;
> > +
> > +       /*
> > +        * Set CPU and policy min and max frequencies based on bl_freqs:
> > +        */
> > +       err = cpufreq_frequency_table_cpuinfo(policy, bl_freqs);
> > +       if (err)
> > +               goto error;
> > +
> > +       /*
> > +        * No need for locking here:
> > +        * cpufreq is not active until initialisation has finished.
> 
> s/initialisation/initialization/

http://en.wikipedia.org/wiki/Ize

In fact, about 12% of instances of this word in the kernel use the -ise
spelling.

> > +        * Ideally, transition_latency should be calibrated here.
> > +        */
> > +       policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL;
> > +       policy->cur = get_current_freq();
> > +
> > +       /*
> > +        * A b.L switch can be triggered from any CPU, but will affect them all.
> > +        * The set of related CPUs should perhaps be determined from the
> > +        * system CPU topology, rather than just the set of CPUs present...
> > +        */
> > +       policy->shared_type = CPUFREQ_SHARED_TYPE_ANY;
> > +       cpumask_copy(policy->related_cpus, cpu_present_mask);
> > +       /*
> > +        * We do not set ->cpus here, because it doesn't actually matter if
> > +        * we try to switch on two CPUs at the same time.  Setting ->cpus
> > +        * to cpu_present_mask might provide a way to avoid the need to take
> > +        * switcher_lock when switching, though.
> > +        */
> > +
> > +       info("cpufreq initialised successfully\n");
> > +       return 0;
> > +
> > +error:
> > +       warn("%s: cpufreq initialisation failed (%d)\n", __func__, err);
> > +       return err;
> > +}
> > +
> > +static int bl_cpufreq_verify(struct cpufreq_policy *policy)
> > +{
> > +       return cpufreq_frequency_table_verify(policy, bl_freqs);
> > +}
> > +
> > +static int bl_cpufreq_target(struct cpufreq_policy *policy,
> > +                            unsigned int target_freq,
> > +                            unsigned int relation)
> > +{
> > +       int err;
> > +       int index;
> > +
> > +       if(cpufreq_frequency_table_target(policy, bl_freqs, target_freq,
> > +                                         relation, &index))
> > +               return err;
> 
> Seems like err is never assigned.

Good spot -- thanks

(I'm wonder why the compiler doesn't generate a warning for that... it's
a pretty clear case of use-before-assignment.)

---Dave
Dave Martin March 21, 2012, 12:58 p.m. UTC | #5
On Wed, Mar 21, 2012 at 06:45:28AM -0500, Omar Ramirez Luna wrote:
> On 21 March 2012 05:11, Avik Sil <avik.sil@linaro.org> wrote:
> > With this patch, the default governor is userspace but after one hour of
> > booting it's switching to performance governor!
> 
> Same here, but mine went for 25 min before switching from userspace to
> performance.

Aha, I've found out what's doing this...

There's a boot-time job /etc/init.d/ondemand which sets the governor to
ondemand after a while :P

It contains the following rather cringeworthy comment:

        sleep 60 # probably enough time for desktop login


This looks like a distro hack that sort-of-works for desktop-like
systems buit doesn't appear to have been very carefully thought through.
Certainly it is foolish to change the governor in this way if the
current governor is "userspace", since that explicitly implies that
something else is trying to control cpufreq.

Fixing this properly might require a lockfile or something for race-free
coordination of ownership and changes to the cpufreq governor in
userspace: "sleep 60" would obviously be a poor way to ensure that any
cpufreq daemon has started up.

See: https://bugs.launchpad.net/ubuntu/+source/sysvinit/+bug/341573

It might be worth raising a bug on the sysvinit package.


Without extra coordination this init script is obviously totally
inappropriate for scenarios where you want control over the governor.
For the linaro filesystems, you can disable this job by:

# update-rc.d -f ondemand remove

(or simply delete the /etc/rc?.d/S??ondemand links)

> Also, I'm not seeing echo $freq > scaling_setspeed update the
> frequency when switching to LITTLE, however the cluster switch is
> occurring because in the model I can see the transitions between
> cluster0 and cluster1.

What do you expect to see?

For me, reading scaling_cur_freq or scaling_setspeed, you always
see 1000000 -- I guess this is what you're referring to.

If you look at cpuinfo_cur_freq, you should see the current actual
performance point (1000000 or 100000 as appropriate).

I don't currently understand the difference here -- it may just be a
"feature" of the cpufreq core framework -- but reading cpuinfo_cur_freq
is a reasonable thing to do and it gives the right answer, so I'm
recommending that everyone does that.

I may still be missing some driver methods; I'll fix the discrepancy if
I can work out what causes it and it turns out to be a real bug.  If
anyone knows the answer, I'd like to hear it :)

Cheers
---Dave
Omar Ramirez Luna March 21, 2012, 2 p.m. UTC | #6
On 21 March 2012 07:33, Dave Martin <dave.martin@linaro.org> wrote:
>> > +       /*
>> > +        * No need for locking here:
>> > +        * cpufreq is not active until initialisation has finished.
>>
>> s/initialisation/initialization/
>
> http://en.wikipedia.org/wiki/Ize
>
> In fact, about 12% of instances of this word in the kernel use the -ise
> spelling.

Interesting, not being a native English speaker my comments about
grammar or spelling are not very reliable, I blindly trust on the
spelling checker instead ;)

Thanks,

Omar
Dave Martin March 21, 2012, 3:29 p.m. UTC | #7
On Wed, Mar 21, 2012 at 09:00:36AM -0500, Omar Ramirez Luna wrote:
> On 21 March 2012 07:33, Dave Martin <dave.martin@linaro.org> wrote:
> >> > +       /*
> >> > +        * No need for locking here:
> >> > +        * cpufreq is not active until initialisation has finished.
> >>
> >> s/initialisation/initialization/
> >
> > http://en.wikipedia.org/wiki/Ize
> >
> > In fact, about 12% of instances of this word in the kernel use the -ise
> > spelling.
> 
> Interesting, not being a native English speaker my comments about
> grammar or spelling are not very reliable, I blindly trust on the
> spelling checker instead ;)

I know; one of those silly things, I'm afraid.

Thanks for reading the patch so carefully, anyway :)

Cheers
---Dave
Omar Ramirez Luna March 21, 2012, 4:53 p.m. UTC | #8
On 21 March 2012 07:58, Dave Martin <dave.martin@linaro.org> wrote:
>> Also, I'm not seeing echo $freq > scaling_setspeed update the
>> frequency when switching to LITTLE, however the cluster switch is
>> occurring because in the model I can see the transitions between
>> cluster0 and cluster1.
>
> What do you expect to see?
>
> For me, reading scaling_cur_freq or scaling_setspeed, you always
> see 1000000 -- I guess this is what you're referring to.

Yes, somehow I expected to see the updated value in scaling_setspeed
(given that my ubuntu desktop does update the value). However, reading
scaling_setspeed is not good to check as there could be a
cpuinfo_transition_latency involved for the real value which should be
in cpuinfo_cur_freq.

> If you look at cpuinfo_cur_freq, you should see the current actual
> performance point (1000000 or 100000 as appropriate).

Yes it does. Agree to read cpuinfo_cur_freq instead.

Thanks,

Omar
Dave Martin March 21, 2012, 6:08 p.m. UTC | #9
On Wed, Mar 21, 2012 at 11:53:05AM -0500, Omar Ramirez Luna wrote:
> On 21 March 2012 07:58, Dave Martin <dave.martin@linaro.org> wrote:
> >> Also, I'm not seeing echo $freq > scaling_setspeed update the
> >> frequency when switching to LITTLE, however the cluster switch is
> >> occurring because in the model I can see the transitions between
> >> cluster0 and cluster1.
> >
> > What do you expect to see?
> >
> > For me, reading scaling_cur_freq or scaling_setspeed, you always
> > see 1000000 -- I guess this is what you're referring to.
> 
> Yes, somehow I expected to see the updated value in scaling_setspeed
> (given that my ubuntu desktop does update the value). However, reading

I would expect to see that too -- however, it may not be interesting any
any case.  If it just returns the last number written (or the nearest
supported frequency), this doesn't tell us whether the switch actually
happened.

> scaling_setspeed is not good to check as there could be a
> cpuinfo_transition_latency involved for the real value which should be
> in cpuinfo_cur_freq.

There is an important question here ---

How do we wait for the frequency to settle to that we can see whether
the switch really took effect?

This may not affect the ARM switcher, since the switching is pretty
synchronous, but I would need to take a look at how the cpufreq core
code works.  If the switch request only causes a new frequency to be
programmed and there is additional settling latency, then it is possible
that cpuinfo_cur_freq wouldn't return the expected thing for a while on
real systems, especially when read from a different CPU.

> > If you look at cpuinfo_cur_freq, you should see the current actual
> > performance point (1000000 or 100000 as appropriate).
> 
> Yes it does. Agree to read cpuinfo_cur_freq instead.

OK -- I suggest we should stick to this for now, and revisit the issue
later it is becomes a problem.

Cheers
---Dave
diff mbox

Patch

diff --git a/Documentation/cpu-freq/cpufreq-arm-bl.txt b/Documentation/cpu-freq/cpufreq-arm-bl.txt
new file mode 100644
index 0000000..52e2f3a
--- /dev/null
+++ b/Documentation/cpu-freq/cpufreq-arm-bl.txt
@@ -0,0 +1,47 @@ 
+Synchronous cluster switching interface for the ARM big.LITTLE switcher
+-----------------------------------------------------------------------
+
+The arm-bl-cpufreq driver provides a simple interface which models two
+clusters as two performance points.
+
+Within each CPU's cpufreq directory in sysfs
+(/sys/devices/system/cpu/cpu?/cpufreq/):
+
+cpuinfo_max_freq:
+
+	reports the dummy frequency value which corresponds to the "big"
+	cluster.
+
+cpuinfo_min_freq:
+
+	reports the dummy frequency value which corresponds to the
+	"little" cluster.
+
+cpuinfo_cur_freq:
+
+	reports the dummy frequency corresponding to the currently
+	running cluster.
+
+
+To switch clusters, either the built-in "powersave" or "performance"
+governors can be used to force the "little" or "big" cluster
+respectively; or alternatively the "userspace" governor can be used,
+
+The following script fragment demonstrates how the userspace governor
+can be used to switch:
+
+
+for x in /sys/devices/system/cpu/cpu[0-9]*; do
+	echo userspace >$x/cpufreq/scaling_governor
+done
+
+big_freq=`cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq`
+little_freq=`cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq`
+
+switch_to_big () {
+	echo $big_freq >/sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed
+}
+
+switch_to_little () {
+	echo $little_freq >/sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed
+}
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 455367d..907d44a 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -301,6 +301,7 @@  config ARCH_VERSATILE
 config ARCH_VEXPRESS
 	bool "ARM Ltd. Versatile Express family"
 	select ARCH_WANT_OPTIONAL_GPIOLIB
+	select ARCH_HAS_CPUFREQ
 	select ARM_AMBA
 	select ARM_TIMER_SP804
 	select CLKDEV_LOOKUP
diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
index 72a0044..0e4f6d0 100644
--- a/drivers/cpufreq/Kconfig.arm
+++ b/drivers/cpufreq/Kconfig.arm
@@ -30,3 +30,21 @@  config ARM_EXYNOS4210_CPUFREQ
 	  SoC (S5PV310 or S5PC210).
 
 	  If in doubt, say N.
+
+config ARM_BL_CPUFREQ
+	depends on EXPERIMENTAL
+	depends on ARCH_VEXPRESS_DT
+	tristate "Simple cpufreq interface for the ARM big.LITTLE switcher"
+	help
+	  Provides a simple cpufreq interface to control the ARM
+	  big.LITTLE switcher.
+
+	  Note that this code is not currently safe unless the
+	  big.LITTLE switcher binary has been loaded separately by an
+	  external bootloader or firmware before entering the kernel.
+	  Otherwise, you can still build this code as a module,
+	  providing that you don't load it.
+
+	  Refer to Documentation/cpufreq/cpufreq-arm-bl.txt for details.
+
+	  If unsure, say N.
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index a48bc02..ecf492d 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -40,6 +40,10 @@  obj-$(CONFIG_X86_CPUFREQ_NFORCE2)	+= cpufreq-nforce2.o
 ##################################################################################
 # ARM SoC drivers
 obj-$(CONFIG_UX500_SOC_DB8500)		+= db8500-cpufreq.o
+obj-$(CONFIG_ARM_BL_CPUFREQ)		+= arm-bl-cpufreq.o
+arm-bl-cpufreq-y			+= arm-bl-cpufreq_driver.o \
+					   arm-bl-cpufreq_hvc.o
+AFLAGS_arm-bl-cpufreq_hvc.o		:= -march=armv7-a
 obj-$(CONFIG_ARM_S3C64XX_CPUFREQ)	+= s3c64xx-cpufreq.o
 obj-$(CONFIG_ARM_S5PV210_CPUFREQ)	+= s5pv210-cpufreq.o
 obj-$(CONFIG_ARM_EXYNOS4210_CPUFREQ)	+= exynos4210-cpufreq.o
diff --git a/drivers/cpufreq/arm-bl-cpufreq.h b/drivers/cpufreq/arm-bl-cpufreq.h
new file mode 100644
index 0000000..2f9b0dc
--- /dev/null
+++ b/drivers/cpufreq/arm-bl-cpufreq.h
@@ -0,0 +1,13 @@ 
+#ifndef ARM_BL_CPUFREQ_HVC_H
+#define ARM_BL_CPUFREQ_HVC_H
+
+#ifndef __ASSEMBLY__
+int __arm_bl_get_cluster(void);
+void __arm_bl_switch_cluster(void);
+#endif /* ! __ASSEMBLY__ */
+
+/* Hypervisor call numbers for the ARM big.LITTLE switcher: */
+#define ARM_BL_HVC_SWITCH_CLUSTER 1
+#define ARM_BL_HVC_GET_MPIDR 2
+
+#endif /* ! ARM_BL_CPUFREQ_HVC_H */
diff --git a/drivers/cpufreq/arm-bl-cpufreq_driver.c b/drivers/cpufreq/arm-bl-cpufreq_driver.c
new file mode 100644
index 0000000..faf2fc0
--- /dev/null
+++ b/drivers/cpufreq/arm-bl-cpufreq_driver.c
@@ -0,0 +1,217 @@ 
+/*
+ * arm-bl-cpufreq.c: Simple cpufreq backend for the ARM big.LITTLE switcher
+ * Copyright (C) 2012  Linaro Limited
+ *
+ * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* WARNING: This code is experimental and depends on external firmware */
+
+#include <linux/bug.h>
+#include <linux/cache.h>
+#include <linux/cpufreq.h>
+#include <linux/cpumask.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/printk.h>
+#include <linux/string.h>
+#include <linux/spinlock.h>
+
+#include "arm-bl-cpufreq.h"
+
+#define DRIVER_NAME "arm-bl"
+#define MODULE_NAME "arm-bl-cpufreq"
+
+#define info(format...) printk(KERN_INFO MODULE_NAME ": " format)
+#define warn(format...) printk(KERN_WARNING MODULE_NAME ": " format)
+
+/* Dummy frequencies representing the big and little clusters: */
+#define FREQ_BIG	1000000
+#define FREQ_LITTLE	 100000
+
+/*  Cluster numbers */
+#define CLUSTER_BIG	0
+#define CLUSTER_LITTLE	1
+
+static DEFINE_SPINLOCK(switcher_lock);
+
+static struct cpufreq_frequency_table __read_mostly bl_freqs[] = {
+	{ CLUSTER_BIG,		FREQ_BIG		},
+	{ CLUSTER_LITTLE,	FREQ_LITTLE		},
+	{ 0,			CPUFREQ_TABLE_END	},
+};
+
+
+/* Miscellaneous helpers */
+
+static unsigned int cluster_to_freq(int cluster)
+{
+	unsigned int i;
+
+	for(i = 0; bl_freqs[i].frequency != CPUFREQ_TABLE_END; i++)
+		if(bl_freqs[i].index == cluster)
+			return bl_freqs[i].frequency;
+
+	WARN(1, "%s: %s(): invalid cluster number %d, assuming 0\n",
+	     MODULE_NAME, __func__, cluster);
+	return bl_freqs[0].frequency;
+}
+
+/*
+ * Functions to get the current status.
+ *
+ * If you intend to use the result (i.e., it's not just for diagnostic
+ * purposes) then you should be holding switcher_lock ... otherwise
+ * the current cluster may change unexpectedly.
+ */
+static int get_current_cluster(void)
+{
+	return (__arm_bl_get_cluster() >> 8) & 0xF;
+}
+
+static unsigned int get_current_freq(void)
+{
+	return cluster_to_freq(get_current_cluster());
+}
+
+/*
+ * Switch to the requested cluster.
+ * There is no "switch_to_frequency" function, because the cpufreq frequency
+ * table helpers can easily look up the appropriate cluster number for us.
+ */
+static void switch_to_cluster(int cluster)
+{
+	info("Switching to cluster %d\n", cluster);
+
+	spin_lock(&switcher_lock);
+	if(cluster != get_current_cluster())
+		__arm_bl_switch_cluster();
+	spin_unlock(&switcher_lock);
+}
+
+
+/* Cpufreq methods and module code */
+
+static int bl_cpufreq_init(struct cpufreq_policy *policy)
+{
+	int err;
+
+	/*
+	 * Set CPU and policy min and max frequencies based on bl_freqs:
+	 */
+	err = cpufreq_frequency_table_cpuinfo(policy, bl_freqs);
+	if (err)
+		goto error;
+
+	/*
+	 * No need for locking here:
+	 * cpufreq is not active until initialisation has finished.
+	 * Ideally, transition_latency should be calibrated here.
+	 */
+	policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL;
+	policy->cur = get_current_freq();
+
+	/*
+	 * A b.L switch can be triggered from any CPU, but will affect them all.
+	 * The set of related CPUs should perhaps be determined from the
+	 * system CPU topology, rather than just the set of CPUs present...
+	 */
+	policy->shared_type = CPUFREQ_SHARED_TYPE_ANY;
+	cpumask_copy(policy->related_cpus, cpu_present_mask);
+	/*
+	 * We do not set ->cpus here, because it doesn't actually matter if
+	 * we try to switch on two CPUs at the same time.  Setting ->cpus
+	 * to cpu_present_mask might provide a way to avoid the need to take
+	 * switcher_lock when switching, though.
+	 */
+
+	info("cpufreq initialised successfully\n");
+	return 0;
+	
+error:
+	warn("%s: cpufreq initialisation failed (%d)\n", __func__, err);
+	return err;
+}
+
+static int bl_cpufreq_verify(struct cpufreq_policy *policy)
+{
+	return cpufreq_frequency_table_verify(policy, bl_freqs);
+}
+
+static int bl_cpufreq_target(struct cpufreq_policy *policy,
+			     unsigned int target_freq,
+			     unsigned int relation)
+{
+	int err;
+	int index;
+
+	if(cpufreq_frequency_table_target(policy, bl_freqs, target_freq,
+					  relation, &index))
+		return err;
+
+	switch_to_cluster(bl_freqs[index].index);
+	return 0;
+}
+
+static unsigned int bl_cpufreq_get(unsigned int __always_unused cpu)
+{
+	/*
+	 * The cpu argument is ignored, because all CPUs have the same
+	 * performance point, by definition.
+	 */
+	return get_current_freq();
+}
+
+static struct cpufreq_driver __read_mostly bl_cpufreq_driver = {
+	.owner = THIS_MODULE,
+	.name = DRIVER_NAME,
+
+	.init = bl_cpufreq_init,
+	.verify = bl_cpufreq_verify,
+	.target = bl_cpufreq_target,
+	.get = bl_cpufreq_get,
+	/* what else? */
+};	
+
+static int __init bl_cpufreq_module_init(void)
+{
+	int err;
+
+	err = cpufreq_register_driver(&bl_cpufreq_driver);
+	if(err)
+		info("cpufreq backend driver registration failed (%d)\n", err);
+	else
+		info("cpufreq backend driver registered.\n");
+
+	return err;
+}
+module_init(bl_cpufreq_module_init);
+
+static void __exit bl_cpufreq_module_exit(void)
+{
+	cpufreq_unregister_driver(&bl_cpufreq_driver);
+
+	/* Restore the "default" cluster: */
+	switch_to_cluster(CLUSTER_BIG);
+
+	info("cpufreq backend driver unloaded.\n");
+}
+module_exit(bl_cpufreq_module_exit);
+
+
+MODULE_AUTHOR("Dave Martin");
+MODULE_DESCRIPTION("Simple cpufreq interface for the ARM big.LITTLE switcher");
+MODULE_LICENSE("GPL");
diff --git a/drivers/cpufreq/arm-bl-cpufreq_hvc.S b/drivers/cpufreq/arm-bl-cpufreq_hvc.S
new file mode 100644
index 0000000..6c7eb6d
--- /dev/null
+++ b/drivers/cpufreq/arm-bl-cpufreq_hvc.S
@@ -0,0 +1,15 @@ 
+#include <linux/linkage.h>
+
+#include "arm-bl-cpufreq.h"
+
+.arch_extension virt
+
+ENTRY(__arm_bl_get_cluster)
+	hvc	#ARM_BL_HVC_GET_MPIDR
+	bx	lr
+ENDPROC(__arm_bl_get_cluster)
+
+ENTRY(__arm_bl_switch_cluster)
+	hvc	#ARM_BL_HVC_SWITCH_CLUSTER
+	bx	lr
+ENDPROC(__arm_bl_switch_cluster)