/* * 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);