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 34 struct s3c64xx_pm_domain { 35 char *const name; 36 u32 ena; 37 u32 pwr_stat; 38 struct generic_pm_domain pd; 39 }; 40 41 static 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 55 static 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 84 static 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 93 static 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 103 static 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 113 static 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 123 static 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 133 static 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 143 static 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 152 static 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 162 static struct s3c64xx_pm_domain *s3c64xx_always_on_pm_domains[] = { 163 &s3c64xx_pm_irom, 164 }; 165 166 static 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 177 static struct sleep_save core_save[] = { 178 SAVE_ITEM(S3C64XX_MEM0DRVCON), 179 SAVE_ITEM(S3C64XX_MEM1DRVCON), 180 }; 181 182 static 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 201 void s3c_pm_configure_extint(void) 202 { 203 __raw_writel(s3c_irqwake_eintmask, S3C64XX_EINT_MASK); 204 } 205 206 void 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 214 void 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 226 static 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 */ 261 static 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 274 static 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 286 int __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 307 static __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 } 317 arch_initcall(s3c64xx_pm_initcall); 318