#include <linux/linkage.h>
#include <asm/blackfin.h>
#include <asm/dpmc.h>

#include <asm/context.S>

#define PM_STACK   (COREA_L1_SCRATCH_START + L1_SCRATCH_LENGTH - 12)

.section .l1.text
ENTRY(_enter_hibernate)
	/* switch stack to L1 scratch, prepare for ddr srfr */
	P0.H = HI(PM_STACK);
	P0.L = LO(PM_STACK);
	SP = P0;

	call _bf609_ddr_sr;
	call _bfin_hibernate_syscontrol;

	P0.H = HI(DPM0_RESTORE4);
	P0.L = LO(DPM0_RESTORE4);
	P1.H = _bf609_pm_data;
	P1.L = _bf609_pm_data;
	[P0] = P1;

	P0.H = HI(DPM0_CTL);
	P0.L = LO(DPM0_CTL);
	R3.H = HI(0x00000010);
	R3.L = LO(0x00000010);

	bfin_init_pm_bench_cycles;

	[P0] = R3;

	SSYNC;
ENDPROC(_enter_hibernate)

/* DPM wake up interrupt won't wake up core on bf60x if its core IMASK
 * is disabled. This behavior differ from bf5xx serial processor.
 */
ENTRY(_dummy_deepsleep)
	[--sp] = SYSCFG;
	[--sp] = (R7:0,P5:0);
	cli r0;

	/* get wake up interrupt ID */
	P0.l = LO(SEC_SCI_BASE + SEC_CSID);
	P0.h = HI(SEC_SCI_BASE + SEC_CSID);
	R0 = [P0];

	/* ACK wake up interrupt in SEC */
	P1.l = LO(SEC_END);
	P1.h = HI(SEC_END);

	[P1] = R0;
	SSYNC;

	/* restore EVT 11 entry */
	p0.h = hi(EVT11);
	p0.l = lo(EVT11);
	p1.h = _evt_evt11;
	p1.l = _evt_evt11;

	[p0] = p1;
	SSYNC;

	(R7:0,P5:0) = [sp++];
	SYSCFG = [sp++];
	RTI;
ENDPROC(_dummy_deepsleep)

ENTRY(_enter_deepsleep)
	LINK 0xC;
	[--sp] = (R7:0,P5:0);

	/* Change EVT 11 entry to dummy handler for wake up event */
	p0.h = hi(EVT11);
	p0.l = lo(EVT11);
	p1.h = _dummy_deepsleep;
	p1.l = _dummy_deepsleep;

	[p0] = p1;

	P0.H = HI(PM_STACK);
	P0.L = LO(PM_STACK);

	EX_SCRATCH_REG = SP;
	SP = P0;

	SSYNC;

	/* should put ddr to self refresh mode before sleep */
	call _bf609_ddr_sr;

	/* Set DPM controller to deep sleep mode */
	P0.H = HI(DPM0_CTL);
	P0.L = LO(DPM0_CTL);
	R3.H = HI(0x00000008);
	R3.L = LO(0x00000008);
	[P0] = R3;
	CSYNC;

	/* Enable evt 11 in IMASK before idle, otherwise core doesn't wake up. */
	r0.l = 0x800;
	r0.h = 0;
	sti r0;
	SSYNC;

	bfin_init_pm_bench_cycles;

	/* Fall into deep sleep in idle*/
	idle;
	SSYNC;

	/* Restore PLL after wake up from deep sleep */
	call _bf609_resume_ccbuf;

	/* turn ddr out of self refresh mode */
	call _bf609_ddr_sr_exit;

	SP = EX_SCRATCH_REG;

	(R7:0,P5:0) = [SP++];
	UNLINK;
	RTS;
ENDPROC(_enter_deepsleep)

.section .text
ENTRY(_bf609_hibernate)
	bfin_cpu_reg_save;
	bfin_core_mmr_save;

	P0.H = _bf609_pm_data;
	P0.L = _bf609_pm_data;
	R1.H = 0xDEAD;
	R1.L = 0xBEEF;
	R2.H = .Lpm_resume_here;
	R2.L = .Lpm_resume_here;
	[P0++] = R1;
	[P0++] = R2;
	[P0++] = SP;

	P1.H = _enter_hibernate;
	P1.L = _enter_hibernate;

	call (P1);
.Lpm_resume_here:

	bfin_core_mmr_restore;
	bfin_cpu_reg_restore;

	[--sp] = RETI;  /* Clear Global Interrupt Disable */
	SP += 4;

	RTS;

ENDPROC(_bf609_hibernate)