Commit 366bb35a authored by Huacai Chen's avatar Huacai Chen

LoongArch: Add suspend (ACPI S3) support

Add suspend (Suspend To RAM, aka ACPI S3) support for LoongArch.
Signed-off-by: default avatarHuacai Chen <chenhuacai@loongson.cn>
parent 27cab431
...@@ -56,6 +56,7 @@ config LOONGARCH ...@@ -56,6 +56,7 @@ config LOONGARCH
select ARCH_WANTS_NO_INSTR select ARCH_WANTS_NO_INSTR
select BUILDTIME_TABLE_SORT select BUILDTIME_TABLE_SORT
select COMMON_CLK select COMMON_CLK
select CPU_PM
select EFI select EFI
select GENERIC_CLOCKEVENTS select GENERIC_CLOCKEVENTS
select GENERIC_CMOS_UPDATE select GENERIC_CMOS_UPDATE
...@@ -517,6 +518,10 @@ config ARCH_MMAP_RND_BITS_MAX ...@@ -517,6 +518,10 @@ config ARCH_MMAP_RND_BITS_MAX
menu "Power management options" menu "Power management options"
config ARCH_SUSPEND_POSSIBLE
def_bool y
source "kernel/power/Kconfig"
source "drivers/acpi/Kconfig" source "drivers/acpi/Kconfig"
endmenu endmenu
......
...@@ -104,6 +104,9 @@ endif ...@@ -104,6 +104,9 @@ endif
libs-y += arch/loongarch/lib/ libs-y += arch/loongarch/lib/
libs-$(CONFIG_EFI_STUB) += $(objtree)/drivers/firmware/efi/libstub/lib.a libs-$(CONFIG_EFI_STUB) += $(objtree)/drivers/firmware/efi/libstub/lib.a
# suspend and hibernation support
drivers-$(CONFIG_PM) += arch/loongarch/power/
ifeq ($(KBUILD_EXTMOD),) ifeq ($(KBUILD_EXTMOD),)
prepare: vdso_prepare prepare: vdso_prepare
vdso_prepare: prepare0 vdso_prepare: prepare0
......
...@@ -35,4 +35,14 @@ extern struct list_head acpi_wakeup_device_list; ...@@ -35,4 +35,14 @@ extern struct list_head acpi_wakeup_device_list;
#define ACPI_TABLE_UPGRADE_MAX_PHYS ARCH_LOW_ADDRESS_LIMIT #define ACPI_TABLE_UPGRADE_MAX_PHYS ARCH_LOW_ADDRESS_LIMIT
extern int loongarch_acpi_suspend(void);
extern int (*acpi_suspend_lowlevel)(void);
extern void loongarch_suspend_enter(void);
static inline unsigned long acpi_get_wakeup_address(void)
{
extern void loongarch_wakeup_start(void);
return (unsigned long)loongarch_wakeup_start;
}
#endif /* _ASM_LOONGARCH_ACPI_H */ #endif /* _ASM_LOONGARCH_ACPI_H */
...@@ -32,6 +32,7 @@ struct loongson_system_configuration { ...@@ -32,6 +32,7 @@ struct loongson_system_configuration {
int cores_per_node; int cores_per_node;
int cores_per_package; int cores_per_package;
unsigned long cores_io_master; unsigned long cores_io_master;
unsigned long suspend_addr;
const char *cpuname; const char *cpuname;
}; };
......
...@@ -136,4 +136,7 @@ typedef enum { ...@@ -136,4 +136,7 @@ typedef enum {
#define ls7a_writel(val, addr) *(volatile unsigned int *)TO_UNCACHE(addr) = (val) #define ls7a_writel(val, addr) *(volatile unsigned int *)TO_UNCACHE(addr) = (val)
#define ls7a_writeq(val, addr) *(volatile unsigned long *)TO_UNCACHE(addr) = (val) #define ls7a_writeq(val, addr) *(volatile unsigned long *)TO_UNCACHE(addr) = (val)
void enable_gpe_wakeup(void);
void enable_pci_wakeup(void);
#endif /* __ASM_LOONGSON_H */ #endif /* __ASM_LOONGSON_H */
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
extern u64 cpu_clock_freq; extern u64 cpu_clock_freq;
extern u64 const_clock_freq; extern u64 const_clock_freq;
extern void save_counter(void);
extern void sync_counter(void); extern void sync_counter(void);
static inline unsigned int calc_const_freq(void) static inline unsigned int calc_const_freq(void)
......
...@@ -140,6 +140,12 @@ static void __init acpi_process_madt(void) ...@@ -140,6 +140,12 @@ static void __init acpi_process_madt(void)
loongson_sysconf.nr_cpus = num_processors; loongson_sysconf.nr_cpus = num_processors;
} }
#ifndef CONFIG_SUSPEND
int (*acpi_suspend_lowlevel)(void);
#else
int (*acpi_suspend_lowlevel)(void) = loongarch_acpi_suspend;
#endif
void __init acpi_boot_table_init(void) void __init acpi_boot_table_init(void)
{ {
/* /*
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include <linux/smp.h> #include <linux/smp.h>
#include <linux/threads.h> #include <linux/threads.h>
#include <linux/export.h> #include <linux/export.h>
#include <linux/syscore_ops.h>
#include <linux/time.h> #include <linux/time.h>
#include <linux/tracepoint.h> #include <linux/tracepoint.h>
#include <linux/sched/hotplug.h> #include <linux/sched/hotplug.h>
......
...@@ -115,12 +115,17 @@ static unsigned long __init get_loops_per_jiffy(void) ...@@ -115,12 +115,17 @@ static unsigned long __init get_loops_per_jiffy(void)
return lpj; return lpj;
} }
static long init_timeval; static long init_offset __nosavedata;
void save_counter(void)
{
init_offset = drdtime();
}
void sync_counter(void) void sync_counter(void)
{ {
/* Ensure counter begin at 0 */ /* Ensure counter begin at 0 */
csr_write64(-init_timeval, LOONGARCH_CSR_CNTC); csr_write64(init_offset, LOONGARCH_CSR_CNTC);
} }
static int get_timer_irq(void) static int get_timer_irq(void)
...@@ -219,7 +224,7 @@ void __init time_init(void) ...@@ -219,7 +224,7 @@ void __init time_init(void)
else else
const_clock_freq = calc_const_freq(); const_clock_freq = calc_const_freq();
init_timeval = drdtime() - csr_read64(LOONGARCH_CSR_CNTC); init_offset = -(drdtime() - csr_read64(LOONGARCH_CSR_CNTC));
constant_clockevent_init(); constant_clockevent_init();
constant_clocksource_init(); constant_clocksource_init();
......
obj-y += platform.o
obj-$(CONFIG_SUSPEND) += suspend.o suspend_asm.o
// SPDX-License-Identifier: GPL-2.0
/*
* Author: Huacai Chen <chenhuacai@loongson.cn>
* Copyright (C) 2020-2022 Loongson Technology Corporation Limited
*/
#include <linux/acpi.h>
#include <linux/platform_device.h>
#include <asm/bootinfo.h>
#include <asm/loongson.h>
void enable_gpe_wakeup(void)
{
if (acpi_disabled)
return;
if (acpi_gbl_reduced_hardware)
return;
acpi_enable_all_wakeup_gpes();
}
void enable_pci_wakeup(void)
{
if (acpi_disabled)
return;
if (acpi_gbl_reduced_hardware)
return;
acpi_write_bit_register(ACPI_BITREG_PCIEXP_WAKE_STATUS, 1);
if (acpi_gbl_FADT.flags & ACPI_FADT_PCI_EXPRESS_WAKE)
acpi_write_bit_register(ACPI_BITREG_PCIEXP_WAKE_DISABLE, 0);
}
static int __init loongson3_acpi_suspend_init(void)
{
#ifdef CONFIG_ACPI
acpi_status status;
uint64_t suspend_addr = 0;
if (acpi_disabled || acpi_gbl_reduced_hardware)
return 0;
acpi_write_bit_register(ACPI_BITREG_SCI_ENABLE, 1);
status = acpi_evaluate_integer(NULL, "\\SADR", NULL, &suspend_addr);
if (ACPI_FAILURE(status) || !suspend_addr) {
pr_err("ACPI S3 is not support!\n");
return -1;
}
loongson_sysconf.suspend_addr = (u64)phys_to_virt(PHYSADDR(suspend_addr));
#endif
return 0;
}
device_initcall(loongson3_acpi_suspend_init);
// SPDX-License-Identifier: GPL-2.0
/*
* loongson-specific suspend support
*
* Author: Huacai Chen <chenhuacai@loongson.cn>
* Copyright (C) 2020-2022 Loongson Technology Corporation Limited
*/
#include <linux/acpi.h>
#include <linux/pm.h>
#include <linux/suspend.h>
#include <asm/loongarch.h>
#include <asm/loongson.h>
#include <asm/setup.h>
#include <asm/time.h>
#include <asm/tlbflush.h>
u64 loongarch_suspend_addr;
struct saved_registers {
u32 ecfg;
u32 euen;
u64 pgd;
u64 kpgd;
u32 pwctl0;
u32 pwctl1;
};
static struct saved_registers saved_regs;
static void arch_common_suspend(void)
{
save_counter();
saved_regs.pgd = csr_read64(LOONGARCH_CSR_PGDL);
saved_regs.kpgd = csr_read64(LOONGARCH_CSR_PGDH);
saved_regs.pwctl0 = csr_read32(LOONGARCH_CSR_PWCTL0);
saved_regs.pwctl1 = csr_read32(LOONGARCH_CSR_PWCTL1);
saved_regs.ecfg = csr_read32(LOONGARCH_CSR_ECFG);
saved_regs.euen = csr_read32(LOONGARCH_CSR_EUEN);
loongarch_suspend_addr = loongson_sysconf.suspend_addr;
}
static void arch_common_resume(void)
{
sync_counter();
local_flush_tlb_all();
csr_write64(per_cpu_offset(0), PERCPU_BASE_KS);
csr_write64(eentry, LOONGARCH_CSR_EENTRY);
csr_write64(eentry, LOONGARCH_CSR_MERRENTRY);
csr_write64(tlbrentry, LOONGARCH_CSR_TLBRENTRY);
csr_write64(saved_regs.pgd, LOONGARCH_CSR_PGDL);
csr_write64(saved_regs.kpgd, LOONGARCH_CSR_PGDH);
csr_write32(saved_regs.pwctl0, LOONGARCH_CSR_PWCTL0);
csr_write32(saved_regs.pwctl1, LOONGARCH_CSR_PWCTL1);
csr_write32(saved_regs.ecfg, LOONGARCH_CSR_ECFG);
csr_write32(saved_regs.euen, LOONGARCH_CSR_EUEN);
}
int loongarch_acpi_suspend(void)
{
enable_gpe_wakeup();
enable_pci_wakeup();
arch_common_suspend();
/* processor specific suspend */
loongarch_suspend_enter();
arch_common_resume();
return 0;
}
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Sleep helper for Loongson-3 sleep mode.
*
* Author: Huacai Chen <chenhuacai@loongson.cn>
* Copyright (C) 2020-2022 Loongson Technology Corporation Limited
*/
#include <asm/asm.h>
#include <asm/asmmacro.h>
#include <asm/addrspace.h>
#include <asm/loongarch.h>
#include <asm/stackframe.h>
/* preparatory stuff */
.macro SETUP_SLEEP
addi.d sp, sp, -PT_SIZE
st.d $r1, sp, PT_R1
st.d $r2, sp, PT_R2
st.d $r3, sp, PT_R3
st.d $r4, sp, PT_R4
st.d $r21, sp, PT_R21
st.d $r22, sp, PT_R22
st.d $r23, sp, PT_R23
st.d $r24, sp, PT_R24
st.d $r25, sp, PT_R25
st.d $r26, sp, PT_R26
st.d $r27, sp, PT_R27
st.d $r28, sp, PT_R28
st.d $r29, sp, PT_R29
st.d $r30, sp, PT_R30
st.d $r31, sp, PT_R31
la.pcrel t0, acpi_saved_sp
st.d sp, t0, 0
.endm
.macro SETUP_WAKEUP
ld.d $r1, sp, PT_R1
ld.d $r2, sp, PT_R2
ld.d $r3, sp, PT_R3
ld.d $r4, sp, PT_R4
ld.d $r21, sp, PT_R21
ld.d $r22, sp, PT_R22
ld.d $r23, sp, PT_R23
ld.d $r24, sp, PT_R24
ld.d $r25, sp, PT_R25
ld.d $r26, sp, PT_R26
ld.d $r27, sp, PT_R27
ld.d $r28, sp, PT_R28
ld.d $r29, sp, PT_R29
ld.d $r30, sp, PT_R30
ld.d $r31, sp, PT_R31
.endm
.text
.align 12
/* Sleep/wakeup code for Loongson-3 */
SYM_FUNC_START(loongarch_suspend_enter)
SETUP_SLEEP
bl __flush_cache_all
/* Pass RA and SP to BIOS */
addi.d a1, sp, 0
la.pcrel a0, loongarch_wakeup_start
la.pcrel t0, loongarch_suspend_addr
ld.d t0, t0, 0
jirl a0, t0, 0 /* Call BIOS's STR sleep routine */
/*
* This is where we return upon wakeup.
* Reload all of the registers and return.
*/
SYM_INNER_LABEL(loongarch_wakeup_start, SYM_L_GLOBAL)
li.d t0, CSR_DMW0_INIT # UC, PLV0
csrwr t0, LOONGARCH_CSR_DMWIN0
li.d t0, CSR_DMW1_INIT # CA, PLV0
csrwr t0, LOONGARCH_CSR_DMWIN1
la.abs t0, 0f
jr t0
0:
la.pcrel t0, acpi_saved_sp
ld.d sp, t0, 0
SETUP_WAKEUP
addi.d sp, sp, PT_SIZE
jr ra
SYM_FUNC_END(loongarch_suspend_enter)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment