pm-s3c64xx.c (9553B)
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_S3C_PM_DEBUG_LED_SMDK 177void s3c_pm_debug_smdkled(u32 set, u32 clear) 178{ 179 unsigned long flags; 180 int i; 181 182 local_irq_save(flags); 183 for (i = 0; i < 4; i++) { 184 if (clear & (1 << i)) 185 gpio_set_value(S3C64XX_GPN(12 + i), 0); 186 if (set & (1 << i)) 187 gpio_set_value(S3C64XX_GPN(12 + i), 1); 188 } 189 local_irq_restore(flags); 190} 191#endif 192 193#ifdef CONFIG_PM_SLEEP 194static struct sleep_save core_save[] = { 195 SAVE_ITEM(S3C64XX_MEM0DRVCON), 196 SAVE_ITEM(S3C64XX_MEM1DRVCON), 197}; 198 199static struct sleep_save misc_save[] = { 200 SAVE_ITEM(S3C64XX_AHB_CON0), 201 SAVE_ITEM(S3C64XX_AHB_CON1), 202 SAVE_ITEM(S3C64XX_AHB_CON2), 203 204 SAVE_ITEM(S3C64XX_SPCON), 205 206 SAVE_ITEM(S3C64XX_MEM0CONSTOP), 207 SAVE_ITEM(S3C64XX_MEM1CONSTOP), 208 SAVE_ITEM(S3C64XX_MEM0CONSLP0), 209 SAVE_ITEM(S3C64XX_MEM0CONSLP1), 210 SAVE_ITEM(S3C64XX_MEM1CONSLP), 211 212 SAVE_ITEM(S3C64XX_SDMA_SEL), 213 SAVE_ITEM(S3C64XX_MODEM_MIFPCON), 214 215 SAVE_ITEM(S3C64XX_NORMAL_CFG), 216}; 217 218void s3c_pm_configure_extint(void) 219{ 220 __raw_writel(s3c_irqwake_eintmask, S3C64XX_EINT_MASK); 221} 222 223void s3c_pm_restore_core(void) 224{ 225 __raw_writel(0, S3C64XX_EINT_MASK); 226 227 s3c_pm_debug_smdkled(1 << 2, 0); 228 229 s3c_pm_do_restore_core(core_save, ARRAY_SIZE(core_save)); 230 s3c_pm_do_restore(misc_save, ARRAY_SIZE(misc_save)); 231} 232 233void s3c_pm_save_core(void) 234{ 235 s3c_pm_do_save(misc_save, ARRAY_SIZE(misc_save)); 236 s3c_pm_do_save(core_save, ARRAY_SIZE(core_save)); 237} 238#endif 239 240/* since both s3c6400 and s3c6410 share the same sleep pm calls, we 241 * put the per-cpu code in here until any new cpu comes along and changes 242 * this. 243 */ 244 245static int s3c64xx_cpu_suspend(unsigned long arg) 246{ 247 unsigned long tmp; 248 249 /* set our standby method to sleep */ 250 251 tmp = __raw_readl(S3C64XX_PWR_CFG); 252 tmp &= ~S3C64XX_PWRCFG_CFG_WFI_MASK; 253 tmp |= S3C64XX_PWRCFG_CFG_WFI_SLEEP; 254 __raw_writel(tmp, S3C64XX_PWR_CFG); 255 256 /* clear any old wakeup */ 257 258 __raw_writel(__raw_readl(S3C64XX_WAKEUP_STAT), 259 S3C64XX_WAKEUP_STAT); 260 261 /* set the LED state to 0110 over sleep */ 262 s3c_pm_debug_smdkled(3 << 1, 0xf); 263 264 /* issue the standby signal into the pm unit. Note, we 265 * issue a write-buffer drain just in case */ 266 267 tmp = 0; 268 269 asm("b 1f\n\t" 270 ".align 5\n\t" 271 "1:\n\t" 272 "mcr p15, 0, %0, c7, c10, 5\n\t" 273 "mcr p15, 0, %0, c7, c10, 4\n\t" 274 "mcr p15, 0, %0, c7, c0, 4" :: "r" (tmp)); 275 276 /* we should never get past here */ 277 278 pr_info("Failed to suspend the system\n"); 279 return 1; /* Aborting suspend */ 280} 281 282/* mapping of interrupts to parts of the wakeup mask */ 283static const struct samsung_wakeup_mask wake_irqs[] = { 284 { .irq = IRQ_RTC_ALARM, .bit = S3C64XX_PWRCFG_RTC_ALARM_DISABLE, }, 285 { .irq = IRQ_RTC_TIC, .bit = S3C64XX_PWRCFG_RTC_TICK_DISABLE, }, 286 { .irq = IRQ_PENDN, .bit = S3C64XX_PWRCFG_TS_DISABLE, }, 287 { .irq = IRQ_HSMMC0, .bit = S3C64XX_PWRCFG_MMC0_DISABLE, }, 288 { .irq = IRQ_HSMMC1, .bit = S3C64XX_PWRCFG_MMC1_DISABLE, }, 289 { .irq = IRQ_HSMMC2, .bit = S3C64XX_PWRCFG_MMC2_DISABLE, }, 290 { .irq = NO_WAKEUP_IRQ, .bit = S3C64XX_PWRCFG_BATF_DISABLE}, 291 { .irq = NO_WAKEUP_IRQ, .bit = S3C64XX_PWRCFG_MSM_DISABLE }, 292 { .irq = NO_WAKEUP_IRQ, .bit = S3C64XX_PWRCFG_HSI_DISABLE }, 293 { .irq = NO_WAKEUP_IRQ, .bit = S3C64XX_PWRCFG_MSM_DISABLE }, 294}; 295 296static void s3c64xx_pm_prepare(void) 297{ 298 samsung_sync_wakemask(S3C64XX_PWR_CFG, 299 wake_irqs, ARRAY_SIZE(wake_irqs)); 300 301 /* store address of resume. */ 302 __raw_writel(__pa_symbol(s3c_cpu_resume), S3C64XX_INFORM0); 303 304 /* ensure previous wakeup state is cleared before sleeping */ 305 __raw_writel(__raw_readl(S3C64XX_WAKEUP_STAT), S3C64XX_WAKEUP_STAT); 306} 307 308#ifdef CONFIG_SAMSUNG_PM_DEBUG 309void s3c_pm_arch_update_uart(void __iomem *regs, struct pm_uart_save *save) 310{ 311 u32 ucon; 312 u32 ucon_clk 313 u32 save_clk; 314 u32 new_ucon; 315 u32 delta; 316 317 if (!soc_is_s3c64xx()) 318 return; 319 320 ucon = __raw_readl(regs + S3C2410_UCON); 321 ucon_clk = ucon & S3C6400_UCON_CLKMASK; 322 sav_clk = save->ucon & S3C6400_UCON_CLKMASK; 323 324 /* S3C64XX UART blocks only support level interrupts, so ensure that 325 * when we restore unused UART blocks we force the level interrupt 326 * settings. */ 327 save->ucon |= S3C2410_UCON_TXILEVEL | S3C2410_UCON_RXILEVEL; 328 329 /* We have a constraint on changing the clock type of the UART 330 * between UCLKx and PCLK, so ensure that when we restore UCON 331 * that the CLK field is correctly modified if the bootloader 332 * has changed anything. 333 */ 334 if (ucon_clk != save_clk) { 335 new_ucon = save->ucon; 336 delta = ucon_clk ^ save_clk; 337 338 /* change from UCLKx => wrong PCLK, 339 * either UCLK can be tested for by a bit-test 340 * with UCLK0 */ 341 if (ucon_clk & S3C6400_UCON_UCLK0 && 342 !(save_clk & S3C6400_UCON_UCLK0) && 343 delta & S3C6400_UCON_PCLK2) { 344 new_ucon &= ~S3C6400_UCON_UCLK0; 345 } else if (delta == S3C6400_UCON_PCLK2) { 346 /* as an precaution, don't change from 347 * PCLK2 => PCLK or vice-versa */ 348 new_ucon ^= S3C6400_UCON_PCLK2; 349 } 350 351 S3C_PMDBG("ucon change %04x => %04x (save=%04x)\n", 352 ucon, new_ucon, save->ucon); 353 save->ucon = new_ucon; 354 } 355} 356#endif 357 358int __init s3c64xx_pm_init(void) 359{ 360 int i; 361 362 s3c_pm_init(); 363 364 for (i = 0; i < ARRAY_SIZE(s3c64xx_always_on_pm_domains); i++) 365 pm_genpd_init(&s3c64xx_always_on_pm_domains[i]->pd, 366 &pm_domain_always_on_gov, false); 367 368 for (i = 0; i < ARRAY_SIZE(s3c64xx_pm_domains); i++) 369 pm_genpd_init(&s3c64xx_pm_domains[i]->pd, NULL, false); 370 371#ifdef CONFIG_S3C_DEV_FB 372 if (dev_get_platdata(&s3c_device_fb.dev)) 373 pm_genpd_add_device(&s3c64xx_pm_f.pd, &s3c_device_fb.dev); 374#endif 375 376 return 0; 377} 378 379static __init int s3c64xx_pm_initcall(void) 380{ 381 if (!soc_is_s3c64xx()) 382 return 0; 383 384 pm_cpu_prep = s3c64xx_pm_prepare; 385 pm_cpu_sleep = s3c64xx_cpu_suspend; 386 387#ifdef CONFIG_S3C_PM_DEBUG_LED_SMDK 388 gpio_request(S3C64XX_GPN(12), "DEBUG_LED0"); 389 gpio_request(S3C64XX_GPN(13), "DEBUG_LED1"); 390 gpio_request(S3C64XX_GPN(14), "DEBUG_LED2"); 391 gpio_request(S3C64XX_GPN(15), "DEBUG_LED3"); 392 gpio_direction_output(S3C64XX_GPN(12), 0); 393 gpio_direction_output(S3C64XX_GPN(13), 0); 394 gpio_direction_output(S3C64XX_GPN(14), 0); 395 gpio_direction_output(S3C64XX_GPN(15), 0); 396#endif 397 398 return 0; 399} 400arch_initcall(s3c64xx_pm_initcall);