]> git.karo-electronics.de Git - karo-tx-linux.git/blob - arch/arm/mach-exynos/cpuidle.c
4e46a12b405697ca9dc59854dfb15bb3bed3802a
[karo-tx-linux.git] / arch / arm / mach-exynos / cpuidle.c
1 /* linux/arch/arm/mach-exynos/cpuidle.c
2  *
3  * Copyright (c) 2011 Samsung Electronics Co., Ltd.
4  *              http://www.samsung.com
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 2 as
8  * published by the Free Software Foundation.
9 */
10
11 #include <linux/kernel.h>
12 #include <linux/init.h>
13 #include <linux/cpuidle.h>
14 #include <linux/cpu_pm.h>
15 #include <linux/io.h>
16 #include <linux/export.h>
17 #include <linux/module.h>
18 #include <linux/time.h>
19 #include <linux/platform_device.h>
20
21 #include <asm/proc-fns.h>
22 #include <asm/suspend.h>
23 #include <asm/unified.h>
24 #include <asm/cpuidle.h>
25
26 #include <plat/cpu.h>
27 #include <plat/pm.h>
28
29 #include <mach/map.h>
30
31 #include "common.h"
32 #include "regs-pmu.h"
33
34 #define REG_DIRECTGO_ADDR       (samsung_rev() == EXYNOS4210_REV_1_1 ? \
35                         S5P_INFORM7 : (samsung_rev() == EXYNOS4210_REV_1_0 ? \
36                         (S5P_VA_SYSRAM + 0x24) : S5P_INFORM0))
37 #define REG_DIRECTGO_FLAG       (samsung_rev() == EXYNOS4210_REV_1_1 ? \
38                         S5P_INFORM6 : (samsung_rev() == EXYNOS4210_REV_1_0 ? \
39                         (S5P_VA_SYSRAM + 0x20) : S5P_INFORM1))
40
41 #define S5P_CHECK_AFTR          0xFCBA0D10
42
43 /* Ext-GIC nIRQ/nFIQ is the only wakeup source in AFTR */
44 static void exynos_set_wakeupmask(long mask)
45 {
46         __raw_writel(mask, S5P_WAKEUP_MASK);
47 }
48
49 static void exynos_cpu_set_boot_vector(long flags)
50 {
51         __raw_writel(virt_to_phys(exynos_cpu_resume), REG_DIRECTGO_ADDR);
52         __raw_writel(flags, REG_DIRECTGO_FLAG);
53 }
54
55 static int idle_finisher(unsigned long flags)
56 {
57         exynos_set_wakeupmask(0x0000ff3e);
58         exynos_cpu_set_boot_vector(S5P_CHECK_AFTR);
59         /* Set value of power down register for aftr mode */
60         exynos_sys_powerdown_conf(SYS_AFTR);
61
62         cpu_do_idle();
63
64         return 1;
65 }
66
67 static int exynos_enter_core0_aftr(struct cpuidle_device *dev,
68                                 struct cpuidle_driver *drv,
69                                 int index)
70 {
71         unsigned long tmp;
72
73         /* Setting Central Sequence Register for power down mode */
74         tmp = __raw_readl(S5P_CENTRAL_SEQ_CONFIGURATION);
75         tmp &= ~S5P_CENTRAL_LOWPWR_CFG;
76         __raw_writel(tmp, S5P_CENTRAL_SEQ_CONFIGURATION);
77
78         cpu_pm_enter();
79         cpu_suspend(0, idle_finisher);
80         cpu_pm_exit();
81
82         /*
83          * If PMU failed while entering sleep mode, WFI will be
84          * ignored by PMU and then exiting cpu_do_idle().
85          * S5P_CENTRAL_LOWPWR_CFG bit will not be set automatically
86          * in this situation.
87          */
88         tmp = __raw_readl(S5P_CENTRAL_SEQ_CONFIGURATION);
89         if (!(tmp & S5P_CENTRAL_LOWPWR_CFG)) {
90                 tmp |= S5P_CENTRAL_LOWPWR_CFG;
91                 __raw_writel(tmp, S5P_CENTRAL_SEQ_CONFIGURATION);
92                 /* Clear wakeup state register */
93                 __raw_writel(0x0, S5P_WAKEUP_STAT);
94         }
95
96         return index;
97 }
98
99 static int exynos_enter_lowpower(struct cpuidle_device *dev,
100                                 struct cpuidle_driver *drv,
101                                 int index)
102 {
103         int new_index = index;
104
105         /* AFTR can only be entered when cores other than CPU0 are offline */
106         if (num_online_cpus() > 1 || dev->cpu != 0)
107                 new_index = drv->safe_state_index;
108
109         if (new_index == 0)
110                 return arm_cpuidle_simple_enter(dev, drv, new_index);
111         else
112                 return exynos_enter_core0_aftr(dev, drv, new_index);
113 }
114
115 static struct cpuidle_driver exynos_idle_driver = {
116         .name                   = "exynos_idle",
117         .owner                  = THIS_MODULE,
118         .states = {
119                 [0] = ARM_CPUIDLE_WFI_STATE,
120                 [1] = {
121                         .enter                  = exynos_enter_lowpower,
122                         .exit_latency           = 300,
123                         .target_residency       = 100000,
124                         .flags                  = CPUIDLE_FLAG_TIME_VALID,
125                         .name                   = "C1",
126                         .desc                   = "ARM power down",
127                 },
128         },
129         .state_count = 2,
130         .safe_state_index = 0,
131 };
132
133 static int exynos_cpuidle_probe(struct platform_device *pdev)
134 {
135         int ret;
136
137         if (soc_is_exynos5440())
138                 exynos_idle_driver.state_count = 1;
139
140         ret = cpuidle_register(&exynos_idle_driver, NULL);
141         if (ret) {
142                 dev_err(&pdev->dev, "failed to register cpuidle driver\n");
143                 return ret;
144         }
145
146         return 0;
147 }
148
149 static struct platform_driver exynos_cpuidle_driver = {
150         .probe  = exynos_cpuidle_probe,
151         .driver = {
152                 .name = "exynos_cpuidle",
153                 .owner = THIS_MODULE,
154         },
155 };
156
157 module_platform_driver(exynos_cpuidle_driver);