Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7938e631 authored by Nick Hu's avatar Nick Hu Committed by Greentime Hu
Browse files

nds32: Power management for nds32



There are three sleep states in nds32:
	suspend to idle,
	suspend to standby,
	suspend to ram

In suspend to ram, we use the 'standby' instruction to emulate
power management device to hang the system util wakeup source
send wakeup events to break the loop.

First, we push the general purpose registers and system registers
to stack. Second, we translate stack pointer to physical address
and store to memory to save the stack pointer. Third, after write
back and invalid the cache we hang in 'standby' intruction.
When wakeup source trigger wake up events, the loop will be break
and resume the system.

Signed-off-by: default avatarNick Hu <nickhu@andestech.com>
Acked-by: default avatarPavel Machek <pavel@ucw.cz>
Acked-by: default avatarGreentime Hu <greentime@andestech.com>
Signed-off-by: default avatarGreentime Hu <greentime@andestech.com>
parent cf26edd8
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -93,3 +93,13 @@ endmenu
menu "Kernel Features"
source "kernel/Kconfig.hz"
endmenu

menu "Power management options"
config SYS_SUPPORTS_APM_EMULATION
	bool

config ARCH_SUSPEND_POSSIBLE
	def_bool y

source "kernel/power/Kconfig"
endmenu
+11 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0 */
// Copyright (C) 2008-2017 Andes Technology Corporation

#ifndef __ASM_NDS32_SUSPEND_H
#define __ASM_NDS32_SUSPEND_H

extern void suspend2ram(void);
extern void cpu_resume(void);
extern unsigned long wake_mask;

#endif
+1 −1
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@ obj-$(CONFIG_STACKTRACE) += stacktrace.o
obj-$(CONFIG_OF)		+= devtree.o
obj-$(CONFIG_CACHE_L2)		+= atl2c.o
obj-$(CONFIG_PERF_EVENTS) += perf_event_cpu.o

obj-$(CONFIG_PM)		+= pm.o sleep.o
extra-y := head.o vmlinux.lds

obj-y				+= vdso/

arch/nds32/kernel/pm.c

0 → 100644
+79 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2008-2017 Andes Technology Corporation

#include <linux/init.h>
#include <linux/suspend.h>
#include <linux/device.h>
#include <linux/printk.h>
#include <linux/suspend.h>
#include <asm/suspend.h>
#include <nds32_intrinsic.h>

unsigned int resume_addr;
unsigned int *phy_addr_sp_tmp;

static void nds32_suspend2ram(void)
{
	pgd_t *pgdv;
	pud_t *pudv;
	pmd_t *pmdv;
	pte_t *ptev;

	pgdv = (pgd_t *)__va((__nds32__mfsr(NDS32_SR_L1_PPTB) &
		L1_PPTB_mskBASE)) + pgd_index((unsigned int)cpu_resume);

	pudv = pud_offset(pgdv, (unsigned int)cpu_resume);
	pmdv = pmd_offset(pudv, (unsigned int)cpu_resume);
	ptev = pte_offset_map(pmdv, (unsigned int)cpu_resume);

	resume_addr = ((*ptev) & TLB_DATA_mskPPN)
			| ((unsigned int)cpu_resume & 0x00000fff);

	suspend2ram();
}

static void nds32_suspend_cpu(void)
{
	while (!(__nds32__mfsr(NDS32_SR_INT_PEND) & wake_mask))
		__asm__ volatile ("standby no_wake_grant\n\t");
}

static int nds32_pm_valid(suspend_state_t state)
{
	switch (state) {
	case PM_SUSPEND_ON:
	case PM_SUSPEND_STANDBY:
	case PM_SUSPEND_MEM:
		return 1;
	default:
		return 0;
	}
}

static int nds32_pm_enter(suspend_state_t state)
{
	pr_debug("%s:state:%d\n", __func__, state);
	switch (state) {
	case PM_SUSPEND_STANDBY:
		nds32_suspend_cpu();
		return 0;
	case PM_SUSPEND_MEM:
		nds32_suspend2ram();
		return 0;
	default:
		return -EINVAL;
	}
}

