linux/arch/blackfin/mach-common/pm.c
Aaron Wu ef7dcaf1e7 pm: sometimes wake up from suspend to RAM would fail
Sometimes it fails to wake up from suspend to RAM, this is because
we would flush the data cache by assemble command FLUSHINV before
suspend to RAM, and there is a delay between this command execution
and cache flush completion. Add a 1uS delay to works around this.

Signed-off-by: Aaron Wu <Aaron.wu@analog.com>
2015-04-23 21:34:32 +08:00

302 lines
6.5 KiB
C

/*
* Blackfin power management
*
* Copyright 2006-2009 Analog Devices Inc.
*
* Licensed under the GPL-2
* based on arm/mach-omap/pm.c
* Copyright 2001, Cliff Brake <cbrake@accelent.com> and others
*/
#include <linux/suspend.h>
#include <linux/sched.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <asm/cplb.h>
#include <asm/gpio.h>
#include <asm/dma.h>
#include <asm/dpmc.h>
#include <asm/pm.h>
#ifdef CONFIG_BF60x
struct bfin_cpu_pm_fns *bfin_cpu_pm;
#endif
void bfin_pm_suspend_standby_enter(void)
{
#if !BFIN_GPIO_PINT
bfin_pm_standby_setup();
#endif
#ifdef CONFIG_BF60x
bfin_cpu_pm->enter(PM_SUSPEND_STANDBY);
#else
# ifdef CONFIG_PM_BFIN_SLEEP_DEEPER
sleep_deeper(bfin_sic_iwr[0], bfin_sic_iwr[1], bfin_sic_iwr[2]);
# else
sleep_mode(bfin_sic_iwr[0], bfin_sic_iwr[1], bfin_sic_iwr[2]);
# endif
#endif
#if !BFIN_GPIO_PINT
bfin_pm_standby_restore();
#endif
#ifndef CONFIG_BF60x
#ifdef SIC_IWR0
bfin_write_SIC_IWR0(IWR_DISABLE_ALL);
# ifdef SIC_IWR1
/* BF52x system reset does not properly reset SIC_IWR1 which
* will screw up the bootrom as it relies on MDMA0/1 waking it
* up from IDLE instructions. See this report for more info:
* http://blackfin.uclinux.org/gf/tracker/4323
*/
if (ANOMALY_05000435)
bfin_write_SIC_IWR1(IWR_ENABLE(10) | IWR_ENABLE(11));
else
bfin_write_SIC_IWR1(IWR_DISABLE_ALL);
# endif
# ifdef SIC_IWR2
bfin_write_SIC_IWR2(IWR_DISABLE_ALL);
# endif
#else
bfin_write_SIC_IWR(IWR_DISABLE_ALL);
#endif
#endif
}
int bf53x_suspend_l1_mem(unsigned char *memptr)
{
dma_memcpy_nocache(memptr, (const void *) L1_CODE_START,
L1_CODE_LENGTH);
dma_memcpy_nocache(memptr + L1_CODE_LENGTH,
(const void *) L1_DATA_A_START, L1_DATA_A_LENGTH);
dma_memcpy_nocache(memptr + L1_CODE_LENGTH + L1_DATA_A_LENGTH,
(const void *) L1_DATA_B_START, L1_DATA_B_LENGTH);
memcpy(memptr + L1_CODE_LENGTH + L1_DATA_A_LENGTH +
L1_DATA_B_LENGTH, (const void *) L1_SCRATCH_START,
L1_SCRATCH_LENGTH);
return 0;
}
int bf53x_resume_l1_mem(unsigned char *memptr)
{
dma_memcpy_nocache((void *) L1_CODE_START, memptr, L1_CODE_LENGTH);
dma_memcpy_nocache((void *) L1_DATA_A_START, memptr + L1_CODE_LENGTH,
L1_DATA_A_LENGTH);
dma_memcpy_nocache((void *) L1_DATA_B_START, memptr + L1_CODE_LENGTH +
L1_DATA_A_LENGTH, L1_DATA_B_LENGTH);
memcpy((void *) L1_SCRATCH_START, memptr + L1_CODE_LENGTH +
L1_DATA_A_LENGTH + L1_DATA_B_LENGTH, L1_SCRATCH_LENGTH);
return 0;
}
#if defined(CONFIG_BFIN_EXTMEM_WRITEBACK) || defined(CONFIG_BFIN_L2_WRITEBACK)
# ifdef CONFIG_BF60x
__attribute__((l1_text))
# endif
static void flushinv_all_dcache(void)
{
register u32 way, bank, subbank, set;
register u32 status, addr;
u32 dmem_ctl = bfin_read_DMEM_CONTROL();
for (bank = 0; bank < 2; ++bank) {
if (!(dmem_ctl & (1 << (DMC1_P - bank))))
continue;
for (way = 0; way < 2; ++way)
for (subbank = 0; subbank < 4; ++subbank)
for (set = 0; set < 64; ++set) {
bfin_write_DTEST_COMMAND(
way << 26 |
bank << 23 |
subbank << 16 |
set << 5
);
CSYNC();
status = bfin_read_DTEST_DATA0();
/* only worry about valid/dirty entries */
if ((status & 0x3) != 0x3)
continue;
/* construct the address using the tag */
addr = (status & 0xFFFFC800) | (subbank << 12) | (set << 5);
/* flush it */
__asm__ __volatile__("FLUSHINV[%0];" : : "a"(addr));
}
}
}
#endif
int bfin_pm_suspend_mem_enter(void)
{
int ret;
#ifndef CONFIG_BF60x
int wakeup;
#endif
unsigned char *memptr = kmalloc(L1_CODE_LENGTH + L1_DATA_A_LENGTH
+ L1_DATA_B_LENGTH + L1_SCRATCH_LENGTH,
GFP_ATOMIC);
if (memptr == NULL) {
panic("bf53x_suspend_l1_mem malloc failed");
return -ENOMEM;
}
#ifndef CONFIG_BF60x
wakeup = bfin_read_VR_CTL() & ~FREQ;
wakeup |= SCKELOW;
#ifdef CONFIG_PM_BFIN_WAKE_PH6
wakeup |= PHYWE;
#endif
#ifdef CONFIG_PM_BFIN_WAKE_GP
wakeup |= GPWE;
#endif
#endif
ret = blackfin_dma_suspend();
if (ret) {
kfree(memptr);
return ret;
}
#ifdef CONFIG_GPIO_ADI
bfin_gpio_pm_hibernate_suspend();
#endif
#if defined(CONFIG_BFIN_EXTMEM_WRITEBACK) || defined(CONFIG_BFIN_L2_WRITEBACK)
flushinv_all_dcache();
udelay(1);
#endif
_disable_dcplb();
_disable_icplb();
bf53x_suspend_l1_mem(memptr);
#ifndef CONFIG_BF60x
do_hibernate(wakeup | vr_wakeup); /* See you later! */
#else
bfin_cpu_pm->enter(PM_SUSPEND_MEM);
#endif
bf53x_resume_l1_mem(memptr);
_enable_icplb();
_enable_dcplb();
#ifdef CONFIG_GPIO_ADI
bfin_gpio_pm_hibernate_restore();
#endif
blackfin_dma_resume();
kfree(memptr);
return 0;
}
/*
* bfin_pm_valid - Tell the PM core that we only support the standby sleep
* state
* @state: suspend state we're checking.
*
*/
static int bfin_pm_valid(suspend_state_t state)
{
return (state == PM_SUSPEND_STANDBY
#if !(defined(BF533_FAMILY) || defined(CONFIG_BF561))
/*
* On BF533/2/1:
* If we enter Hibernate the SCKE Pin is driven Low,
* so that the SDRAM enters Self Refresh Mode.
* However when the reset sequence that follows hibernate
* state is executed, SCKE is driven High, taking the
* SDRAM out of Self Refresh.
*
* If you reconfigure and access the SDRAM "very quickly",
* you are likely to avoid errors, otherwise the SDRAM
* start losing its contents.
* An external HW workaround is possible using logic gates.
*/
|| state == PM_SUSPEND_MEM
#endif
);
}
/*
* bfin_pm_enter - Actually enter a sleep state.
* @state: State we're entering.
*
*/
static int bfin_pm_enter(suspend_state_t state)
{
switch (state) {
case PM_SUSPEND_STANDBY:
bfin_pm_suspend_standby_enter();
break;
case PM_SUSPEND_MEM:
bfin_pm_suspend_mem_enter();
break;
default:
return -EINVAL;
}
return 0;
}
#ifdef CONFIG_BFIN_PM_WAKEUP_TIME_BENCH
void bfin_pm_end(void)
{
u32 cycle, cycle2;
u64 usec64;
u32 usec;
__asm__ __volatile__ (
"1: %0 = CYCLES2\n"
"%1 = CYCLES\n"
"%2 = CYCLES2\n"
"CC = %2 == %0\n"
"if ! CC jump 1b\n"
: "=d,a" (cycle2), "=d,a" (cycle), "=d,a" (usec) : : "CC"
);
usec64 = ((u64)cycle2 << 32) + cycle;
do_div(usec64, get_cclk() / USEC_PER_SEC);
usec = usec64;
if (usec == 0)
usec = 1;
pr_info("PM: resume of kernel completes after %ld msec %03ld usec\n",
usec / USEC_PER_MSEC, usec % USEC_PER_MSEC);
}
#endif
static const struct platform_suspend_ops bfin_pm_ops = {
.enter = bfin_pm_enter,
.valid = bfin_pm_valid,
#ifdef CONFIG_BFIN_PM_WAKEUP_TIME_BENCH
.end = bfin_pm_end,
#endif
};
static int __init bfin_pm_init(void)
{
suspend_set_ops(&bfin_pm_ops);
return 0;
}
__initcall(bfin_pm_init);