| /* |
| * linux/arch/arm/mach-mmp/asr18xx_lowpower.c |
| * |
| * Author: Raul Xiong <xjian@marvell.com> |
| * Fangsuo Wu <fswu@marvell.com> |
| * Copyright: (C) 2012 Marvell International Ltd. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| */ |
| #include <linux/cpuidle.h> |
| #include <linux/cpu_pm.h> |
| #include <linux/clk/dvfs-dvc.h> |
| #include <linux/kernel.h> |
| #include <linux/errno.h> |
| #include <linux/err.h> |
| #include <linux/io.h> |
| #include <linux/edge_wakeup_mmp.h> |
| #include <linux/slab.h> |
| #include <soc/asr/regs-addr.h> |
| #include <linux/cputype.h> |
| #include <asm/cpuidle.h> |
| #include <asm/mach/map.h> |
| #include <asm/mcpm.h> |
| #include <soc/asr/mmp_cpuidle.h> |
| #include <soc/asr/asr18xx_lowpower.h> |
| #include "addr-map.h" |
| |
| static void __iomem *icu_virt_addr; |
| /* All these regs are per-cpu owned */ |
| static void __iomem *APMU_CORE_IDLE_CFG; |
| static void __iomem *APMU_MP_IDLE_CFG; |
| static void __iomem *ICU_GBL_INT_MSK; |
| static void __iomem *APMU_DEBUG_REG; |
| |
| static u32 s_apcr, s_awucrm; |
| static struct cpuidle_state asr18xx_modes[] = { |
| [0] = { |
| .exit_latency = 18, |
| .target_residency = 36, |
| /* |
| * Use CPUIDLE_FLAG_TIMER_STOP flag to let the cpuidle |
| * framework handle the CLOCK_EVENT_NOTIFY_BROADCAST_ |
| * ENTER/EXIT when entering idle states. |
| */ |
| .flags = /* CPUIDLE_FLAG_TIME_VALID */0, |
| .name = "C1", |
| .desc = "C1: Core internal clock gated", |
| .enter = arm_cpuidle_simple_enter, |
| }, |
| [1] = { |
| .exit_latency = 350, |
| .target_residency = 700, |
| .flags = /* CPUIDLE_FLAG_TIME_VALID | \ */ |
| CPUIDLE_FLAG_TIMER_STOP, |
| .name = "C2", |
| .desc = "C2: Core power down", |
| }, |
| [2] = { |
| .exit_latency = 500, |
| .target_residency = 1000, |
| .flags = /* CPUIDLE_FLAG_TIME_VALID | \ */ |
| CPUIDLE_FLAG_TIMER_STOP, |
| .name = "D1p", |
| .desc = "D1p: AP idle state", |
| }, |
| [3] = { |
| .exit_latency = 600, |
| .target_residency = 1200, |
| .flags = /* CPUIDLE_FLAG_TIME_VALID | \ */ |
| CPUIDLE_FLAG_TIMER_STOP, |
| .name = "D1", |
| .desc = "D1: Chip idle state", |
| }, |
| |
| }; |
| |
| static struct platform_power_ops asr18xx_power_ops = { |
| .set_pmu = asr18xx_set_pmu, |
| .clr_pmu = asr18xx_clr_pmu, |
| .save_wakeup = asr18xx_save_wakeup, |
| .restore_wakeup = asr18xx_restore_wakeup, |
| .power_up_setup = ca7_power_up_setup, |
| }; |
| |
| static struct platform_idle asr18xx_idle = { |
| .cpudown_state = POWER_MODE_CORE_POWERDOWN, |
| .wakeup_state = POWER_MODE_SYS_SLEEP, |
| .hotplug_state = POWER_MODE_UDR, |
| .l2_flush_state = POWER_MODE_UDR, |
| .ops = &asr18xx_power_ops, |
| .states = asr18xx_modes, |
| .state_count = ARRAY_SIZE(asr18xx_modes), |
| }; |
| |
| /* |
| * edge wakeup |
| * |
| * To enable edge wakeup: |
| * 1. Set mfp reg edge detection bits; |
| * 2. Enable mfp ICU interrupt, but disable gic interrupt; |
| * 3. Enable corrosponding wakeup port; |
| */ |
| #define ICU_IRQ_ENABLE ((1 << 6) | (1 << 4) | (1 << 0)) |
| #define EDGE_WAKEUP_ICU 60 //icu interrupt num for edge wakeup |
| |
| void edge_wakeup_icu_enable(void) |
| { |
| writel_relaxed(ICU_IRQ_ENABLE, icu_virt_addr + (EDGE_WAKEUP_ICU << 2)); |
| } |
| |
| static void edge_wakeup_icu_disable(void) |
| { |
| writel_relaxed(0, icu_virt_addr + (EDGE_WAKEUP_ICU << 2)); |
| } |
| |
| static int need_restore_pad_wakeup; |
| /* |
| * low power config |
| */ |
| void asr18xx_lowpower_config(u32 power_state, \ |
| u32 lowpower_enable) |
| { |
| u32 core_idle_cfg, mp_idle_cfg, apcr, awucrm; |
| core_idle_cfg = readl_relaxed(APMU_CORE_IDLE_CFG); |
| apcr = readl_relaxed(regs_addr_get_va(REGS_ADDR_MPMU) + APCR); |
| |
| mp_idle_cfg = readl_relaxed(APMU_MP_IDLE_CFG); |
| |
| if (lowpower_enable) { |
| /* Suppose that there should be no one touch the MPMU_APCR |
| * but only Last man entering the low power modes which are |
| * deeper than M2. Thus, add BUG check here. |
| */ |
| #ifdef CONFIG_CPU_ASR18XX |
| if (apcr & (PMUM_DDRCORSD | PMUM_APBSD | PMUM_AXISD | |
| PMUM_VCTCXOSD | PMUM_STBYEN)) { |
| pr_err("APCR is set to 0x%X by the Non-lastman\n", apcr); |
| BUG(); |
| } |
| #else |
| if (apcr & (PMUM_DDRCORSD | PMUM_APBSD | PMUM_AXISD | |
| PMUM_VCTCXOSD | PMUM_STBYEN | PMUM_SLPEN)) { |
| pr_err("APCR is set to 0x%X by the Non-lastman\n", apcr); |
| BUG(); |
| } |
| #endif |
| if((power_state >= asr18xx_idle.l2_flush_state) && (cpu_is_asr1828() || cpu_is_asr1806() || cpu_is_asr1903())) |
| mp_idle_cfg |= PMUA_MP_L2_SRAM_POWER_DOWN; |
| |
| switch (power_state) { |
| case POWER_MODE_UDR: |
| /* |
| * VCTXO can't shutdown since Falcon PCIE PHY ref clock is VCTXO |
| */ |
| #if 0 /*disable this feature as pcie suspend power is too big */ |
| #ifdef CONFIG_PCIE_ASR1803 |
| if (!falcon_pcie_device()) |
| #endif |
| #endif |
| apcr |= PMUM_VCTCXOSD; |
| /* fall through */ |
| /* case POWER_MODE_UDR_VCTCXO: */ |
| /* There's a silicon bug on PXA1826, when PCIe host is initialized, |
| * some chips could hang when system enter/exit UDR randomly. |
| * Therefore, we will not vote UDR to workaround this until a better |
| * solution is found. |
| */ |
| #ifdef CONFIG_PCIE_NZ3 |
| if (!pcie_nz3_initialized()) |
| #endif |
| apcr |= PMUM_STBYEN; |
| |
| #ifdef CONFIG_CPU_ASR18XX |
| /* v2102 doesn't support asr1802s now |
| if (!asr1802s_is_a2plus() && cpu_is_asr1802s()) |
| apcr &= ~PMUM_STBYEN; |
| */ |
| #endif |
| /* |
| * APBSD is voted in UDR mode |
| */ |
| if (unlikely(asr_dont_shutdown_apb_in_d1pp())) |
| apcr |= PMUM_APBSD; |
| /* fall through */ |
| case POWER_MODE_SYS_SLEEP: |
| /* |
| * don't vote PMUM_APBSD to enable D1PP mode if UART |
| * needs to be wakeup from D1PP mode, to avoid the first |
| * RX byte to be lost |
| */ |
| if (likely(!asr_dont_shutdown_apb_in_d1pp())) |
| apcr |= PMUM_APBSD; |
| apcr |= PMUM_SLPEN; |
| apcr |= PMUM_DDRCORSD; |
| /* fall through */ |
| case POWER_MODE_APPS_IDLE: |
| apcr |= PMUM_AXISD; |
| /* |
| * Normally edge wakeup should only take effect in D1 and |
| * deeper modes. But due to SD host hardware requirement, |
| * it has to be put in D1P mode. |
| */ |
| edge_wakeup_mfp_enable(); |
| if(POWER_MODE_UDR == power_state) |
| asr_mfpr_udr_cfg(); |
| edge_wakeup_icu_enable(); |
| awucrm = readl_relaxed(regs_addr_get_va(REGS_ADDR_MPMU) + AWUCRM); |
| /* open usb wakeup ports for usb wakeup from d1p/d1 mode */ |
| if (cpu_is_asr1802s() || cpu_is_asr1803()) { |
| awucrm |= PMUM_WAKEUP5; |
| apcr &= ~PMUM_SLPWP5; |
| } |
| writel_relaxed(awucrm | PMUM_WAKEUP2, regs_addr_get_va(REGS_ADDR_MPMU) + AWUCRM); |
| apcr &= ~PMUM_SLPWP2; |
| need_restore_pad_wakeup = 1; |
| /* fall through */ |
| case POWER_MODE_CORE_POWERDOWN: |
| core_idle_cfg |= PMUA_CORE_POWER_DOWN; |
| mp_idle_cfg |= PMUA_MP_POWER_DOWN; |
| mp_idle_cfg |= PMUA_MP_IDLE; |
| core_idle_cfg |= PMUA_CORE_IDLE; |
| /* fall through */ |
| case POWER_MODE_CORE_INTIDLE: |
| break; |
| default: |
| WARN(1, "Invalid power state!\n"); |
| } |
| } else { |
| core_idle_cfg &= ~(PMUA_CORE_IDLE | PMUA_CORE_POWER_DOWN); |
| mp_idle_cfg &= ~(PMUA_MP_IDLE | PMUA_MP_POWER_DOWN | |
| PMUA_MP_L2_SRAM_POWER_DOWN | |
| PMUA_MP_MASK_CLK_OFF); |
| #ifdef CONFIG_CPU_ASR18XX |
| apcr &= ~(PMUM_DDRCORSD | PMUM_APBSD | PMUM_AXISD | |
| PMUM_VCTCXOSD | PMUM_STBYEN); |
| #else |
| apcr &= ~(PMUM_DDRCORSD | PMUM_APBSD | PMUM_AXISD | |
| PMUM_VCTCXOSD | PMUM_STBYEN | PMUM_SLPEN); |
| #endif |
| /* disable pad wakeup if needed */ |
| if (need_restore_pad_wakeup) { |
| awucrm = readl_relaxed(regs_addr_get_va(REGS_ADDR_MPMU) + AWUCRM); |
| writel_relaxed(awucrm & ~PMUM_WAKEUP2, regs_addr_get_va(REGS_ADDR_MPMU) + AWUCRM); |
| apcr |= PMUM_SLPWP2; |
| edge_wakeup_icu_disable(); |
| asr_mfpr_udr_restore(); |
| edge_wakeup_mfp_disable(); |
| need_restore_pad_wakeup = 0; |
| } |
| } |
| writel_relaxed(core_idle_cfg, APMU_CORE_IDLE_CFG); |
| writel_relaxed(apcr, regs_addr_get_va(REGS_ADDR_MPMU) + APCR); |
| |
| writel_relaxed(mp_idle_cfg, APMU_MP_IDLE_CFG); |
| |
| return; |
| } |
| |
| #define DISABLE_ALL_WAKEUP_PORTS \ |
| (PMUM_SLPWP0 | PMUM_SLPWP1 | PMUM_SLPWP2 | PMUM_SLPWP3 | \ |
| PMUM_SLPWP4 | PMUM_SLPWP5 | PMUM_SLPWP6 | PMUM_SLPWP7) |
| /* Here we don't enable CP wakeup sources since CP will enable them */ |
| #define ENABLE_AP_WAKEUP_SOURCES \ |
| (PMUM_AP_ASYNC_INT | PMUM_AP_FULL_IDLE | PMUM_SQU_SDH1 | \ |
| PMUM_SDH_23 | PMUM_WDT | PMUM_RTC_ALARM | PMUM_AP0_2_TIMER_1 | \ |
| PMUM_AP0_2_TIMER_2 | PMUM_AP1_TIMER_1 | PMUM_AP1_TIMER_2 | \ |
| PMUM_WAKEUP7 | PMUM_WAKEUP6 | PMUM_WAKEUP5 | PMUM_WAKEUP4 | \ |
| PMUM_WAKEUP3 | PMUM_WAKEUP2) |
| /* |
| * Enable AP wakeup sources and ports. To enalbe wakeup |
| * ports, it needs both AP side to configure MPMU_APCR |
| * and CP side to configure MPMU_CPCR to really enable |
| * it. To enable wakeup sources, either AP side to set |
| * MPMU_AWUCRM or CP side to set MPMU_CWRCRM can really |
| * enable it. |
| */ |
| void asr18xx_save_wakeup(void) |
| { |
| s_awucrm = readl_relaxed(regs_addr_get_va(REGS_ADDR_MPMU) + AWUCRM); |
| s_apcr = readl_relaxed(regs_addr_get_va(REGS_ADDR_MPMU) + APCR); |
| if (cpu_is_asr1803() || cpu_is_asr1828() || cpu_is_asr1806() |
| || cpu_is_asr1903()) { |
| /* EMAC use PMUM_NEWROTARY for wakeup */ |
| writel_relaxed(s_awucrm | ENABLE_AP_WAKEUP_SOURCES | \ |
| PMUM_NEWROTARY, regs_addr_get_va(REGS_ADDR_MPMU) + AWUCRM); |
| } else { |
| writel_relaxed(s_awucrm | ENABLE_AP_WAKEUP_SOURCES, \ |
| regs_addr_get_va(REGS_ADDR_MPMU) + AWUCRM); |
| } |
| writel_relaxed(s_apcr & ~DISABLE_ALL_WAKEUP_PORTS, \ |
| regs_addr_get_va(REGS_ADDR_MPMU) + APCR); |
| } |
| |
| void asr18xx_restore_wakeup(void) |
| { |
| writel_relaxed(s_awucrm, regs_addr_get_va(REGS_ADDR_MPMU) + AWUCRM); |
| writel_relaxed(s_apcr, regs_addr_get_va(REGS_ADDR_MPMU) + APCR); |
| } |
| |
| static void asr18xx_icu_global_mask(u32 mask) |
| { |
| u32 icu_msk; |
| |
| icu_msk = readl_relaxed(ICU_GBL_INT_MSK); |
| |
| if (mask) { |
| icu_msk |= ICU_MASK_FIQ; |
| icu_msk |= ICU_MASK_IRQ; |
| } else { |
| icu_msk &= ~(ICU_MASK_FIQ | ICU_MASK_IRQ); |
| } |
| writel_relaxed(icu_msk, ICU_GBL_INT_MSK); |
| } |
| |
| void asr18xx_set_pmu(u32 cpu, u32 power_mode) |
| { |
| (void)cpu; |
| |
| asr18xx_lowpower_config(power_mode, 1); |
| /* Mask ICU global interrupt */ |
| asr18xx_icu_global_mask(1); |
| } |
| |
| void asr18xx_clr_pmu(u32 cpu) |
| { |
| (void)cpu; |
| |
| /* Mask ICU global interrupt */ |
| writel_relaxed(0, APMU_MP_IDLE_CFG); |
| asr18xx_icu_global_mask(0); |
| |
| asr18xx_lowpower_config(POWER_MODE_CORE_INTIDLE, 0); |
| } |
| |
| static void __init asr18xx_reg_init(void) |
| { |
| icu_virt_addr = icu_get_base_addr(); |
| |
| APMU_CORE_IDLE_CFG = regs_addr_get_va(REGS_ADDR_APMU) + CORE0_IDLE; |
| APMU_MP_IDLE_CFG = regs_addr_get_va(REGS_ADDR_APMU) + MP_CFG0; |
| APMU_DEBUG_REG = regs_addr_get_va(REGS_ADDR_APMU) + DEBUG_REG; |
| ICU_GBL_INT_MSK = icu_virt_addr + 0x114; |
| } |
| |
| static int __init asr18xx_lowpower_init(void) |
| { |
| u32 apcr, debug_reg; |
| |
| asr18xx_reg_init(); |
| |
| mmp_platform_power_register(&asr18xx_idle); |
| |
| /* set DSPSD, DTCMSD, BBSD, MSASLPEN */ |
| apcr = readl_relaxed(regs_addr_get_va(REGS_ADDR_MPMU) + APCR); |
| apcr |= PMUM_DTCMSD | PMUM_BBSD | PMUM_MSASLPEN; |
| apcr &= ~PMUM_STBYEN; |
| |
| #ifdef CONFIG_CPU_ASR18XX |
| /* To solve the pmu resume stuck at DDR issue */ |
| apcr |= PMUM_SLPEN; |
| #endif |
| writel_relaxed(apcr, regs_addr_get_va(REGS_ADDR_MPMU) + APCR); |
| |
| debug_reg = readl_relaxed(APMU_DEBUG_REG); |
| if(cpu_is_asr1828()) { |
| debug_reg |= AP_GBL_IRQ_MASK_CLR_DIS; |
| writel_relaxed(debug_reg, APMU_DEBUG_REG); |
| } |
| |
| #ifdef CONFIG_CPU_ASR1903 |
| __raw_writel(0x2C30B, regs_addr_get_va(REGS_ADDR_APMU) + STBL_TIMER); |
| #else |
| __raw_writel(0x28207, regs_addr_get_va(REGS_ADDR_APMU) + STBL_TIMER); |
| #endif |
| return 0; |
| } |
| late_initcall(asr18xx_lowpower_init); |