/*
 * Copyright 2017 NXP
 *
 * Peng Fan <peng.fan@nxp.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <arm.h>
#include <arm32_macros.S>
#include <arm32_macros_cortex_a9.S>
#include <asm.S>
#include <asm-defines.h>
#include <keep.h>
#include <kernel/asan.h>
#include <kernel/unwind.h>
#include <platform_config.h>

.section .text

/*
 * int sm_pm_cpu_suspend(uint32_t arg, int (*fn)(uint32_t))
 * @arg will be passed to fn as argument
 * return value: 0 - cpu resumed from suspended state.
 *		 -1 - cpu not suspended.
 */
FUNC sm_pm_cpu_suspend, :
UNWIND(	.fnstart)
UNWIND(	.cantunwind)
	push	{r4 - r12, lr}
	mov	r5, sp
	sub	sp, sp, #SM_PM_CTX_SIZE
	push	{r0, r1}

	mov	r1, r5
	add	r0, sp, #8
	blx	sm_pm_cpu_suspend_save
	adr	lr, aborted
	/* Jump to arch specific suspend */
	pop	{r0, pc}
aborted:
	/* cpu not suspended */
	add	sp, sp, #SM_PM_CTX_SIZE
	/* Return -1 to the caller */
	mov	r0, #(-1)
suspend_return:
	pop	{r4 - r12, pc}
UNWIND(	.fnend)
END_FUNC sm_pm_cpu_suspend

FUNC sm_pm_cpu_do_suspend, :
UNWIND(	.fnstart)
UNWIND(	.cantunwind)
	push	{r4 - r11}
	read_midr r4
	ubfx	r5, r4, #4, #12
	ldr     r4, =CORTEX_A7_PART_NUM
	cmp	r5, r4
	beq	a7_suspend
	ldr     r4, =CORTEX_A9_PART_NUM
	cmp	r5, r4
	beq	a9_suspend
	/* cpu not supported */
	b	.
	/* A9 needs PCR/DIAG */
a9_suspend:
	read_pcr r4
	read_diag r5
	stmia	r0!, {r4 - r5}
a7_suspend:
	read_fcseidr r4
	read_tpidruro r5
	stmia	r0!, {r4 - r5}
	read_dacr r4
#ifdef CFG_WITH_LPAE
#error "Not supported"
#else
	read_ttbr0 r5
	read_ttbr1 r6
	read_ttbcr r7
#endif
	read_sctlr r8
	read_actlr r9
	read_cpacr r10
	read_mvbar r11
	stmia	r0!, {r4 - r11}
	read_prrr r4
	read_nmrr r5
	read_vbar r6
	read_nsacr r7
	stmia	r0, {r4 - r7}
	pop	{r4 - r11}
	bx	lr
UNWIND(	.fnend)
END_FUNC sm_pm_cpu_do_suspend

FUNC sm_pm_cpu_resume, :
UNWIND(	.fnstart)
UNWIND(	.cantunwind)
	cpsid	aif

	/* Call into the runtime address of get_core_pos */
	adr	r0, _core_pos
	ldr	r1, [r0]
	add	r0, r0, r1
	blx	r0

	/*
	 * At this point, MMU is not enabled now.
	 * 1. Get the runtime physical address of _suspend_sp
	 * 2. Get the offset from _suspend_sp to &thread_core_local
	 * 3. Get the runtime physical address of thread_core_local
	 * Since moving towards non-linear mapping,
	 * `ldr r0, =thread_core_local` is not used here.
	 */
	adr	r4, _suspend_sp
	ldr	r5, [r4]
	add	r4, r4, r5

	mov_imm r1, THREAD_CORE_LOCAL_SIZE
	mla	r0, r0, r1, r4

	ldr	r0, [r0, #THREAD_CORE_LOCAL_SM_PM_CTX_PHYS]
	/* Need to use r0!, because sm_pm_cpu_do_resume needs it */
	ldmia	r0!, {sp, pc}
UNWIND(	.fnend)
END_FUNC sm_pm_cpu_resume

/*
 * The following will be located in text section whose attribute is
 * marked as readonly, but we only need to read here
 * _suspend_sp stores the offset between thread_core_local to _suspend_sp.
 * _core_pos stores the offset between get_core_pos to _core_pos.
 */
.align 2
.extern thread_core_local
_suspend_sp:
	.long	thread_core_local - .
.extern get_core_pos
_core_pos:
	.long	get_core_pos - .

/*
 * void sm_do_cpu_do_resume(paddr suspend_regs) __noreturn;
 * Restore the registers stored when sm_pm_cpu_do_suspend
 * r0 points to the physical base address of the suspend_regs
 * field of struct sm_pm_ctx.
 */
FUNC sm_pm_cpu_do_resume, :
UNWIND(	.fnstart)
UNWIND(	.cantunwind)
	read_midr r4
	ubfx	r5, r4, #4, #12
	ldr     r4, =CORTEX_A7_PART_NUM
	cmp	r5, r4
	beq	a7_resume

	/*
	 * A9 needs PCR/DIAG
	 */
	ldmia   r0!, {r4 - r5}
	write_pcr r4
	write_diag r5

a7_resume:
	/* v7 resume */
	mov	ip, #0
	/* Invalidate icache to PoU */
	write_iciallu
	/* set reserved context */
	write_contextidr ip
	ldmia	r0!, {r4 - r5}
	write_fcseidr r4
	write_tpidruro r5
	ldmia	r0!, {r4 - r11}
	/* Invalidate entire TLB */
	write_tlbiall
	write_dacr r4
#ifdef CFG_WITH_LPAE
#error "Not supported -"
#else
	write_ttbr0 r5
	write_ttbr1 r6
	write_ttbcr r7
#endif

	ldmia	r0, {r4 - r7}
	write_prrr r4
	write_nmrr r5
	write_vbar r6
	write_nsacr r7

	write_actlr r9
	write_cpacr r10
	write_mvbar r11
	write_bpiall
	isb
	dsb
	/* MMU will be enabled here */
	write_sctlr r8
	isb
	mov	r0, #0
	b	suspend_return
UNWIND(	.fnend)
END_FUNC sm_pm_cpu_do_resume

