| /* | |
| * 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 <[email protected]> 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); |