1// SPDX-License-Identifier: GPL-2.0 2// 3// Copyright 2008 Openmoko, Inc. 4// Copyright 2008 Simtec Electronics 5// Ben Dooks <ben@simtec.co.uk> 6// http://armlinux.simtec.co.uk/ 7// 8// S3C64XX CPU PM support. 9 10#include <linux/init.h> 11#include <linux/suspend.h> 12#include <linux/serial_core.h> 13#include <linux/io.h> 14#include <linux/gpio.h> 15#include <linux/pm_domain.h> 16 17#include "map.h" 18#include "irqs.h" 19 20#include "cpu.h" 21#include "devs.h" 22#include "pm.h" 23#include "wakeup-mask.h" 24 25#include "regs-gpio.h" 26#include "regs-clock.h" 27#include "gpio-samsung.h" 28 29#include "regs-gpio-memport-s3c64xx.h" 30#include "regs-modem-s3c64xx.h" 31#include "regs-sys-s3c64xx.h" 32#include "regs-syscon-power-s3c64xx.h" 33 34struct s3c64xx_pm_domain { 35 char *const name; 36 u32 ena; 37 u32 pwr_stat; 38 struct generic_pm_domain pd; 39}; 40 41static int s3c64xx_pd_off(struct generic_pm_domain *domain) 42{ 43 struct s3c64xx_pm_domain *pd; 44 u32 val; 45 46 pd = container_of(domain, struct s3c64xx_pm_domain, pd); 47 48 val = __raw_readl(S3C64XX_NORMAL_CFG); 49 val &= ~(pd->ena); 50 __raw_writel(val, S3C64XX_NORMAL_CFG); 51 52 return 0; 53} 54 55static int s3c64xx_pd_on(struct generic_pm_domain *domain) 56{ 57 struct s3c64xx_pm_domain *pd; 58 u32 val; 59 long retry = 1000000L; 60 61 pd = container_of(domain, struct s3c64xx_pm_domain, pd); 62 63 val = __raw_readl(S3C64XX_NORMAL_CFG); 64 val |= pd->ena; 65 __raw_writel(val, S3C64XX_NORMAL_CFG); 66 67 /* Not all domains provide power status readback */ 68 if (pd->pwr_stat) { 69 do { 70 cpu_relax(); 71 if (__raw_readl(S3C64XX_BLK_PWR_STAT) & pd->pwr_stat) 72 break; 73 } while (retry--); 74 75 if (!retry) { 76 pr_err("Failed to start domain %s\n", pd->name); 77 return -EBUSY; 78 } 79 } 80 81 return 0; 82} 83 84static struct s3c64xx_pm_domain s3c64xx_pm_irom = { 85 .name = "IROM", 86 .ena = S3C64XX_NORMALCFG_IROM_ON, 87 .pd = { 88 .power_off = s3c64xx_pd_off, 89 .power_on = s3c64xx_pd_on, 90 }, 91}; 92 93static struct s3c64xx_pm_domain s3c64xx_pm_etm = { 94 .name = "ETM", 95 .ena = S3C64XX_NORMALCFG_DOMAIN_ETM_ON, 96 .pwr_stat = S3C64XX_BLKPWRSTAT_ETM, 97 .pd = { 98 .power_off = s3c64xx_pd_off, 99 .power_on = s3c64xx_pd_on, 100 }, 101}; 102 103static struct s3c64xx_pm_domain s3c64xx_pm_s = { 104 .name = "S", 105 .ena = S3C64XX_NORMALCFG_DOMAIN_S_ON, 106 .pwr_stat = S3C64XX_BLKPWRSTAT_S, 107 .pd = { 108 .power_off = s3c64xx_pd_off, 109 .power_on = s3c64xx_pd_on, 110 }, 111}; 112 113static struct s3c64xx_pm_domain s3c64xx_pm_f = { 114 .name = "F", 115 .ena = S3C64XX_NORMALCFG_DOMAIN_F_ON, 116 .pwr_stat = S3C64XX_BLKPWRSTAT_F, 117 .pd = { 118 .power_off = s3c64xx_pd_off, 119 .power_on = s3c64xx_pd_on, 120 }, 121}; 122 123static struct s3c64xx_pm_domain s3c64xx_pm_p = { 124 .name = "P", 125 .ena = S3C64XX_NORMALCFG_DOMAIN_P_ON, 126 .pwr_stat = S3C64XX_BLKPWRSTAT_P, 127 .pd = { 128 .power_off = s3c64xx_pd_off, 129 .power_on = s3c64xx_pd_on, 130 }, 131}; 132 133static struct s3c64xx_pm_domain s3c64xx_pm_i = { 134 .name = "I", 135 .ena = S3C64XX_NORMALCFG_DOMAIN_I_ON, 136 .pwr_stat = S3C64XX_BLKPWRSTAT_I, 137 .pd = { 138 .power_off = s3c64xx_pd_off, 139 .power_on = s3c64xx_pd_on, 140 }, 141}; 142 143static struct s3c64xx_pm_domain s3c64xx_pm_g = { 144 .name = "G", 145 .ena = S3C64XX_NORMALCFG_DOMAIN_G_ON, 146 .pd = { 147 .power_off = s3c64xx_pd_off, 148 .power_on = s3c64xx_pd_on, 149 }, 150}; 151 152static struct s3c64xx_pm_domain s3c64xx_pm_v = { 153 .name = "V", 154 .ena = S3C64XX_NORMALCFG_DOMAIN_V_ON, 155 .pwr_stat = S3C64XX_BLKPWRSTAT_V, 156 .pd = { 157 .power_off = s3c64xx_pd_off, 158 .power_on = s3c64xx_pd_on, 159 }, 160}; 161 162static struct s3c64xx_pm_domain *s3c64xx_always_on_pm_domains[] = { 163 &s3c64xx_pm_irom, 164}; 165 166static struct s3c64xx_pm_domain *s3c64xx_pm_domains[] = { 167 &s3c64xx_pm_etm, 168 &s3c64xx_pm_g, 169 &s3c64xx_pm_v, 170 &s3c64xx_pm_i, 171 &s3c64xx_pm_p, 172 &s3c64xx_pm_s, 173 &s3c64xx_pm_f, 174}; 175 176#ifdef CONFIG_PM_SLEEP 177static struct sleep_save core_save[] = { 178 SAVE_ITEM(S3C64XX_MEM0DRVCON), 179 SAVE_ITEM(S3C64XX_MEM1DRVCON), 180}; 181 182static struct sleep_save misc_save[] = { 183 SAVE_ITEM(S3C64XX_AHB_CON0), 184 SAVE_ITEM(S3C64XX_AHB_CON1), 185 SAVE_ITEM(S3C64XX_AHB_CON2), 186 187 SAVE_ITEM(S3C64XX_SPCON), 188 189 SAVE_ITEM(S3C64XX_MEM0CONSTOP), 190 SAVE_ITEM(S3C64XX_MEM1CONSTOP), 191 SAVE_ITEM(S3C64XX_MEM0CONSLP0), 192 SAVE_ITEM(S3C64XX_MEM0CONSLP1), 193 SAVE_ITEM(S3C64XX_MEM1CONSLP), 194 195 SAVE_ITEM(S3C64XX_SDMA_SEL), 196 SAVE_ITEM(S3C64XX_MODEM_MIFPCON), 197 198 SAVE_ITEM(S3C64XX_NORMAL_CFG), 199}; 200 201void s3c_pm_configure_extint(void) 202{ 203 __raw_writel(s3c_irqwake_eintmask, S3C64XX_EINT_MASK); 204} 205 206void s3c_pm_restore_core(void) 207{ 208 __raw_writel(0, S3C64XX_EINT_MASK); 209 210 s3c_pm_do_restore_core(core_save, ARRAY_SIZE(core_save)); 211 s3c_pm_do_restore(misc_save, ARRAY_SIZE(misc_save)); 212} 213 214void s3c_pm_save_core(void) 215{ 216 s3c_pm_do_save(misc_save, ARRAY_SIZE(misc_save)); 217 s3c_pm_do_save(core_save, ARRAY_SIZE(core_save)); 218} 219#endif 220 221/* since both s3c6400 and s3c6410 share the same sleep pm calls, we 222 * put the per-cpu code in here until any new cpu comes along and changes 223 * this. 224 */ 225 226static int s3c64xx_cpu_suspend(unsigned long arg) 227{ 228 unsigned long tmp; 229 230 /* set our standby method to sleep */ 231 232 tmp = __raw_readl(S3C64XX_PWR_CFG); 233 tmp &= ~S3C64XX_PWRCFG_CFG_WFI_MASK; 234 tmp |= S3C64XX_PWRCFG_CFG_WFI_SLEEP; 235 __raw_writel(tmp, S3C64XX_PWR_CFG); 236 237 /* clear any old wakeup */ 238 239 __raw_writel(__raw_readl(S3C64XX_WAKEUP_STAT), 240 S3C64XX_WAKEUP_STAT); 241 242 /* issue the standby signal into the pm unit. Note, we 243 * issue a write-buffer drain just in case */ 244 245 tmp = 0; 246 247 asm("b 1f\n\t" 248 ".align 5\n\t" 249 "1:\n\t" 250 "mcr p15, 0, %0, c7, c10, 5\n\t" 251 "mcr p15, 0, %0, c7, c10, 4\n\t" 252 "mcr p15, 0, %0, c7, c0, 4" :: "r" (tmp)); 253 254 /* we should never get past here */ 255 256 pr_info("Failed to suspend the system\n"); 257 return 1; /* Aborting suspend */ 258} 259 260/* mapping of interrupts to parts of the wakeup mask */ 261static const struct samsung_wakeup_mask wake_irqs[] = { 262 { .irq = IRQ_RTC_ALARM, .bit = S3C64XX_PWRCFG_RTC_ALARM_DISABLE, }, 263 { .irq = IRQ_RTC_TIC, .bit = S3C64XX_PWRCFG_RTC_TICK_DISABLE, }, 264 { .irq = IRQ_PENDN, .bit = S3C64XX_PWRCFG_TS_DISABLE, }, 265 { .irq = IRQ_HSMMC0, .bit = S3C64XX_PWRCFG_MMC0_DISABLE, }, 266 { .irq = IRQ_HSMMC1, .bit = S3C64XX_PWRCFG_MMC1_DISABLE, }, 267 { .irq = IRQ_HSMMC2, .bit = S3C64XX_PWRCFG_MMC2_DISABLE, }, 268 { .irq = NO_WAKEUP_IRQ, .bit = S3C64XX_PWRCFG_BATF_DISABLE}, 269 { .irq = NO_WAKEUP_IRQ, .bit = S3C64XX_PWRCFG_MSM_DISABLE }, 270 { .irq = NO_WAKEUP_IRQ, .bit = S3C64XX_PWRCFG_HSI_DISABLE }, 271 { .irq = NO_WAKEUP_IRQ, .bit = S3C64XX_PWRCFG_MSM_DISABLE }, 272}; 273 274static void s3c64xx_pm_prepare(void) 275{ 276 samsung_sync_wakemask(S3C64XX_PWR_CFG, 277 wake_irqs, ARRAY_SIZE(wake_irqs)); 278 279 /* store address of resume. */ 280 __raw_writel(__pa_symbol(s3c_cpu_resume), S3C64XX_INFORM0); 281 282 /* ensure previous wakeup state is cleared before sleeping */ 283 __raw_writel(__raw_readl(S3C64XX_WAKEUP_STAT), S3C64XX_WAKEUP_STAT); 284} 285 286int __init s3c64xx_pm_init(void) 287{ 288 int i; 289 290 s3c_pm_init(); 291 292 for (i = 0; i < ARRAY_SIZE(s3c64xx_always_on_pm_domains); i++) 293 pm_genpd_init(&s3c64xx_always_on_pm_domains[i]->pd, 294 &pm_domain_always_on_gov, false); 295 296 for (i = 0; i < ARRAY_SIZE(s3c64xx_pm_domains); i++) 297 pm_genpd_init(&s3c64xx_pm_domains[i]->pd, NULL, false); 298 299#ifdef CONFIG_S3C_DEV_FB 300 if (dev_get_platdata(&s3c_device_fb.dev)) 301 pm_genpd_add_device(&s3c64xx_pm_f.pd, &s3c_device_fb.dev); 302#endif 303 304 return 0; 305} 306 307static __init int s3c64xx_pm_initcall(void) 308{ 309 if (!soc_is_s3c64xx()) 310 return 0; 311 312 pm_cpu_prep = s3c64xx_pm_prepare; 313 pm_cpu_sleep = s3c64xx_cpu_suspend; 314 315 return 0; 316} 317arch_initcall(s3c64xx_pm_initcall); 318