linux/arch/arm/mach-mmp/pm-pxa910.c

274 lines
6.9 KiB
C
Raw Normal View History

/*
* PXA910 Power Management Routines
*
* This software program is licensed subject to the GNU General Public License
* (GPL).Version 2,June 1991, available at http://www.fsf.org/copyleft/gpl.html
*
* (C) Copyright 2009 Marvell International Ltd.
* All Rights Reserved
*/
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/time.h>
#include <linux/delay.h>
#include <linux/suspend.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <asm/mach-types.h>
#include <mach/hardware.h>
#include <mach/cputype.h>
#include <mach/addr-map.h>
#include <mach/pm-pxa910.h>
#include <mach/regs-icu.h>
#include <mach/irqs.h>
int pxa910_set_wake(struct irq_data *data, unsigned int on)
{
uint32_t awucrm = 0, apcr = 0;
arm: mmp: Remove pointless fiddling with irq internals The pm-mmp2 and pm-pxa910 power management related irq_set_wake callbacks fiddle pointlessly with the irq actions for no reason except for lack of understanding how the wakeup mechanism works. On supsend the core disables all interrupts lazily, i.e. it does not mask them at the irq controller level. So any interrupt which is firing during suspend will mark the corresponding interrupt line as pending. Just before the core powers down it checks whether there are interrupts pending from interrupt lines which are marked as wakeup sources and if so it aborts the suspend and resends the interrupts. If there was no interrupt at this point, the cpu goes into suspend with these interrupts unmasked. The IRQF_NO_SUSPEND flag for interrupt actions is a totally different mechanism. That allows the device driver to prevent the core from disabling the interrupt despite the fact that it is not marked as a wakeup source. This has nothing to do with the case at hand. It was introduced for special cases where lazy disable is not possible. Remove the nonsense along with the braindamaged boundary check. The core code does NOT call these functions out of boundary. Add a FIXME comment to an unhandled error path which merily printks some useless blurb instead of returning a proper error code. Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Cc: Peter Zijlstra <peterz@infradead.org> Cc: arm <linux-arm-kernel@lists.infradead.org> Cc: Eric Miao <eric.y.miao@gmail.com> Cc: Haojian Zhuang <haojian.zhuang@gmail.com> Cc: Russell King <linux@arm.linux.org.uk> Link: http://lkml.kernel.org/r/20140223212737.214342433@linutronix.de Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
2014-02-23 21:40:13 +00:00
int irq = data->irq;
/* setting wakeup sources */
switch (irq) {
/* wakeup line 2 */
case IRQ_PXA910_AP_GPIO:
awucrm = MPMU_AWUCRM_WAKEUP(2);
apcr |= MPMU_APCR_SLPWP2;
break;
/* wakeup line 3 */
case IRQ_PXA910_KEYPAD:
awucrm = MPMU_AWUCRM_WAKEUP(3) | MPMU_AWUCRM_KEYPRESS;
apcr |= MPMU_APCR_SLPWP3;
break;
case IRQ_PXA910_ROTARY:
awucrm = MPMU_AWUCRM_WAKEUP(3) | MPMU_AWUCRM_NEWROTARY;
apcr |= MPMU_APCR_SLPWP3;
break;
case IRQ_PXA910_TRACKBALL:
awucrm = MPMU_AWUCRM_WAKEUP(3) | MPMU_AWUCRM_TRACKBALL;
apcr |= MPMU_APCR_SLPWP3;
break;
/* wakeup line 4 */
case IRQ_PXA910_AP1_TIMER1:
awucrm = MPMU_AWUCRM_WAKEUP(4) | MPMU_AWUCRM_AP1_TIMER_1;
apcr |= MPMU_APCR_SLPWP4;
break;
case IRQ_PXA910_AP1_TIMER2:
awucrm = MPMU_AWUCRM_WAKEUP(4) | MPMU_AWUCRM_AP1_TIMER_2;
apcr |= MPMU_APCR_SLPWP4;
break;
case IRQ_PXA910_AP1_TIMER3:
awucrm = MPMU_AWUCRM_WAKEUP(4) | MPMU_AWUCRM_AP1_TIMER_3;
apcr |= MPMU_APCR_SLPWP4;
break;
case IRQ_PXA910_AP2_TIMER1:
awucrm = MPMU_AWUCRM_WAKEUP(4) | MPMU_AWUCRM_AP2_TIMER_1;
apcr |= MPMU_APCR_SLPWP4;
break;
case IRQ_PXA910_AP2_TIMER2:
awucrm = MPMU_AWUCRM_WAKEUP(4) | MPMU_AWUCRM_AP2_TIMER_2;
apcr |= MPMU_APCR_SLPWP4;
break;
case IRQ_PXA910_AP2_TIMER3:
awucrm = MPMU_AWUCRM_WAKEUP(4) | MPMU_AWUCRM_AP2_TIMER_3;
apcr |= MPMU_APCR_SLPWP4;
break;
case IRQ_PXA910_RTC_ALARM:
awucrm = MPMU_AWUCRM_WAKEUP(4) | MPMU_AWUCRM_RTC_ALARM;
apcr |= MPMU_APCR_SLPWP4;
break;
/* wakeup line 5 */
case IRQ_PXA910_USB1:
case IRQ_PXA910_USB2:
awucrm = MPMU_AWUCRM_WAKEUP(5);
apcr |= MPMU_APCR_SLPWP5;
break;
/* wakeup line 6 */
case IRQ_PXA910_MMC:
awucrm = MPMU_AWUCRM_WAKEUP(6)
| MPMU_AWUCRM_SDH1
| MPMU_AWUCRM_SDH2;
apcr |= MPMU_APCR_SLPWP6;
break;
/* wakeup line 7 */
case IRQ_PXA910_PMIC_INT:
awucrm = MPMU_AWUCRM_WAKEUP(7);
apcr |= MPMU_APCR_SLPWP7;
break;
default:
if (irq >= IRQ_GPIO_START && irq < IRQ_BOARD_START) {
awucrm = MPMU_AWUCRM_WAKEUP(2);
apcr |= MPMU_APCR_SLPWP2;
arm: mmp: Remove pointless fiddling with irq internals The pm-mmp2 and pm-pxa910 power management related irq_set_wake callbacks fiddle pointlessly with the irq actions for no reason except for lack of understanding how the wakeup mechanism works. On supsend the core disables all interrupts lazily, i.e. it does not mask them at the irq controller level. So any interrupt which is firing during suspend will mark the corresponding interrupt line as pending. Just before the core powers down it checks whether there are interrupts pending from interrupt lines which are marked as wakeup sources and if so it aborts the suspend and resends the interrupts. If there was no interrupt at this point, the cpu goes into suspend with these interrupts unmasked. The IRQF_NO_SUSPEND flag for interrupt actions is a totally different mechanism. That allows the device driver to prevent the core from disabling the interrupt despite the fact that it is not marked as a wakeup source. This has nothing to do with the case at hand. It was introduced for special cases where lazy disable is not possible. Remove the nonsense along with the braindamaged boundary check. The core code does NOT call these functions out of boundary. Add a FIXME comment to an unhandled error path which merily printks some useless blurb instead of returning a proper error code. Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Cc: Peter Zijlstra <peterz@infradead.org> Cc: arm <linux-arm-kernel@lists.infradead.org> Cc: Eric Miao <eric.y.miao@gmail.com> Cc: Haojian Zhuang <haojian.zhuang@gmail.com> Cc: Russell King <linux@arm.linux.org.uk> Link: http://lkml.kernel.org/r/20140223212737.214342433@linutronix.de Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
2014-02-23 21:40:13 +00:00
} else {
/* FIXME: This should return a proper error code ! */
printk(KERN_ERR "Error: no defined wake up source irq: %d\n",
irq);
arm: mmp: Remove pointless fiddling with irq internals The pm-mmp2 and pm-pxa910 power management related irq_set_wake callbacks fiddle pointlessly with the irq actions for no reason except for lack of understanding how the wakeup mechanism works. On supsend the core disables all interrupts lazily, i.e. it does not mask them at the irq controller level. So any interrupt which is firing during suspend will mark the corresponding interrupt line as pending. Just before the core powers down it checks whether there are interrupts pending from interrupt lines which are marked as wakeup sources and if so it aborts the suspend and resends the interrupts. If there was no interrupt at this point, the cpu goes into suspend with these interrupts unmasked. The IRQF_NO_SUSPEND flag for interrupt actions is a totally different mechanism. That allows the device driver to prevent the core from disabling the interrupt despite the fact that it is not marked as a wakeup source. This has nothing to do with the case at hand. It was introduced for special cases where lazy disable is not possible. Remove the nonsense along with the braindamaged boundary check. The core code does NOT call these functions out of boundary. Add a FIXME comment to an unhandled error path which merily printks some useless blurb instead of returning a proper error code. Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Cc: Peter Zijlstra <peterz@infradead.org> Cc: arm <linux-arm-kernel@lists.infradead.org> Cc: Eric Miao <eric.y.miao@gmail.com> Cc: Haojian Zhuang <haojian.zhuang@gmail.com> Cc: Russell King <linux@arm.linux.org.uk> Link: http://lkml.kernel.org/r/20140223212737.214342433@linutronix.de Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
2014-02-23 21:40:13 +00:00
}
}
if (on) {
if (awucrm) {
awucrm |= __raw_readl(MPMU_AWUCRM);
__raw_writel(awucrm, MPMU_AWUCRM);
}
if (apcr) {
apcr = ~apcr & __raw_readl(MPMU_APCR);
__raw_writel(apcr, MPMU_APCR);
}
} else {
if (awucrm) {
awucrm = ~awucrm & __raw_readl(MPMU_AWUCRM);
__raw_writel(awucrm, MPMU_AWUCRM);
}
if (apcr) {
apcr |= __raw_readl(MPMU_APCR);
__raw_writel(apcr, MPMU_APCR);
}
}
return 0;
}
void pxa910_pm_enter_lowpower_mode(int state)
{
uint32_t idle_cfg, apcr;
idle_cfg = __raw_readl(APMU_MOH_IDLE_CFG);
apcr = __raw_readl(MPMU_APCR);
apcr &= ~(MPMU_APCR_DDRCORSD | MPMU_APCR_APBSD | MPMU_APCR_AXISD
| MPMU_APCR_VCTCXOSD | MPMU_APCR_STBYEN);
idle_cfg &= ~(APMU_MOH_IDLE_CFG_MOH_IDLE
| APMU_MOH_IDLE_CFG_MOH_PWRDWN);
switch (state) {
case POWER_MODE_UDR:
/* only shutdown APB in UDR */
apcr |= MPMU_APCR_STBYEN | MPMU_APCR_APBSD;
/* fall through */
case POWER_MODE_SYS_SLEEP:
apcr |= MPMU_APCR_SLPEN; /* set the SLPEN bit */
apcr |= MPMU_APCR_VCTCXOSD; /* set VCTCXOSD */
/* fall through */
case POWER_MODE_APPS_SLEEP:
apcr |= MPMU_APCR_DDRCORSD; /* set DDRCORSD */
/* fall through */
case POWER_MODE_APPS_IDLE:
apcr |= MPMU_APCR_AXISD; /* set AXISDD bit */
/* fall through */
case POWER_MODE_CORE_EXTIDLE:
idle_cfg |= APMU_MOH_IDLE_CFG_MOH_IDLE;
idle_cfg |= APMU_MOH_IDLE_CFG_MOH_PWRDWN;
idle_cfg |= APMU_MOH_IDLE_CFG_MOH_PWR_SW(3)
| APMU_MOH_IDLE_CFG_MOH_L2_PWR_SW(3);
/* fall through */
case POWER_MODE_CORE_INTIDLE:
break;
}
/* program the memory controller hardware sleep type and auto wakeup */
idle_cfg |= APMU_MOH_IDLE_CFG_MOH_DIS_MC_SW_REQ;
idle_cfg |= APMU_MOH_IDLE_CFG_MOH_MC_WAKE_EN;
__raw_writel(0x0, APMU_MC_HW_SLP_TYPE); /* auto refresh */
/* set DSPSD, DTCMSD, BBSD, MSASLPEN */
apcr |= MPMU_APCR_DSPSD | MPMU_APCR_DTCMSD | MPMU_APCR_BBSD
| MPMU_APCR_MSASLPEN;
/*always set SLEPEN bit mainly for MSA*/
apcr |= MPMU_APCR_SLPEN;
/* finally write the registers back */
__raw_writel(idle_cfg, APMU_MOH_IDLE_CFG);
__raw_writel(apcr, MPMU_APCR);
}
static int pxa910_pm_enter(suspend_state_t state)
{
unsigned int idle_cfg, reg = 0;
/*pmic thread not completed,exit;otherwise system can't be waked up*/
reg = __raw_readl(ICU_INT_CONF(IRQ_PXA910_PMIC_INT));
if ((reg & 0x3) == 0)
return -EAGAIN;
idle_cfg = __raw_readl(APMU_MOH_IDLE_CFG);
idle_cfg |= APMU_MOH_IDLE_CFG_MOH_PWRDWN
| APMU_MOH_IDLE_CFG_MOH_SRAM_PWRDWN;
__raw_writel(idle_cfg, APMU_MOH_IDLE_CFG);
/* disable L2 */
outer_disable();
/* wait for l2 idle */
while (!(readl(CIU_REG(0x8)) & (1 << 16)))
udelay(1);
cpu_do_idle();
/* enable L2 */
outer_resume();
/* wait for l2 idle */
while (!(readl(CIU_REG(0x8)) & (1 << 16)))
udelay(1);
idle_cfg = __raw_readl(APMU_MOH_IDLE_CFG);
idle_cfg &= ~(APMU_MOH_IDLE_CFG_MOH_PWRDWN
| APMU_MOH_IDLE_CFG_MOH_SRAM_PWRDWN);
__raw_writel(idle_cfg, APMU_MOH_IDLE_CFG);
return 0;
}
/*
* Called after processes are frozen, but before we shut down devices.
*/
static int pxa910_pm_prepare(void)
{
pxa910_pm_enter_lowpower_mode(POWER_MODE_UDR);
return 0;
}
/*
* Called after devices are re-setup, but before processes are thawed.
*/
static void pxa910_pm_finish(void)
{
pxa910_pm_enter_lowpower_mode(POWER_MODE_CORE_INTIDLE);
}
static int pxa910_pm_valid(suspend_state_t state)
{
return ((state == PM_SUSPEND_STANDBY) || (state == PM_SUSPEND_MEM));
}
static const struct platform_suspend_ops pxa910_pm_ops = {
.valid = pxa910_pm_valid,
.prepare = pxa910_pm_prepare,
.enter = pxa910_pm_enter,
.finish = pxa910_pm_finish,
};
static int __init pxa910_pm_init(void)
{
uint32_t awucrm = 0;
if (!cpu_is_pxa910())
return -EIO;
suspend_set_ops(&pxa910_pm_ops);
/* Set the following bits for MMP3 playback with VCTXO on */
__raw_writel(__raw_readl(APMU_SQU_CLK_GATE_CTRL) | (1 << 30),
APMU_SQU_CLK_GATE_CTRL);
__raw_writel(__raw_readl(MPMU_FCCR) | (1 << 28), MPMU_FCCR);
awucrm |= MPMU_AWUCRM_AP_ASYNC_INT | MPMU_AWUCRM_AP_FULL_IDLE;
__raw_writel(awucrm, MPMU_AWUCRM);
return 0;
}
late_initcall(pxa910_pm_init);