summaryrefslogtreecommitdiffstats
path: root/arch/mips/loongson64
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
commitace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch)
treeb2d64bc10158fdd5497876388cd68142ca374ed3 /arch/mips/loongson64
parentInitial commit. (diff)
downloadlinux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz
linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'arch/mips/loongson64')
-rw-r--r--arch/mips/loongson64/Kconfig15
-rw-r--r--arch/mips/loongson64/Makefile14
-rw-r--r--arch/mips/loongson64/Platform7
-rw-r--r--arch/mips/loongson64/boardinfo.c48
-rw-r--r--arch/mips/loongson64/cop2-ex.c341
-rw-r--r--arch/mips/loongson64/cpucfg-emul.c227
-rw-r--r--arch/mips/loongson64/dma.c28
-rw-r--r--arch/mips/loongson64/env.c231
-rw-r--r--arch/mips/loongson64/hpet.c285
-rw-r--r--arch/mips/loongson64/init.c226
-rw-r--r--arch/mips/loongson64/numa.c208
-rw-r--r--arch/mips/loongson64/pm.c104
-rw-r--r--arch/mips/loongson64/reset.c185
-rw-r--r--arch/mips/loongson64/setup.c23
-rw-r--r--arch/mips/loongson64/smp.c870
-rw-r--r--arch/mips/loongson64/smp.h31
-rw-r--r--arch/mips/loongson64/time.c47
-rw-r--r--arch/mips/loongson64/vbios_quirk.c28
18 files changed, 2918 insertions, 0 deletions
diff --git a/arch/mips/loongson64/Kconfig b/arch/mips/loongson64/Kconfig
new file mode 100644
index 0000000000..517f1f8e81
--- /dev/null
+++ b/arch/mips/loongson64/Kconfig
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0
+if MACH_LOONGSON64
+
+config RS780_HPET
+ bool "RS780/SBX00 HPET Timer"
+ depends on MACH_LOONGSON64
+ depends on BROKEN
+ select MIPS_EXTERNAL_TIMER
+ help
+ This option enables the hpet timer of AMD RS780/SBX00.
+
+ Note: This driver is doing some dangerous hack. Please only enable
+ it on RS780E systems.
+
+endif # MACH_LOONGSON64
diff --git a/arch/mips/loongson64/Makefile b/arch/mips/loongson64/Makefile
new file mode 100644
index 0000000000..e806280bbb
--- /dev/null
+++ b/arch/mips/loongson64/Makefile
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for Loongson-3 family machines
+#
+obj-$(CONFIG_MACH_LOONGSON64) += cop2-ex.o dma.o \
+ setup.o init.o env.o time.o reset.o \
+
+obj-$(CONFIG_SMP) += smp.o
+obj-$(CONFIG_NUMA) += numa.o
+obj-$(CONFIG_RS780_HPET) += hpet.o
+obj-$(CONFIG_SUSPEND) += pm.o
+obj-$(CONFIG_PCI_QUIRKS) += vbios_quirk.o
+obj-$(CONFIG_CPU_LOONGSON3_CPUCFG_EMULATION) += cpucfg-emul.o
+obj-$(CONFIG_SYSFS) += boardinfo.o
diff --git a/arch/mips/loongson64/Platform b/arch/mips/loongson64/Platform
new file mode 100644
index 0000000000..49c9889e3d
--- /dev/null
+++ b/arch/mips/loongson64/Platform
@@ -0,0 +1,7 @@
+#
+# Loongson Machines' Support
+#
+
+cflags-$(CONFIG_MACH_LOONGSON64) += -I$(srctree)/arch/mips/include/asm/mach-loongson64
+cflags-$(CONFIG_CC_HAS_MNO_BRANCH_LIKELY) += -mno-branch-likely
+load-$(CONFIG_CPU_LOONGSON64) += 0xffffffff80200000
diff --git a/arch/mips/loongson64/boardinfo.c b/arch/mips/loongson64/boardinfo.c
new file mode 100644
index 0000000000..280989c5a1
--- /dev/null
+++ b/arch/mips/loongson64/boardinfo.c
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kobject.h>
+#include <boot_param.h>
+
+static ssize_t boardinfo_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ char board_manufacturer[64] = {0};
+ char *tmp_board_manufacturer = board_manufacturer;
+ char bios_vendor[64] = {0};
+ char *tmp_bios_vendor = bios_vendor;
+
+ strcpy(board_manufacturer, eboard->name);
+ strcpy(bios_vendor, einter->description);
+
+ return sprintf(buf,
+ "Board Info\n"
+ "Manufacturer\t\t: %s\n"
+ "Board Name\t\t: %s\n"
+ "Family\t\t\t: LOONGSON3\n\n"
+ "BIOS Info\n"
+ "Vendor\t\t\t: %s\n"
+ "Version\t\t\t: %s\n"
+ "ROM Size\t\t: %d KB\n"
+ "Release Date\t\t: %s\n",
+ strsep(&tmp_board_manufacturer, "-"),
+ eboard->name,
+ strsep(&tmp_bios_vendor, "-"),
+ einter->description,
+ einter->size,
+ especial->special_name);
+}
+static struct kobj_attribute boardinfo_attr = __ATTR(boardinfo, 0444,
+ boardinfo_show, NULL);
+
+static int __init boardinfo_init(void)
+{
+ struct kobject *lefi_kobj;
+
+ lefi_kobj = kobject_create_and_add("lefi", firmware_kobj);
+ if (!lefi_kobj) {
+ pr_err("lefi: Firmware registration failed.\n");
+ return -ENOMEM;
+ }
+
+ return sysfs_create_file(lefi_kobj, &boardinfo_attr.attr);
+}
+late_initcall(boardinfo_init);
diff --git a/arch/mips/loongson64/cop2-ex.c b/arch/mips/loongson64/cop2-ex.c
new file mode 100644
index 0000000000..00055d4b60
--- /dev/null
+++ b/arch/mips/loongson64/cop2-ex.c
@@ -0,0 +1,341 @@
+/*
+ * 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.
+ *
+ * Copyright (C) 2014 Lemote Corporation.
+ * written by Huacai Chen <chenhc@lemote.com>
+ *
+ * based on arch/mips/cavium-octeon/cpu.c
+ * Copyright (C) 2009 Wind River Systems,
+ * written by Ralf Baechle <ralf@linux-mips.org>
+ */
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/notifier.h>
+#include <linux/ptrace.h>
+#include <linux/uaccess.h>
+#include <linux/sched/signal.h>
+
+#include <asm/fpu.h>
+#include <asm/cop2.h>
+#include <asm/inst.h>
+#include <asm/branch.h>
+#include <asm/current.h>
+#include <asm/mipsregs.h>
+#include <asm/unaligned-emul.h>
+
+static int loongson_cu2_call(struct notifier_block *nfb, unsigned long action,
+ void *data)
+{
+ unsigned int res, fpu_owned;
+ unsigned long ra, value, value_next;
+ union mips_instruction insn;
+ int fr = !test_thread_flag(TIF_32BIT_FPREGS);
+ struct pt_regs *regs = (struct pt_regs *)data;
+ void __user *addr = (void __user *)regs->cp0_badvaddr;
+ unsigned int __user *pc = (unsigned int __user *)exception_epc(regs);
+
+ ra = regs->regs[31];
+ __get_user(insn.word, pc);
+
+ switch (action) {
+ case CU2_EXCEPTION:
+ preempt_disable();
+ fpu_owned = __is_fpu_owner();
+ if (!fr)
+ set_c0_status(ST0_CU1 | ST0_CU2);
+ else
+ set_c0_status(ST0_CU1 | ST0_CU2 | ST0_FR);
+ enable_fpu_hazard();
+ KSTK_STATUS(current) |= (ST0_CU1 | ST0_CU2);
+ if (fr)
+ KSTK_STATUS(current) |= ST0_FR;
+ else
+ KSTK_STATUS(current) &= ~ST0_FR;
+ /* If FPU is owned, we needn't init or restore fp */
+ if (!fpu_owned) {
+ set_thread_flag(TIF_USEDFPU);
+ init_fp_ctx(current);
+ _restore_fp(current);
+ }
+ preempt_enable();
+
+ return NOTIFY_STOP; /* Don't call default notifier */
+
+ case CU2_LWC2_OP:
+ if (insn.loongson3_lswc2_format.ls == 0)
+ goto sigbus;
+
+ if (insn.loongson3_lswc2_format.fr == 0) { /* gslq */
+ if (!access_ok(addr, 16))
+ goto sigbus;
+
+ LoadDW(addr, value, res);
+ if (res)
+ goto fault;
+
+ LoadDW(addr + 8, value_next, res);
+ if (res)
+ goto fault;
+
+ regs->regs[insn.loongson3_lswc2_format.rt] = value;
+ regs->regs[insn.loongson3_lswc2_format.rq] = value_next;
+ compute_return_epc(regs);
+ } else { /* gslqc1 */
+ if (!access_ok(addr, 16))
+ goto sigbus;
+
+ lose_fpu(1);
+ LoadDW(addr, value, res);
+ if (res)
+ goto fault;
+
+ LoadDW(addr + 8, value_next, res);
+ if (res)
+ goto fault;
+
+ set_fpr64(&current->thread.fpu.fpr[insn.loongson3_lswc2_format.rt], 0, value);
+ set_fpr64(&current->thread.fpu.fpr[insn.loongson3_lswc2_format.rq], 0, value_next);
+ compute_return_epc(regs);
+ own_fpu(1);
+ }
+ return NOTIFY_STOP; /* Don't call default notifier */
+
+ case CU2_SWC2_OP:
+ if (insn.loongson3_lswc2_format.ls == 0)
+ goto sigbus;
+
+ if (insn.loongson3_lswc2_format.fr == 0) { /* gssq */
+ if (!access_ok(addr, 16))
+ goto sigbus;
+
+ /* write upper 8 bytes first */
+ value_next = regs->regs[insn.loongson3_lswc2_format.rq];
+
+ StoreDW(addr + 8, value_next, res);
+ if (res)
+ goto fault;
+ value = regs->regs[insn.loongson3_lswc2_format.rt];
+
+ StoreDW(addr, value, res);
+ if (res)
+ goto fault;
+
+ compute_return_epc(regs);
+ } else { /* gssqc1 */
+ if (!access_ok(addr, 16))
+ goto sigbus;
+
+ lose_fpu(1);
+ value_next = get_fpr64(&current->thread.fpu.fpr[insn.loongson3_lswc2_format.rq], 0);
+
+ StoreDW(addr + 8, value_next, res);
+ if (res)
+ goto fault;
+
+ value = get_fpr64(&current->thread.fpu.fpr[insn.loongson3_lswc2_format.rt], 0);
+
+ StoreDW(addr, value, res);
+ if (res)
+ goto fault;
+
+ compute_return_epc(regs);
+ own_fpu(1);
+ }
+ return NOTIFY_STOP; /* Don't call default notifier */
+
+ case CU2_LDC2_OP:
+ switch (insn.loongson3_lsdc2_format.opcode1) {
+ /*
+ * Loongson-3 overridden ldc2 instructions.
+ * opcode1 instruction
+ * 0x1 gslhx: load 2 bytes to GPR
+ * 0x2 gslwx: load 4 bytes to GPR
+ * 0x3 gsldx: load 8 bytes to GPR
+ * 0x6 gslwxc1: load 4 bytes to FPR
+ * 0x7 gsldxc1: load 8 bytes to FPR
+ */
+ case 0x1:
+ if (!access_ok(addr, 2))
+ goto sigbus;
+
+ LoadHW(addr, value, res);
+ if (res)
+ goto fault;
+
+ compute_return_epc(regs);
+ regs->regs[insn.loongson3_lsdc2_format.rt] = value;
+ break;
+ case 0x2:
+ if (!access_ok(addr, 4))
+ goto sigbus;
+
+ LoadW(addr, value, res);
+ if (res)
+ goto fault;
+
+ compute_return_epc(regs);
+ regs->regs[insn.loongson3_lsdc2_format.rt] = value;
+ break;
+ case 0x3:
+ if (!access_ok(addr, 8))
+ goto sigbus;
+
+ LoadDW(addr, value, res);
+ if (res)
+ goto fault;
+
+ compute_return_epc(regs);
+ regs->regs[insn.loongson3_lsdc2_format.rt] = value;
+ break;
+ case 0x6:
+ die_if_kernel("Unaligned FP access in kernel code", regs);
+ BUG_ON(!used_math());
+ if (!access_ok(addr, 4))
+ goto sigbus;
+
+ lose_fpu(1);
+ LoadW(addr, value, res);
+ if (res)
+ goto fault;
+
+ set_fpr64(&current->thread.fpu.fpr[insn.loongson3_lsdc2_format.rt], 0, value);
+ compute_return_epc(regs);
+ own_fpu(1);
+
+ break;
+ case 0x7:
+ die_if_kernel("Unaligned FP access in kernel code", regs);
+ BUG_ON(!used_math());
+ if (!access_ok(addr, 8))
+ goto sigbus;
+
+ lose_fpu(1);
+ LoadDW(addr, value, res);
+ if (res)
+ goto fault;
+
+ set_fpr64(&current->thread.fpu.fpr[insn.loongson3_lsdc2_format.rt], 0, value);
+ compute_return_epc(regs);
+ own_fpu(1);
+ break;
+
+ }
+ return NOTIFY_STOP; /* Don't call default notifier */
+
+ case CU2_SDC2_OP:
+ switch (insn.loongson3_lsdc2_format.opcode1) {
+ /*
+ * Loongson-3 overridden sdc2 instructions.
+ * opcode1 instruction
+ * 0x1 gsshx: store 2 bytes from GPR
+ * 0x2 gsswx: store 4 bytes from GPR
+ * 0x3 gssdx: store 8 bytes from GPR
+ * 0x6 gsswxc1: store 4 bytes from FPR
+ * 0x7 gssdxc1: store 8 bytes from FPR
+ */
+ case 0x1:
+ if (!access_ok(addr, 2))
+ goto sigbus;
+
+ compute_return_epc(regs);
+ value = regs->regs[insn.loongson3_lsdc2_format.rt];
+
+ StoreHW(addr, value, res);
+ if (res)
+ goto fault;
+
+ break;
+ case 0x2:
+ if (!access_ok(addr, 4))
+ goto sigbus;
+
+ compute_return_epc(regs);
+ value = regs->regs[insn.loongson3_lsdc2_format.rt];
+
+ StoreW(addr, value, res);
+ if (res)
+ goto fault;
+
+ break;
+ case 0x3:
+ if (!access_ok(addr, 8))
+ goto sigbus;
+
+ compute_return_epc(regs);
+ value = regs->regs[insn.loongson3_lsdc2_format.rt];
+
+ StoreDW(addr, value, res);
+ if (res)
+ goto fault;
+
+ break;
+
+ case 0x6:
+ die_if_kernel("Unaligned FP access in kernel code", regs);
+ BUG_ON(!used_math());
+
+ if (!access_ok(addr, 4))
+ goto sigbus;
+
+ lose_fpu(1);
+ value = get_fpr64(&current->thread.fpu.fpr[insn.loongson3_lsdc2_format.rt], 0);
+
+ StoreW(addr, value, res);
+ if (res)
+ goto fault;
+
+ compute_return_epc(regs);
+ own_fpu(1);
+
+ break;
+ case 0x7:
+ die_if_kernel("Unaligned FP access in kernel code", regs);
+ BUG_ON(!used_math());
+
+ if (!access_ok(addr, 8))
+ goto sigbus;
+
+ lose_fpu(1);
+ value = get_fpr64(&current->thread.fpu.fpr[insn.loongson3_lsdc2_format.rt], 0);
+
+ StoreDW(addr, value, res);
+ if (res)
+ goto fault;
+
+ compute_return_epc(regs);
+ own_fpu(1);
+
+ break;
+ }
+ return NOTIFY_STOP; /* Don't call default notifier */
+ }
+
+ return NOTIFY_OK; /* Let default notifier send signals */
+
+fault:
+ /* roll back jump/branch */
+ regs->regs[31] = ra;
+ regs->cp0_epc = (unsigned long)pc;
+ /* Did we have an exception handler installed? */
+ if (fixup_exception(regs))
+ return NOTIFY_STOP; /* Don't call default notifier */
+
+ die_if_kernel("Unhandled kernel unaligned access", regs);
+ force_sig(SIGSEGV);
+
+ return NOTIFY_STOP; /* Don't call default notifier */
+
+sigbus:
+ die_if_kernel("Unhandled kernel unaligned access", regs);
+ force_sig(SIGBUS);
+
+ return NOTIFY_STOP; /* Don't call default notifier */
+}
+
+static int __init loongson_cu2_setup(void)
+{
+ return cu2_notifier(loongson_cu2_call, 0);
+}
+early_initcall(loongson_cu2_setup);
diff --git a/arch/mips/loongson64/cpucfg-emul.c b/arch/mips/loongson64/cpucfg-emul.c
new file mode 100644
index 0000000000..630927e46d
--- /dev/null
+++ b/arch/mips/loongson64/cpucfg-emul.c
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/smp.h>
+#include <linux/types.h>
+#include <asm/cpu.h>
+#include <asm/cpu-info.h>
+#include <asm/elf.h>
+
+#include <loongson_regs.h>
+#include <cpucfg-emul.h>
+
+static bool is_loongson(struct cpuinfo_mips *c)
+{
+ switch (c->processor_id & PRID_COMP_MASK) {
+ case PRID_COMP_LEGACY:
+ return ((c->processor_id & PRID_IMP_MASK) ==
+ PRID_IMP_LOONGSON_64C);
+
+ case PRID_COMP_LOONGSON:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+static u32 get_loongson_fprev(struct cpuinfo_mips *c)
+{
+ return c->fpu_id & LOONGSON_FPREV_MASK;
+}
+
+static bool cpu_has_uca(void)
+{
+ u32 diag = read_c0_diag();
+ u32 new_diag;
+
+ if (diag & LOONGSON_DIAG_UCAC)
+ /* UCA is already enabled. */
+ return true;
+
+ /* See if UCAC bit can be flipped on. This should be safe. */
+ new_diag = diag | LOONGSON_DIAG_UCAC;
+ write_c0_diag(new_diag);
+ new_diag = read_c0_diag();
+ write_c0_diag(diag);
+
+ return (new_diag & LOONGSON_DIAG_UCAC) != 0;
+}
+
+static void probe_uca(struct cpuinfo_mips *c)
+{
+ if (cpu_has_uca())
+ c->loongson3_cpucfg_data[0] |= LOONGSON_CFG1_LSUCA;
+}
+
+static void decode_loongson_config6(struct cpuinfo_mips *c)
+{
+ u32 config6 = read_c0_config6();
+
+ if (config6 & LOONGSON_CONF6_SFBEN)
+ c->loongson3_cpucfg_data[0] |= LOONGSON_CFG1_SFBP;
+ if (config6 & LOONGSON_CONF6_LLEXC)
+ c->loongson3_cpucfg_data[0] |= LOONGSON_CFG1_LLEXC;
+ if (config6 & LOONGSON_CONF6_SCRAND)
+ c->loongson3_cpucfg_data[0] |= LOONGSON_CFG1_SCRAND;
+}
+
+static void patch_cpucfg_sel1(struct cpuinfo_mips *c)
+{
+ u64 ases = c->ases;
+ u64 options = c->options;
+ u32 data = c->loongson3_cpucfg_data[0];
+
+ if (options & MIPS_CPU_FPU) {
+ data |= LOONGSON_CFG1_FP;
+ data |= get_loongson_fprev(c) << LOONGSON_CFG1_FPREV_OFFSET;
+ }
+ if (ases & MIPS_ASE_LOONGSON_MMI)
+ data |= LOONGSON_CFG1_MMI;
+ if (ases & MIPS_ASE_MSA)
+ data |= LOONGSON_CFG1_MSA1;
+
+ c->loongson3_cpucfg_data[0] = data;
+}
+
+static void patch_cpucfg_sel2(struct cpuinfo_mips *c)
+{
+ u64 ases = c->ases;
+ u64 options = c->options;
+ u32 data = c->loongson3_cpucfg_data[1];
+
+ if (ases & MIPS_ASE_LOONGSON_EXT)
+ data |= LOONGSON_CFG2_LEXT1;
+ if (ases & MIPS_ASE_LOONGSON_EXT2)
+ data |= LOONGSON_CFG2_LEXT2;
+ if (options & MIPS_CPU_LDPTE)
+ data |= LOONGSON_CFG2_LSPW;
+
+ if (ases & MIPS_ASE_VZ)
+ data |= LOONGSON_CFG2_LVZP;
+ else
+ data &= ~LOONGSON_CFG2_LVZREV;
+
+ c->loongson3_cpucfg_data[1] = data;
+}
+
+static void patch_cpucfg_sel3(struct cpuinfo_mips *c)
+{
+ u64 ases = c->ases;
+ u32 data = c->loongson3_cpucfg_data[2];
+
+ if (ases & MIPS_ASE_LOONGSON_CAM) {
+ data |= LOONGSON_CFG3_LCAMP;
+ } else {
+ data &= ~LOONGSON_CFG3_LCAMREV;
+ data &= ~LOONGSON_CFG3_LCAMNUM;
+ data &= ~LOONGSON_CFG3_LCAMKW;
+ data &= ~LOONGSON_CFG3_LCAMVW;
+ }
+
+ c->loongson3_cpucfg_data[2] = data;
+}
+
+void loongson3_cpucfg_synthesize_data(struct cpuinfo_mips *c)
+{
+ /* Only engage the logic on Loongson processors. */
+ if (!is_loongson(c))
+ return;
+
+ /* CPUs with CPUCFG support don't need to synthesize anything. */
+ if (cpu_has_cfg())
+ goto have_cpucfg_now;
+
+ c->loongson3_cpucfg_data[0] = 0;
+ c->loongson3_cpucfg_data[1] = 0;
+ c->loongson3_cpucfg_data[2] = 0;
+
+ /* Add CPUCFG features non-discoverable otherwise. */
+ switch (c->processor_id & (PRID_IMP_MASK | PRID_REV_MASK)) {
+ case PRID_IMP_LOONGSON_64R | PRID_REV_LOONGSON2K_R1_0:
+ case PRID_IMP_LOONGSON_64R | PRID_REV_LOONGSON2K_R1_1:
+ case PRID_IMP_LOONGSON_64R | PRID_REV_LOONGSON2K_R1_2:
+ case PRID_IMP_LOONGSON_64R | PRID_REV_LOONGSON2K_R1_3:
+ decode_loongson_config6(c);
+ probe_uca(c);
+
+ c->loongson3_cpucfg_data[0] |= (LOONGSON_CFG1_LSLDR0 |
+ LOONGSON_CFG1_LSSYNCI | LOONGSON_CFG1_LLSYNC |
+ LOONGSON_CFG1_TGTSYNC);
+ c->loongson3_cpucfg_data[1] |= (LOONGSON_CFG2_LBT1 |
+ LOONGSON_CFG2_LBT2 | LOONGSON_CFG2_LPMP |
+ LOONGSON_CFG2_LPM_REV2);
+ c->loongson3_cpucfg_data[2] = 0;
+ break;
+
+ case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3A_R1:
+ c->loongson3_cpucfg_data[0] |= (LOONGSON_CFG1_LSLDR0 |
+ LOONGSON_CFG1_LSSYNCI | LOONGSON_CFG1_LSUCA |
+ LOONGSON_CFG1_LLSYNC | LOONGSON_CFG1_TGTSYNC);
+ c->loongson3_cpucfg_data[1] |= (LOONGSON_CFG2_LBT1 |
+ LOONGSON_CFG2_LPMP | LOONGSON_CFG2_LPM_REV1);
+ c->loongson3_cpucfg_data[2] |= (
+ LOONGSON_CFG3_LCAM_REV1 |
+ LOONGSON_CFG3_LCAMNUM_REV1 |
+ LOONGSON_CFG3_LCAMKW_REV1 |
+ LOONGSON_CFG3_LCAMVW_REV1);
+ break;
+
+ case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3B_R1:
+ case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3B_R2:
+ c->loongson3_cpucfg_data[0] |= (LOONGSON_CFG1_LSLDR0 |
+ LOONGSON_CFG1_LSSYNCI | LOONGSON_CFG1_LSUCA |
+ LOONGSON_CFG1_LLSYNC | LOONGSON_CFG1_TGTSYNC);
+ c->loongson3_cpucfg_data[1] |= (LOONGSON_CFG2_LBT1 |
+ LOONGSON_CFG2_LPMP | LOONGSON_CFG2_LPM_REV1);
+ c->loongson3_cpucfg_data[2] |= (
+ LOONGSON_CFG3_LCAM_REV1 |
+ LOONGSON_CFG3_LCAMNUM_REV1 |
+ LOONGSON_CFG3_LCAMKW_REV1 |
+ LOONGSON_CFG3_LCAMVW_REV1);
+ break;
+
+ case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3A_R2_0:
+ case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3A_R2_1:
+ case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3A_R3_0:
+ case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3A_R3_1:
+ decode_loongson_config6(c);
+ probe_uca(c);
+
+ c->loongson3_cpucfg_data[0] |= (LOONGSON_CFG1_CNT64 |
+ LOONGSON_CFG1_LSLDR0 | LOONGSON_CFG1_LSPREF |
+ LOONGSON_CFG1_LSPREFX | LOONGSON_CFG1_LSSYNCI |
+ LOONGSON_CFG1_LLSYNC | LOONGSON_CFG1_TGTSYNC);
+ c->loongson3_cpucfg_data[1] |= (LOONGSON_CFG2_LBT1 |
+ LOONGSON_CFG2_LBT2 | LOONGSON_CFG2_LBTMMU |
+ LOONGSON_CFG2_LPMP | LOONGSON_CFG2_LPM_REV1 |
+ LOONGSON_CFG2_LVZ_REV1);
+ c->loongson3_cpucfg_data[2] |= (LOONGSON_CFG3_LCAM_REV1 |
+ LOONGSON_CFG3_LCAMNUM_REV1 |
+ LOONGSON_CFG3_LCAMKW_REV1 |
+ LOONGSON_CFG3_LCAMVW_REV1);
+ break;
+
+ default:
+ /* It is possible that some future Loongson cores still do
+ * not have CPUCFG, so do not emulate anything for these
+ * cores.
+ */
+ return;
+ }
+
+ /* This feature is set by firmware, but all known Loongson-64 systems
+ * are configured this way.
+ */
+ c->loongson3_cpucfg_data[0] |= LOONGSON_CFG1_CDMAP;
+
+ /* Patch in dynamically probed bits. */
+ patch_cpucfg_sel1(c);
+ patch_cpucfg_sel2(c);
+ patch_cpucfg_sel3(c);
+
+have_cpucfg_now:
+ /* We have usable CPUCFG now, emulated or not.
+ * Announce CPUCFG availability to userspace via hwcap.
+ */
+ elf_hwcap |= HWCAP_LOONGSON_CPUCFG;
+}
diff --git a/arch/mips/loongson64/dma.c b/arch/mips/loongson64/dma.c
new file mode 100644
index 0000000000..8220a1bc0d
--- /dev/null
+++ b/arch/mips/loongson64/dma.c
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/dma-direct.h>
+#include <linux/init.h>
+#include <linux/swiotlb.h>
+#include <boot_param.h>
+
+dma_addr_t phys_to_dma(struct device *dev, phys_addr_t paddr)
+{
+ /* We extract 2bit node id (bit 44~47, only bit 44~45 used now) from
+ * Loongson-3's 48bit address space and embed it into 40bit */
+ long nid = (paddr >> 44) & 0x3;
+
+ return ((nid << 44) ^ paddr) | (nid << node_id_offset);
+}
+
+phys_addr_t dma_to_phys(struct device *dev, dma_addr_t daddr)
+{
+ /* We extract 2bit node id (bit 44~47, only bit 44~45 used now) from
+ * Loongson-3's 48bit address space and embed it into 40bit */
+ long nid = (daddr >> node_id_offset) & 0x3;
+
+ return ((nid << node_id_offset) ^ daddr) | (nid << 44);
+}
+
+void __init plat_swiotlb_setup(void)
+{
+ swiotlb_init(true, SWIOTLB_VERBOSE);
+}
diff --git a/arch/mips/loongson64/env.c b/arch/mips/loongson64/env.c
new file mode 100644
index 0000000000..ef3750a6ff
--- /dev/null
+++ b/arch/mips/loongson64/env.c
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Based on Ocelot Linux port, which is
+ * Copyright 2001 MontaVista Software Inc.
+ * Author: jsun@mvista.com or jsun@junsun.net
+ *
+ * Copyright 2003 ICT CAS
+ * Author: Michael Guo <guoyi@ict.ac.cn>
+ *
+ * Copyright (C) 2007 Lemote Inc. & Institute of Computing Technology
+ * Author: Fuxin Zhang, zhangfx@lemote.com
+ *
+ * Copyright (C) 2009 Lemote Inc.
+ * Author: Wu Zhangjin, wuzhangjin@gmail.com
+ */
+
+#include <linux/dma-map-ops.h>
+#include <linux/export.h>
+#include <linux/pci_ids.h>
+#include <asm/bootinfo.h>
+#include <loongson.h>
+#include <boot_param.h>
+#include <builtin_dtbs.h>
+#include <workarounds.h>
+
+#define HOST_BRIDGE_CONFIG_ADDR ((void __iomem *)TO_UNCAC(0x1a000000))
+
+u32 cpu_clock_freq;
+EXPORT_SYMBOL(cpu_clock_freq);
+struct efi_memory_map_loongson *loongson_memmap;
+struct loongson_system_configuration loongson_sysconf;
+
+struct board_devices *eboard;
+struct interface_info *einter;
+struct loongson_special_attribute *especial;
+
+u64 loongson_chipcfg[MAX_PACKAGES] = {0xffffffffbfc00180};
+u64 loongson_chiptemp[MAX_PACKAGES];
+u64 loongson_freqctrl[MAX_PACKAGES];
+
+unsigned long long smp_group[4];
+
+const char *get_system_type(void)
+{
+ return "Generic Loongson64 System";
+}
+
+
+void __init prom_dtb_init_env(void)
+{
+ if ((fw_arg2 < CKSEG0 || fw_arg2 > CKSEG1)
+ && (fw_arg2 < XKPHYS || fw_arg2 > XKSEG))
+
+ loongson_fdt_blob = __dtb_loongson64_2core_2k1000_begin;
+ else
+ loongson_fdt_blob = (void *)fw_arg2;
+}
+
+void __init prom_lefi_init_env(void)
+{
+ struct boot_params *boot_p;
+ struct loongson_params *loongson_p;
+ struct system_loongson *esys;
+ struct efi_cpuinfo_loongson *ecpu;
+ struct irq_source_routing_table *eirq_source;
+ u32 id;
+ u16 vendor;
+
+ /* firmware arguments are initialized in head.S */
+ boot_p = (struct boot_params *)fw_arg2;
+ loongson_p = &(boot_p->efi.smbios.lp);
+
+ esys = (struct system_loongson *)
+ ((u64)loongson_p + loongson_p->system_offset);
+ ecpu = (struct efi_cpuinfo_loongson *)
+ ((u64)loongson_p + loongson_p->cpu_offset);
+ eboard = (struct board_devices *)
+ ((u64)loongson_p + loongson_p->boarddev_table_offset);
+ einter = (struct interface_info *)
+ ((u64)loongson_p + loongson_p->interface_offset);
+ especial = (struct loongson_special_attribute *)
+ ((u64)loongson_p + loongson_p->special_offset);
+ eirq_source = (struct irq_source_routing_table *)
+ ((u64)loongson_p + loongson_p->irq_offset);
+ loongson_memmap = (struct efi_memory_map_loongson *)
+ ((u64)loongson_p + loongson_p->memory_offset);
+
+ cpu_clock_freq = ecpu->cpu_clock_freq;
+ loongson_sysconf.cputype = ecpu->cputype;
+ switch (ecpu->cputype) {
+ case Legacy_3A:
+ case Loongson_3A:
+ loongson_sysconf.cores_per_node = 4;
+ loongson_sysconf.cores_per_package = 4;
+ smp_group[0] = 0x900000003ff01000;
+ smp_group[1] = 0x900010003ff01000;
+ smp_group[2] = 0x900020003ff01000;
+ smp_group[3] = 0x900030003ff01000;
+ loongson_chipcfg[0] = 0x900000001fe00180;
+ loongson_chipcfg[1] = 0x900010001fe00180;
+ loongson_chipcfg[2] = 0x900020001fe00180;
+ loongson_chipcfg[3] = 0x900030001fe00180;
+ loongson_chiptemp[0] = 0x900000001fe0019c;
+ loongson_chiptemp[1] = 0x900010001fe0019c;
+ loongson_chiptemp[2] = 0x900020001fe0019c;
+ loongson_chiptemp[3] = 0x900030001fe0019c;
+ loongson_freqctrl[0] = 0x900000001fe001d0;
+ loongson_freqctrl[1] = 0x900010001fe001d0;
+ loongson_freqctrl[2] = 0x900020001fe001d0;
+ loongson_freqctrl[3] = 0x900030001fe001d0;
+ loongson_sysconf.workarounds = WORKAROUND_CPUFREQ;
+ break;
+ case Legacy_3B:
+ case Loongson_3B:
+ loongson_sysconf.cores_per_node = 4; /* One chip has 2 nodes */
+ loongson_sysconf.cores_per_package = 8;
+ smp_group[0] = 0x900000003ff01000;
+ smp_group[1] = 0x900010003ff05000;
+ smp_group[2] = 0x900020003ff09000;
+ smp_group[3] = 0x900030003ff0d000;
+ loongson_chipcfg[0] = 0x900000001fe00180;
+ loongson_chipcfg[1] = 0x900020001fe00180;
+ loongson_chipcfg[2] = 0x900040001fe00180;
+ loongson_chipcfg[3] = 0x900060001fe00180;
+ loongson_chiptemp[0] = 0x900000001fe0019c;
+ loongson_chiptemp[1] = 0x900020001fe0019c;
+ loongson_chiptemp[2] = 0x900040001fe0019c;
+ loongson_chiptemp[3] = 0x900060001fe0019c;
+ loongson_freqctrl[0] = 0x900000001fe001d0;
+ loongson_freqctrl[1] = 0x900020001fe001d0;
+ loongson_freqctrl[2] = 0x900040001fe001d0;
+ loongson_freqctrl[3] = 0x900060001fe001d0;
+ loongson_sysconf.workarounds = WORKAROUND_CPUHOTPLUG;
+ break;
+ default:
+ loongson_sysconf.cores_per_node = 1;
+ loongson_sysconf.cores_per_package = 1;
+ loongson_chipcfg[0] = 0x900000001fe00180;
+ }
+
+ loongson_sysconf.nr_cpus = ecpu->nr_cpus;
+ loongson_sysconf.boot_cpu_id = ecpu->cpu_startup_core_id;
+ loongson_sysconf.reserved_cpus_mask = ecpu->reserved_cores_mask;
+ if (ecpu->nr_cpus > NR_CPUS || ecpu->nr_cpus == 0)
+ loongson_sysconf.nr_cpus = NR_CPUS;
+ loongson_sysconf.nr_nodes = (loongson_sysconf.nr_cpus +
+ loongson_sysconf.cores_per_node - 1) /
+ loongson_sysconf.cores_per_node;
+
+ loongson_sysconf.dma_mask_bits = eirq_source->dma_mask_bits;
+ if (loongson_sysconf.dma_mask_bits < 32 ||
+ loongson_sysconf.dma_mask_bits > 64) {
+ loongson_sysconf.dma_mask_bits = 32;
+ dma_default_coherent = true;
+ } else {
+ dma_default_coherent = !eirq_source->dma_noncoherent;
+ }
+
+ pr_info("Firmware: Coherent DMA: %s\n", dma_default_coherent ? "on" : "off");
+
+ loongson_sysconf.restart_addr = boot_p->reset_system.ResetWarm;
+ loongson_sysconf.poweroff_addr = boot_p->reset_system.Shutdown;
+ loongson_sysconf.suspend_addr = boot_p->reset_system.DoSuspend;
+
+ loongson_sysconf.vgabios_addr = boot_p->efi.smbios.vga_bios;
+ pr_debug("Shutdown Addr: %llx, Restart Addr: %llx, VBIOS Addr: %llx\n",
+ loongson_sysconf.poweroff_addr, loongson_sysconf.restart_addr,
+ loongson_sysconf.vgabios_addr);
+
+ loongson_sysconf.workarounds |= esys->workarounds;
+
+ pr_info("CpuClock = %u\n", cpu_clock_freq);
+
+ /* Read the ID of PCI host bridge to detect bridge type */
+ id = readl(HOST_BRIDGE_CONFIG_ADDR);
+ vendor = id & 0xffff;
+
+ switch (vendor) {
+ case PCI_VENDOR_ID_LOONGSON:
+ pr_info("The bridge chip is LS7A\n");
+ loongson_sysconf.bridgetype = LS7A;
+ loongson_sysconf.early_config = ls7a_early_config;
+ break;
+ case PCI_VENDOR_ID_AMD:
+ case PCI_VENDOR_ID_ATI:
+ pr_info("The bridge chip is RS780E or SR5690\n");
+ loongson_sysconf.bridgetype = RS780E;
+ loongson_sysconf.early_config = rs780e_early_config;
+ break;
+ default:
+ pr_info("The bridge chip is VIRTUAL\n");
+ loongson_sysconf.bridgetype = VIRTUAL;
+ loongson_sysconf.early_config = virtual_early_config;
+ loongson_fdt_blob = __dtb_loongson64v_4core_virtio_begin;
+ break;
+ }
+
+ if ((read_c0_prid() & PRID_IMP_MASK) == PRID_IMP_LOONGSON_64C) {
+ switch (read_c0_prid() & PRID_REV_MASK) {
+ case PRID_REV_LOONGSON3A_R1:
+ case PRID_REV_LOONGSON3A_R2_0:
+ case PRID_REV_LOONGSON3A_R2_1:
+ case PRID_REV_LOONGSON3A_R3_0:
+ case PRID_REV_LOONGSON3A_R3_1:
+ switch (loongson_sysconf.bridgetype) {
+ case LS7A:
+ loongson_fdt_blob = __dtb_loongson64c_4core_ls7a_begin;
+ break;
+ case RS780E:
+ loongson_fdt_blob = __dtb_loongson64c_4core_rs780e_begin;
+ break;
+ default:
+ break;
+ }
+ break;
+ case PRID_REV_LOONGSON3B_R1:
+ case PRID_REV_LOONGSON3B_R2:
+ if (loongson_sysconf.bridgetype == RS780E)
+ loongson_fdt_blob = __dtb_loongson64c_8core_rs780e_begin;
+ break;
+ default:
+ break;
+ }
+ } else if ((read_c0_prid() & PRID_IMP_MASK) == PRID_IMP_LOONGSON_64G) {
+ if (loongson_sysconf.bridgetype == LS7A)
+ loongson_fdt_blob = __dtb_loongson64g_4core_ls7a_begin;
+ }
+
+ if (!loongson_fdt_blob)
+ pr_err("Failed to determine built-in Loongson64 dtb\n");
+}
diff --git a/arch/mips/loongson64/hpet.c b/arch/mips/loongson64/hpet.c
new file mode 100644
index 0000000000..e428259257
--- /dev/null
+++ b/arch/mips/loongson64/hpet.c
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/percpu.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+
+#include <asm/hpet.h>
+#include <asm/time.h>
+
+#define SMBUS_CFG_BASE (loongson_sysconf.ht_control_base + 0x0300a000)
+#define SMBUS_PCI_REG40 0x40
+#define SMBUS_PCI_REG64 0x64
+#define SMBUS_PCI_REGB4 0xb4
+
+#define HPET_MIN_CYCLES 16
+#define HPET_MIN_PROG_DELTA (HPET_MIN_CYCLES * 12)
+
+static DEFINE_SPINLOCK(hpet_lock);
+DEFINE_PER_CPU(struct clock_event_device, hpet_clockevent_device);
+
+static unsigned int smbus_read(int offset)
+{
+ return *(volatile unsigned int *)(SMBUS_CFG_BASE + offset);
+}
+
+static void smbus_write(int offset, int data)
+{
+ *(volatile unsigned int *)(SMBUS_CFG_BASE + offset) = data;
+}
+
+static void smbus_enable(int offset, int bit)
+{
+ unsigned int cfg = smbus_read(offset);
+
+ cfg |= bit;
+ smbus_write(offset, cfg);
+}
+
+static int hpet_read(int offset)
+{
+ return *(volatile unsigned int *)(HPET_MMIO_ADDR + offset);
+}
+
+static void hpet_write(int offset, int data)
+{
+ *(volatile unsigned int *)(HPET_MMIO_ADDR + offset) = data;
+}
+
+static void hpet_start_counter(void)
+{
+ unsigned int cfg = hpet_read(HPET_CFG);
+
+ cfg |= HPET_CFG_ENABLE;
+ hpet_write(HPET_CFG, cfg);
+}
+
+static void hpet_stop_counter(void)
+{
+ unsigned int cfg = hpet_read(HPET_CFG);
+
+ cfg &= ~HPET_CFG_ENABLE;
+ hpet_write(HPET_CFG, cfg);
+}
+
+static void hpet_reset_counter(void)
+{
+ hpet_write(HPET_COUNTER, 0);
+ hpet_write(HPET_COUNTER + 4, 0);
+}
+
+static void hpet_restart_counter(void)
+{
+ hpet_stop_counter();
+ hpet_reset_counter();
+ hpet_start_counter();
+}
+
+static void hpet_enable_legacy_int(void)
+{
+ /* Do nothing on Loongson-3 */
+}
+
+static int hpet_set_state_periodic(struct clock_event_device *evt)
+{
+ int cfg;
+
+ spin_lock(&hpet_lock);
+
+ pr_info("set clock event to periodic mode!\n");
+ /* stop counter */
+ hpet_stop_counter();
+
+ /* enables the timer0 to generate a periodic interrupt */
+ cfg = hpet_read(HPET_T0_CFG);
+ cfg &= ~HPET_TN_LEVEL;
+ cfg |= HPET_TN_ENABLE | HPET_TN_PERIODIC | HPET_TN_SETVAL |
+ HPET_TN_32BIT;
+ hpet_write(HPET_T0_CFG, cfg);
+
+ /* set the comparator */
+ hpet_write(HPET_T0_CMP, HPET_COMPARE_VAL);
+ udelay(1);
+ hpet_write(HPET_T0_CMP, HPET_COMPARE_VAL);
+
+ /* start counter */
+ hpet_start_counter();
+
+ spin_unlock(&hpet_lock);
+ return 0;
+}
+
+static int hpet_set_state_shutdown(struct clock_event_device *evt)
+{
+ int cfg;
+
+ spin_lock(&hpet_lock);
+
+ cfg = hpet_read(HPET_T0_CFG);
+ cfg &= ~HPET_TN_ENABLE;
+ hpet_write(HPET_T0_CFG, cfg);
+
+ spin_unlock(&hpet_lock);
+ return 0;
+}
+
+static int hpet_set_state_oneshot(struct clock_event_device *evt)
+{
+ int cfg;
+
+ spin_lock(&hpet_lock);
+
+ pr_info("set clock event to one shot mode!\n");
+ cfg = hpet_read(HPET_T0_CFG);
+ /*
+ * set timer0 type
+ * 1 : periodic interrupt
+ * 0 : non-periodic(oneshot) interrupt
+ */
+ cfg &= ~HPET_TN_PERIODIC;
+ cfg |= HPET_TN_ENABLE | HPET_TN_32BIT;
+ hpet_write(HPET_T0_CFG, cfg);
+
+ spin_unlock(&hpet_lock);
+ return 0;
+}
+
+static int hpet_tick_resume(struct clock_event_device *evt)
+{
+ spin_lock(&hpet_lock);
+ hpet_enable_legacy_int();
+ spin_unlock(&hpet_lock);
+
+ return 0;
+}
+
+static int hpet_next_event(unsigned long delta,
+ struct clock_event_device *evt)
+{
+ u32 cnt;
+ s32 res;
+
+ cnt = hpet_read(HPET_COUNTER);
+ cnt += (u32) delta;
+ hpet_write(HPET_T0_CMP, cnt);
+
+ res = (s32)(cnt - hpet_read(HPET_COUNTER));
+
+ return res < HPET_MIN_CYCLES ? -ETIME : 0;
+}
+
+static irqreturn_t hpet_irq_handler(int irq, void *data)
+{
+ int is_irq;
+ struct clock_event_device *cd;
+ unsigned int cpu = smp_processor_id();
+
+ is_irq = hpet_read(HPET_STATUS);
+ if (is_irq & HPET_T0_IRS) {
+ /* clear the TIMER0 irq status register */
+ hpet_write(HPET_STATUS, HPET_T0_IRS);
+ cd = &per_cpu(hpet_clockevent_device, cpu);
+ cd->event_handler(cd);
+ return IRQ_HANDLED;
+ }
+ return IRQ_NONE;
+}
+
+/*
+ * hpet address assignation and irq setting should be done in bios.
+ * but pmon don't do this, we just setup here directly.
+ * The operation under is normal. unfortunately, hpet_setup process
+ * is before pci initialize.
+ *
+ * {
+ * struct pci_dev *pdev;
+ *
+ * pdev = pci_get_device(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS, NULL);
+ * pci_write_config_word(pdev, SMBUS_PCI_REGB4, HPET_ADDR);
+ *
+ * ...
+ * }
+ */
+static void hpet_setup(void)
+{
+ /* set hpet base address */
+ smbus_write(SMBUS_PCI_REGB4, HPET_ADDR);
+
+ /* enable decoding of access to HPET MMIO*/
+ smbus_enable(SMBUS_PCI_REG40, (1 << 28));
+
+ /* HPET irq enable */
+ smbus_enable(SMBUS_PCI_REG64, (1 << 10));
+
+ hpet_enable_legacy_int();
+}
+
+void __init setup_hpet_timer(void)
+{
+ unsigned long flags = IRQF_NOBALANCING | IRQF_TIMER;
+ unsigned int cpu = smp_processor_id();
+ struct clock_event_device *cd;
+
+ hpet_setup();
+
+ cd = &per_cpu(hpet_clockevent_device, cpu);
+ cd->name = "hpet";
+ cd->rating = 100;
+ cd->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
+ cd->set_state_shutdown = hpet_set_state_shutdown;
+ cd->set_state_periodic = hpet_set_state_periodic;
+ cd->set_state_oneshot = hpet_set_state_oneshot;
+ cd->tick_resume = hpet_tick_resume;
+ cd->set_next_event = hpet_next_event;
+ cd->irq = HPET_T0_IRQ;
+ cd->cpumask = cpumask_of(cpu);
+ clockevent_set_clock(cd, HPET_FREQ);
+ cd->max_delta_ns = clockevent_delta2ns(0x7fffffff, cd);
+ cd->max_delta_ticks = 0x7fffffff;
+ cd->min_delta_ns = clockevent_delta2ns(HPET_MIN_PROG_DELTA, cd);
+ cd->min_delta_ticks = HPET_MIN_PROG_DELTA;
+
+ clockevents_register_device(cd);
+ if (request_irq(HPET_T0_IRQ, hpet_irq_handler, flags, "hpet", NULL))
+ pr_err("Failed to request irq %d (hpet)\n", HPET_T0_IRQ);
+ pr_info("hpet clock event device register\n");
+}
+
+static u64 hpet_read_counter(struct clocksource *cs)
+{
+ return (u64)hpet_read(HPET_COUNTER);
+}
+
+static void hpet_suspend(struct clocksource *cs)
+{
+}
+
+static void hpet_resume(struct clocksource *cs)
+{
+ hpet_setup();
+ hpet_restart_counter();
+}
+
+static struct clocksource csrc_hpet = {
+ .name = "hpet",
+ /* mips clocksource rating is less than 300, so hpet is better. */
+ .rating = 300,
+ .read = hpet_read_counter,
+ .mask = CLOCKSOURCE_MASK(32),
+ /* oneshot mode work normal with this flag */
+ .flags = CLOCK_SOURCE_IS_CONTINUOUS,
+ .suspend = hpet_suspend,
+ .resume = hpet_resume,
+ .mult = 0,
+ .shift = 10,
+};
+
+int __init init_hpet_clocksource(void)
+{
+ csrc_hpet.mult = clocksource_hz2mult(HPET_FREQ, csrc_hpet.shift);
+ return clocksource_register_hz(&csrc_hpet, HPET_FREQ);
+}
+
+arch_initcall(init_hpet_clocksource);
diff --git a/arch/mips/loongson64/init.c b/arch/mips/loongson64/init.c
new file mode 100644
index 0000000000..f25caa6aa9
--- /dev/null
+++ b/arch/mips/loongson64/init.c
@@ -0,0 +1,226 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2009 Lemote Inc.
+ * Author: Wu Zhangjin, wuzhangjin@gmail.com
+ */
+
+#include <linux/irqchip.h>
+#include <linux/logic_pio.h>
+#include <linux/memblock.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <asm/bootinfo.h>
+#include <asm/traps.h>
+#include <asm/smp-ops.h>
+#include <asm/cacheflush.h>
+#include <asm/fw/fw.h>
+
+#include <loongson.h>
+#include <boot_param.h>
+
+#define NODE_ID_OFFSET_ADDR ((void __iomem *)TO_UNCAC(0x1001041c))
+
+u32 node_id_offset;
+
+static void __init mips_nmi_setup(void)
+{
+ void *base;
+
+ base = (void *)(CAC_BASE + 0x380);
+ memcpy(base, except_vec_nmi, 0x80);
+ flush_icache_range((unsigned long)base, (unsigned long)base + 0x80);
+}
+
+void ls7a_early_config(void)
+{
+ node_id_offset = ((readl(NODE_ID_OFFSET_ADDR) >> 8) & 0x1f) + 36;
+}
+
+void rs780e_early_config(void)
+{
+ node_id_offset = 37;
+}
+
+void virtual_early_config(void)
+{
+ node_id_offset = 44;
+}
+
+void __init szmem(unsigned int node)
+{
+ u32 i, mem_type;
+ phys_addr_t node_id, mem_start, mem_size;
+
+ /* Otherwise come from DTB */
+ if (loongson_sysconf.fw_interface != LOONGSON_LEFI)
+ return;
+
+ /* Parse memory information and activate */
+ for (i = 0; i < loongson_memmap->nr_map; i++) {
+ node_id = loongson_memmap->map[i].node_id;
+ if (node_id != node)
+ continue;
+
+ mem_type = loongson_memmap->map[i].mem_type;
+ mem_size = loongson_memmap->map[i].mem_size;
+
+ /* Memory size comes in MB if MEM_SIZE_IS_IN_BYTES not set */
+ if (mem_size & MEM_SIZE_IS_IN_BYTES)
+ mem_size &= ~MEM_SIZE_IS_IN_BYTES;
+ else
+ mem_size = mem_size << 20;
+
+ mem_start = (node_id << 44) | loongson_memmap->map[i].mem_start;
+
+ switch (mem_type) {
+ case SYSTEM_RAM_LOW:
+ case SYSTEM_RAM_HIGH:
+ case UMA_VIDEO_RAM:
+ pr_info("Node %d, mem_type:%d\t[%pa], %pa bytes usable\n",
+ (u32)node_id, mem_type, &mem_start, &mem_size);
+ memblock_add_node(mem_start, mem_size, node,
+ MEMBLOCK_NONE);
+ break;
+ case SYSTEM_RAM_RESERVED:
+ case VIDEO_ROM:
+ case ADAPTER_ROM:
+ case ACPI_TABLE:
+ case SMBIOS_TABLE:
+ pr_info("Node %d, mem_type:%d\t[%pa], %pa bytes reserved\n",
+ (u32)node_id, mem_type, &mem_start, &mem_size);
+ memblock_reserve(mem_start, mem_size);
+ break;
+ /* We should not reserve VUMA_VIDEO_RAM as it overlaps with MMIO */
+ case VUMA_VIDEO_RAM:
+ default:
+ pr_info("Node %d, mem_type:%d\t[%pa], %pa bytes unhandled\n",
+ (u32)node_id, mem_type, &mem_start, &mem_size);
+ break;
+ }
+ }
+
+ /* Reserve vgabios if it comes from firmware */
+ if (loongson_sysconf.vgabios_addr)
+ memblock_reserve(virt_to_phys((void *)loongson_sysconf.vgabios_addr),
+ SZ_256K);
+}
+
+#ifndef CONFIG_NUMA
+static void __init prom_init_memory(void)
+{
+ szmem(0);
+}
+#endif
+
+void __init prom_init(void)
+{
+ fw_init_cmdline();
+
+ if (fw_arg2 == 0 || (fdt_magic(fw_arg2) == FDT_MAGIC)) {
+ loongson_sysconf.fw_interface = LOONGSON_DTB;
+ prom_dtb_init_env();
+ } else {
+ loongson_sysconf.fw_interface = LOONGSON_LEFI;
+ prom_lefi_init_env();
+ }
+
+ /* init base address of io space */
+ set_io_port_base(PCI_IOBASE);
+
+ if (loongson_sysconf.early_config)
+ loongson_sysconf.early_config();
+
+#ifdef CONFIG_NUMA
+ prom_init_numa_memory();
+#else
+ prom_init_memory();
+#endif
+
+ /* Hardcode to CPU UART 0 */
+ if ((read_c0_prid() & PRID_IMP_MASK) == PRID_IMP_LOONGSON_64R)
+ setup_8250_early_printk_port(TO_UNCAC(LOONGSON_REG_BASE), 0, 1024);
+ else
+ setup_8250_early_printk_port(TO_UNCAC(LOONGSON_REG_BASE + 0x1e0), 0, 1024);
+
+ register_smp_ops(&loongson3_smp_ops);
+ board_nmi_handler_setup = mips_nmi_setup;
+}
+
+static int __init add_legacy_isa_io(struct fwnode_handle *fwnode, resource_size_t hw_start,
+ resource_size_t size)
+{
+ int ret = 0;
+ struct logic_pio_hwaddr *range;
+ unsigned long vaddr;
+
+ range = kzalloc(sizeof(*range), GFP_ATOMIC);
+ if (!range)
+ return -ENOMEM;
+
+ range->fwnode = fwnode;
+ range->size = size = round_up(size, PAGE_SIZE);
+ range->hw_start = hw_start;
+ range->flags = LOGIC_PIO_CPU_MMIO;
+
+ ret = logic_pio_register_range(range);
+ if (ret) {
+ kfree(range);
+ return ret;
+ }
+
+ /* Legacy ISA must placed at the start of PCI_IOBASE */
+ if (range->io_start != 0) {
+ logic_pio_unregister_range(range);
+ kfree(range);
+ return -EINVAL;
+ }
+
+ vaddr = PCI_IOBASE + range->io_start;
+
+ ioremap_page_range(vaddr, vaddr + size, hw_start, pgprot_device(PAGE_KERNEL));
+
+ return 0;
+}
+
+static __init void reserve_pio_range(void)
+{
+ struct device_node *np;
+
+ for_each_node_by_name(np, "isa") {
+ struct of_range range;
+ struct of_range_parser parser;
+
+ pr_info("ISA Bridge: %pOF\n", np);
+
+ if (of_range_parser_init(&parser, np)) {
+ pr_info("Failed to parse resources.\n");
+ of_node_put(np);
+ break;
+ }
+
+ for_each_of_range(&parser, &range) {
+ switch (range.flags & IORESOURCE_TYPE_BITS) {
+ case IORESOURCE_IO:
+ pr_info(" IO 0x%016llx..0x%016llx -> 0x%016llx\n",
+ range.cpu_addr,
+ range.cpu_addr + range.size - 1,
+ range.bus_addr);
+ if (add_legacy_isa_io(&np->fwnode, range.cpu_addr, range.size))
+ pr_warn("Failed to reserve legacy IO in Logic PIO\n");
+ break;
+ case IORESOURCE_MEM:
+ pr_info(" MEM 0x%016llx..0x%016llx -> 0x%016llx\n",
+ range.cpu_addr,
+ range.cpu_addr + range.size - 1,
+ range.bus_addr);
+ break;
+ }
+ }
+ }
+}
+
+void __init arch_init_irq(void)
+{
+ reserve_pio_range();
+ irqchip_init();
+}
diff --git a/arch/mips/loongson64/numa.c b/arch/mips/loongson64/numa.c
new file mode 100644
index 0000000000..8f61e93c0c
--- /dev/null
+++ b/arch/mips/loongson64/numa.c
@@ -0,0 +1,208 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2010 Loongson Inc. & Lemote Inc. &
+ * Institute of Computing Technology
+ * Author: Xiang Gao, gaoxiang@ict.ac.cn
+ * Huacai Chen, chenhc@lemote.com
+ * Xiaofu Meng, Shuangshuang Zhang
+ */
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/mmzone.h>
+#include <linux/export.h>
+#include <linux/nodemask.h>
+#include <linux/swap.h>
+#include <linux/memblock.h>
+#include <linux/pfn.h>
+#include <linux/highmem.h>
+#include <asm/page.h>
+#include <asm/pgalloc.h>
+#include <asm/sections.h>
+#include <linux/irq.h>
+#include <asm/bootinfo.h>
+#include <asm/mc146818-time.h>
+#include <asm/time.h>
+#include <asm/wbflush.h>
+#include <boot_param.h>
+#include <loongson.h>
+
+unsigned char __node_distances[MAX_NUMNODES][MAX_NUMNODES];
+EXPORT_SYMBOL(__node_distances);
+struct pglist_data *__node_data[MAX_NUMNODES];
+EXPORT_SYMBOL(__node_data);
+
+cpumask_t __node_cpumask[MAX_NUMNODES];
+EXPORT_SYMBOL(__node_cpumask);
+
+static void cpu_node_probe(void)
+{
+ int i;
+
+ nodes_clear(node_possible_map);
+ nodes_clear(node_online_map);
+ for (i = 0; i < loongson_sysconf.nr_nodes; i++) {
+ node_set_state(num_online_nodes(), N_POSSIBLE);
+ node_set_online(num_online_nodes());
+ }
+
+ pr_info("NUMA: Discovered %d cpus on %d nodes\n",
+ loongson_sysconf.nr_cpus, num_online_nodes());
+}
+
+static int __init compute_node_distance(int row, int col)
+{
+ int package_row = row * loongson_sysconf.cores_per_node /
+ loongson_sysconf.cores_per_package;
+ int package_col = col * loongson_sysconf.cores_per_node /
+ loongson_sysconf.cores_per_package;
+
+ if (col == row)
+ return LOCAL_DISTANCE;
+ else if (package_row == package_col)
+ return 40;
+ else
+ return 100;
+}
+
+static void __init init_topology_matrix(void)
+{
+ int row, col;
+
+ for (row = 0; row < MAX_NUMNODES; row++)
+ for (col = 0; col < MAX_NUMNODES; col++)
+ __node_distances[row][col] = -1;
+
+ for_each_online_node(row) {
+ for_each_online_node(col) {
+ __node_distances[row][col] =
+ compute_node_distance(row, col);
+ }
+ }
+}
+
+static void __init node_mem_init(unsigned int node)
+{
+ struct pglist_data *nd;
+ unsigned long node_addrspace_offset;
+ unsigned long start_pfn, end_pfn;
+ unsigned long nd_pa;
+ int tnid;
+ const size_t nd_size = roundup(sizeof(pg_data_t), SMP_CACHE_BYTES);
+
+ node_addrspace_offset = nid_to_addrbase(node);
+ pr_info("Node%d's addrspace_offset is 0x%lx\n",
+ node, node_addrspace_offset);
+
+ get_pfn_range_for_nid(node, &start_pfn, &end_pfn);
+ pr_info("Node%d: start_pfn=0x%lx, end_pfn=0x%lx\n",
+ node, start_pfn, end_pfn);
+
+ nd_pa = memblock_phys_alloc_try_nid(nd_size, SMP_CACHE_BYTES, node);
+ if (!nd_pa)
+ panic("Cannot allocate %zu bytes for node %d data\n",
+ nd_size, node);
+ nd = __va(nd_pa);
+ memset(nd, 0, sizeof(struct pglist_data));
+ tnid = early_pfn_to_nid(nd_pa >> PAGE_SHIFT);
+ if (tnid != node)
+ pr_info("NODE_DATA(%d) on node %d\n", node, tnid);
+ __node_data[node] = nd;
+ NODE_DATA(node)->node_start_pfn = start_pfn;
+ NODE_DATA(node)->node_spanned_pages = end_pfn - start_pfn;
+
+ if (node == 0) {
+ /* kernel start address */
+ unsigned long kernel_start_pfn = PFN_DOWN(__pa_symbol(&_text));
+
+ /* kernel end address */
+ unsigned long kernel_end_pfn = PFN_UP(__pa_symbol(&_end));
+
+ /* used by finalize_initrd() */
+ max_low_pfn = end_pfn;
+
+ /* Reserve the kernel text/data/bss */
+ memblock_reserve(kernel_start_pfn << PAGE_SHIFT,
+ ((kernel_end_pfn - kernel_start_pfn) << PAGE_SHIFT));
+
+ /* Reserve 0xfe000000~0xffffffff for RS780E integrated GPU */
+ if (node_end_pfn(0) >= (0xffffffff >> PAGE_SHIFT))
+ memblock_reserve((node_addrspace_offset | 0xfe000000),
+ 32 << 20);
+
+ /* Reserve pfn range 0~node[0]->node_start_pfn */
+ memblock_reserve(0, PAGE_SIZE * start_pfn);
+ }
+}
+
+static __init void prom_meminit(void)
+{
+ unsigned int node, cpu, active_cpu = 0;
+
+ cpu_node_probe();
+ init_topology_matrix();
+
+ for (node = 0; node < loongson_sysconf.nr_nodes; node++) {
+ if (node_online(node)) {
+ szmem(node);
+ node_mem_init(node);
+ cpumask_clear(&__node_cpumask[node]);
+ }
+ }
+ max_low_pfn = PHYS_PFN(memblock_end_of_DRAM());
+
+ for (cpu = 0; cpu < loongson_sysconf.nr_cpus; cpu++) {
+ node = cpu / loongson_sysconf.cores_per_node;
+ if (node >= num_online_nodes())
+ node = 0;
+
+ if (loongson_sysconf.reserved_cpus_mask & (1<<cpu))
+ continue;
+
+ cpumask_set_cpu(active_cpu, &__node_cpumask[node]);
+ pr_info("NUMA: set cpumask cpu %d on node %d\n", active_cpu, node);
+
+ active_cpu++;
+ }
+}
+
+void __init paging_init(void)
+{
+ unsigned long zones_size[MAX_NR_ZONES] = {0, };
+
+ pagetable_init();
+ zones_size[ZONE_DMA32] = MAX_DMA32_PFN;
+ zones_size[ZONE_NORMAL] = max_low_pfn;
+ free_area_init(zones_size);
+}
+
+void __init mem_init(void)
+{
+ high_memory = (void *) __va(get_num_physpages() << PAGE_SHIFT);
+ memblock_free_all();
+ setup_zero_pages(); /* This comes from node 0 */
+}
+
+/* All PCI device belongs to logical Node-0 */
+int pcibus_to_node(struct pci_bus *bus)
+{
+ return 0;
+}
+EXPORT_SYMBOL(pcibus_to_node);
+
+void __init prom_init_numa_memory(void)
+{
+ pr_info("CP0_Config3: CP0 16.3 (0x%x)\n", read_c0_config3());
+ pr_info("CP0_PageGrain: CP0 5.1 (0x%x)\n", read_c0_pagegrain());
+ prom_meminit();
+}
+
+pg_data_t * __init arch_alloc_nodedata(int nid)
+{
+ return memblock_alloc(sizeof(pg_data_t), SMP_CACHE_BYTES);
+}
+
+void arch_refresh_nodedata(int nid, pg_data_t *pgdat)
+{
+ __node_data[nid] = pgdat;
+}
diff --git a/arch/mips/loongson64/pm.c b/arch/mips/loongson64/pm.c
new file mode 100644
index 0000000000..7c8556f097
--- /dev/null
+++ b/arch/mips/loongson64/pm.c
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * loongson-specific suspend support
+ *
+ * Copyright (C) 2009 Lemote Inc.
+ * Author: Wu Zhangjin <wuzhangjin@gmail.com>
+ */
+#include <linux/suspend.h>
+#include <linux/interrupt.h>
+#include <linux/pm.h>
+
+#include <asm/i8259.h>
+#include <asm/mipsregs.h>
+
+#include <loongson.h>
+
+static unsigned int __maybe_unused cached_master_mask; /* i8259A */
+static unsigned int __maybe_unused cached_slave_mask;
+static unsigned int __maybe_unused cached_bonito_irq_mask; /* bonito */
+
+void arch_suspend_disable_irqs(void)
+{
+ /* disable all mips events */
+ local_irq_disable();
+
+#ifdef CONFIG_I8259
+ /* disable all events of i8259A */
+ cached_slave_mask = inb(PIC_SLAVE_IMR);
+ cached_master_mask = inb(PIC_MASTER_IMR);
+
+ outb(0xff, PIC_SLAVE_IMR);
+ inb(PIC_SLAVE_IMR);
+ outb(0xff, PIC_MASTER_IMR);
+ inb(PIC_MASTER_IMR);
+#endif
+ /* disable all events of bonito */
+ cached_bonito_irq_mask = LOONGSON_INTEN;
+ LOONGSON_INTENCLR = 0xffff;
+ (void)LOONGSON_INTENCLR;
+}
+
+void arch_suspend_enable_irqs(void)
+{
+ /* enable all mips events */
+ local_irq_enable();
+#ifdef CONFIG_I8259
+ /* only enable the cached events of i8259A */
+ outb(cached_slave_mask, PIC_SLAVE_IMR);
+ outb(cached_master_mask, PIC_MASTER_IMR);
+#endif
+ /* enable all cached events of bonito */
+ LOONGSON_INTENSET = cached_bonito_irq_mask;
+ (void)LOONGSON_INTENSET;
+}
+
+/*
+ * Setup the board-specific events for waking up loongson from wait mode
+ */
+void __weak setup_wakeup_events(void)
+{
+}
+
+void __weak mach_suspend(void)
+{
+}
+
+void __weak mach_resume(void)
+{
+}
+
+static int loongson_pm_enter(suspend_state_t state)
+{
+ mach_suspend();
+
+ mach_resume();
+
+ return 0;
+}
+
+static int loongson_pm_valid_state(suspend_state_t state)
+{
+ switch (state) {
+ case PM_SUSPEND_ON:
+ case PM_SUSPEND_STANDBY:
+ case PM_SUSPEND_MEM:
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
+static const struct platform_suspend_ops loongson_pm_ops = {
+ .valid = loongson_pm_valid_state,
+ .enter = loongson_pm_enter,
+};
+
+static int __init loongson_pm_init(void)
+{
+ suspend_set_ops(&loongson_pm_ops);
+
+ return 0;
+}
+arch_initcall(loongson_pm_init);
diff --git a/arch/mips/loongson64/reset.c b/arch/mips/loongson64/reset.c
new file mode 100644
index 0000000000..e420800043
--- /dev/null
+++ b/arch/mips/loongson64/reset.c
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ * Copyright (C) 2007 Lemote, Inc. & Institute of Computing Technology
+ * Author: Fuxin Zhang, zhangfx@lemote.com
+ * Copyright (C) 2009 Lemote, Inc.
+ * Author: Zhangjin Wu, wuzhangjin@gmail.com
+ */
+#include <linux/cpu.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/kexec.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+
+#include <asm/bootinfo.h>
+#include <asm/idle.h>
+#include <asm/reboot.h>
+#include <asm/bug.h>
+
+#include <loongson.h>
+#include <boot_param.h>
+
+static void loongson_restart(char *command)
+{
+
+ void (*fw_restart)(void) = (void *)loongson_sysconf.restart_addr;
+
+ fw_restart();
+ while (1) {
+ if (cpu_wait)
+ cpu_wait();
+ }
+}
+
+static void loongson_poweroff(void)
+{
+ void (*fw_poweroff)(void) = (void *)loongson_sysconf.poweroff_addr;
+
+ fw_poweroff();
+ while (1) {
+ if (cpu_wait)
+ cpu_wait();
+ }
+}
+
+static void loongson_halt(void)
+{
+ pr_notice("\n\n** You can safely turn off the power now **\n\n");
+ while (1) {
+ if (cpu_wait)
+ cpu_wait();
+ }
+}
+
+#ifdef CONFIG_KEXEC
+
+/* 0X80000000~0X80200000 is safe */
+#define MAX_ARGS 64
+#define KEXEC_CTRL_CODE 0xFFFFFFFF80100000UL
+#define KEXEC_ARGV_ADDR 0xFFFFFFFF80108000UL
+#define KEXEC_ARGV_SIZE COMMAND_LINE_SIZE
+#define KEXEC_ENVP_SIZE 4800
+
+static int kexec_argc;
+static int kdump_argc;
+static void *kexec_argv;
+static void *kdump_argv;
+static void *kexec_envp;
+
+static int loongson_kexec_prepare(struct kimage *image)
+{
+ int i, argc = 0;
+ unsigned int *argv;
+ char *str, *ptr, *bootloader = "kexec";
+
+ /* argv at offset 0, argv[] at offset KEXEC_ARGV_SIZE/2 */
+ if (image->type == KEXEC_TYPE_DEFAULT)
+ argv = (unsigned int *)kexec_argv;
+ else
+ argv = (unsigned int *)kdump_argv;
+
+ argv[argc++] = (unsigned int)(KEXEC_ARGV_ADDR + KEXEC_ARGV_SIZE/2);
+
+ for (i = 0; i < image->nr_segments; i++) {
+ if (!strncmp(bootloader, (char *)image->segment[i].buf,
+ strlen(bootloader))) {
+ /*
+ * convert command line string to array
+ * of parameters (as bootloader does).
+ */
+ int offt;
+ str = (char *)argv + KEXEC_ARGV_SIZE/2;
+ memcpy(str, image->segment[i].buf, KEXEC_ARGV_SIZE/2);
+ ptr = strchr(str, ' ');
+
+ while (ptr && (argc < MAX_ARGS)) {
+ *ptr = '\0';
+ if (ptr[1] != ' ') {
+ offt = (int)(ptr - str + 1);
+ argv[argc] = KEXEC_ARGV_ADDR + KEXEC_ARGV_SIZE/2 + offt;
+ argc++;
+ }
+ ptr = strchr(ptr + 1, ' ');
+ }
+ break;
+ }
+ }
+
+ if (image->type == KEXEC_TYPE_DEFAULT)
+ kexec_argc = argc;
+ else
+ kdump_argc = argc;
+
+ /* kexec/kdump need a safe page to save reboot_code_buffer */
+ image->control_code_page = virt_to_page((void *)KEXEC_CTRL_CODE);
+
+ return 0;
+}
+
+static void loongson_kexec_shutdown(void)
+{
+#ifdef CONFIG_SMP
+ int cpu;
+
+ /* All CPUs go to reboot_code_buffer */
+ for_each_possible_cpu(cpu)
+ if (!cpu_online(cpu))
+ cpu_device_up(get_cpu_device(cpu));
+
+ secondary_kexec_args[0] = TO_UNCAC(0x3ff01000);
+#endif
+ kexec_args[0] = kexec_argc;
+ kexec_args[1] = fw_arg1;
+ kexec_args[2] = fw_arg2;
+ memcpy((void *)fw_arg1, kexec_argv, KEXEC_ARGV_SIZE);
+ memcpy((void *)fw_arg2, kexec_envp, KEXEC_ENVP_SIZE);
+}
+
+static void loongson_crash_shutdown(struct pt_regs *regs)
+{
+ default_machine_crash_shutdown(regs);
+ kexec_args[0] = kdump_argc;
+ kexec_args[1] = fw_arg1;
+ kexec_args[2] = fw_arg2;
+#ifdef CONFIG_SMP
+ secondary_kexec_args[0] = TO_UNCAC(0x3ff01000);
+#endif
+ memcpy((void *)fw_arg1, kdump_argv, KEXEC_ARGV_SIZE);
+ memcpy((void *)fw_arg2, kexec_envp, KEXEC_ENVP_SIZE);
+}
+
+#endif
+
+static int __init mips_reboot_setup(void)
+{
+ _machine_restart = loongson_restart;
+ _machine_halt = loongson_halt;
+ pm_power_off = loongson_poweroff;
+
+#ifdef CONFIG_KEXEC
+ kexec_argv = kmalloc(KEXEC_ARGV_SIZE, GFP_KERNEL);
+ if (WARN_ON(!kexec_argv))
+ return -ENOMEM;
+
+ kdump_argv = kmalloc(KEXEC_ARGV_SIZE, GFP_KERNEL);
+ if (WARN_ON(!kdump_argv))
+ return -ENOMEM;
+
+ kexec_envp = kmalloc(KEXEC_ENVP_SIZE, GFP_KERNEL);
+ if (WARN_ON(!kexec_envp))
+ return -ENOMEM;
+
+ fw_arg1 = KEXEC_ARGV_ADDR;
+ memcpy(kexec_envp, (void *)fw_arg2, KEXEC_ENVP_SIZE);
+
+ _machine_kexec_prepare = loongson_kexec_prepare;
+ _machine_kexec_shutdown = loongson_kexec_shutdown;
+ _machine_crash_shutdown = loongson_crash_shutdown;
+#endif
+
+ return 0;
+}
+
+arch_initcall(mips_reboot_setup);
diff --git a/arch/mips/loongson64/setup.c b/arch/mips/loongson64/setup.c
new file mode 100644
index 0000000000..257038e187
--- /dev/null
+++ b/arch/mips/loongson64/setup.c
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Lemote Inc. & Institute of Computing Technology
+ * Author: Fuxin Zhang, zhangfx@lemote.com
+ */
+#include <linux/export.h>
+#include <linux/init.h>
+
+#include <asm/bootinfo.h>
+#include <linux/libfdt.h>
+#include <linux/of_fdt.h>
+
+#include <asm/prom.h>
+
+#include <loongson.h>
+
+void *loongson_fdt_blob;
+
+void __init plat_mem_setup(void)
+{
+ if (loongson_fdt_blob)
+ __dt_setup_arch(loongson_fdt_blob);
+}
diff --git a/arch/mips/loongson64/smp.c b/arch/mips/loongson64/smp.c
new file mode 100644
index 0000000000..e015a26a40
--- /dev/null
+++ b/arch/mips/loongson64/smp.c
@@ -0,0 +1,870 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2010, 2011, 2012, Lemote, Inc.
+ * Author: Chen Huacai, chenhc@lemote.com
+ */
+
+#include <irq.h>
+#include <linux/init.h>
+#include <linux/cpu.h>
+#include <linux/sched.h>
+#include <linux/sched/hotplug.h>
+#include <linux/sched/task_stack.h>
+#include <linux/smp.h>
+#include <linux/cpufreq.h>
+#include <linux/kexec.h>
+#include <asm/processor.h>
+#include <asm/smp.h>
+#include <asm/time.h>
+#include <asm/tlbflush.h>
+#include <asm/cacheflush.h>
+#include <loongson.h>
+#include <loongson_regs.h>
+#include <workarounds.h>
+
+#include "smp.h"
+
+DEFINE_PER_CPU(int, cpu_state);
+
+#define LS_IPI_IRQ (MIPS_CPU_IRQ_BASE + 6)
+
+static void __iomem *ipi_set0_regs[16];
+static void __iomem *ipi_clear0_regs[16];
+static void __iomem *ipi_status0_regs[16];
+static void __iomem *ipi_en0_regs[16];
+static void __iomem *ipi_mailbox_buf[16];
+static uint32_t core0_c0count[NR_CPUS];
+
+static u32 (*ipi_read_clear)(int cpu);
+static void (*ipi_write_action)(int cpu, u32 action);
+static void (*ipi_write_enable)(int cpu);
+static void (*ipi_clear_buf)(int cpu);
+static void (*ipi_write_buf)(int cpu, struct task_struct *idle);
+
+/* send mail via Mail_Send register for 3A4000+ CPU */
+static void csr_mail_send(uint64_t data, int cpu, int mailbox)
+{
+ uint64_t val;
+
+ /* send high 32 bits */
+ val = CSR_MAIL_SEND_BLOCK;
+ val |= (CSR_MAIL_SEND_BOX_HIGH(mailbox) << CSR_MAIL_SEND_BOX_SHIFT);
+ val |= (cpu << CSR_MAIL_SEND_CPU_SHIFT);
+ val |= (data & CSR_MAIL_SEND_H32_MASK);
+ csr_writeq(val, LOONGSON_CSR_MAIL_SEND);
+
+ /* send low 32 bits */
+ val = CSR_MAIL_SEND_BLOCK;
+ val |= (CSR_MAIL_SEND_BOX_LOW(mailbox) << CSR_MAIL_SEND_BOX_SHIFT);
+ val |= (cpu << CSR_MAIL_SEND_CPU_SHIFT);
+ val |= (data << CSR_MAIL_SEND_BUF_SHIFT);
+ csr_writeq(val, LOONGSON_CSR_MAIL_SEND);
+};
+
+static u32 csr_ipi_read_clear(int cpu)
+{
+ u32 action;
+
+ /* Load the ipi register to figure out what we're supposed to do */
+ action = csr_readl(LOONGSON_CSR_IPI_STATUS);
+ /* Clear the ipi register to clear the interrupt */
+ csr_writel(action, LOONGSON_CSR_IPI_CLEAR);
+
+ return action;
+}
+
+static void csr_ipi_write_action(int cpu, u32 action)
+{
+ unsigned int irq = 0;
+
+ while ((irq = ffs(action))) {
+ uint32_t val = CSR_IPI_SEND_BLOCK;
+ val |= (irq - 1);
+ val |= (cpu << CSR_IPI_SEND_CPU_SHIFT);
+ csr_writel(val, LOONGSON_CSR_IPI_SEND);
+ action &= ~BIT(irq - 1);
+ }
+}
+
+static void csr_ipi_write_enable(int cpu)
+{
+ csr_writel(0xffffffff, LOONGSON_CSR_IPI_EN);
+}
+
+static void csr_ipi_clear_buf(int cpu)
+{
+ csr_writeq(0, LOONGSON_CSR_MAIL_BUF0);
+}
+
+static void csr_ipi_write_buf(int cpu, struct task_struct *idle)
+{
+ unsigned long startargs[4];
+
+ /* startargs[] are initial PC, SP and GP for secondary CPU */
+ startargs[0] = (unsigned long)&smp_bootstrap;
+ startargs[1] = (unsigned long)__KSTK_TOS(idle);
+ startargs[2] = (unsigned long)task_thread_info(idle);
+ startargs[3] = 0;
+
+ pr_debug("CPU#%d, func_pc=%lx, sp=%lx, gp=%lx\n",
+ cpu, startargs[0], startargs[1], startargs[2]);
+
+ csr_mail_send(startargs[3], cpu_logical_map(cpu), 3);
+ csr_mail_send(startargs[2], cpu_logical_map(cpu), 2);
+ csr_mail_send(startargs[1], cpu_logical_map(cpu), 1);
+ csr_mail_send(startargs[0], cpu_logical_map(cpu), 0);
+}
+
+static u32 legacy_ipi_read_clear(int cpu)
+{
+ u32 action;
+
+ /* Load the ipi register to figure out what we're supposed to do */
+ action = readl_relaxed(ipi_status0_regs[cpu_logical_map(cpu)]);
+ /* Clear the ipi register to clear the interrupt */
+ writel_relaxed(action, ipi_clear0_regs[cpu_logical_map(cpu)]);
+ nudge_writes();
+
+ return action;
+}
+
+static void legacy_ipi_write_action(int cpu, u32 action)
+{
+ writel_relaxed((u32)action, ipi_set0_regs[cpu]);
+ nudge_writes();
+}
+
+static void legacy_ipi_write_enable(int cpu)
+{
+ writel_relaxed(0xffffffff, ipi_en0_regs[cpu_logical_map(cpu)]);
+}
+
+static void legacy_ipi_clear_buf(int cpu)
+{
+ writeq_relaxed(0, ipi_mailbox_buf[cpu_logical_map(cpu)] + 0x0);
+}
+
+static void legacy_ipi_write_buf(int cpu, struct task_struct *idle)
+{
+ unsigned long startargs[4];
+
+ /* startargs[] are initial PC, SP and GP for secondary CPU */
+ startargs[0] = (unsigned long)&smp_bootstrap;
+ startargs[1] = (unsigned long)__KSTK_TOS(idle);
+ startargs[2] = (unsigned long)task_thread_info(idle);
+ startargs[3] = 0;
+
+ pr_debug("CPU#%d, func_pc=%lx, sp=%lx, gp=%lx\n",
+ cpu, startargs[0], startargs[1], startargs[2]);
+
+ writeq_relaxed(startargs[3],
+ ipi_mailbox_buf[cpu_logical_map(cpu)] + 0x18);
+ writeq_relaxed(startargs[2],
+ ipi_mailbox_buf[cpu_logical_map(cpu)] + 0x10);
+ writeq_relaxed(startargs[1],
+ ipi_mailbox_buf[cpu_logical_map(cpu)] + 0x8);
+ writeq_relaxed(startargs[0],
+ ipi_mailbox_buf[cpu_logical_map(cpu)] + 0x0);
+ nudge_writes();
+}
+
+static void csr_ipi_probe(void)
+{
+ if (cpu_has_csr() && csr_readl(LOONGSON_CSR_FEATURES) & LOONGSON_CSRF_IPI) {
+ ipi_read_clear = csr_ipi_read_clear;
+ ipi_write_action = csr_ipi_write_action;
+ ipi_write_enable = csr_ipi_write_enable;
+ ipi_clear_buf = csr_ipi_clear_buf;
+ ipi_write_buf = csr_ipi_write_buf;
+ } else {
+ ipi_read_clear = legacy_ipi_read_clear;
+ ipi_write_action = legacy_ipi_write_action;
+ ipi_write_enable = legacy_ipi_write_enable;
+ ipi_clear_buf = legacy_ipi_clear_buf;
+ ipi_write_buf = legacy_ipi_write_buf;
+ }
+}
+
+static void ipi_set0_regs_init(void)
+{
+ ipi_set0_regs[0] = (void __iomem *)
+ (SMP_CORE_GROUP0_BASE + SMP_CORE0_OFFSET + SET0);
+ ipi_set0_regs[1] = (void __iomem *)
+ (SMP_CORE_GROUP0_BASE + SMP_CORE1_OFFSET + SET0);
+ ipi_set0_regs[2] = (void __iomem *)
+ (SMP_CORE_GROUP0_BASE + SMP_CORE2_OFFSET + SET0);
+ ipi_set0_regs[3] = (void __iomem *)
+ (SMP_CORE_GROUP0_BASE + SMP_CORE3_OFFSET + SET0);
+ ipi_set0_regs[4] = (void __iomem *)
+ (SMP_CORE_GROUP1_BASE + SMP_CORE0_OFFSET + SET0);
+ ipi_set0_regs[5] = (void __iomem *)
+ (SMP_CORE_GROUP1_BASE + SMP_CORE1_OFFSET + SET0);
+ ipi_set0_regs[6] = (void __iomem *)
+ (SMP_CORE_GROUP1_BASE + SMP_CORE2_OFFSET + SET0);
+ ipi_set0_regs[7] = (void __iomem *)
+ (SMP_CORE_GROUP1_BASE + SMP_CORE3_OFFSET + SET0);
+ ipi_set0_regs[8] = (void __iomem *)
+ (SMP_CORE_GROUP2_BASE + SMP_CORE0_OFFSET + SET0);
+ ipi_set0_regs[9] = (void __iomem *)
+ (SMP_CORE_GROUP2_BASE + SMP_CORE1_OFFSET + SET0);
+ ipi_set0_regs[10] = (void __iomem *)
+ (SMP_CORE_GROUP2_BASE + SMP_CORE2_OFFSET + SET0);
+ ipi_set0_regs[11] = (void __iomem *)
+ (SMP_CORE_GROUP2_BASE + SMP_CORE3_OFFSET + SET0);
+ ipi_set0_regs[12] = (void __iomem *)
+ (SMP_CORE_GROUP3_BASE + SMP_CORE0_OFFSET + SET0);
+ ipi_set0_regs[13] = (void __iomem *)
+ (SMP_CORE_GROUP3_BASE + SMP_CORE1_OFFSET + SET0);
+ ipi_set0_regs[14] = (void __iomem *)
+ (SMP_CORE_GROUP3_BASE + SMP_CORE2_OFFSET + SET0);
+ ipi_set0_regs[15] = (void __iomem *)
+ (SMP_CORE_GROUP3_BASE + SMP_CORE3_OFFSET + SET0);
+}
+
+static void ipi_clear0_regs_init(void)
+{
+ ipi_clear0_regs[0] = (void __iomem *)
+ (SMP_CORE_GROUP0_BASE + SMP_CORE0_OFFSET + CLEAR0);
+ ipi_clear0_regs[1] = (void __iomem *)
+ (SMP_CORE_GROUP0_BASE + SMP_CORE1_OFFSET + CLEAR0);
+ ipi_clear0_regs[2] = (void __iomem *)
+ (SMP_CORE_GROUP0_BASE + SMP_CORE2_OFFSET + CLEAR0);
+ ipi_clear0_regs[3] = (void __iomem *)
+ (SMP_CORE_GROUP0_BASE + SMP_CORE3_OFFSET + CLEAR0);
+ ipi_clear0_regs[4] = (void __iomem *)
+ (SMP_CORE_GROUP1_BASE + SMP_CORE0_OFFSET + CLEAR0);
+ ipi_clear0_regs[5] = (void __iomem *)
+ (SMP_CORE_GROUP1_BASE + SMP_CORE1_OFFSET + CLEAR0);
+ ipi_clear0_regs[6] = (void __iomem *)
+ (SMP_CORE_GROUP1_BASE + SMP_CORE2_OFFSET + CLEAR0);
+ ipi_clear0_regs[7] = (void __iomem *)
+ (SMP_CORE_GROUP1_BASE + SMP_CORE3_OFFSET + CLEAR0);
+ ipi_clear0_regs[8] = (void __iomem *)
+ (SMP_CORE_GROUP2_BASE + SMP_CORE0_OFFSET + CLEAR0);
+ ipi_clear0_regs[9] = (void __iomem *)
+ (SMP_CORE_GROUP2_BASE + SMP_CORE1_OFFSET + CLEAR0);
+ ipi_clear0_regs[10] = (void __iomem *)
+ (SMP_CORE_GROUP2_BASE + SMP_CORE2_OFFSET + CLEAR0);
+ ipi_clear0_regs[11] = (void __iomem *)
+ (SMP_CORE_GROUP2_BASE + SMP_CORE3_OFFSET + CLEAR0);
+ ipi_clear0_regs[12] = (void __iomem *)
+ (SMP_CORE_GROUP3_BASE + SMP_CORE0_OFFSET + CLEAR0);
+ ipi_clear0_regs[13] = (void __iomem *)
+ (SMP_CORE_GROUP3_BASE + SMP_CORE1_OFFSET + CLEAR0);
+ ipi_clear0_regs[14] = (void __iomem *)
+ (SMP_CORE_GROUP3_BASE + SMP_CORE2_OFFSET + CLEAR0);
+ ipi_clear0_regs[15] = (void __iomem *)
+ (SMP_CORE_GROUP3_BASE + SMP_CORE3_OFFSET + CLEAR0);
+}
+
+static void ipi_status0_regs_init(void)
+{
+ ipi_status0_regs[0] = (void __iomem *)
+ (SMP_CORE_GROUP0_BASE + SMP_CORE0_OFFSET + STATUS0);
+ ipi_status0_regs[1] = (void __iomem *)
+ (SMP_CORE_GROUP0_BASE + SMP_CORE1_OFFSET + STATUS0);
+ ipi_status0_regs[2] = (void __iomem *)
+ (SMP_CORE_GROUP0_BASE + SMP_CORE2_OFFSET + STATUS0);
+ ipi_status0_regs[3] = (void __iomem *)
+ (SMP_CORE_GROUP0_BASE + SMP_CORE3_OFFSET + STATUS0);
+ ipi_status0_regs[4] = (void __iomem *)
+ (SMP_CORE_GROUP1_BASE + SMP_CORE0_OFFSET + STATUS0);
+ ipi_status0_regs[5] = (void __iomem *)
+ (SMP_CORE_GROUP1_BASE + SMP_CORE1_OFFSET + STATUS0);
+ ipi_status0_regs[6] = (void __iomem *)
+ (SMP_CORE_GROUP1_BASE + SMP_CORE2_OFFSET + STATUS0);
+ ipi_status0_regs[7] = (void __iomem *)
+ (SMP_CORE_GROUP1_BASE + SMP_CORE3_OFFSET + STATUS0);
+ ipi_status0_regs[8] = (void __iomem *)
+ (SMP_CORE_GROUP2_BASE + SMP_CORE0_OFFSET + STATUS0);
+ ipi_status0_regs[9] = (void __iomem *)
+ (SMP_CORE_GROUP2_BASE + SMP_CORE1_OFFSET + STATUS0);
+ ipi_status0_regs[10] = (void __iomem *)
+ (SMP_CORE_GROUP2_BASE + SMP_CORE2_OFFSET + STATUS0);
+ ipi_status0_regs[11] = (void __iomem *)
+ (SMP_CORE_GROUP2_BASE + SMP_CORE3_OFFSET + STATUS0);
+ ipi_status0_regs[12] = (void __iomem *)
+ (SMP_CORE_GROUP3_BASE + SMP_CORE0_OFFSET + STATUS0);
+ ipi_status0_regs[13] = (void __iomem *)
+ (SMP_CORE_GROUP3_BASE + SMP_CORE1_OFFSET + STATUS0);
+ ipi_status0_regs[14] = (void __iomem *)
+ (SMP_CORE_GROUP3_BASE + SMP_CORE2_OFFSET + STATUS0);
+ ipi_status0_regs[15] = (void __iomem *)
+ (SMP_CORE_GROUP3_BASE + SMP_CORE3_OFFSET + STATUS0);
+}
+
+static void ipi_en0_regs_init(void)
+{
+ ipi_en0_regs[0] = (void __iomem *)
+ (SMP_CORE_GROUP0_BASE + SMP_CORE0_OFFSET + EN0);
+ ipi_en0_regs[1] = (void __iomem *)
+ (SMP_CORE_GROUP0_BASE + SMP_CORE1_OFFSET + EN0);
+ ipi_en0_regs[2] = (void __iomem *)
+ (SMP_CORE_GROUP0_BASE + SMP_CORE2_OFFSET + EN0);
+ ipi_en0_regs[3] = (void __iomem *)
+ (SMP_CORE_GROUP0_BASE + SMP_CORE3_OFFSET + EN0);
+ ipi_en0_regs[4] = (void __iomem *)
+ (SMP_CORE_GROUP1_BASE + SMP_CORE0_OFFSET + EN0);
+ ipi_en0_regs[5] = (void __iomem *)
+ (SMP_CORE_GROUP1_BASE + SMP_CORE1_OFFSET + EN0);
+ ipi_en0_regs[6] = (void __iomem *)
+ (SMP_CORE_GROUP1_BASE + SMP_CORE2_OFFSET + EN0);
+ ipi_en0_regs[7] = (void __iomem *)
+ (SMP_CORE_GROUP1_BASE + SMP_CORE3_OFFSET + EN0);
+ ipi_en0_regs[8] = (void __iomem *)
+ (SMP_CORE_GROUP2_BASE + SMP_CORE0_OFFSET + EN0);
+ ipi_en0_regs[9] = (void __iomem *)
+ (SMP_CORE_GROUP2_BASE + SMP_CORE1_OFFSET + EN0);
+ ipi_en0_regs[10] = (void __iomem *)
+ (SMP_CORE_GROUP2_BASE + SMP_CORE2_OFFSET + EN0);
+ ipi_en0_regs[11] = (void __iomem *)
+ (SMP_CORE_GROUP2_BASE + SMP_CORE3_OFFSET + EN0);
+ ipi_en0_regs[12] = (void __iomem *)
+ (SMP_CORE_GROUP3_BASE + SMP_CORE0_OFFSET + EN0);
+ ipi_en0_regs[13] = (void __iomem *)
+ (SMP_CORE_GROUP3_BASE + SMP_CORE1_OFFSET + EN0);
+ ipi_en0_regs[14] = (void __iomem *)
+ (SMP_CORE_GROUP3_BASE + SMP_CORE2_OFFSET + EN0);
+ ipi_en0_regs[15] = (void __iomem *)
+ (SMP_CORE_GROUP3_BASE + SMP_CORE3_OFFSET + EN0);
+}
+
+static void ipi_mailbox_buf_init(void)
+{
+ ipi_mailbox_buf[0] = (void __iomem *)
+ (SMP_CORE_GROUP0_BASE + SMP_CORE0_OFFSET + BUF);
+ ipi_mailbox_buf[1] = (void __iomem *)
+ (SMP_CORE_GROUP0_BASE + SMP_CORE1_OFFSET + BUF);
+ ipi_mailbox_buf[2] = (void __iomem *)
+ (SMP_CORE_GROUP0_BASE + SMP_CORE2_OFFSET + BUF);
+ ipi_mailbox_buf[3] = (void __iomem *)
+ (SMP_CORE_GROUP0_BASE + SMP_CORE3_OFFSET + BUF);
+ ipi_mailbox_buf[4] = (void __iomem *)
+ (SMP_CORE_GROUP1_BASE + SMP_CORE0_OFFSET + BUF);
+ ipi_mailbox_buf[5] = (void __iomem *)
+ (SMP_CORE_GROUP1_BASE + SMP_CORE1_OFFSET + BUF);
+ ipi_mailbox_buf[6] = (void __iomem *)
+ (SMP_CORE_GROUP1_BASE + SMP_CORE2_OFFSET + BUF);
+ ipi_mailbox_buf[7] = (void __iomem *)
+ (SMP_CORE_GROUP1_BASE + SMP_CORE3_OFFSET + BUF);
+ ipi_mailbox_buf[8] = (void __iomem *)
+ (SMP_CORE_GROUP2_BASE + SMP_CORE0_OFFSET + BUF);
+ ipi_mailbox_buf[9] = (void __iomem *)
+ (SMP_CORE_GROUP2_BASE + SMP_CORE1_OFFSET + BUF);
+ ipi_mailbox_buf[10] = (void __iomem *)
+ (SMP_CORE_GROUP2_BASE + SMP_CORE2_OFFSET + BUF);
+ ipi_mailbox_buf[11] = (void __iomem *)
+ (SMP_CORE_GROUP2_BASE + SMP_CORE3_OFFSET + BUF);
+ ipi_mailbox_buf[12] = (void __iomem *)
+ (SMP_CORE_GROUP3_BASE + SMP_CORE0_OFFSET + BUF);
+ ipi_mailbox_buf[13] = (void __iomem *)
+ (SMP_CORE_GROUP3_BASE + SMP_CORE1_OFFSET + BUF);
+ ipi_mailbox_buf[14] = (void __iomem *)
+ (SMP_CORE_GROUP3_BASE + SMP_CORE2_OFFSET + BUF);
+ ipi_mailbox_buf[15] = (void __iomem *)
+ (SMP_CORE_GROUP3_BASE + SMP_CORE3_OFFSET + BUF);
+}
+
+/*
+ * Simple enough, just poke the appropriate ipi register
+ */
+static void loongson3_send_ipi_single(int cpu, unsigned int action)
+{
+ ipi_write_action(cpu_logical_map(cpu), (u32)action);
+}
+
+static void
+loongson3_send_ipi_mask(const struct cpumask *mask, unsigned int action)
+{
+ unsigned int i;
+
+ for_each_cpu(i, mask)
+ ipi_write_action(cpu_logical_map(i), (u32)action);
+}
+
+
+static irqreturn_t loongson3_ipi_interrupt(int irq, void *dev_id)
+{
+ int i, cpu = smp_processor_id();
+ unsigned int action, c0count;
+
+ action = ipi_read_clear(cpu);
+
+ if (action & SMP_RESCHEDULE_YOURSELF)
+ scheduler_ipi();
+
+ if (action & SMP_CALL_FUNCTION) {
+ irq_enter();
+ generic_smp_call_function_interrupt();
+ irq_exit();
+ }
+
+ if (action & SMP_ASK_C0COUNT) {
+ BUG_ON(cpu != 0);
+ c0count = read_c0_count();
+ c0count = c0count ? c0count : 1;
+ for (i = 1; i < nr_cpu_ids; i++)
+ core0_c0count[i] = c0count;
+ nudge_writes(); /* Let others see the result ASAP */
+ }
+
+ return IRQ_HANDLED;
+}
+
+#define MAX_LOOPS 800
+/*
+ * SMP init and finish on secondary CPUs
+ */
+static void loongson3_init_secondary(void)
+{
+ int i;
+ uint32_t initcount;
+ unsigned int cpu = smp_processor_id();
+ unsigned int imask = STATUSF_IP7 | STATUSF_IP6 |
+ STATUSF_IP3 | STATUSF_IP2;
+
+ /* Set interrupt mask, but don't enable */
+ change_c0_status(ST0_IM, imask);
+ ipi_write_enable(cpu);
+
+ per_cpu(cpu_state, cpu) = CPU_ONLINE;
+ cpu_set_core(&cpu_data[cpu],
+ cpu_logical_map(cpu) % loongson_sysconf.cores_per_package);
+ cpu_data[cpu].package =
+ cpu_logical_map(cpu) / loongson_sysconf.cores_per_package;
+
+ i = 0;
+ core0_c0count[cpu] = 0;
+ loongson3_send_ipi_single(0, SMP_ASK_C0COUNT);
+ while (!core0_c0count[cpu]) {
+ i++;
+ cpu_relax();
+ }
+
+ if (i > MAX_LOOPS)
+ i = MAX_LOOPS;
+ if (cpu_data[cpu].package)
+ initcount = core0_c0count[cpu] + i;
+ else /* Local access is faster for loops */
+ initcount = core0_c0count[cpu] + i/2;
+
+ write_c0_count(initcount);
+}
+
+static void loongson3_smp_finish(void)
+{
+ int cpu = smp_processor_id();
+
+ write_c0_compare(read_c0_count() + mips_hpt_frequency/HZ);
+ local_irq_enable();
+ ipi_clear_buf(cpu);
+
+ pr_info("CPU#%d finished, CP0_ST=%x\n",
+ smp_processor_id(), read_c0_status());
+}
+
+static void __init loongson3_smp_setup(void)
+{
+ int i = 0, num = 0; /* i: physical id, num: logical id */
+
+ init_cpu_possible(cpu_none_mask);
+
+ /* For unified kernel, NR_CPUS is the maximum possible value,
+ * loongson_sysconf.nr_cpus is the really present value
+ */
+ while (i < loongson_sysconf.nr_cpus) {
+ if (loongson_sysconf.reserved_cpus_mask & (1<<i)) {
+ /* Reserved physical CPU cores */
+ __cpu_number_map[i] = -1;
+ } else {
+ __cpu_number_map[i] = num;
+ __cpu_logical_map[num] = i;
+ set_cpu_possible(num, true);
+ /* Loongson processors are always grouped by 4 */
+ cpu_set_cluster(&cpu_data[num], i / 4);
+ num++;
+ }
+ i++;
+ }
+ pr_info("Detected %i available CPU(s)\n", num);
+
+ while (num < loongson_sysconf.nr_cpus) {
+ __cpu_logical_map[num] = -1;
+ num++;
+ }
+
+ csr_ipi_probe();
+ ipi_set0_regs_init();
+ ipi_clear0_regs_init();
+ ipi_status0_regs_init();
+ ipi_en0_regs_init();
+ ipi_mailbox_buf_init();
+ ipi_write_enable(0);
+
+ cpu_set_core(&cpu_data[0],
+ cpu_logical_map(0) % loongson_sysconf.cores_per_package);
+ cpu_data[0].package = cpu_logical_map(0) / loongson_sysconf.cores_per_package;
+}
+
+static void __init loongson3_prepare_cpus(unsigned int max_cpus)
+{
+ if (request_irq(LS_IPI_IRQ, loongson3_ipi_interrupt,
+ IRQF_PERCPU | IRQF_NO_SUSPEND, "SMP_IPI", NULL))
+ pr_err("Failed to request IPI IRQ\n");
+ init_cpu_present(cpu_possible_mask);
+ per_cpu(cpu_state, smp_processor_id()) = CPU_ONLINE;
+}
+
+/*
+ * Setup the PC, SP, and GP of a secondary processor and start it runing!
+ */
+static int loongson3_boot_secondary(int cpu, struct task_struct *idle)
+{
+ pr_info("Booting CPU#%d...\n", cpu);
+
+ ipi_write_buf(cpu, idle);
+
+ return 0;
+}
+
+#ifdef CONFIG_HOTPLUG_CPU
+
+static int loongson3_cpu_disable(void)
+{
+ unsigned long flags;
+ unsigned int cpu = smp_processor_id();
+
+ set_cpu_online(cpu, false);
+ calculate_cpu_foreign_map();
+ local_irq_save(flags);
+ clear_c0_status(ST0_IM);
+ local_irq_restore(flags);
+ local_flush_tlb_all();
+
+ return 0;
+}
+
+
+static void loongson3_cpu_die(unsigned int cpu)
+{
+ while (per_cpu(cpu_state, cpu) != CPU_DEAD)
+ cpu_relax();
+
+ mb();
+}
+
+/* To shutdown a core in Loongson 3, the target core should go to CKSEG1 and
+ * flush all L1 entries at first. Then, another core (usually Core 0) can
+ * safely disable the clock of the target core. loongson3_play_dead() is
+ * called via CKSEG1 (uncached and unmmaped)
+ */
+static void loongson3_type1_play_dead(int *state_addr)
+{
+ register int val;
+ register long cpuid, core, node, count;
+ register void *addr, *base, *initfunc;
+
+ __asm__ __volatile__(
+ " .set push \n"
+ " .set noreorder \n"
+ " li %[addr], 0x80000000 \n" /* KSEG0 */
+ "1: cache 0, 0(%[addr]) \n" /* flush L1 ICache */
+ " cache 0, 1(%[addr]) \n"
+ " cache 0, 2(%[addr]) \n"
+ " cache 0, 3(%[addr]) \n"
+ " cache 1, 0(%[addr]) \n" /* flush L1 DCache */
+ " cache 1, 1(%[addr]) \n"
+ " cache 1, 2(%[addr]) \n"
+ " cache 1, 3(%[addr]) \n"
+ " addiu %[sets], %[sets], -1 \n"
+ " bnez %[sets], 1b \n"
+ " addiu %[addr], %[addr], 0x20 \n"
+ " li %[val], 0x7 \n" /* *state_addr = CPU_DEAD; */
+ " sw %[val], (%[state_addr]) \n"
+ " sync \n"
+ " cache 21, (%[state_addr]) \n" /* flush entry of *state_addr */
+ " .set pop \n"
+ : [addr] "=&r" (addr), [val] "=&r" (val)
+ : [state_addr] "r" (state_addr),
+ [sets] "r" (cpu_data[smp_processor_id()].dcache.sets));
+
+ __asm__ __volatile__(
+ " .set push \n"
+ " .set noreorder \n"
+ " .set mips64 \n"
+ " mfc0 %[cpuid], $15, 1 \n"
+ " andi %[cpuid], 0x3ff \n"
+ " dli %[base], 0x900000003ff01000 \n"
+ " andi %[core], %[cpuid], 0x3 \n"
+ " sll %[core], 8 \n" /* get core id */
+ " or %[base], %[base], %[core] \n"
+ " andi %[node], %[cpuid], 0xc \n"
+ " dsll %[node], 42 \n" /* get node id */
+ " or %[base], %[base], %[node] \n"
+ "1: li %[count], 0x100 \n" /* wait for init loop */
+ "2: bnez %[count], 2b \n" /* limit mailbox access */
+ " addiu %[count], -1 \n"
+ " ld %[initfunc], 0x20(%[base]) \n" /* get PC via mailbox */
+ " beqz %[initfunc], 1b \n"
+ " nop \n"
+ " ld $sp, 0x28(%[base]) \n" /* get SP via mailbox */
+ " ld $gp, 0x30(%[base]) \n" /* get GP via mailbox */
+ " ld $a1, 0x38(%[base]) \n"
+ " jr %[initfunc] \n" /* jump to initial PC */
+ " nop \n"
+ " .set pop \n"
+ : [core] "=&r" (core), [node] "=&r" (node),
+ [base] "=&r" (base), [cpuid] "=&r" (cpuid),
+ [count] "=&r" (count), [initfunc] "=&r" (initfunc)
+ : /* No Input */
+ : "a1");
+}
+
+static void loongson3_type2_play_dead(int *state_addr)
+{
+ register int val;
+ register long cpuid, core, node, count;
+ register void *addr, *base, *initfunc;
+
+ __asm__ __volatile__(
+ " .set push \n"
+ " .set noreorder \n"
+ " li %[addr], 0x80000000 \n" /* KSEG0 */
+ "1: cache 0, 0(%[addr]) \n" /* flush L1 ICache */
+ " cache 0, 1(%[addr]) \n"
+ " cache 0, 2(%[addr]) \n"
+ " cache 0, 3(%[addr]) \n"
+ " cache 1, 0(%[addr]) \n" /* flush L1 DCache */
+ " cache 1, 1(%[addr]) \n"
+ " cache 1, 2(%[addr]) \n"
+ " cache 1, 3(%[addr]) \n"
+ " addiu %[sets], %[sets], -1 \n"
+ " bnez %[sets], 1b \n"
+ " addiu %[addr], %[addr], 0x20 \n"
+ " li %[val], 0x7 \n" /* *state_addr = CPU_DEAD; */
+ " sw %[val], (%[state_addr]) \n"
+ " sync \n"
+ " cache 21, (%[state_addr]) \n" /* flush entry of *state_addr */
+ " .set pop \n"
+ : [addr] "=&r" (addr), [val] "=&r" (val)
+ : [state_addr] "r" (state_addr),
+ [sets] "r" (cpu_data[smp_processor_id()].dcache.sets));
+
+ __asm__ __volatile__(
+ " .set push \n"
+ " .set noreorder \n"
+ " .set mips64 \n"
+ " mfc0 %[cpuid], $15, 1 \n"
+ " andi %[cpuid], 0x3ff \n"
+ " dli %[base], 0x900000003ff01000 \n"
+ " andi %[core], %[cpuid], 0x3 \n"
+ " sll %[core], 8 \n" /* get core id */
+ " or %[base], %[base], %[core] \n"
+ " andi %[node], %[cpuid], 0xc \n"
+ " dsll %[node], 42 \n" /* get node id */
+ " or %[base], %[base], %[node] \n"
+ " dsrl %[node], 30 \n" /* 15:14 */
+ " or %[base], %[base], %[node] \n"
+ "1: li %[count], 0x100 \n" /* wait for init loop */
+ "2: bnez %[count], 2b \n" /* limit mailbox access */
+ " addiu %[count], -1 \n"
+ " ld %[initfunc], 0x20(%[base]) \n" /* get PC via mailbox */
+ " beqz %[initfunc], 1b \n"
+ " nop \n"
+ " ld $sp, 0x28(%[base]) \n" /* get SP via mailbox */
+ " ld $gp, 0x30(%[base]) \n" /* get GP via mailbox */
+ " ld $a1, 0x38(%[base]) \n"
+ " jr %[initfunc] \n" /* jump to initial PC */
+ " nop \n"
+ " .set pop \n"
+ : [core] "=&r" (core), [node] "=&r" (node),
+ [base] "=&r" (base), [cpuid] "=&r" (cpuid),
+ [count] "=&r" (count), [initfunc] "=&r" (initfunc)
+ : /* No Input */
+ : "a1");
+}
+
+static void loongson3_type3_play_dead(int *state_addr)
+{
+ register int val;
+ register long cpuid, core, node, count;
+ register void *addr, *base, *initfunc;
+
+ __asm__ __volatile__(
+ " .set push \n"
+ " .set noreorder \n"
+ " li %[addr], 0x80000000 \n" /* KSEG0 */
+ "1: cache 0, 0(%[addr]) \n" /* flush L1 ICache */
+ " cache 0, 1(%[addr]) \n"
+ " cache 0, 2(%[addr]) \n"
+ " cache 0, 3(%[addr]) \n"
+ " cache 1, 0(%[addr]) \n" /* flush L1 DCache */
+ " cache 1, 1(%[addr]) \n"
+ " cache 1, 2(%[addr]) \n"
+ " cache 1, 3(%[addr]) \n"
+ " addiu %[sets], %[sets], -1 \n"
+ " bnez %[sets], 1b \n"
+ " addiu %[addr], %[addr], 0x40 \n"
+ " li %[addr], 0x80000000 \n" /* KSEG0 */
+ "2: cache 2, 0(%[addr]) \n" /* flush L1 VCache */
+ " cache 2, 1(%[addr]) \n"
+ " cache 2, 2(%[addr]) \n"
+ " cache 2, 3(%[addr]) \n"
+ " cache 2, 4(%[addr]) \n"
+ " cache 2, 5(%[addr]) \n"
+ " cache 2, 6(%[addr]) \n"
+ " cache 2, 7(%[addr]) \n"
+ " cache 2, 8(%[addr]) \n"
+ " cache 2, 9(%[addr]) \n"
+ " cache 2, 10(%[addr]) \n"
+ " cache 2, 11(%[addr]) \n"
+ " cache 2, 12(%[addr]) \n"
+ " cache 2, 13(%[addr]) \n"
+ " cache 2, 14(%[addr]) \n"
+ " cache 2, 15(%[addr]) \n"
+ " addiu %[vsets], %[vsets], -1 \n"
+ " bnez %[vsets], 2b \n"
+ " addiu %[addr], %[addr], 0x40 \n"
+ " li %[val], 0x7 \n" /* *state_addr = CPU_DEAD; */
+ " sw %[val], (%[state_addr]) \n"
+ " sync \n"
+ " cache 21, (%[state_addr]) \n" /* flush entry of *state_addr */
+ " .set pop \n"
+ : [addr] "=&r" (addr), [val] "=&r" (val)
+ : [state_addr] "r" (state_addr),
+ [sets] "r" (cpu_data[smp_processor_id()].dcache.sets),
+ [vsets] "r" (cpu_data[smp_processor_id()].vcache.sets));
+
+ __asm__ __volatile__(
+ " .set push \n"
+ " .set noreorder \n"
+ " .set mips64 \n"
+ " mfc0 %[cpuid], $15, 1 \n"
+ " andi %[cpuid], 0x3ff \n"
+ " dli %[base], 0x900000003ff01000 \n"
+ " andi %[core], %[cpuid], 0x3 \n"
+ " sll %[core], 8 \n" /* get core id */
+ " or %[base], %[base], %[core] \n"
+ " andi %[node], %[cpuid], 0xc \n"
+ " dsll %[node], 42 \n" /* get node id */
+ " or %[base], %[base], %[node] \n"
+ "1: li %[count], 0x100 \n" /* wait for init loop */
+ "2: bnez %[count], 2b \n" /* limit mailbox access */
+ " addiu %[count], -1 \n"
+ " lw %[initfunc], 0x20(%[base]) \n" /* check lower 32-bit as jump indicator */
+ " beqz %[initfunc], 1b \n"
+ " nop \n"
+ " ld %[initfunc], 0x20(%[base]) \n" /* get PC (whole 64-bit) via mailbox */
+ " ld $sp, 0x28(%[base]) \n" /* get SP via mailbox */
+ " ld $gp, 0x30(%[base]) \n" /* get GP via mailbox */
+ " ld $a1, 0x38(%[base]) \n"
+ " jr %[initfunc] \n" /* jump to initial PC */
+ " nop \n"
+ " .set pop \n"
+ : [core] "=&r" (core), [node] "=&r" (node),
+ [base] "=&r" (base), [cpuid] "=&r" (cpuid),
+ [count] "=&r" (count), [initfunc] "=&r" (initfunc)
+ : /* No Input */
+ : "a1");
+}
+
+void play_dead(void)
+{
+ int prid_imp, prid_rev, *state_addr;
+ unsigned int cpu = smp_processor_id();
+ void (*play_dead_at_ckseg1)(int *);
+
+ idle_task_exit();
+ cpuhp_ap_report_dead();
+
+ prid_imp = read_c0_prid() & PRID_IMP_MASK;
+ prid_rev = read_c0_prid() & PRID_REV_MASK;
+
+ if (prid_imp == PRID_IMP_LOONGSON_64G) {
+ play_dead_at_ckseg1 =
+ (void *)CKSEG1ADDR((unsigned long)loongson3_type3_play_dead);
+ goto out;
+ }
+
+ switch (prid_rev) {
+ case PRID_REV_LOONGSON3A_R1:
+ default:
+ play_dead_at_ckseg1 =
+ (void *)CKSEG1ADDR((unsigned long)loongson3_type1_play_dead);
+ break;
+ case PRID_REV_LOONGSON3B_R1:
+ case PRID_REV_LOONGSON3B_R2:
+ play_dead_at_ckseg1 =
+ (void *)CKSEG1ADDR((unsigned long)loongson3_type2_play_dead);
+ break;
+ case PRID_REV_LOONGSON3A_R2_0:
+ case PRID_REV_LOONGSON3A_R2_1:
+ case PRID_REV_LOONGSON3A_R3_0:
+ case PRID_REV_LOONGSON3A_R3_1:
+ play_dead_at_ckseg1 =
+ (void *)CKSEG1ADDR((unsigned long)loongson3_type3_play_dead);
+ break;
+ }
+
+out:
+ state_addr = &per_cpu(cpu_state, cpu);
+ mb();
+ play_dead_at_ckseg1(state_addr);
+ BUG();
+}
+
+static int loongson3_disable_clock(unsigned int cpu)
+{
+ uint64_t core_id = cpu_core(&cpu_data[cpu]);
+ uint64_t package_id = cpu_data[cpu].package;
+
+ if ((read_c0_prid() & PRID_REV_MASK) == PRID_REV_LOONGSON3A_R1) {
+ LOONGSON_CHIPCFG(package_id) &= ~(1 << (12 + core_id));
+ } else {
+ if (!(loongson_sysconf.workarounds & WORKAROUND_CPUHOTPLUG))
+ LOONGSON_FREQCTRL(package_id) &= ~(1 << (core_id * 4 + 3));
+ }
+ return 0;
+}
+
+static int loongson3_enable_clock(unsigned int cpu)
+{
+ uint64_t core_id = cpu_core(&cpu_data[cpu]);
+ uint64_t package_id = cpu_data[cpu].package;
+
+ if ((read_c0_prid() & PRID_REV_MASK) == PRID_REV_LOONGSON3A_R1) {
+ LOONGSON_CHIPCFG(package_id) |= 1 << (12 + core_id);
+ } else {
+ if (!(loongson_sysconf.workarounds & WORKAROUND_CPUHOTPLUG))
+ LOONGSON_FREQCTRL(package_id) |= 1 << (core_id * 4 + 3);
+ }
+ return 0;
+}
+
+static int register_loongson3_notifier(void)
+{
+ return cpuhp_setup_state_nocalls(CPUHP_MIPS_SOC_PREPARE,
+ "mips/loongson:prepare",
+ loongson3_enable_clock,
+ loongson3_disable_clock);
+}
+early_initcall(register_loongson3_notifier);
+
+#endif
+
+const struct plat_smp_ops loongson3_smp_ops = {
+ .send_ipi_single = loongson3_send_ipi_single,
+ .send_ipi_mask = loongson3_send_ipi_mask,
+ .init_secondary = loongson3_init_secondary,
+ .smp_finish = loongson3_smp_finish,
+ .boot_secondary = loongson3_boot_secondary,
+ .smp_setup = loongson3_smp_setup,
+ .prepare_cpus = loongson3_prepare_cpus,
+#ifdef CONFIG_HOTPLUG_CPU
+ .cpu_disable = loongson3_cpu_disable,
+ .cpu_die = loongson3_cpu_die,
+#endif
+#ifdef CONFIG_KEXEC
+ .kexec_nonboot_cpu = kexec_nonboot_cpu_jump,
+#endif
+};
diff --git a/arch/mips/loongson64/smp.h b/arch/mips/loongson64/smp.h
new file mode 100644
index 0000000000..957bde81e0
--- /dev/null
+++ b/arch/mips/loongson64/smp.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __LOONGSON_SMP_H_
+#define __LOONGSON_SMP_H_
+
+/* for Loongson-3 smp support */
+extern unsigned long long smp_group[4];
+
+/* 4 groups(nodes) in maximum in numa case */
+#define SMP_CORE_GROUP0_BASE (smp_group[0])
+#define SMP_CORE_GROUP1_BASE (smp_group[1])
+#define SMP_CORE_GROUP2_BASE (smp_group[2])
+#define SMP_CORE_GROUP3_BASE (smp_group[3])
+
+/* 4 cores in each group(node) */
+#define SMP_CORE0_OFFSET 0x000
+#define SMP_CORE1_OFFSET 0x100
+#define SMP_CORE2_OFFSET 0x200
+#define SMP_CORE3_OFFSET 0x300
+
+/* ipi registers offsets */
+#define STATUS0 0x00
+#define EN0 0x04
+#define SET0 0x08
+#define CLEAR0 0x0c
+#define STATUS1 0x10
+#define MASK1 0x14
+#define SET1 0x18
+#define CLEAR1 0x1c
+#define BUF 0x20
+
+#endif
diff --git a/arch/mips/loongson64/time.c b/arch/mips/loongson64/time.c
new file mode 100644
index 0000000000..f6d2c1e305
--- /dev/null
+++ b/arch/mips/loongson64/time.c
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Lemote, Inc. & Institute of Computing Technology
+ * Author: Fuxin Zhang, zhangfx@lemote.com
+ *
+ * Copyright (C) 2009 Lemote Inc.
+ * Author: Wu Zhangjin, wuzhangjin@gmail.com
+ */
+
+#include <asm/time.h>
+#include <asm/hpet.h>
+
+#include <loongson.h>
+#include <linux/clk.h>
+#include <linux/of_clk.h>
+
+void __init plat_time_init(void)
+{
+ struct clk *clk;
+ struct device_node *np;
+
+ if (loongson_sysconf.fw_interface == LOONGSON_DTB) {
+ of_clk_init(NULL);
+
+ np = of_get_cpu_node(0, NULL);
+ if (!np) {
+ pr_err("Failed to get CPU node\n");
+ return;
+ }
+
+ clk = of_clk_get(np, 0);
+ if (IS_ERR(clk)) {
+ pr_err("Failed to get CPU clock: %ld\n", PTR_ERR(clk));
+ return;
+ }
+
+ cpu_clock_freq = clk_get_rate(clk);
+ clk_put(clk);
+ }
+
+ /* setup mips r4k timer */
+ mips_hpt_frequency = cpu_clock_freq / 2;
+
+#ifdef CONFIG_RS780_HPET
+ setup_hpet_timer();
+#endif
+}
diff --git a/arch/mips/loongson64/vbios_quirk.c b/arch/mips/loongson64/vbios_quirk.c
new file mode 100644
index 0000000000..3115d4de98
--- /dev/null
+++ b/arch/mips/loongson64/vbios_quirk.c
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <linux/pci.h>
+#include <loongson.h>
+
+static void pci_fixup_video(struct pci_dev *pdev)
+{
+ struct resource *res = &pdev->resource[PCI_ROM_RESOURCE];
+
+ if (res->start)
+ return;
+
+ if (!loongson_sysconf.vgabios_addr)
+ return;
+
+ pci_disable_rom(pdev);
+ if (res->parent)
+ release_resource(res);
+
+ res->start = virt_to_phys((void *) loongson_sysconf.vgabios_addr);
+ res->end = res->start + 256*1024 - 1;
+ res->flags = IORESOURCE_MEM | IORESOURCE_ROM_SHADOW |
+ IORESOURCE_PCI_FIXED;
+
+ dev_info(&pdev->dev, "Video device with shadowed ROM at %pR\n", res);
+}
+DECLARE_PCI_FIXUP_CLASS_HEADER(PCI_VENDOR_ID_ATI, 0x9615,
+ PCI_CLASS_DISPLAY_VGA, 8, pci_fixup_video);