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