1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
212bb3440SShawn Guo /*
312bb3440SShawn Guo  * Copyright (C) 2012 Freescale Semiconductor, Inc.
412bb3440SShawn Guo  */
512bb3440SShawn Guo 
612bb3440SShawn Guo #include <linux/cpuidle.h>
712bb3440SShawn Guo #include <linux/module.h>
812bb3440SShawn Guo #include <asm/cpuidle.h>
912bb3440SShawn Guo 
1047096103SBen Dooks #include <soc/imx/cpuidle.h>
1147096103SBen Dooks 
12e5f9dec8SShawn Guo #include "common.h"
1312bb3440SShawn Guo #include "cpuidle.h"
14a25d67a4SAnson Huang #include "hardware.h"
1512bb3440SShawn Guo 
1691740fc8SKohji Okuno static int num_idle_cpus = 0;
1799ae52edSSebastian Andrzej Siewior static DEFINE_RAW_SPINLOCK(cpuidle_lock);
18e5f9dec8SShawn Guo 
19e5f9dec8SShawn Guo static int imx6q_enter_wait(struct cpuidle_device *dev,
20e5f9dec8SShawn Guo 			    struct cpuidle_driver *drv, int index)
21e5f9dec8SShawn Guo {
2299ae52edSSebastian Andrzej Siewior 	raw_spin_lock(&cpuidle_lock);
2391740fc8SKohji Okuno 	if (++num_idle_cpus == num_online_cpus())
248fb76a07SShawn Guo 		imx6_set_lpm(WAIT_UNCLOCKED);
2599ae52edSSebastian Andrzej Siewior 	raw_spin_unlock(&cpuidle_lock);
26e5f9dec8SShawn Guo 
27e5f9dec8SShawn Guo 	cpu_do_idle();
2891740fc8SKohji Okuno 
2999ae52edSSebastian Andrzej Siewior 	raw_spin_lock(&cpuidle_lock);
3091740fc8SKohji Okuno 	if (num_idle_cpus-- == num_online_cpus())
3191740fc8SKohji Okuno 		imx6_set_lpm(WAIT_CLOCKED);
3299ae52edSSebastian Andrzej Siewior 	raw_spin_unlock(&cpuidle_lock);
33e5f9dec8SShawn Guo 
34e5f9dec8SShawn Guo 	return index;
35e5f9dec8SShawn Guo }
36e5f9dec8SShawn Guo 
3712bb3440SShawn Guo static struct cpuidle_driver imx6q_cpuidle_driver = {
3812bb3440SShawn Guo 	.name = "imx6q_cpuidle",
3912bb3440SShawn Guo 	.owner = THIS_MODULE,
40e5f9dec8SShawn Guo 	.states = {
41e5f9dec8SShawn Guo 		/* WFI */
42e5f9dec8SShawn Guo 		ARM_CPUIDLE_WFI_STATE,
43e5f9dec8SShawn Guo 		/* WAIT */
44e5f9dec8SShawn Guo 		{
45e5f9dec8SShawn Guo 			.exit_latency = 50,
46e5f9dec8SShawn Guo 			.target_residency = 75,
47b82b6ccaSDaniel Lezcano 			.flags = CPUIDLE_FLAG_TIMER_STOP,
48e5f9dec8SShawn Guo 			.enter = imx6q_enter_wait,
49e5f9dec8SShawn Guo 			.name = "WAIT",
50e5f9dec8SShawn Guo 			.desc = "Clock off",
51e5f9dec8SShawn Guo 		},
52e5f9dec8SShawn Guo 	},
53e5f9dec8SShawn Guo 	.state_count = 2,
54e5f9dec8SShawn Guo 	.safe_state_index = 0,
5512bb3440SShawn Guo };
5612bb3440SShawn Guo 
5729380905SLucas Stach /*
5829380905SLucas Stach  * i.MX6 Q/DL has an erratum (ERR006687) that prevents the FEC from waking the
5929380905SLucas Stach  * CPUs when they are in wait(unclocked) state. As the hardware workaround isn't
6029380905SLucas Stach  * applicable to all boards, disable the deeper idle state when the workaround
6129380905SLucas Stach  * isn't present and the FEC is in use.
6229380905SLucas Stach  */
6329380905SLucas Stach void imx6q_cpuidle_fec_irqs_used(void)
6429380905SLucas Stach {
65cbda56d5SRafael J. Wysocki 	cpuidle_driver_state_disabled(&imx6q_cpuidle_driver, 1, true);
6629380905SLucas Stach }
672cb9caa4SShawn Guo EXPORT_SYMBOL_GPL(imx6q_cpuidle_fec_irqs_used);
6829380905SLucas Stach 
6929380905SLucas Stach void imx6q_cpuidle_fec_irqs_unused(void)
7029380905SLucas Stach {
71cbda56d5SRafael J. Wysocki 	cpuidle_driver_state_disabled(&imx6q_cpuidle_driver, 1, false);
7229380905SLucas Stach }
732cb9caa4SShawn Guo EXPORT_SYMBOL_GPL(imx6q_cpuidle_fec_irqs_unused);
7429380905SLucas Stach 
7512bb3440SShawn Guo int __init imx6q_cpuidle_init(void)
7612bb3440SShawn Guo {
77fa6be65eSFabio Estevam 	/* Set INT_MEM_CLK_LPM bit to get a reliable WAIT mode support */
788765caa5SAnson Huang 	imx6_set_int_mem_clk_lpm(true);
79e5f9dec8SShawn Guo 
8054a4644bSDaniel Lezcano 	return cpuidle_register(&imx6q_cpuidle_driver, NULL);
8112bb3440SShawn Guo }
82