root/arch/arm/mach-socfpga/self-refresh.S
/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * Copyright (C) 2014-2015 Altera Corporation. All rights reserved.
 */
#include <linux/linkage.h>
#include <asm/assembler.h>

#define MAX_LOOP_COUNT          1000

/* Register offset */
#define SDR_CTRLGRP_LOWPWREQ_ADDR       0x54
#define SDR_CTRLGRP_LOWPWRACK_ADDR      0x58

/* Bitfield positions */
#define SELFRSHREQ_POS                  3
#define SELFRSHREQ_MASK                 0x8

#define SELFRFSHACK_POS                 1
#define SELFRFSHACK_MASK                0x2

        /*
         * This code assumes that when the bootloader configured
         * the sdram controller for the DDR on the board it
         * configured the following fields depending on the DDR
         * vendor/configuration:
         *
         * sdr.ctrlcfg.lowpwreq.selfrfshmask
         * sdr.ctrlcfg.lowpwrtiming.clkdisablecycles
         * sdr.ctrlcfg.dramtiming4.selfrfshexit
         */

        .arch   armv7-a
        .text
        .align 3

        /*
         * socfpga_sdram_self_refresh
         *
         *  r0 : sdr_ctl_base_addr
         *  r1 : temp storage of return value
         *  r2 : temp storage of register values
         *  r3 : loop counter
         *
         *  return value: lower 16 bits: loop count going into self refresh
         *                upper 16 bits: loop count exiting self refresh
         */
ENTRY(socfpga_sdram_self_refresh)
        /* Enable dynamic clock gating in the Power Control Register. */
        mrc     p15, 0, r2, c15, c0, 0
        orr     r2, r2, #1
        mcr     p15, 0, r2, c15, c0, 0

        /* Enable self refresh: set sdr.ctrlgrp.lowpwreq.selfrshreq = 1 */
        ldr     r2, [r0, #SDR_CTRLGRP_LOWPWREQ_ADDR]
        orr     r2, r2, #SELFRSHREQ_MASK
        str     r2, [r0, #SDR_CTRLGRP_LOWPWREQ_ADDR]

        /* Poll until sdr.ctrlgrp.lowpwrack.selfrfshack == 1 or hit max loops */
        mov     r3, #0
while_ack_0:
        ldr     r2, [r0, #SDR_CTRLGRP_LOWPWRACK_ADDR]
        and     r2, r2, #SELFRFSHACK_MASK
        cmp     r2, #SELFRFSHACK_MASK
        beq     ack_1

        add     r3, #1
        cmp     r3, #MAX_LOOP_COUNT
        bne     while_ack_0

ack_1:
        mov     r1, r3

        /*
         * Execute an ISB instruction to ensure that all of the
         * CP15 register changes have been committed.
         */
        isb

        /*
         * Execute a barrier instruction to ensure that all cache,
         * TLB and branch predictor maintenance operations issued
         * by any CPU in the cluster have completed.
         */
        dsb
        dmb

        wfi

        /* Disable self-refresh: set sdr.ctrlgrp.lowpwreq.selfrshreq = 0 */
        ldr     r2, [r0, #SDR_CTRLGRP_LOWPWREQ_ADDR]
        bic     r2, r2, #SELFRSHREQ_MASK
        str     r2, [r0, #SDR_CTRLGRP_LOWPWREQ_ADDR]

        /* Poll until sdr.ctrlgrp.lowpwrack.selfrfshack == 0 or hit max loops */
        mov     r3, #0
while_ack_1:
        ldr     r2, [r0, #SDR_CTRLGRP_LOWPWRACK_ADDR]
        and     r2, r2, #SELFRFSHACK_MASK
        cmp     r2, #SELFRFSHACK_MASK
        bne     ack_0

        add     r3, #1
        cmp     r3, #MAX_LOOP_COUNT
        bne     while_ack_1

ack_0:
        /*
         * Prepare return value:
         * Shift loop count for exiting self refresh into upper 16 bits.
         * Leave loop count for requesting self refresh in lower 16 bits.
         */
        mov     r3, r3, lsl #16
        add     r1, r1, r3

        /* Disable dynamic clock gating in the Power Control Register. */
        mrc     p15, 0, r2, c15, c0, 0
        bic     r2, r2, #1
        mcr     p15, 0, r2, c15, c0, 0

        mov     r0, r1                  @ return value
        bx      lr                      @ return

ENDPROC(socfpga_sdram_self_refresh)
ENTRY(socfpga_sdram_self_refresh_sz)
        .word   . - socfpga_sdram_self_refresh