403 lines
6.7 KiB
ArmAsm
403 lines
6.7 KiB
ArmAsm
/* SPDX-License-Identifier: GPL-2.0
|
|
*
|
|
* arch/sh/kernel/cpu/sh4a/sleep-sh_mobile.S
|
|
*
|
|
* Sleep mode and Standby modes support for SuperH Mobile
|
|
*
|
|
* Copyright (C) 2009 Magnus Damm
|
|
*/
|
|
|
|
#include <linux/sys.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/linkage.h>
|
|
#include <asm/asm-offsets.h>
|
|
#include <asm/suspend.h>
|
|
|
|
/*
|
|
* Kernel mode register usage, see entry.S:
|
|
* k0 scratch
|
|
* k1 scratch
|
|
*/
|
|
#define k0 r0
|
|
#define k1 r1
|
|
|
|
/* manage self-refresh and enter standby mode. must be self-contained.
|
|
* this code will be copied to on-chip memory and executed from there.
|
|
*/
|
|
.balign 4
|
|
ENTRY(sh_mobile_sleep_enter_start)
|
|
|
|
/* save mode flags */
|
|
mov.l r4, @(SH_SLEEP_MODE, r5)
|
|
|
|
/* save original vbr */
|
|
stc vbr, r0
|
|
mov.l r0, @(SH_SLEEP_VBR, r5)
|
|
|
|
/* point vbr to our on-chip memory page */
|
|
ldc r5, vbr
|
|
|
|
/* save return address */
|
|
sts pr, r0
|
|
mov.l r0, @(SH_SLEEP_SPC, r5)
|
|
|
|
/* save sr */
|
|
stc sr, r0
|
|
mov.l r0, @(SH_SLEEP_SR, r5)
|
|
|
|
/* save general purpose registers to stack if needed */
|
|
mov.l @(SH_SLEEP_MODE, r5), r0
|
|
tst #SUSP_SH_REGS, r0
|
|
bt skip_regs_save
|
|
|
|
sts.l pr, @-r15
|
|
mov.l r14, @-r15
|
|
mov.l r13, @-r15
|
|
mov.l r12, @-r15
|
|
mov.l r11, @-r15
|
|
mov.l r10, @-r15
|
|
mov.l r9, @-r15
|
|
mov.l r8, @-r15
|
|
|
|
/* make sure bank0 is selected, save low registers */
|
|
mov.l rb_bit, r9
|
|
not r9, r9
|
|
bsr set_sr
|
|
mov #0, r10
|
|
|
|
bsr save_low_regs
|
|
nop
|
|
|
|
/* switch to bank 1, save low registers */
|
|
mov.l rb_bit, r10
|
|
bsr set_sr
|
|
mov #-1, r9
|
|
|
|
bsr save_low_regs
|
|
nop
|
|
|
|
/* switch back to bank 0 */
|
|
mov.l rb_bit, r9
|
|
not r9, r9
|
|
bsr set_sr
|
|
mov #0, r10
|
|
|
|
skip_regs_save:
|
|
|
|
/* save sp, also set to internal ram */
|
|
mov.l r15, @(SH_SLEEP_SP, r5)
|
|
mov r5, r15
|
|
|
|
/* save stbcr */
|
|
bsr save_register
|
|
mov #SH_SLEEP_REG_STBCR, r0
|
|
|
|
/* save mmu and cache context if needed */
|
|
mov.l @(SH_SLEEP_MODE, r5), r0
|
|
tst #SUSP_SH_MMU, r0
|
|
bt skip_mmu_save_disable
|
|
|
|
/* save mmu state */
|
|
bsr save_register
|
|
mov #SH_SLEEP_REG_PTEH, r0
|
|
|
|
bsr save_register
|
|
mov #SH_SLEEP_REG_PTEL, r0
|
|
|
|
bsr save_register
|
|
mov #SH_SLEEP_REG_TTB, r0
|
|
|
|
bsr save_register
|
|
mov #SH_SLEEP_REG_TEA, r0
|
|
|
|
bsr save_register
|
|
mov #SH_SLEEP_REG_MMUCR, r0
|
|
|
|
bsr save_register
|
|
mov #SH_SLEEP_REG_PTEA, r0
|
|
|
|
bsr save_register
|
|
mov #SH_SLEEP_REG_PASCR, r0
|
|
|
|
bsr save_register
|
|
mov #SH_SLEEP_REG_IRMCR, r0
|
|
|
|
/* invalidate TLBs and disable the MMU */
|
|
bsr get_register
|
|
mov #SH_SLEEP_REG_MMUCR, r0
|
|
mov #4, r1
|
|
mov.l r1, @r0
|
|
icbi @r0
|
|
|
|
/* save cache registers and disable caches */
|
|
bsr save_register
|
|
mov #SH_SLEEP_REG_CCR, r0
|
|
|
|
bsr save_register
|
|
mov #SH_SLEEP_REG_RAMCR, r0
|
|
|
|
bsr get_register
|
|
mov #SH_SLEEP_REG_CCR, r0
|
|
mov #0, r1
|
|
mov.l r1, @r0
|
|
icbi @r0
|
|
|
|
skip_mmu_save_disable:
|
|
/* call self-refresh entering code if needed */
|
|
mov.l @(SH_SLEEP_MODE, r5), r0
|
|
tst #SUSP_SH_SF, r0
|
|
bt skip_set_sf
|
|
|
|
mov.l @(SH_SLEEP_SF_PRE, r5), r0
|
|
jsr @r0
|
|
nop
|
|
|
|
skip_set_sf:
|
|
mov.l @(SH_SLEEP_MODE, r5), r0
|
|
tst #SUSP_SH_STANDBY, r0
|
|
bt test_rstandby
|
|
|
|
/* set mode to "software standby mode" */
|
|
bra do_sleep
|
|
mov #0x80, r1
|
|
|
|
test_rstandby:
|
|
tst #SUSP_SH_RSTANDBY, r0
|
|
bt test_ustandby
|
|
|
|
/* setup BAR register */
|
|
bsr get_register
|
|
mov #SH_SLEEP_REG_BAR, r0
|
|
mov.l @(SH_SLEEP_RESUME, r5), r1
|
|
mov.l r1, @r0
|
|
|
|
/* set mode to "r-standby mode" */
|
|
bra do_sleep
|
|
mov #0x20, r1
|
|
|
|
test_ustandby:
|
|
tst #SUSP_SH_USTANDBY, r0
|
|
bt force_sleep
|
|
|
|
/* set mode to "u-standby mode" */
|
|
bra do_sleep
|
|
mov #0x10, r1
|
|
|
|
force_sleep:
|
|
|
|
/* set mode to "sleep mode" */
|
|
mov #0x00, r1
|
|
|
|
do_sleep:
|
|
/* setup and enter selected standby mode */
|
|
bsr get_register
|
|
mov #SH_SLEEP_REG_STBCR, r0
|
|
mov.l r1, @r0
|
|
again:
|
|
sleep
|
|
bra again
|
|
nop
|
|
|
|
save_register:
|
|
add #SH_SLEEP_BASE_ADDR, r0
|
|
mov.l @(r0, r5), r1
|
|
add #-SH_SLEEP_BASE_ADDR, r0
|
|
mov.l @r1, r1
|
|
add #SH_SLEEP_BASE_DATA, r0
|
|
mov.l r1, @(r0, r5)
|
|
add #-SH_SLEEP_BASE_DATA, r0
|
|
rts
|
|
nop
|
|
|
|
get_register:
|
|
add #SH_SLEEP_BASE_ADDR, r0
|
|
mov.l @(r0, r5), r0
|
|
rts
|
|
nop
|
|
|
|
set_sr:
|
|
stc sr, r8
|
|
and r9, r8
|
|
or r10, r8
|
|
ldc r8, sr
|
|
rts
|
|
nop
|
|
|
|
save_low_regs:
|
|
mov.l r7, @-r15
|
|
mov.l r6, @-r15
|
|
mov.l r5, @-r15
|
|
mov.l r4, @-r15
|
|
mov.l r3, @-r15
|
|
mov.l r2, @-r15
|
|
mov.l r1, @-r15
|
|
rts
|
|
mov.l r0, @-r15
|
|
|
|
.balign 4
|
|
rb_bit: .long 0x20000000 ! RB=1
|
|
|
|
ENTRY(sh_mobile_sleep_enter_end)
|
|
|
|
.balign 4
|
|
ENTRY(sh_mobile_sleep_resume_start)
|
|
|
|
/* figure out start address */
|
|
bsr 0f
|
|
nop
|
|
0:
|
|
sts pr, k1
|
|
mov.l 1f, k0
|
|
and k0, k1
|
|
|
|
/* store pointer to data area in VBR */
|
|
ldc k1, vbr
|
|
|
|
/* setup sr with saved sr */
|
|
mov.l @(SH_SLEEP_SR, k1), k0
|
|
ldc k0, sr
|
|
|
|
/* now: user register set! */
|
|
stc vbr, r5
|
|
|
|
/* setup spc with return address to c code */
|
|
mov.l @(SH_SLEEP_SPC, r5), r0
|
|
ldc r0, spc
|
|
|
|
/* restore vbr */
|
|
mov.l @(SH_SLEEP_VBR, r5), r0
|
|
ldc r0, vbr
|
|
|
|
/* setup ssr with saved sr */
|
|
mov.l @(SH_SLEEP_SR, r5), r0
|
|
ldc r0, ssr
|
|
|
|
/* restore sp */
|
|
mov.l @(SH_SLEEP_SP, r5), r15
|
|
|
|
/* restore sleep mode register */
|
|
bsr restore_register
|
|
mov #SH_SLEEP_REG_STBCR, r0
|
|
|
|
/* call self-refresh resume code if needed */
|
|
mov.l @(SH_SLEEP_MODE, r5), r0
|
|
tst #SUSP_SH_SF, r0
|
|
bt skip_restore_sf
|
|
|
|
mov.l @(SH_SLEEP_SF_POST, r5), r0
|
|
jsr @r0
|
|
nop
|
|
|
|
skip_restore_sf:
|
|
/* restore mmu and cache state if needed */
|
|
mov.l @(SH_SLEEP_MODE, r5), r0
|
|
tst #SUSP_SH_MMU, r0
|
|
bt skip_restore_mmu
|
|
|
|
/* restore mmu state */
|
|
bsr restore_register
|
|
mov #SH_SLEEP_REG_PTEH, r0
|
|
|
|
bsr restore_register
|
|
mov #SH_SLEEP_REG_PTEL, r0
|
|
|
|
bsr restore_register
|
|
mov #SH_SLEEP_REG_TTB, r0
|
|
|
|
bsr restore_register
|
|
mov #SH_SLEEP_REG_TEA, r0
|
|
|
|
bsr restore_register
|
|
mov #SH_SLEEP_REG_PTEA, r0
|
|
|
|
bsr restore_register
|
|
mov #SH_SLEEP_REG_PASCR, r0
|
|
|
|
bsr restore_register
|
|
mov #SH_SLEEP_REG_IRMCR, r0
|
|
|
|
bsr restore_register
|
|
mov #SH_SLEEP_REG_MMUCR, r0
|
|
icbi @r0
|
|
|
|
/* restore cache settings */
|
|
bsr restore_register
|
|
mov #SH_SLEEP_REG_RAMCR, r0
|
|
icbi @r0
|
|
|
|
bsr restore_register
|
|
mov #SH_SLEEP_REG_CCR, r0
|
|
icbi @r0
|
|
|
|
skip_restore_mmu:
|
|
|
|
/* restore general purpose registers if needed */
|
|
mov.l @(SH_SLEEP_MODE, r5), r0
|
|
tst #SUSP_SH_REGS, r0
|
|
bt skip_restore_regs
|
|
|
|
/* switch to bank 1, restore low registers */
|
|
mov.l _rb_bit, r10
|
|
bsr _set_sr
|
|
mov #-1, r9
|
|
|
|
bsr restore_low_regs
|
|
nop
|
|
|
|
/* switch to bank0, restore low registers */
|
|
mov.l _rb_bit, r9
|
|
not r9, r9
|
|
bsr _set_sr
|
|
mov #0, r10
|
|
|
|
bsr restore_low_regs
|
|
nop
|
|
|
|
/* restore the rest of the registers */
|
|
mov.l @r15+, r8
|
|
mov.l @r15+, r9
|
|
mov.l @r15+, r10
|
|
mov.l @r15+, r11
|
|
mov.l @r15+, r12
|
|
mov.l @r15+, r13
|
|
mov.l @r15+, r14
|
|
lds.l @r15+, pr
|
|
|
|
skip_restore_regs:
|
|
rte
|
|
nop
|
|
|
|
restore_register:
|
|
add #SH_SLEEP_BASE_DATA, r0
|
|
mov.l @(r0, r5), r1
|
|
add #-SH_SLEEP_BASE_DATA, r0
|
|
add #SH_SLEEP_BASE_ADDR, r0
|
|
mov.l @(r0, r5), r0
|
|
mov.l r1, @r0
|
|
rts
|
|
nop
|
|
|
|
_set_sr:
|
|
stc sr, r8
|
|
and r9, r8
|
|
or r10, r8
|
|
ldc r8, sr
|
|
rts
|
|
nop
|
|
|
|
restore_low_regs:
|
|
mov.l @r15+, r0
|
|
mov.l @r15+, r1
|
|
mov.l @r15+, r2
|
|
mov.l @r15+, r3
|
|
mov.l @r15+, r4
|
|
mov.l @r15+, r5
|
|
mov.l @r15+, r6
|
|
rts
|
|
mov.l @r15+, r7
|
|
|
|
.balign 4
|
|
_rb_bit: .long 0x20000000 ! RB=1
|
|
1: .long ~0x7ff
|
|
ENTRY(sh_mobile_sleep_resume_end)
|