static const struct platform_suspend_ops nds32_pm_ops = {
	.valid = nds32_pm_valid,
	.enter = nds32_pm_enter,
};

static int __init nds32_pm_init(void)
{
	pr_debug("Enter %s\n", __func__);
	suspend_set_ops(&nds32_pm_ops);
	return 0;
}
late_initcall(nds32_pm_init);
+129 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (C) 2017 Andes Technology Corporation */

#include <asm/memory.h>

.data
.global sp_tmp
sp_tmp:
.long

.text
.globl suspend2ram
.globl cpu_resume

suspend2ram:
	pushm   $r0, $r31
#if defined(CONFIG_HWZOL)
	mfusr   $r0, $lc
	mfusr   $r1, $le
	mfusr   $r2, $lb
#endif
	mfsr	$r3, $mr0
	mfsr    $r4, $mr1
	mfsr    $r5, $mr4
	mfsr    $r6, $mr6
	mfsr    $r7, $mr7
	mfsr    $r8, $mr8
	mfsr    $r9, $ir0
	mfsr    $r10, $ir1
	mfsr    $r11, $ir2
	mfsr    $r12, $ir3
	mfsr    $r13, $ir9
	mfsr    $r14, $ir10
	mfsr    $r15, $ir12
	mfsr    $r16, $ir13
	mfsr    $r17, $ir14
	mfsr    $r18, $ir15
	pushm   $r0, $r19

	tlbop	FlushAll
	isb

	// transfer $sp from va to pa
	sethi	$r0, hi20(PAGE_OFFSET)
	ori	$r0, $r0, lo12(PAGE_OFFSET)
	movi	$r2, PHYS_OFFSET
	sub	$r1, $sp, $r0
	add	$r2, $r1, $r2

	// store pa($sp) to sp_tmp
	sethi 	$r1, hi20(sp_tmp)
	swi	$r2, [$r1 + lo12(sp_tmp)]

	pushm	$r16, $r25
	pushm	$r29, $r30
#ifdef	CONFIG_CACHE_L2
	jal	dcache_wb_all_level
#else
	jal	cpu_dcache_wb_all
#endif
	popm	$r29, $r30
	popm	$r16, $r25

	// get wake_mask and loop in standby
	la	$r1, wake_mask
	lwi	$r1, [$r1]
self_loop:
	standby wake_grant
	mfsr	$r2, $ir15
	and	$r2, $r1, $r2
	beqz	$r2, self_loop

	// set ipc to resume address
	la	$r1, resume_addr
	lwi	$r1, [$r1]
	mtsr	$r1, $ipc
	isb

	// reset psw, turn off the address translation
	li      $r2, 0x7000a
	mtsr    $r2, $ipsw
	isb

	iret
cpu_resume:
	// translate the address of sp_tmp variable to pa
	la	$r1, sp_tmp
	sethi   $r0, hi20(PAGE_OFFSET)
	ori     $r0, $r0, lo12(PAGE_OFFSET)
	movi    $r2, PHYS_OFFSET
	sub     $r1, $r1, $r0
	add     $r1, $r1, $r2

	// access the sp_tmp to get stack pointer
	lwi	$sp, [$r1]

	popm	$r0, $r19
#if defined(CONFIG_HWZOL)
	mtusr   $r0, $lb
	mtusr   $r1, $lc
	mtusr   $r2, $le
#endif
	mtsr	$r3, $mr0
	mtsr    $r4, $mr1
	mtsr    $r5, $mr4
	mtsr    $r6, $mr6
	mtsr    $r7, $mr7
	mtsr    $r8, $mr8
	// set original psw to ipsw
	mtsr    $r9, $ir1

	mtsr    $r11, $ir2
	mtsr    $r12, $ir3

	// set ipc to RR
	la	$r13, RR
	mtsr	$r13, $ir9

	mtsr    $r14, $ir10
	mtsr    $r15, $ir12
	mtsr    $r16, $ir13
	mtsr    $r17, $ir14
	mtsr    $r18, $ir15
	popm    $r0, $r31

	isb
	iret
RR:
	ret
Loading