blob: 437c3d8e87ebd80211b0fca6b0724b0ae53dab6b [file] [log] [blame]
/*
* linux/arch/arm/mach-mmp/pm.c
*
* Author: Fan Wu <fwu@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 <asm/cputype.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <asm/mcpm.h>
#include <linux/suspend.h>
#include <asm/suspend.h>
#include <soc/asr/asr18xx_lowpower.h>
#include <linux/irqchip/arm-gic.h>
#include <linux/irqchip/mmp.h>
#include <linux/cpu_pm.h>
#include "irqs.h"
#include "pm.h"
#include <soc/asr/mmp_cpuidle.h>
struct platform_suspend *mmp_suspend;
u32 detect_wakeup_status = 0;
EXPORT_SYMBOL(detect_wakeup_status);
#if !defined(CONFIG_CPU_ASR18XX)
static int notrace mcpm_powerdown_finisher(unsigned long arg)
{
u32 mpidr = read_cpuid_mpidr();
u32 cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0);
u32 this_cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1);
mcpm_set_entry_vector(cpu, this_cluster, cpu_resume);
asr_set_target_lpm_state(cpu, this_cluster, *((int *)arg), (int *)arg);
mcpm_cpu_suspend();
return 1;
}
#endif
#if defined(CONFIG_CPU_ASR18XX)
extern void asr18xx_pm_power_down(void);
extern void asr18xx_pm_powered_up(void);
extern void asr18xx_dcstat(unsigned long addr, unsigned int flag);
extern int asr18xx_pm_check_constraint(void);
static int notrace mmp_suspend_finish(unsigned long val)
{
asr18xx_pm_power_down();
return 1;
}
#endif
static int mmp_pm_enter(suspend_state_t state)
{
unsigned int real_idx = mmp_suspend->suspend_state;
if (mmp_suspend->ops->pre_suspend_check) {
if (mmp_suspend->ops->pre_suspend_check())
return -EAGAIN;
}
#if !defined(CONFIG_CPU_ASR18XX)
cpu_suspend((unsigned long)&real_idx, mcpm_powerdown_finisher);
#else
cpu_suspend((unsigned long)&real_idx, mmp_suspend_finish);
#endif
if (mmp_suspend->ops->post_chk_wakeup)
detect_wakeup_status = mmp_suspend->ops->post_chk_wakeup();
if (mmp_suspend->ops->post_clr_wakeup)
mmp_suspend->ops->post_clr_wakeup(detect_wakeup_status);
if (real_idx != mmp_suspend->suspend_state)
pr_info("WARNING!!! "
"Suspend Didn't enter the Expected Low power mode\n");
#if !defined(CONFIG_CPU_ASR18XX)
mcpm_cpu_powered_up();
#else
asr18xx_pm_powered_up();
#endif
return 0;
}
static int mmp_pm_valid(suspend_state_t state)
{
return ((state == PM_SUSPEND_STANDBY) || (state == PM_SUSPEND_MEM));
}
extern void asr_clear_wakeup_event_idx(void);
/* Called after devices suspend, before noirq devices suspend */
static int mmp_pm_prepare(void)
{
#ifdef CONFIG_CPU_ASR18XX
asr18xx_dcstat(0, 0);
#endif
asr_clear_wakeup_event_idx();
return 0;
}
/* Called after noirq devices resume, before devices resume */
static void mmp_pm_finish(void)
{
}
/* Called after Non-boot CPUs have been Hotplug in if necessary */
static void mmp_pm_wake(void)
{
}
static const struct platform_suspend_ops mmp_pm_ops = {
.valid = mmp_pm_valid,
.prepare = mmp_pm_prepare,
.enter = mmp_pm_enter,
.finish = mmp_pm_finish,
.wake = mmp_pm_wake,
};
int mmp_set_wake(struct irq_data *data, unsigned int on)
{
int irq = (int)data->hwirq;
struct irq_desc *desc = irq_to_desc(data->irq);
if (!mmp_suspend->ops->set_wake) {
WARN_ON("Platform set_wake function is not Existed!!");
return -EINVAL;
}
if (!desc) {
pr_err("irq_desc is NULL\n");
return -EINVAL;
}
if (on) {
if (desc->action)
desc->action->flags |= IRQF_NO_SUSPEND;
} else {
if (desc->action)
desc->action->flags &= ~IRQF_NO_SUSPEND;
}
mmp_suspend->ops->set_wake(irq, on);
return 0;
}
int __init mmp_platform_suspend_register(struct platform_suspend *plt_suspend)
{
if (mmp_suspend)
return -EBUSY;
mmp_suspend = plt_suspend;
suspend_set_ops(&mmp_pm_ops);
#ifndef CONFIG_CPU_ASR18XX
gic_arch_extn.irq_set_wake = mmp_set_wake;
#else
icu_irq_chip.irq_set_wake = mmp_set_wake;
#endif
if (mmp_suspend->ops->plt_suspend_init)
mmp_suspend->ops->plt_suspend_init();
return 0;
}