1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
|
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* arch/arm/mach-lpc32xx/suspend.S
*
* Original authors: Dmitry Chigirev, Vitaly Wool <source@mvista.com>
* Modified by Kevin Wells <kevin.wells@nxp.com>
*
* 2005 (c) MontaVista Software, Inc.
*/
#include <linux/linkage.h>
#include <asm/assembler.h>
#include "lpc32xx.h"
/* Using named register defines makes the code easier to follow */
#define WORK1_REG r0
#define WORK2_REG r1
#define SAVED_HCLK_DIV_REG r2
#define SAVED_HCLK_PLL_REG r3
#define SAVED_DRAM_CLKCTRL_REG r4
#define SAVED_PWR_CTRL_REG r5
#define CLKPWRBASE_REG r6
#define EMCBASE_REG r7
#define LPC32XX_EMC_STATUS_OFFS 0x04
#define LPC32XX_EMC_STATUS_BUSY 0x1
#define LPC32XX_EMC_STATUS_SELF_RFSH 0x4
#define LPC32XX_CLKPWR_PWR_CTRL_OFFS 0x44
#define LPC32XX_CLKPWR_HCLK_DIV_OFFS 0x40
#define LPC32XX_CLKPWR_HCLKPLL_CTRL_OFFS 0x58
#define CLKPWR_PCLK_DIV_MASK 0xFFFFFE7F
.text
ENTRY(lpc32xx_sys_suspend)
@ Save a copy of the used registers in IRAM, r0 is corrupted
adr r0, tmp_stack_end
stmfd r0!, {r3 - r7, sp, lr}
@ Load a few common register addresses
adr WORK1_REG, reg_bases
ldr CLKPWRBASE_REG, [WORK1_REG, #0]
ldr EMCBASE_REG, [WORK1_REG, #4]
ldr SAVED_PWR_CTRL_REG, [CLKPWRBASE_REG,\
#LPC32XX_CLKPWR_PWR_CTRL_OFFS]
orr WORK1_REG, SAVED_PWR_CTRL_REG, #LPC32XX_CLKPWR_SDRAM_SELF_RFSH
@ Wait for SDRAM busy status to go busy and then idle
@ This guarantees a small windows where DRAM isn't busy
1:
ldr WORK2_REG, [EMCBASE_REG, #LPC32XX_EMC_STATUS_OFFS]
and WORK2_REG, WORK2_REG, #LPC32XX_EMC_STATUS_BUSY
cmp WORK2_REG, #LPC32XX_EMC_STATUS_BUSY
bne 1b @ Branch while idle
2:
ldr WORK2_REG, [EMCBASE_REG, #LPC32XX_EMC_STATUS_OFFS]
and WORK2_REG, WORK2_REG, #LPC32XX_EMC_STATUS_BUSY
cmp WORK2_REG, #LPC32XX_EMC_STATUS_BUSY
beq 2b @ Branch until idle
@ Setup self-refresh with support for manual exit of
@ self-refresh mode
str WORK1_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_PWR_CTRL_OFFS]
orr WORK2_REG, WORK1_REG, #LPC32XX_CLKPWR_UPD_SDRAM_SELF_RFSH
str WORK2_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_PWR_CTRL_OFFS]
str WORK1_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_PWR_CTRL_OFFS]
@ Wait for self-refresh acknowledge, clocks to the DRAM device
@ will automatically stop on start of self-refresh
3:
ldr WORK2_REG, [EMCBASE_REG, #LPC32XX_EMC_STATUS_OFFS]
and WORK2_REG, WORK2_REG, #LPC32XX_EMC_STATUS_SELF_RFSH
cmp WORK2_REG, #LPC32XX_EMC_STATUS_SELF_RFSH
bne 3b @ Branch until self-refresh mode starts
@ Enter direct-run mode from run mode
bic WORK1_REG, WORK1_REG, #LPC32XX_CLKPWR_SELECT_RUN_MODE
str WORK1_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_PWR_CTRL_OFFS]
@ Safe disable of DRAM clock in EMC block, prevents DDR sync
@ issues on restart
ldr SAVED_HCLK_DIV_REG, [CLKPWRBASE_REG,\
#LPC32XX_CLKPWR_HCLK_DIV_OFFS]
and WORK2_REG, SAVED_HCLK_DIV_REG, #CLKPWR_PCLK_DIV_MASK
str WORK2_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_HCLK_DIV_OFFS]
@ Save HCLK PLL state and disable HCLK PLL
ldr SAVED_HCLK_PLL_REG, [CLKPWRBASE_REG,\
#LPC32XX_CLKPWR_HCLKPLL_CTRL_OFFS]
bic WORK2_REG, SAVED_HCLK_PLL_REG, #LPC32XX_CLKPWR_HCLKPLL_POWER_UP
str WORK2_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_HCLKPLL_CTRL_OFFS]
@ Enter stop mode until an enabled event occurs
orr WORK1_REG, WORK1_REG, #LPC32XX_CLKPWR_STOP_MODE_CTRL
str WORK1_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_PWR_CTRL_OFFS]
.rept 9
nop
.endr
@ Clear stop status
bic WORK1_REG, WORK1_REG, #LPC32XX_CLKPWR_STOP_MODE_CTRL
@ Restore original HCLK PLL value and wait for PLL lock
str SAVED_HCLK_PLL_REG, [CLKPWRBASE_REG,\
#LPC32XX_CLKPWR_HCLKPLL_CTRL_OFFS]
4:
ldr WORK2_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_HCLKPLL_CTRL_OFFS]
and WORK2_REG, WORK2_REG, #LPC32XX_CLKPWR_HCLKPLL_PLL_STS
bne 4b
@ Re-enter run mode with self-refresh flag cleared, but no DRAM
@ update yet. DRAM is still in self-refresh
str SAVED_PWR_CTRL_REG, [CLKPWRBASE_REG,\
#LPC32XX_CLKPWR_PWR_CTRL_OFFS]
@ Restore original DRAM clock mode to restore DRAM clocks
str SAVED_HCLK_DIV_REG, [CLKPWRBASE_REG,\
#LPC32XX_CLKPWR_HCLK_DIV_OFFS]
@ Clear self-refresh mode
orr WORK1_REG, SAVED_PWR_CTRL_REG,\
#LPC32XX_CLKPWR_UPD_SDRAM_SELF_RFSH
str WORK1_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_PWR_CTRL_OFFS]
str SAVED_PWR_CTRL_REG, [CLKPWRBASE_REG,\
#LPC32XX_CLKPWR_PWR_CTRL_OFFS]
@ Wait for EMC to clear self-refresh mode
5:
ldr WORK2_REG, [EMCBASE_REG, #LPC32XX_EMC_STATUS_OFFS]
and WORK2_REG, WORK2_REG, #LPC32XX_EMC_STATUS_SELF_RFSH
bne 5b @ Branch until self-refresh has exited
@ restore regs and return
adr r0, tmp_stack
ldmfd r0!, {r3 - r7, sp, pc}
reg_bases:
.long IO_ADDRESS(LPC32XX_CLK_PM_BASE)
.long IO_ADDRESS(LPC32XX_EMC_BASE)
tmp_stack:
.long 0, 0, 0, 0, 0, 0, 0
tmp_stack_end:
ENTRY(lpc32xx_sys_suspend_sz)
.word . - lpc32xx_sys_suspend
|