18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * MMP2 Power Management Routines 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * (C) Copyright 2012 Marvell International Ltd. 68c2ecf20Sopenharmony_ci * All Rights Reserved 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/kernel.h> 108c2ecf20Sopenharmony_ci#include <linux/errno.h> 118c2ecf20Sopenharmony_ci#include <linux/err.h> 128c2ecf20Sopenharmony_ci#include <linux/time.h> 138c2ecf20Sopenharmony_ci#include <linux/delay.h> 148c2ecf20Sopenharmony_ci#include <linux/suspend.h> 158c2ecf20Sopenharmony_ci#include <linux/irq.h> 168c2ecf20Sopenharmony_ci#include <linux/io.h> 178c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 188c2ecf20Sopenharmony_ci#include <asm/mach-types.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#include <linux/soc/mmp/cputype.h> 218c2ecf20Sopenharmony_ci#include "addr-map.h" 228c2ecf20Sopenharmony_ci#include "pm-mmp2.h" 238c2ecf20Sopenharmony_ci#include "regs-icu.h" 248c2ecf20Sopenharmony_ci#include "irqs.h" 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ciint mmp2_set_wake(struct irq_data *d, unsigned int on) 278c2ecf20Sopenharmony_ci{ 288c2ecf20Sopenharmony_ci unsigned long data = 0; 298c2ecf20Sopenharmony_ci int irq = d->irq; 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci /* enable wakeup sources */ 328c2ecf20Sopenharmony_ci switch (irq) { 338c2ecf20Sopenharmony_ci case IRQ_MMP2_RTC: 348c2ecf20Sopenharmony_ci case IRQ_MMP2_RTC_ALARM: 358c2ecf20Sopenharmony_ci data = MPMU_WUCRM_PJ_WAKEUP(4) | MPMU_WUCRM_PJ_RTC_ALARM; 368c2ecf20Sopenharmony_ci break; 378c2ecf20Sopenharmony_ci case IRQ_MMP2_PMIC: 388c2ecf20Sopenharmony_ci data = MPMU_WUCRM_PJ_WAKEUP(7); 398c2ecf20Sopenharmony_ci break; 408c2ecf20Sopenharmony_ci case IRQ_MMP2_MMC2: 418c2ecf20Sopenharmony_ci /* mmc use WAKEUP2, same as GPIO wakeup source */ 428c2ecf20Sopenharmony_ci data = MPMU_WUCRM_PJ_WAKEUP(2); 438c2ecf20Sopenharmony_ci break; 448c2ecf20Sopenharmony_ci } 458c2ecf20Sopenharmony_ci if (on) { 468c2ecf20Sopenharmony_ci if (data) { 478c2ecf20Sopenharmony_ci data |= __raw_readl(MPMU_WUCRM_PJ); 488c2ecf20Sopenharmony_ci __raw_writel(data, MPMU_WUCRM_PJ); 498c2ecf20Sopenharmony_ci } 508c2ecf20Sopenharmony_ci } else { 518c2ecf20Sopenharmony_ci if (data) { 528c2ecf20Sopenharmony_ci data = ~data & __raw_readl(MPMU_WUCRM_PJ); 538c2ecf20Sopenharmony_ci __raw_writel(data, MPMU_WUCRM_PJ); 548c2ecf20Sopenharmony_ci } 558c2ecf20Sopenharmony_ci } 568c2ecf20Sopenharmony_ci return 0; 578c2ecf20Sopenharmony_ci} 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_cistatic void pm_scu_clk_disable(void) 608c2ecf20Sopenharmony_ci{ 618c2ecf20Sopenharmony_ci unsigned int val; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci /* close AXI fabric clock gate */ 648c2ecf20Sopenharmony_ci __raw_writel(0x0, CIU_REG(0x64)); 658c2ecf20Sopenharmony_ci __raw_writel(0x0, CIU_REG(0x68)); 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci /* close MCB master clock gate */ 688c2ecf20Sopenharmony_ci val = __raw_readl(CIU_REG(0x1c)); 698c2ecf20Sopenharmony_ci val |= 0xf0; 708c2ecf20Sopenharmony_ci __raw_writel(val, CIU_REG(0x1c)); 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci return ; 738c2ecf20Sopenharmony_ci} 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_cistatic void pm_scu_clk_enable(void) 768c2ecf20Sopenharmony_ci{ 778c2ecf20Sopenharmony_ci unsigned int val; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci /* open AXI fabric clock gate */ 808c2ecf20Sopenharmony_ci __raw_writel(0x03003003, CIU_REG(0x64)); 818c2ecf20Sopenharmony_ci __raw_writel(0x00303030, CIU_REG(0x68)); 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci /* open MCB master clock gate */ 848c2ecf20Sopenharmony_ci val = __raw_readl(CIU_REG(0x1c)); 858c2ecf20Sopenharmony_ci val &= ~(0xf0); 868c2ecf20Sopenharmony_ci __raw_writel(val, CIU_REG(0x1c)); 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci return ; 898c2ecf20Sopenharmony_ci} 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_cistatic void pm_mpmu_clk_disable(void) 928c2ecf20Sopenharmony_ci{ 938c2ecf20Sopenharmony_ci /* 948c2ecf20Sopenharmony_ci * disable clocks in MPMU_CGR_PJ register 958c2ecf20Sopenharmony_ci * except clock for APMU_PLL1, APMU_PLL1_2 and AP_26M 968c2ecf20Sopenharmony_ci */ 978c2ecf20Sopenharmony_ci __raw_writel(0x0000a010, MPMU_CGR_PJ); 988c2ecf20Sopenharmony_ci} 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_cistatic void pm_mpmu_clk_enable(void) 1018c2ecf20Sopenharmony_ci{ 1028c2ecf20Sopenharmony_ci unsigned int val; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci __raw_writel(0xdffefffe, MPMU_CGR_PJ); 1058c2ecf20Sopenharmony_ci val = __raw_readl(MPMU_PLL2_CTRL1); 1068c2ecf20Sopenharmony_ci val |= (1 << 29); 1078c2ecf20Sopenharmony_ci __raw_writel(val, MPMU_PLL2_CTRL1); 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci return ; 1108c2ecf20Sopenharmony_ci} 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_civoid mmp2_pm_enter_lowpower_mode(int state) 1138c2ecf20Sopenharmony_ci{ 1148c2ecf20Sopenharmony_ci uint32_t idle_cfg, apcr; 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci idle_cfg = __raw_readl(APMU_PJ_IDLE_CFG); 1178c2ecf20Sopenharmony_ci apcr = __raw_readl(MPMU_PCR_PJ); 1188c2ecf20Sopenharmony_ci apcr &= ~(MPMU_PCR_PJ_SLPEN | MPMU_PCR_PJ_DDRCORSD | MPMU_PCR_PJ_APBSD 1198c2ecf20Sopenharmony_ci | MPMU_PCR_PJ_AXISD | MPMU_PCR_PJ_VCTCXOSD | (1 << 13)); 1208c2ecf20Sopenharmony_ci idle_cfg &= ~APMU_PJ_IDLE_CFG_PJ_IDLE; 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci switch (state) { 1238c2ecf20Sopenharmony_ci case POWER_MODE_SYS_SLEEP: 1248c2ecf20Sopenharmony_ci apcr |= MPMU_PCR_PJ_SLPEN; /* set the SLPEN bit */ 1258c2ecf20Sopenharmony_ci apcr |= MPMU_PCR_PJ_VCTCXOSD; /* set VCTCXOSD */ 1268c2ecf20Sopenharmony_ci fallthrough; 1278c2ecf20Sopenharmony_ci case POWER_MODE_CHIP_SLEEP: 1288c2ecf20Sopenharmony_ci apcr |= MPMU_PCR_PJ_SLPEN; 1298c2ecf20Sopenharmony_ci fallthrough; 1308c2ecf20Sopenharmony_ci case POWER_MODE_APPS_SLEEP: 1318c2ecf20Sopenharmony_ci apcr |= MPMU_PCR_PJ_APBSD; /* set APBSD */ 1328c2ecf20Sopenharmony_ci fallthrough; 1338c2ecf20Sopenharmony_ci case POWER_MODE_APPS_IDLE: 1348c2ecf20Sopenharmony_ci apcr |= MPMU_PCR_PJ_AXISD; /* set AXISDD bit */ 1358c2ecf20Sopenharmony_ci apcr |= MPMU_PCR_PJ_DDRCORSD; /* set DDRCORSD bit */ 1368c2ecf20Sopenharmony_ci idle_cfg |= APMU_PJ_IDLE_CFG_PJ_PWRDWN; /* PJ power down */ 1378c2ecf20Sopenharmony_ci apcr |= MPMU_PCR_PJ_SPSD; 1388c2ecf20Sopenharmony_ci fallthrough; 1398c2ecf20Sopenharmony_ci case POWER_MODE_CORE_EXTIDLE: 1408c2ecf20Sopenharmony_ci idle_cfg |= APMU_PJ_IDLE_CFG_PJ_IDLE; /* set the IDLE bit */ 1418c2ecf20Sopenharmony_ci idle_cfg &= ~APMU_PJ_IDLE_CFG_ISO_MODE_CNTRL_MASK; 1428c2ecf20Sopenharmony_ci idle_cfg |= APMU_PJ_IDLE_CFG_PWR_SW(3) 1438c2ecf20Sopenharmony_ci | APMU_PJ_IDLE_CFG_L2_PWR_SW; 1448c2ecf20Sopenharmony_ci break; 1458c2ecf20Sopenharmony_ci case POWER_MODE_CORE_INTIDLE: 1468c2ecf20Sopenharmony_ci apcr &= ~MPMU_PCR_PJ_SPSD; 1478c2ecf20Sopenharmony_ci break; 1488c2ecf20Sopenharmony_ci } 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci /* set reserve bits */ 1518c2ecf20Sopenharmony_ci apcr |= (1 << 30) | (1 << 25); 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci /* finally write the registers back */ 1548c2ecf20Sopenharmony_ci __raw_writel(idle_cfg, APMU_PJ_IDLE_CFG); 1558c2ecf20Sopenharmony_ci __raw_writel(apcr, MPMU_PCR_PJ); /* 0xfe086000 */ 1568c2ecf20Sopenharmony_ci} 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_cistatic int mmp2_pm_enter(suspend_state_t state) 1598c2ecf20Sopenharmony_ci{ 1608c2ecf20Sopenharmony_ci int temp; 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci temp = __raw_readl(MMP2_ICU_INT4_MASK); 1638c2ecf20Sopenharmony_ci if (temp & (1 << 1)) { 1648c2ecf20Sopenharmony_ci printk(KERN_ERR "%s: PMIC interrupt is handling\n", __func__); 1658c2ecf20Sopenharmony_ci return -EAGAIN; 1668c2ecf20Sopenharmony_ci } 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci temp = __raw_readl(APMU_SRAM_PWR_DWN); 1698c2ecf20Sopenharmony_ci temp |= ((1 << 19) | (1 << 18)); 1708c2ecf20Sopenharmony_ci __raw_writel(temp, APMU_SRAM_PWR_DWN); 1718c2ecf20Sopenharmony_ci pm_mpmu_clk_disable(); 1728c2ecf20Sopenharmony_ci pm_scu_clk_disable(); 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci printk(KERN_INFO "%s: before suspend\n", __func__); 1758c2ecf20Sopenharmony_ci cpu_do_idle(); 1768c2ecf20Sopenharmony_ci printk(KERN_INFO "%s: after suspend\n", __func__); 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci pm_mpmu_clk_enable(); /* enable clocks in MPMU */ 1798c2ecf20Sopenharmony_ci pm_scu_clk_enable(); /* enable clocks in SCU */ 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci return 0; 1828c2ecf20Sopenharmony_ci} 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci/* 1858c2ecf20Sopenharmony_ci * Called after processes are frozen, but before we shut down devices. 1868c2ecf20Sopenharmony_ci */ 1878c2ecf20Sopenharmony_cistatic int mmp2_pm_prepare(void) 1888c2ecf20Sopenharmony_ci{ 1898c2ecf20Sopenharmony_ci mmp2_pm_enter_lowpower_mode(POWER_MODE_SYS_SLEEP); 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci return 0; 1928c2ecf20Sopenharmony_ci} 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci/* 1958c2ecf20Sopenharmony_ci * Called after devices are re-setup, but before processes are thawed. 1968c2ecf20Sopenharmony_ci */ 1978c2ecf20Sopenharmony_cistatic void mmp2_pm_finish(void) 1988c2ecf20Sopenharmony_ci{ 1998c2ecf20Sopenharmony_ci mmp2_pm_enter_lowpower_mode(POWER_MODE_CORE_INTIDLE); 2008c2ecf20Sopenharmony_ci} 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_cistatic int mmp2_pm_valid(suspend_state_t state) 2038c2ecf20Sopenharmony_ci{ 2048c2ecf20Sopenharmony_ci return ((state == PM_SUSPEND_STANDBY) || (state == PM_SUSPEND_MEM)); 2058c2ecf20Sopenharmony_ci} 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci/* 2088c2ecf20Sopenharmony_ci * Set to PM_DISK_FIRMWARE so we can quickly veto suspend-to-disk. 2098c2ecf20Sopenharmony_ci */ 2108c2ecf20Sopenharmony_cistatic const struct platform_suspend_ops mmp2_pm_ops = { 2118c2ecf20Sopenharmony_ci .valid = mmp2_pm_valid, 2128c2ecf20Sopenharmony_ci .prepare = mmp2_pm_prepare, 2138c2ecf20Sopenharmony_ci .enter = mmp2_pm_enter, 2148c2ecf20Sopenharmony_ci .finish = mmp2_pm_finish, 2158c2ecf20Sopenharmony_ci}; 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_cistatic int __init mmp2_pm_init(void) 2188c2ecf20Sopenharmony_ci{ 2198c2ecf20Sopenharmony_ci uint32_t apcr; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci if (!cpu_is_mmp2()) 2228c2ecf20Sopenharmony_ci return -EIO; 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci suspend_set_ops(&mmp2_pm_ops); 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci /* 2278c2ecf20Sopenharmony_ci * Set bit 0, Slow clock Select 32K clock input instead of VCXO 2288c2ecf20Sopenharmony_ci * VCXO is chosen by default, which would be disabled in suspend 2298c2ecf20Sopenharmony_ci */ 2308c2ecf20Sopenharmony_ci __raw_writel(0x5, MPMU_SCCR); 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci /* 2338c2ecf20Sopenharmony_ci * Clear bit 23 of CIU_CPU_CONF 2348c2ecf20Sopenharmony_ci * direct PJ4 to DDR access through Memory Controller slow queue 2358c2ecf20Sopenharmony_ci * fast queue has issue and cause lcd will flick 2368c2ecf20Sopenharmony_ci */ 2378c2ecf20Sopenharmony_ci __raw_writel(__raw_readl(CIU_REG(0x8)) & ~(0x1 << 23), CIU_REG(0x8)); 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci /* Clear default low power control bit */ 2408c2ecf20Sopenharmony_ci apcr = __raw_readl(MPMU_PCR_PJ); 2418c2ecf20Sopenharmony_ci apcr &= ~(MPMU_PCR_PJ_SLPEN | MPMU_PCR_PJ_DDRCORSD 2428c2ecf20Sopenharmony_ci | MPMU_PCR_PJ_APBSD | MPMU_PCR_PJ_AXISD | 1 << 13); 2438c2ecf20Sopenharmony_ci __raw_writel(apcr, MPMU_PCR_PJ); 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci return 0; 2468c2ecf20Sopenharmony_ci} 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_cilate_initcall(mmp2_pm_init); 249