1 /* 2 * Copyright (C) 2012 Freescale Semiconductor, Inc. 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License version 2 as 6 * published by the Free Software Foundation. 7 */ 8 9 #include <linux/clockchips.h> 10 #include <linux/cpuidle.h> 11 #include <linux/module.h> 12 #include <asm/cpuidle.h> 13 #include <asm/proc-fns.h> 14 15 #include "common.h" 16 #include "cpuidle.h" 17 18 static atomic_t master = ATOMIC_INIT(0); 19 static DEFINE_SPINLOCK(master_lock); 20 21 static int imx6q_enter_wait(struct cpuidle_device *dev, 22 struct cpuidle_driver *drv, int index) 23 { 24 int cpu = dev->cpu; 25 26 clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &cpu); 27 28 if (atomic_inc_return(&master) == num_online_cpus()) { 29 /* 30 * With this lock, we prevent other cpu to exit and enter 31 * this function again and become the master. 32 */ 33 if (!spin_trylock(&master_lock)) 34 goto idle; 35 imx6q_set_lpm(WAIT_UNCLOCKED); 36 cpu_do_idle(); 37 imx6q_set_lpm(WAIT_CLOCKED); 38 spin_unlock(&master_lock); 39 goto done; 40 } 41 42 idle: 43 cpu_do_idle(); 44 done: 45 atomic_dec(&master); 46 clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &cpu); 47 48 return index; 49 } 50 51 /* 52 * For each cpu, setup the broadcast timer because local timer 53 * stops for the states other than WFI. 54 */ 55 static void imx6q_setup_broadcast_timer(void *arg) 56 { 57 int cpu = smp_processor_id(); 58 59 clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ON, &cpu); 60 } 61 62 static struct cpuidle_driver imx6q_cpuidle_driver = { 63 .name = "imx6q_cpuidle", 64 .owner = THIS_MODULE, 65 .en_core_tk_irqen = 1, 66 .states = { 67 /* WFI */ 68 ARM_CPUIDLE_WFI_STATE, 69 /* WAIT */ 70 { 71 .exit_latency = 50, 72 .target_residency = 75, 73 .flags = CPUIDLE_FLAG_TIME_VALID, 74 .enter = imx6q_enter_wait, 75 .name = "WAIT", 76 .desc = "Clock off", 77 }, 78 }, 79 .state_count = 2, 80 .safe_state_index = 0, 81 }; 82 83 int __init imx6q_cpuidle_init(void) 84 { 85 /* Need to enable SCU standby for entering WAIT modes */ 86 imx_scu_standby_enable(); 87 88 /* Set chicken bit to get a reliable WAIT mode support */ 89 imx6q_set_chicken_bit(); 90 91 /* Configure the broadcast timer on each cpu */ 92 on_each_cpu(imx6q_setup_broadcast_timer, NULL, 1); 93 94 return imx_cpuidle_init(&imx6q_cpuidle_driver); 95 } 96