diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig index 77de7a1370f7e0ed2ba378be461fdbc947e4402e..c8619a3c16f8ef0e4cab6229dcc7c2106a1665a1 100644 --- a/arch/loongarch/Kconfig +++ b/arch/loongarch/Kconfig @@ -510,6 +510,14 @@ config ARCH_MMAP_RND_BITS_MAX menu "Power management options" +config ARCH_HIBERNATION_POSSIBLE + def_bool y + +config ARCH_SUSPEND_POSSIBLE + def_bool y + +source "kernel/power/Kconfig" + source "drivers/acpi/Kconfig" endmenu diff --git a/arch/loongarch/Makefile b/arch/loongarch/Makefile index 3ab3625946a900dd68d80b536d7e02a843abbcc1..964b779b130dc3cf75ba014ac7d46b09f1c56f41 100644 --- a/arch/loongarch/Makefile +++ b/arch/loongarch/Makefile @@ -96,9 +96,13 @@ endif head-y := arch/loongarch/kernel/head.o core-y += arch/loongarch/ + libs-y += arch/loongarch/lib/ libs-$(CONFIG_EFI_STUB) += $(objtree)/drivers/firmware/efi/libstub/lib.a +# suspend and hibernation support +drivers-$(CONFIG_PM) += arch/loongarch/power/ + ifeq ($(KBUILD_EXTMOD),) prepare: vdso_prepare vdso_prepare: prepare0 diff --git a/arch/loongarch/configs/loongson3_defconfig b/arch/loongarch/configs/loongson3_defconfig index 3b4b63235fa88964987d906a0dbb817da2854f93..def76987a4c53fe5ee2f5f74610ac9bb7d180d09 100644 --- a/arch/loongarch/configs/loongson3_defconfig +++ b/arch/loongarch/configs/loongson3_defconfig @@ -37,8 +37,8 @@ CONFIG_PERF_EVENTS=y # CONFIG_COMPAT_BRK is not set CONFIG_CPU_HAS_LSX=y CONFIG_CPU_HAS_LASX=y -CONFIG_HOTPLUG_CPU=y CONFIG_NUMA=y +CONFIG_HIBERNATION=y CONFIG_ACPI_SPCR_TABLE=y CONFIG_ACPI_DOCK=y CONFIG_ACPI_IPMI=m diff --git a/arch/loongarch/include/asm/acpi.h b/arch/loongarch/include/asm/acpi.h index 825c2519b9d1f7c0eedabf118f20375b4493604e..31f140d4ef26234dcf89c8ff4d9889005e668507 100644 --- a/arch/loongarch/include/asm/acpi.h +++ b/arch/loongarch/include/asm/acpi.h @@ -7,7 +7,7 @@ #ifndef _ASM_LOONGARCH_ACPI_H #define _ASM_LOONGARCH_ACPI_H - +#include #ifdef CONFIG_ACPI extern int acpi_strict; extern int acpi_disabled; @@ -35,4 +35,10 @@ extern struct list_head acpi_wakeup_device_list; #define ACPI_TABLE_UPGRADE_MAX_PHYS ARCH_LOW_ADDRESS_LIMIT +static inline unsigned long acpi_get_wakeup_address(void) +{ + return (unsigned long)loongarch_wakeup_start; +} +extern int loongarch_acpi_suspend(void); +extern int (*acpi_suspend_lowlevel)(void); #endif /* _ASM_LOONGARCH_ACPI_H */ diff --git a/arch/loongarch/include/asm/bootinfo.h b/arch/loongarch/include/asm/bootinfo.h index 8e5881bc5ad19c57b426f5e0b97a1bcc940a008b..068ea523260e1ec94651b08a1d8f18e73f166872 100644 --- a/arch/loongarch/include/asm/bootinfo.h +++ b/arch/loongarch/include/asm/bootinfo.h @@ -33,6 +33,10 @@ struct loongson_system_configuration { int cores_per_package; unsigned long cores_io_master; const char *cpuname; + u64 suspend_addr; + u64 gpe0_ena_reg; + u8 pcie_wake_enabled; + u8 is_soc_cpu; }; extern u64 efi_system_table; diff --git a/arch/loongarch/include/asm/loongson.h b/arch/loongarch/include/asm/loongson.h index 6e8f6972ceb614c9cd96b3814063006507d71064..e4108f674c4e8ebccfb07a2e880e0d94278b2d76 100644 --- a/arch/loongarch/include/asm/loongson.h +++ b/arch/loongarch/include/asm/loongson.h @@ -70,8 +70,6 @@ static inline void xconf_writeq(u64 val64, volatile void __iomem *addr) #define LS7A_CHIPCFG_REG_BASE (LS7A_PCH_REG_BASE + 0x00010000) /* MISC reg base */ #define LS7A_MISC_REG_BASE (LS7A_PCH_REG_BASE + 0x00080000) -/* ACPI regs */ -#define LS7A_ACPI_REG_BASE (LS7A_MISC_REG_BASE + 0x00050000) /* RTC regs */ #define LS7A_RTC_REG_BASE (LS7A_MISC_REG_BASE + 0x00050100) @@ -93,36 +91,6 @@ static inline void xconf_writeq(u64 val64, volatile void __iomem *addr) #define LS7A_LPC_INT_CLR (volatile void *)TO_UNCACHE(LS7A_PCH_REG_BASE + 0x200c) #define LS7A_LPC_INT_POL (volatile void *)TO_UNCACHE(LS7A_PCH_REG_BASE + 0x2010) -#define LS7A_PMCON_SOC_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x000) -#define LS7A_PMCON_RESUME_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x004) -#define LS7A_PMCON_RTC_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x008) -#define LS7A_PM1_EVT_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x00c) -#define LS7A_PM1_ENA_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x010) -#define LS7A_PM1_CNT_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x014) -#define LS7A_PM1_TMR_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x018) -#define LS7A_P_CNT_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x01c) -#define LS7A_GPE0_STS_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x028) -#define LS7A_GPE0_ENA_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x02c) -#define LS7A_RST_CNT_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x030) -#define LS7A_WD_SET_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x034) -#define LS7A_WD_TIMER_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x038) -#define LS7A_THSENS_CNT_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x04c) -#define LS7A_GEN_RTC_1_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x050) -#define LS7A_GEN_RTC_2_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x054) -#define LS7A_DPM_CFG_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x400) -#define LS7A_DPM_STS_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x404) -#define LS7A_DPM_CNT_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x408) - -typedef enum { - ACPI_PCI_HOTPLUG_STATUS = 1 << 1, - ACPI_CPU_HOTPLUG_STATUS = 1 << 2, - ACPI_MEM_HOTPLUG_STATUS = 1 << 3, - ACPI_POWERBUTTON_STATUS = 1 << 8, - ACPI_RTC_WAKE_STATUS = 1 << 10, - ACPI_PCI_WAKE_STATUS = 1 << 14, - ACPI_ANY_WAKE_STATUS = 1 << 15, -} AcpiEventStatusBits; - #define HT1LO_OFFSET 0xe0000000000UL /* PCI Configuration Space Base */ diff --git a/arch/loongarch/include/asm/suspend.h b/arch/loongarch/include/asm/suspend.h new file mode 100644 index 0000000000000000000000000000000000000000..9c42f0fee92fb735887d87e01696159b8c762ba1 --- /dev/null +++ b/arch/loongarch/include/asm/suspend.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_LOONGARCH_SUSPEND_H +#define _ASM_LOONGARCH_SUSPEND_H + +void arch_common_resume(void); +void arch_common_suspend(void); +extern void loongarch_suspend_enter(void); +extern void loongarch_wakeup_start(void); + +#endif /* _ASM_LOONGARCH_SUSPEND_H */ diff --git a/arch/loongarch/include/asm/time.h b/arch/loongarch/include/asm/time.h index 2eae219301d0a963cbb53747569067b53f93286b..d160d2cf683159b5fb3d708c88aa6fc5ef34d697 100644 --- a/arch/loongarch/include/asm/time.h +++ b/arch/loongarch/include/asm/time.h @@ -13,6 +13,7 @@ extern u64 cpu_clock_freq; extern u64 const_clock_freq; extern void sync_counter(void); +extern void save_counter(void); static inline unsigned int calc_const_freq(void) { diff --git a/arch/loongarch/kernel/Makefile b/arch/loongarch/kernel/Makefile index 0b2aea3d94efbcbe4a291d5af1440f8e6b6ae127..11cc09679b406cc6189ee935b0573b5296a45f1d 100644 --- a/arch/loongarch/kernel/Makefile +++ b/arch/loongarch/kernel/Makefile @@ -7,7 +7,8 @@ extra-y := head.o vmlinux.lds obj-y += cpu-probe.o cacheinfo.o env.o setup.o entry.o genex.o \ traps.o irq.o idle.o process.o dma.o mem.o io.o reset.o switch.o \ - elf.o legacy_boot.o syscall.o signal.o time.o topology.o inst.o ptrace.o vdso.o + elf.o legacy_boot.o syscall.o signal.o time.o topology.o inst.o ptrace.o vdso.o\ + platform.o obj-$(CONFIG_ACPI) += acpi.o obj-$(CONFIG_EFI) += efi.o diff --git a/arch/loongarch/kernel/acpi.c b/arch/loongarch/kernel/acpi.c index 3d9759c3ccb50e94790721723069ef9812e0a144..1a2e4c478c9e4642e78737cfaa98b58522311c38 100644 --- a/arch/loongarch/kernel/acpi.c +++ b/arch/loongarch/kernel/acpi.c @@ -74,6 +74,30 @@ void __init acpi_boot_table_init(void) } } +static int __init acpi_parse_fadt(struct acpi_table_header *table) +{ + u64 gpe0_ena; + + if (acpi_gbl_reduced_hardware) + return 0; + + if (acpi_gbl_FADT.xgpe0_block.space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY) + goto err; + gpe0_ena = acpi_gbl_FADT.xgpe0_block.address + + acpi_gbl_FADT.gpe0_block_length / 2; + if (!gpe0_ena) + goto err; + + loongson_sysconf.gpe0_ena_reg = TO_UNCACHE(gpe0_ena); + + return 0; +err: + pr_err(PREFIX "Invalid BIOS FADT, disabling ACPI\n"); + disable_acpi(); + return -1; +} + + #ifdef CONFIG_SMP int set_processor_mask(u32 id, u32 flags) { @@ -169,6 +193,12 @@ static void __init acpi_process_madt(void) loongson_sysconf.nr_cpus = num_processors; } +#ifdef CONFIG_ACPI_SLEEP +int (*acpi_suspend_lowlevel)(void) = loongarch_acpi_suspend; +#else +int (*acpi_suspend_lowlevel)(void); +#endif + int __init acpi_boot_init(void) { /* @@ -179,6 +209,8 @@ int __init acpi_boot_init(void) loongson_sysconf.boot_cpu_id = read_csr_cpuid(); + acpi_table_parse(ACPI_SIG_FADT, acpi_parse_fadt); + /* * Process the Multiple APIC Description Table (MADT), if present */ diff --git a/arch/loongarch/kernel/asm-offsets.c b/arch/loongarch/kernel/asm-offsets.c index 812b0002dbc861571e5d1a4a5e27598917941adf..8733fc347b3ea412fc3eb6ec06666497b501a0e2 100644 --- a/arch/loongarch/kernel/asm-offsets.c +++ b/arch/loongarch/kernel/asm-offsets.c @@ -257,3 +257,15 @@ void output_smpboot_defines(void) BLANK(); } #endif +#ifdef CONFIG_HIBERNATION +void output_pbe_defines(void) +{ + COMMENT(" Linux struct pbe offsets. "); + OFFSET(PBE_ADDRESS, pbe, address); + OFFSET(PBE_ORIG_ADDRESS, pbe, orig_address); + OFFSET(PBE_NEXT, pbe, next); + DEFINE(PBE_SIZE, sizeof(struct pbe)); + BLANK(); +} +#endif + diff --git a/arch/loongarch/kernel/platform.c b/arch/loongarch/kernel/platform.c new file mode 100644 index 0000000000000000000000000000000000000000..da158221fae1a796e8f8fc004289e4310ea5b60f --- /dev/null +++ b/arch/loongarch/kernel/platform.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2009 Lemote Inc. + * Author: Wu Zhangjin, wuzhangjin@gmail.com + * Copyright (C) 2020 Loongson Technology Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +extern int loongson_acpi_init(void); + +static int __init loongson3_acpi_suspend_init(void) +{ +#ifdef CONFIG_ACPI + acpi_status status; + unsigned long long 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(TO_PHYS(suspend_addr)); +#endif + return 0; +} + +device_initcall(loongson3_acpi_suspend_init); diff --git a/arch/loongarch/kernel/setup.c b/arch/loongarch/kernel/setup.c index eca98e9cca25e1f686b0da87904f026277b2ad29..887ae4a9243801f33e2f8ec99f88836285fc6ea3 100644 --- a/arch/loongarch/kernel/setup.c +++ b/arch/loongarch/kernel/setup.c @@ -198,6 +198,21 @@ static int __init early_parse_mem(char *p) return 0; } early_param("mem", early_parse_mem); +static void __init set_pcie_wakeup(void) +{ + acpi_status status; + u32 value; + + if (loongson_sysconf.is_soc_cpu || acpi_gbl_reduced_hardware) + return; + + status = acpi_read_bit_register(ACPI_BITREG_PCIEXP_WAKE_DISABLE, &value); + if (ACPI_FAILURE(status)) { + return; + } + loongson_sysconf.pcie_wake_enabled = !value; +} + void __init platform_init(void) { @@ -210,6 +225,7 @@ void __init platform_init(void) acpi_boot_table_init(); acpi_boot_init(); #endif + set_pcie_wakeup(); #ifdef CONFIG_NUMA init_numa_memory(); diff --git a/arch/loongarch/kernel/smp.c b/arch/loongarch/kernel/smp.c index 883d8d540f67186dd689800bab920752b3cb599f..7e6dde83e6acbf767978ed14be1fe9b713156e3b 100644 --- a/arch/loongarch/kernel/smp.c +++ b/arch/loongarch/kernel/smp.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include diff --git a/arch/loongarch/kernel/time.c b/arch/loongarch/kernel/time.c index 786735dcc8d678ecc6022ca8e8060a0bd4a4153a..8d331a5fae5a03801a05062204c50850f03334b6 100644 --- a/arch/loongarch/kernel/time.c +++ b/arch/loongarch/kernel/time.c @@ -115,7 +115,12 @@ static unsigned long __init get_loops_per_jiffy(void) return lpj; } -static long init_timeval; +static long init_timeval __nosavedata; + +void save_counter(void) +{ + init_timeval = drdtime(); +} void sync_counter(void) { diff --git a/arch/loongarch/power/Makefile b/arch/loongarch/power/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..a77c31a961783787bf4930bdc01c72fd3f5f8bde --- /dev/null +++ b/arch/loongarch/power/Makefile @@ -0,0 +1,4 @@ +OBJECT_FILES_NON_STANDARD_suspend_asm.o := y + +obj-$(CONFIG_SUSPEND) += suspend.o suspend_asm.o +obj-$(CONFIG_HIBERNATION) += cpu.o hibernate.o hibernate_asm.o diff --git a/arch/loongarch/power/cpu.c b/arch/loongarch/power/cpu.c new file mode 100644 index 0000000000000000000000000000000000000000..e3d8fc1099e2682ce7e2adefe661edd23d15639b --- /dev/null +++ b/arch/loongarch/power/cpu.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Suspend support specific for loongarch. + * + * Licensed under the GPLv2 + * Copyright (C) 2020 Loongson Technology Co., Ltd. + */ +#include +#include + +static u64 saved_crmd; +static u64 saved_prmd; +static u64 saved_euen; +static u64 saved_ecfg; +struct pt_regs saved_regs; + +void save_processor_state(void) +{ + saved_crmd = csr_read32(LOONGARCH_CSR_CRMD); + saved_prmd = csr_read32(LOONGARCH_CSR_PRMD); + saved_euen = csr_read32(LOONGARCH_CSR_EUEN); + saved_ecfg = csr_read32(LOONGARCH_CSR_ECFG); + + if (is_fpu_owner()) + save_fp(current); +} + +void restore_processor_state(void) +{ + csr_write32(saved_crmd, LOONGARCH_CSR_CRMD); + csr_write32(saved_prmd, LOONGARCH_CSR_PRMD); + csr_write32(saved_euen, LOONGARCH_CSR_EUEN); + csr_write32(saved_ecfg, LOONGARCH_CSR_ECFG); + + if (is_fpu_owner()) + restore_fp(current); +} + +int pfn_is_nosave(unsigned long pfn) +{ + unsigned long nosave_begin_pfn = PFN_DOWN(__pa(&__nosave_begin)); + unsigned long nosave_end_pfn = PFN_UP(__pa(&__nosave_end)); + + return (pfn >= nosave_begin_pfn) && (pfn < nosave_end_pfn); +} diff --git a/arch/loongarch/power/hibernate.c b/arch/loongarch/power/hibernate.c new file mode 100644 index 0000000000000000000000000000000000000000..9050225d0d3415cabe486afca8ac45a634a2628d --- /dev/null +++ b/arch/loongarch/power/hibernate.c @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 +#include + +extern int restore_image(void); +extern void enable_pcie_wakeup(void); +extern void swsusp_arch_save(void); + +int swsusp_arch_suspend(void) +{ + enable_pcie_wakeup(); + swsusp_arch_save(); + + return 0; +} + +int swsusp_arch_resume(void) +{ + /* Avoid TLB mismatch during and after kernel resume */ + local_flush_tlb_all(); + return restore_image(); +} diff --git a/arch/loongarch/power/hibernate_asm.S b/arch/loongarch/power/hibernate_asm.S new file mode 100644 index 0000000000000000000000000000000000000000..1874e473b293d734053a7817e578139e3ff1a244 --- /dev/null +++ b/arch/loongarch/power/hibernate_asm.S @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Hibernation support specific for loongarch - temporary page tables + * + * Licensed under the GPLv2 + * + * Copyright (C) 2009 Lemote Inc. + * Author: Hu Hongbing + * Wu Zhangjin + * Copyright (C) 2020 Loongson Technology Co., Ltd. + */ +#include +#include +#include +#include + +.text +SYM_FUNC_START(swsusp_arch_save) + la.abs t0, saved_regs + PTR_S ra, t0, PT_R1 + PTR_S sp, t0, PT_R3 + PTR_S fp, t0, PT_R22 + PTR_S tp, t0, PT_R2 + PTR_S s0, t0, PT_R23 + PTR_S s1, t0, PT_R24 + PTR_S s2, t0, PT_R25 + PTR_S s3, t0, PT_R26 + PTR_S s4, t0, PT_R27 + PTR_S s5, t0, PT_R28 + PTR_S s6, t0, PT_R29 + PTR_S s7, t0, PT_R30 + PTR_S s8, t0, PT_R31 + b swsusp_save +SYM_FUNC_END(swsusp_arch_save) + +SYM_FUNC_START(restore_image) + la.pcrel t0, restore_pblist + PTR_L t0, t0, 0 +0: + PTR_L t1, t0, PBE_ADDRESS /* source */ + PTR_L t2, t0, PBE_ORIG_ADDRESS /* destination */ + PTR_LI t3, _PAGE_SIZE + PTR_ADD t3, t3, t1 +1: + REG_L t8, t1, 0 + REG_S t8, t2, 0 + PTR_ADDI t1, t1, SZREG + PTR_ADDI t2, t2, SZREG + bne t1, t3, 1b + PTR_L t0, t0, PBE_NEXT + bnez t0, 0b + la.pcrel t0, saved_regs + PTR_L ra, t0, PT_R1 + PTR_L sp, t0, PT_R3 + PTR_L fp, t0, PT_R22 + PTR_L tp, t0, PT_R2 + PTR_L s0, t0, PT_R23 + PTR_L s1, t0, PT_R24 + PTR_L s2, t0, PT_R25 + PTR_L s3, t0, PT_R26 + PTR_L s4, t0, PT_R27 + PTR_L s5, t0, PT_R28 + PTR_L s6, t0, PT_R29 + PTR_L s7, t0, PT_R30 + PTR_L s8, t0, PT_R31 + PTR_LI a0, 0x0 + jirl zero, ra, 0 +SYM_FUNC_END(restore_image) diff --git a/arch/loongarch/power/suspend.c b/arch/loongarch/power/suspend.c new file mode 100644 index 0000000000000000000000000000000000000000..3d0b0291e8f0292ebc3f20c707e48d425eb3ee0c --- /dev/null +++ b/arch/loongarch/power/suspend.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * loongson-specific suspend support + * + * Copyright (C) 2020 Loongson Technology Co., Ltd. + * Author: Huacai Chen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +u64 loongarch_suspend_addr; + +extern unsigned long eentry; +extern unsigned long tlbrentry; +struct saved_registers { + u32 ecfg; + u64 pgd; + u64 kpgd; + u32 pwctl0; + u32 pwctl1; + u32 euen; +}; +static struct saved_registers saved_regs; + +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; +} + +void arch_common_resume(void) +{ + sync_counter(); + local_flush_tlb_all(); + csr_write64(per_cpu_offset(0), PERCPU_BASE_KS); + + 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); + csr_write64(eentry, LOONGARCH_CSR_EENTRY); + csr_write64(tlbrentry, LOONGARCH_CSR_TLBRENTRY); + csr_write64(eentry, LOONGARCH_CSR_MERRENTRY); +} + +static void enable_gpe_wakeup(void) +{ + struct list_head *node, *next; + u32 data = 0; + + data = readl((volatile void *)loongson_sysconf.gpe0_ena_reg); + + list_for_each_safe(node, next, &acpi_wakeup_device_list) { + struct acpi_device *dev = + container_of(node, struct acpi_device, wakeup_list); + + if (!dev->wakeup.flags.valid + || ACPI_STATE_S3 > (u32) dev->wakeup.sleep_state + || !(device_may_wakeup(&dev->dev) + || dev->wakeup.prepare_count)) + continue; + + data |= (1 << dev->wakeup.gpe_number); + } + writel(data, (volatile void *)loongson_sysconf.gpe0_ena_reg); +} + +void enable_pcie_wakeup(void) +{ + u16 value; + + if (loongson_sysconf.is_soc_cpu || acpi_gbl_reduced_hardware) + return; + + acpi_write_bit_register(ACPI_BITREG_PCIEXP_WAKE_STATUS, 1); + + if (loongson_sysconf.pcie_wake_enabled) { + acpi_write_bit_register(ACPI_BITREG_PCIEXP_WAKE_DISABLE, 0); + } +} +EXPORT_SYMBOL_GPL(enable_pcie_wakeup); + +int loongarch_acpi_suspend(void) +{ + arch_common_suspend(); + enable_gpe_wakeup(); + enable_pcie_wakeup(); + /* processor specific suspend */ + loongarch_suspend_enter(); + arch_common_resume(); + + return 0; +} + +static int plat_pm_callback(struct notifier_block *nb, unsigned long action, void *ptr) +{ + int ret = 0; + + switch (action) { + case PM_POST_SUSPEND: + enable_gpe_wakeup(); + break; + default: + break; + } + + return notifier_from_errno(ret); +} + +static int __init plat_pm_post_init(void) +{ + if (loongson_sysconf.is_soc_cpu || acpi_gbl_reduced_hardware) + return 0; + + enable_gpe_wakeup(); + pm_notifier(plat_pm_callback, -INT_MAX); + return 0; +} + +late_initcall_sync(plat_pm_post_init); diff --git a/arch/loongarch/power/suspend_asm.S b/arch/loongarch/power/suspend_asm.S new file mode 100644 index 0000000000000000000000000000000000000000..781e38cd35e7e2a1889d69fb80bd1d5889674f7d --- /dev/null +++ b/arch/loongarch/power/suspend_asm.S @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Sleep helper for Loongson-3 sleep mode. + * + * Copyright (C) 2020 Loongson Technology Co., Ltd. + * Author: Huacai Chen + */ + +#include +#include +#include +#include +#include + + .extern loongarch_nr_nodes + .extern loongarch_suspend_addr + .extern loongarch_pcache_ways + .extern loongarch_pcache_sets + .extern loongarch_pcache_linesz + .extern loongarch_scache_ways + .extern loongarch_scache_sets + .extern loongarch_scache_linesz + + .text + .align 5 + +/* 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 $r5, sp, PT_R5 + st.d $r6, sp, PT_R6 + st.d $r7, sp, PT_R7 + st.d $r8, sp, PT_R8 + st.d $r9, sp, PT_R9 + st.d $r10, sp, PT_R10 + st.d $r11, sp, PT_R11 + st.d $r20, sp, PT_R20 + 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 + +#ifdef CONFIG_ACPI + la.pcrel t0, acpi_saved_sp + st.d sp, t0, 0 +#endif +.endm +/* Sleep code for Loongson-3 */ +SYM_CODE_START(loongarch_suspend_enter) + SETUP_SLEEP + bl cpu_flush_caches + + /* Pass RA and SP to BIOS, for machines without CMOS RAM */ + addi.d a1, sp, 0 + la.pcrel a0, loongarch_wakeup_start + + la.pcrel t0, loongarch_suspend_addr + ld.d t0, t0, 0 /* Call BIOS's STR sleep routine */ + jr t0 + nop +SYM_CODE_END(loongarch_suspend_enter) + +.macro SETUP_WAKEUP + + nop + 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 $r5, sp, PT_R5 + ld.d $r6, sp, PT_R6 + ld.d $r7, sp, PT_R7 + ld.d $r8, sp, PT_R8 + ld.d $r9, sp, PT_R9 + ld.d $r10, sp, PT_R10 + ld.d $r11, sp, PT_R11 + ld.d $r20, sp, PT_R20 + 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 + + /* This is where we return upon wakeup. + * Reload all of the registers and return. + */ +SYM_CODE_START(loongarch_wakeup_start) + 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.pcrel t0, acpi_saved_sp + ld.d sp, t0, 0 + SETUP_WAKEUP + addi.d sp, sp, PT_SIZE + jr ra +SYM_CODE_END(loongarch_wakeup_start)