162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci//
362306a36Sopenharmony_ci// Copyright 2008 Openmoko, Inc.
462306a36Sopenharmony_ci// Copyright 2008 Simtec Electronics
562306a36Sopenharmony_ci//	Ben Dooks <ben@simtec.co.uk>
662306a36Sopenharmony_ci//	http://armlinux.simtec.co.uk/
762306a36Sopenharmony_ci//
862306a36Sopenharmony_ci// S3C64XX CPU PM support.
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/init.h>
1162306a36Sopenharmony_ci#include <linux/suspend.h>
1262306a36Sopenharmony_ci#include <linux/serial_core.h>
1362306a36Sopenharmony_ci#include <linux/io.h>
1462306a36Sopenharmony_ci#include <linux/gpio.h>
1562306a36Sopenharmony_ci#include <linux/pm_domain.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include "map.h"
1862306a36Sopenharmony_ci#include "irqs.h"
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#include "cpu.h"
2162306a36Sopenharmony_ci#include "devs.h"
2262306a36Sopenharmony_ci#include "pm.h"
2362306a36Sopenharmony_ci#include "wakeup-mask.h"
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#include "regs-gpio.h"
2662306a36Sopenharmony_ci#include "regs-clock.h"
2762306a36Sopenharmony_ci#include "gpio-samsung.h"
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci#include "regs-gpio-memport-s3c64xx.h"
3062306a36Sopenharmony_ci#include "regs-modem-s3c64xx.h"
3162306a36Sopenharmony_ci#include "regs-sys-s3c64xx.h"
3262306a36Sopenharmony_ci#include "regs-syscon-power-s3c64xx.h"
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_cistruct s3c64xx_pm_domain {
3562306a36Sopenharmony_ci	char *const name;
3662306a36Sopenharmony_ci	u32 ena;
3762306a36Sopenharmony_ci	u32 pwr_stat;
3862306a36Sopenharmony_ci	struct generic_pm_domain pd;
3962306a36Sopenharmony_ci};
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_cistatic int s3c64xx_pd_off(struct generic_pm_domain *domain)
4262306a36Sopenharmony_ci{
4362306a36Sopenharmony_ci	struct s3c64xx_pm_domain *pd;
4462306a36Sopenharmony_ci	u32 val;
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	pd = container_of(domain, struct s3c64xx_pm_domain, pd);
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	val = __raw_readl(S3C64XX_NORMAL_CFG);
4962306a36Sopenharmony_ci	val &= ~(pd->ena);
5062306a36Sopenharmony_ci	__raw_writel(val, S3C64XX_NORMAL_CFG);
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	return 0;
5362306a36Sopenharmony_ci}
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistatic int s3c64xx_pd_on(struct generic_pm_domain *domain)
5662306a36Sopenharmony_ci{
5762306a36Sopenharmony_ci	struct s3c64xx_pm_domain *pd;
5862306a36Sopenharmony_ci	u32 val;
5962306a36Sopenharmony_ci	long retry = 1000000L;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	pd = container_of(domain, struct s3c64xx_pm_domain, pd);
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	val = __raw_readl(S3C64XX_NORMAL_CFG);
6462306a36Sopenharmony_ci	val |= pd->ena;
6562306a36Sopenharmony_ci	__raw_writel(val, S3C64XX_NORMAL_CFG);
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	/* Not all domains provide power status readback */
6862306a36Sopenharmony_ci	if (pd->pwr_stat) {
6962306a36Sopenharmony_ci		do {
7062306a36Sopenharmony_ci			cpu_relax();
7162306a36Sopenharmony_ci			if (__raw_readl(S3C64XX_BLK_PWR_STAT) & pd->pwr_stat)
7262306a36Sopenharmony_ci				break;
7362306a36Sopenharmony_ci		} while (retry--);
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci		if (!retry) {
7662306a36Sopenharmony_ci			pr_err("Failed to start domain %s\n", pd->name);
7762306a36Sopenharmony_ci			return -EBUSY;
7862306a36Sopenharmony_ci		}
7962306a36Sopenharmony_ci	}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	return 0;
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistatic struct s3c64xx_pm_domain s3c64xx_pm_irom = {
8562306a36Sopenharmony_ci	.name = "IROM",
8662306a36Sopenharmony_ci	.ena = S3C64XX_NORMALCFG_IROM_ON,
8762306a36Sopenharmony_ci	.pd = {
8862306a36Sopenharmony_ci		.power_off = s3c64xx_pd_off,
8962306a36Sopenharmony_ci		.power_on = s3c64xx_pd_on,
9062306a36Sopenharmony_ci	},
9162306a36Sopenharmony_ci};
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_cistatic struct s3c64xx_pm_domain s3c64xx_pm_etm = {
9462306a36Sopenharmony_ci	.name = "ETM",
9562306a36Sopenharmony_ci	.ena = S3C64XX_NORMALCFG_DOMAIN_ETM_ON,
9662306a36Sopenharmony_ci	.pwr_stat = S3C64XX_BLKPWRSTAT_ETM,
9762306a36Sopenharmony_ci	.pd = {
9862306a36Sopenharmony_ci		.power_off = s3c64xx_pd_off,
9962306a36Sopenharmony_ci		.power_on = s3c64xx_pd_on,
10062306a36Sopenharmony_ci	},
10162306a36Sopenharmony_ci};
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_cistatic struct s3c64xx_pm_domain s3c64xx_pm_s = {
10462306a36Sopenharmony_ci	.name = "S",
10562306a36Sopenharmony_ci	.ena = S3C64XX_NORMALCFG_DOMAIN_S_ON,
10662306a36Sopenharmony_ci	.pwr_stat = S3C64XX_BLKPWRSTAT_S,
10762306a36Sopenharmony_ci	.pd = {
10862306a36Sopenharmony_ci		.power_off = s3c64xx_pd_off,
10962306a36Sopenharmony_ci		.power_on = s3c64xx_pd_on,
11062306a36Sopenharmony_ci	},
11162306a36Sopenharmony_ci};
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_cistatic struct s3c64xx_pm_domain s3c64xx_pm_f = {
11462306a36Sopenharmony_ci	.name = "F",
11562306a36Sopenharmony_ci	.ena = S3C64XX_NORMALCFG_DOMAIN_F_ON,
11662306a36Sopenharmony_ci	.pwr_stat = S3C64XX_BLKPWRSTAT_F,
11762306a36Sopenharmony_ci	.pd = {
11862306a36Sopenharmony_ci		.power_off = s3c64xx_pd_off,
11962306a36Sopenharmony_ci		.power_on = s3c64xx_pd_on,
12062306a36Sopenharmony_ci	},
12162306a36Sopenharmony_ci};
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_cistatic struct s3c64xx_pm_domain s3c64xx_pm_p = {
12462306a36Sopenharmony_ci	.name = "P",
12562306a36Sopenharmony_ci	.ena = S3C64XX_NORMALCFG_DOMAIN_P_ON,
12662306a36Sopenharmony_ci	.pwr_stat = S3C64XX_BLKPWRSTAT_P,
12762306a36Sopenharmony_ci	.pd = {
12862306a36Sopenharmony_ci		.power_off = s3c64xx_pd_off,
12962306a36Sopenharmony_ci		.power_on = s3c64xx_pd_on,
13062306a36Sopenharmony_ci	},
13162306a36Sopenharmony_ci};
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_cistatic struct s3c64xx_pm_domain s3c64xx_pm_i = {
13462306a36Sopenharmony_ci	.name = "I",
13562306a36Sopenharmony_ci	.ena = S3C64XX_NORMALCFG_DOMAIN_I_ON,
13662306a36Sopenharmony_ci	.pwr_stat = S3C64XX_BLKPWRSTAT_I,
13762306a36Sopenharmony_ci	.pd = {
13862306a36Sopenharmony_ci		.power_off = s3c64xx_pd_off,
13962306a36Sopenharmony_ci		.power_on = s3c64xx_pd_on,
14062306a36Sopenharmony_ci	},
14162306a36Sopenharmony_ci};
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_cistatic struct s3c64xx_pm_domain s3c64xx_pm_g = {
14462306a36Sopenharmony_ci	.name = "G",
14562306a36Sopenharmony_ci	.ena = S3C64XX_NORMALCFG_DOMAIN_G_ON,
14662306a36Sopenharmony_ci	.pd = {
14762306a36Sopenharmony_ci		.power_off = s3c64xx_pd_off,
14862306a36Sopenharmony_ci		.power_on = s3c64xx_pd_on,
14962306a36Sopenharmony_ci	},
15062306a36Sopenharmony_ci};
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_cistatic struct s3c64xx_pm_domain s3c64xx_pm_v = {
15362306a36Sopenharmony_ci	.name = "V",
15462306a36Sopenharmony_ci	.ena = S3C64XX_NORMALCFG_DOMAIN_V_ON,
15562306a36Sopenharmony_ci	.pwr_stat = S3C64XX_BLKPWRSTAT_V,
15662306a36Sopenharmony_ci	.pd = {
15762306a36Sopenharmony_ci		.power_off = s3c64xx_pd_off,
15862306a36Sopenharmony_ci		.power_on = s3c64xx_pd_on,
15962306a36Sopenharmony_ci	},
16062306a36Sopenharmony_ci};
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_cistatic struct s3c64xx_pm_domain *s3c64xx_always_on_pm_domains[] = {
16362306a36Sopenharmony_ci	&s3c64xx_pm_irom,
16462306a36Sopenharmony_ci};
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_cistatic struct s3c64xx_pm_domain *s3c64xx_pm_domains[] = {
16762306a36Sopenharmony_ci	&s3c64xx_pm_etm,
16862306a36Sopenharmony_ci	&s3c64xx_pm_g,
16962306a36Sopenharmony_ci	&s3c64xx_pm_v,
17062306a36Sopenharmony_ci	&s3c64xx_pm_i,
17162306a36Sopenharmony_ci	&s3c64xx_pm_p,
17262306a36Sopenharmony_ci	&s3c64xx_pm_s,
17362306a36Sopenharmony_ci	&s3c64xx_pm_f,
17462306a36Sopenharmony_ci};
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci#ifdef CONFIG_PM_SLEEP
17762306a36Sopenharmony_cistatic struct sleep_save core_save[] = {
17862306a36Sopenharmony_ci	SAVE_ITEM(S3C64XX_MEM0DRVCON),
17962306a36Sopenharmony_ci	SAVE_ITEM(S3C64XX_MEM1DRVCON),
18062306a36Sopenharmony_ci};
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_cistatic struct sleep_save misc_save[] = {
18362306a36Sopenharmony_ci	SAVE_ITEM(S3C64XX_AHB_CON0),
18462306a36Sopenharmony_ci	SAVE_ITEM(S3C64XX_AHB_CON1),
18562306a36Sopenharmony_ci	SAVE_ITEM(S3C64XX_AHB_CON2),
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	SAVE_ITEM(S3C64XX_SPCON),
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	SAVE_ITEM(S3C64XX_MEM0CONSTOP),
19062306a36Sopenharmony_ci	SAVE_ITEM(S3C64XX_MEM1CONSTOP),
19162306a36Sopenharmony_ci	SAVE_ITEM(S3C64XX_MEM0CONSLP0),
19262306a36Sopenharmony_ci	SAVE_ITEM(S3C64XX_MEM0CONSLP1),
19362306a36Sopenharmony_ci	SAVE_ITEM(S3C64XX_MEM1CONSLP),
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	SAVE_ITEM(S3C64XX_SDMA_SEL),
19662306a36Sopenharmony_ci	SAVE_ITEM(S3C64XX_MODEM_MIFPCON),
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	SAVE_ITEM(S3C64XX_NORMAL_CFG),
19962306a36Sopenharmony_ci};
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_civoid s3c_pm_configure_extint(void)
20262306a36Sopenharmony_ci{
20362306a36Sopenharmony_ci	__raw_writel(s3c_irqwake_eintmask, S3C64XX_EINT_MASK);
20462306a36Sopenharmony_ci}
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_civoid s3c_pm_restore_core(void)
20762306a36Sopenharmony_ci{
20862306a36Sopenharmony_ci	__raw_writel(0, S3C64XX_EINT_MASK);
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	s3c_pm_do_restore_core(core_save, ARRAY_SIZE(core_save));
21162306a36Sopenharmony_ci	s3c_pm_do_restore(misc_save, ARRAY_SIZE(misc_save));
21262306a36Sopenharmony_ci}
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_civoid s3c_pm_save_core(void)
21562306a36Sopenharmony_ci{
21662306a36Sopenharmony_ci	s3c_pm_do_save(misc_save, ARRAY_SIZE(misc_save));
21762306a36Sopenharmony_ci	s3c_pm_do_save(core_save, ARRAY_SIZE(core_save));
21862306a36Sopenharmony_ci}
21962306a36Sopenharmony_ci#endif
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci/* since both s3c6400 and s3c6410 share the same sleep pm calls, we
22262306a36Sopenharmony_ci * put the per-cpu code in here until any new cpu comes along and changes
22362306a36Sopenharmony_ci * this.
22462306a36Sopenharmony_ci */
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_cistatic int s3c64xx_cpu_suspend(unsigned long arg)
22762306a36Sopenharmony_ci{
22862306a36Sopenharmony_ci	unsigned long tmp;
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	/* set our standby method to sleep */
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	tmp = __raw_readl(S3C64XX_PWR_CFG);
23362306a36Sopenharmony_ci	tmp &= ~S3C64XX_PWRCFG_CFG_WFI_MASK;
23462306a36Sopenharmony_ci	tmp |= S3C64XX_PWRCFG_CFG_WFI_SLEEP;
23562306a36Sopenharmony_ci	__raw_writel(tmp, S3C64XX_PWR_CFG);
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	/* clear any old wakeup */
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	__raw_writel(__raw_readl(S3C64XX_WAKEUP_STAT),
24062306a36Sopenharmony_ci		     S3C64XX_WAKEUP_STAT);
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	/* issue the standby signal into the pm unit. Note, we
24362306a36Sopenharmony_ci	 * issue a write-buffer drain just in case */
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	tmp = 0;
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	asm("b 1f\n\t"
24862306a36Sopenharmony_ci	    ".align 5\n\t"
24962306a36Sopenharmony_ci	    "1:\n\t"
25062306a36Sopenharmony_ci	    "mcr p15, 0, %0, c7, c10, 5\n\t"
25162306a36Sopenharmony_ci	    "mcr p15, 0, %0, c7, c10, 4\n\t"
25262306a36Sopenharmony_ci	    "mcr p15, 0, %0, c7, c0, 4" :: "r" (tmp));
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	/* we should never get past here */
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	pr_info("Failed to suspend the system\n");
25762306a36Sopenharmony_ci	return 1; /* Aborting suspend */
25862306a36Sopenharmony_ci}
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci/* mapping of interrupts to parts of the wakeup mask */
26162306a36Sopenharmony_cistatic const struct samsung_wakeup_mask wake_irqs[] = {
26262306a36Sopenharmony_ci	{ .irq = IRQ_RTC_ALARM,	.bit = S3C64XX_PWRCFG_RTC_ALARM_DISABLE, },
26362306a36Sopenharmony_ci	{ .irq = IRQ_RTC_TIC,	.bit = S3C64XX_PWRCFG_RTC_TICK_DISABLE, },
26462306a36Sopenharmony_ci	{ .irq = IRQ_PENDN,	.bit = S3C64XX_PWRCFG_TS_DISABLE, },
26562306a36Sopenharmony_ci	{ .irq = IRQ_HSMMC0,	.bit = S3C64XX_PWRCFG_MMC0_DISABLE, },
26662306a36Sopenharmony_ci	{ .irq = IRQ_HSMMC1,	.bit = S3C64XX_PWRCFG_MMC1_DISABLE, },
26762306a36Sopenharmony_ci	{ .irq = IRQ_HSMMC2,	.bit = S3C64XX_PWRCFG_MMC2_DISABLE, },
26862306a36Sopenharmony_ci	{ .irq = NO_WAKEUP_IRQ,	.bit = S3C64XX_PWRCFG_BATF_DISABLE},
26962306a36Sopenharmony_ci	{ .irq = NO_WAKEUP_IRQ,	.bit = S3C64XX_PWRCFG_MSM_DISABLE },
27062306a36Sopenharmony_ci	{ .irq = NO_WAKEUP_IRQ,	.bit = S3C64XX_PWRCFG_HSI_DISABLE },
27162306a36Sopenharmony_ci	{ .irq = NO_WAKEUP_IRQ,	.bit = S3C64XX_PWRCFG_MSM_DISABLE },
27262306a36Sopenharmony_ci};
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_cistatic void s3c64xx_pm_prepare(void)
27562306a36Sopenharmony_ci{
27662306a36Sopenharmony_ci	samsung_sync_wakemask(S3C64XX_PWR_CFG,
27762306a36Sopenharmony_ci			      wake_irqs, ARRAY_SIZE(wake_irqs));
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	/* store address of resume. */
28062306a36Sopenharmony_ci	__raw_writel(__pa_symbol(s3c_cpu_resume), S3C64XX_INFORM0);
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci	/* ensure previous wakeup state is cleared before sleeping */
28362306a36Sopenharmony_ci	__raw_writel(__raw_readl(S3C64XX_WAKEUP_STAT), S3C64XX_WAKEUP_STAT);
28462306a36Sopenharmony_ci}
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ciint __init s3c64xx_pm_init(void)
28762306a36Sopenharmony_ci{
28862306a36Sopenharmony_ci	int i;
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	s3c_pm_init();
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(s3c64xx_always_on_pm_domains); i++)
29362306a36Sopenharmony_ci		pm_genpd_init(&s3c64xx_always_on_pm_domains[i]->pd,
29462306a36Sopenharmony_ci			      &pm_domain_always_on_gov, false);
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(s3c64xx_pm_domains); i++)
29762306a36Sopenharmony_ci		pm_genpd_init(&s3c64xx_pm_domains[i]->pd, NULL, false);
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci#ifdef CONFIG_S3C_DEV_FB
30062306a36Sopenharmony_ci	if (dev_get_platdata(&s3c_device_fb.dev))
30162306a36Sopenharmony_ci		pm_genpd_add_device(&s3c64xx_pm_f.pd, &s3c_device_fb.dev);
30262306a36Sopenharmony_ci#endif
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci	return 0;
30562306a36Sopenharmony_ci}
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_cistatic __init int s3c64xx_pm_initcall(void)
30862306a36Sopenharmony_ci{
30962306a36Sopenharmony_ci	if (!soc_is_s3c64xx())
31062306a36Sopenharmony_ci		return 0;
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	pm_cpu_prep = s3c64xx_pm_prepare;
31362306a36Sopenharmony_ci	pm_cpu_sleep = s3c64xx_cpu_suspend;
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci	return 0;
31662306a36Sopenharmony_ci}
31762306a36Sopenharmony_ciarch_initcall(s3c64xx_pm_initcall);
318