ARM: imx6q: support WAIT mode using cpuidle
authorShawn Guo <shawn.guo@linaro.org>
Tue, 4 Dec 2012 14:55:15 +0000 (22:55 +0800)
committerShawn Guo <shawn.guo@linaro.org>
Wed, 30 Jan 2013 13:09:31 +0000 (21:09 +0800)
Add WAIT mode (ARM core clock gating) support to imx6q cpuidle driver.
As WAIT mode is broken on imx6q TO 1.0 and 1.1, it only enables the
support for revision 1.2 with chicken bit set.

Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
arch/arm/mach-imx/clk-imx6q.c
arch/arm/mach-imx/common.h
arch/arm/mach-imx/cpuidle-imx6q.c
arch/arm/mach-imx/mach-imx6q.c
arch/arm/mach-imx/platsmp.c

index c7e429bd32d065012f94b4b968b90d58e7dba35f..b30b65a8cce5645c5f946c22f67162164e326396 100644 (file)
 #define BM_CLPCR_MASK_SCU_IDLE         (0x1 << 26)
 #define BM_CLPCR_MASK_L2CC_IDLE                (0x1 << 27)
 
+#define CGPR                           0x64
+#define BM_CGPR_CHICKEN_BIT            (0x1 << 17)
+
 static void __iomem *ccm_base;
 
+void imx6q_set_chicken_bit(void)
+{
+       u32 val = readl_relaxed(ccm_base + CGPR);
+
+       val |= BM_CGPR_CHICKEN_BIT;
+       writel_relaxed(val, ccm_base + CGPR);
+}
+
 int imx6q_set_lpm(enum mxc_cpu_pwr_mode mode)
 {
        u32 val = readl_relaxed(ccm_base + CLPCR);
@@ -66,6 +77,7 @@ int imx6q_set_lpm(enum mxc_cpu_pwr_mode mode)
                break;
        case WAIT_UNCLOCKED:
                val |= 0x1 << BP_CLPCR_LPM;
+               val |= BM_CLPCR_ARM_CLK_DIS_ON_LPM;
                break;
        case STOP_POWER_ON:
                val |= 0x2 << BP_CLPCR_LPM;
index 972c9f8cc97a9ccc43bbb07cf87c8a13397db840..c04ec845e3a353e50b6e8d8f839e86ae8586f974 100644 (file)
@@ -117,9 +117,11 @@ extern u32 *pl310_get_save_ptr(void);
 extern void v7_secondary_startup(void);
 extern void imx_scu_map_io(void);
 extern void imx_smp_prepare(void);
+extern void imx_scu_standby_enable(void);
 #else
 static inline void imx_scu_map_io(void) {}
 static inline void imx_smp_prepare(void) {}
+static inline void imx_scu_standby_enable(void) {}
 #endif
 extern void imx_enable_cpu(int cpu, bool enable);
 extern void imx_set_cpu_jump(int cpu, void *jump_addr);
@@ -129,6 +131,7 @@ extern void imx_gpc_init(void);
 extern void imx_gpc_pre_suspend(void);
 extern void imx_gpc_post_resume(void);
 extern int imx6q_set_lpm(enum mxc_cpu_pwr_mode mode);
+extern void imx6q_set_chicken_bit(void);
 
 extern void imx_cpu_die(unsigned int cpu);
 
index 83facc97b5da53be2f3ac32b5fec1a669c828980..d533e2695f0ea2c73f9498e662c071ae189a2a58 100644 (file)
@@ -6,21 +6,90 @@
  * published by the Free Software Foundation.
  */
 
+#include <linux/clockchips.h>
 #include <linux/cpuidle.h>
 #include <linux/module.h>
 #include <asm/cpuidle.h>
+#include <asm/proc-fns.h>
 
+#include "common.h"
 #include "cpuidle.h"
 
+static atomic_t master = ATOMIC_INIT(0);
+static DEFINE_SPINLOCK(master_lock);
+
+static int imx6q_enter_wait(struct cpuidle_device *dev,
+                           struct cpuidle_driver *drv, int index)
+{
+       int cpu = dev->cpu;
+
+       clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &cpu);
+
+       if (atomic_inc_return(&master) == num_online_cpus()) {
+               /*
+                * With this lock, we prevent other cpu to exit and enter
+                * this function again and become the master.
+                */
+               if (!spin_trylock(&master_lock))
+                       goto idle;
+               imx6q_set_lpm(WAIT_UNCLOCKED);
+               cpu_do_idle();
+               imx6q_set_lpm(WAIT_CLOCKED);
+               spin_unlock(&master_lock);
+               goto done;
+       }
+
+idle:
+       cpu_do_idle();
+done:
+       atomic_dec(&master);
+       clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &cpu);
+
+       return index;
+}
+
+/*
+ * For each cpu, setup the broadcast timer because local timer
+ * stops for the states other than WFI.
+ */
+static void imx6q_setup_broadcast_timer(void *arg)
+{
+       int cpu = smp_processor_id();
+
+       clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ON, &cpu);
+}
+
 static struct cpuidle_driver imx6q_cpuidle_driver = {
        .name = "imx6q_cpuidle",
        .owner = THIS_MODULE,
        .en_core_tk_irqen = 1,
-       .states[0] = ARM_CPUIDLE_WFI_STATE,
-       .state_count = 1,
+       .states = {
+               /* WFI */
+               ARM_CPUIDLE_WFI_STATE,
+               /* WAIT */
+               {
+                       .exit_latency = 50,
+                       .target_residency = 75,
+                       .flags = CPUIDLE_FLAG_TIME_VALID,
+                       .enter = imx6q_enter_wait,
+                       .name = "WAIT",
+                       .desc = "Clock off",
+               },
+       },
+       .state_count = 2,
+       .safe_state_index = 0,
 };
 
 int __init imx6q_cpuidle_init(void)
 {
+       /* Need to enable SCU standby for entering WAIT modes */
+       imx_scu_standby_enable();
+
+       /* Set chicken bit to get a reliable WAIT mode support */
+       imx6q_set_chicken_bit();
+
+       /* Configure the broadcast timer on each cpu */
+       on_each_cpu(imx6q_setup_broadcast_timer, NULL, 1);
+
        return imx_cpuidle_init(&imx6q_cpuidle_driver);
 }
