blob: a6e6d19aedca1b4ba6977955a77a74c541ebc946 [file] [log] [blame]
/*
* 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);