index 27726de3537e77bee673b3127fc8cc7eae34e8a4..77a3b4bfff2093fc1602556b7ffa85b7a0328024 100644 (file)
@@ -202,7 +202,12 @@ static void __init imx6q_init_machine(void)
 
 static void __init imx6q_init_late(void)
 {
-       imx6q_cpuidle_init();
+       /*
+        * WAIT mode is broken on TO 1.0 and 1.1, so there is no point
+        * to run cpuidle on them.
+        */
+       if (imx6q_revision() > IMX_CHIP_REVISION_1_1)
+               imx6q_cpuidle_init();
 }
 
 static void __init imx6q_map_io(void)
index 3777b805b76ba8645c50c41993f8ba06b550927d..a70b548771918460e27a655419235d39e73f4fc9 100644 (file)
@@ -20,6 +20,8 @@
 #include "common.h"
 #include "hardware.h"
 
+#define SCU_STANDBY_ENABLE     (1 << 5)
+
 static void __iomem *scu_base;
 
 static struct map_desc scu_io_desc __initdata = {
@@ -42,6 +44,14 @@ void __init imx_scu_map_io(void)
        scu_base = IMX_IO_ADDRESS(base);
 }
 
+void imx_scu_standby_enable(void)
+{
+       u32 val = readl_relaxed(scu_base);
+
+       val |= SCU_STANDBY_ENABLE;
+       writel_relaxed(val, scu_base);
+}
+
 static void __cpuinit imx_secondary_init(unsigned int cpu)
 {
        /*