diff --git a/arch/arm/mach-hisi/Makefile b/arch/arm/mach-hisi/Makefile
index 39476355e..71e2f67cd 100644
--- a/arch/arm/mach-hisi/Makefile
+++ b/arch/arm/mach-hisi/Makefile
@@ -7,4 +7,4 @@ CFLAGS_platmcpm.o	:= -march=armv7-a
 
 obj-y	+= hisilicon.o
 obj-$(CONFIG_MCPM)		+= platmcpm.o
-obj-$(CONFIG_SMP)		+= platsmp.o hotplug.o
+obj-$(CONFIG_SMP)		+= headsmp.o platsmp.o hotplug.o
diff --git a/arch/arm/mach-hisi/core.h b/arch/arm/mach-hisi/core.h
index 61245274f..1ada6503b 100644
--- a/arch/arm/mach-hisi/core.h
+++ b/arch/arm/mach-hisi/core.h
@@ -4,6 +4,9 @@
 
 #include <linux/reboot.h>
 
+extern volatile int hisi_pen_release;
+extern void hisi_secondary_startup(void);
+
 extern void hi3xxx_set_cpu_jump(int cpu, void *jump_addr);
 extern int hi3xxx_get_cpu_jump(int cpu);
 extern void secondary_startup(void);
@@ -16,4 +19,7 @@ extern void hix5hd2_set_cpu(int cpu, bool enable);
 extern void hix5hd2_cpu_die(unsigned int cpu);
 
 extern void hip01_set_cpu(int cpu, bool enable);
+
+extern void hi3798_set_cpu(int cpu, bool enable);
+extern void hi3798_cpu_die(unsigned int cpu);
 #endif
diff --git a/arch/arm/mach-hisi/headsmp.S b/arch/arm/mach-hisi/headsmp.S
new file mode 100644
index 000000000..67cfb584a
--- /dev/null
+++ b/arch/arm/mach-hisi/headsmp.S
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ *  Copyright (c) 2003 ARM Limited
+ *  All Rights Reserved
+ */
+#include <linux/linkage.h>
+#include <linux/init.h>
+#include <asm/assembler.h>
+
+/*
+ * Hisilicon specific entry point for secondary CPUs. This provides
+ * a "holding pen" into which all secondary cores are held until we're
+ * ready for them to initialise.
+ */
+ENTRY(hisi_secondary_startup)
+ ARM_BE8(setend	be)
+	mrc	p15, 0, r0, c0, c0, 5
+	and	r0, r0, #15
+	adr	r4, 1f
+	ldmia	r4, {r5, r6}
+	sub	r4, r4, r5
+	add	r6, r6, r4
+pen:	ldr	r7, [r6]
+	cmp	r7, r0
+	bne	pen
+
+	/*
+	 * we've been released from the holding pen: secondary_stack
+	 * should now contain the SVC stack for this core
+	 */
+	b	secondary_startup
+
+	.align
+1:	.long	.
+	.long	hisi_pen_release
+ENDPROC(hisi_secondary_startup)
diff --git a/arch/arm/mach-hisi/hotplug.c b/arch/arm/mach-hisi/hotplug.c
index c51794141..7102546e7 100644
--- a/arch/arm/mach-hisi/hotplug.c
+++ b/arch/arm/mach-hisi/hotplug.c
@@ -55,7 +55,7 @@
 #define CPU0_SRST_REQ_EN		(1 << 0)
 
 #define HIX5HD2_PERI_CRG20		0x50
-#define CRG20_CPU1_RESET		(1 << 17)
+#define CRG20_ARM_SRST(i)		(1 << ((i) + 16))
 
 #define HIX5HD2_PERI_PMC0		0x1000
 #define PMC0_CPU1_WAIT_MTCOMS_ACK	(1 << 8)
@@ -65,6 +65,12 @@
 #define HIP01_PERI9                    0x50
 #define PERI9_CPU1_RESET               (1 << 1)
 
+#define HI3798_PERI_CRG18		0x48
+#define CRG18_CPU_SW_BEGIN		(1 << 10)
+#define HI3798_PERI_CRG20		0x50
+#define CRG20_ARM_POR_SRST(i)		(1 << ((i) + 12))
+#define CRG20_CLUSTER_DBG_SRST(i)	(1 << ((i) + 20))
+
 enum {
 	HI3620_CTRL,
 	ERROR_CTRL,
@@ -204,7 +210,7 @@ void hix5hd2_set_cpu(int cpu, bool enable)
 		writel_relaxed(val, ctrl_base + HIX5HD2_PERI_PMC0);
 		/* unreset */
 		val = readl_relaxed(ctrl_base + HIX5HD2_PERI_CRG20);
-		val &= ~CRG20_CPU1_RESET;
+		val &= ~CRG20_ARM_SRST(cpu);
 		writel_relaxed(val, ctrl_base + HIX5HD2_PERI_CRG20);
 	} else {
 		/* power down cpu1 */
@@ -212,10 +218,9 @@ void hix5hd2_set_cpu(int cpu, bool enable)
 		val |= PMC0_CPU1_PMC_ENABLE | PMC0_CPU1_POWERDOWN;
 		val &= ~PMC0_CPU1_WAIT_MTCOMS_ACK;
 		writel_relaxed(val, ctrl_base + HIX5HD2_PERI_PMC0);
-
 		/* reset */
 		val = readl_relaxed(ctrl_base + HIX5HD2_PERI_CRG20);
-		val |= CRG20_CPU1_RESET;
+		val |= CRG20_ARM_SRST(cpu);
 		writel_relaxed(val, ctrl_base + HIX5HD2_PERI_CRG20);
 	}
 }
@@ -248,6 +253,54 @@ void hip01_set_cpu(int cpu, bool enable)
 	}
 }
 
+void hi3798_set_cpu(int cpu, bool enable)
+{
+	u32 val;
+	u32 val_crg18;
+
+	if (!ctrl_base)
+		if (!hix5hd2_hotplug_init())
+			BUG();
+
+	if (enable) {
+		val_crg18 = readl_relaxed(ctrl_base + HI3798_PERI_CRG18);
+		/* select 400MHz */
+		val = 0x306;
+		writel_relaxed(val, ctrl_base + HI3798_PERI_CRG18);
+		val |= CRG18_CPU_SW_BEGIN;
+		writel_relaxed(val, ctrl_base + HI3798_PERI_CRG18);
+		/* unreset arm_por_srst_req */
+		val = readl_relaxed(ctrl_base + HI3798_PERI_CRG20);
+		val &= ~CRG20_ARM_POR_SRST(cpu);
+		writel_relaxed(val, ctrl_base + HI3798_PERI_CRG20);
+		/* unreset cluster_dbg_srst_req */
+		val = readl_relaxed(ctrl_base + HI3798_PERI_CRG20);
+		val &= ~CRG20_CLUSTER_DBG_SRST(cpu);
+		writel_relaxed(val, ctrl_base + HI3798_PERI_CRG20);
+		/* unreset */
+		val = readl_relaxed(ctrl_base + HI3798_PERI_CRG20);
+		val &= ~CRG20_ARM_SRST(cpu);
+		writel_relaxed(val, ctrl_base + HI3798_PERI_CRG20);
+		/* restore freq */
+		val = val_crg18 & ~CRG18_CPU_SW_BEGIN;
+		writel_relaxed(val, ctrl_base + HI3798_PERI_CRG18);
+		writel_relaxed(val_crg18, ctrl_base + HI3798_PERI_CRG18);
+	} else {
+		/* reset */
+		val = readl_relaxed(ctrl_base + HI3798_PERI_CRG20);
+		val |= CRG20_ARM_SRST(cpu);
+		writel_relaxed(val, ctrl_base + HI3798_PERI_CRG20);
+		/* reset cluster_dbg_srst_req */
+		val = readl_relaxed(ctrl_base + HI3798_PERI_CRG20);
+		val |= CRG20_CLUSTER_DBG_SRST(cpu);
+		writel_relaxed(val, ctrl_base + HI3798_PERI_CRG20);
+		/* reset arm_por_srst_req */
+		val = readl_relaxed(ctrl_base + HI3798_PERI_CRG20);
+		val |= CRG20_ARM_POR_SRST(cpu);
+		writel_relaxed(val, ctrl_base + HI3798_PERI_CRG20);
+	}
+}
+
 static inline void cpu_enter_lowpower(void)
 {
 	unsigned int v;
@@ -269,6 +322,45 @@ static inline void cpu_enter_lowpower(void)
 	  : "cc");
 }
 
+static inline void cpu_leave_lowpower(void)
+{
+	unsigned int v;
+
+	asm volatile(
+	"	mrc	p15, 0, %0, c1, c0, 0\n"
+	"	orr	%0, %0, #0x04\n"
+	"	mcr	p15, 0, %0, c1, c0, 0\n"
+	"	mrc	p15, 0, %0, c1, c0, 1\n"
+	"	orr	%0, %0, #0x20\n"
+	"	mcr	p15, 0, %0, c1, c0, 1\n"
+	  : "=&r" (v)
+	  :
+	  : "cc");
+}
+
+static inline void hisi_do_lowpower(unsigned int cpu, int *spurious)
+{
+	for (;;) {
+		wfi();
+
+		if (hisi_pen_release == cpu) {
+			/*
+			 * OK, proper wakeup, we're done
+			 */
+			break;
+		}
+
+		/*
+		 * Getting here, means that we have come out of WFI without
+		 * having been woken up - this shouldn't happen
+		 *
+		 * Just note it happening - when we're woken, we can report
+		 * its occurrence.
+		 */
+		(*spurious)++;
+	}
+}
+
 #ifdef CONFIG_HOTPLUG_CPU
 void hi3xxx_cpu_die(unsigned int cpu)
 {
@@ -296,4 +388,24 @@ void hix5hd2_cpu_die(unsigned int cpu)
 	flush_cache_all();
 	hix5hd2_set_cpu(cpu, false);
 }
+
+void hi3798_cpu_die(unsigned int cpu)
+{
+	int spurious = 0;
+
+	/*
+	 * we're ready for shutdown now, so do it
+	 */
+	cpu_enter_lowpower();
+	hisi_do_lowpower(cpu, &spurious);
+
+	/*
+	 * bring this CPU back into the world of cache
+	 * coherency, and then restore interrupts
+	 */
+	cpu_leave_lowpower();
+
+	if (spurious)
+		pr_warn("CPU%u: %u spurious wakeup calls\n", cpu, spurious);
+}
 #endif
diff --git a/arch/arm/mach-hisi/platsmp.c b/arch/arm/mach-hisi/platsmp.c
index 9ce93e0b6..da4c620a1 100644
--- a/arch/arm/mach-hisi/platsmp.c
+++ b/arch/arm/mach-hisi/platsmp.c
@@ -20,6 +20,48 @@
 
 static void __iomem *ctrl_base;
 
+/*
+ * hisi_pen_release controls the release of CPUs from the holding
+ * pen in headsmp.S, which exists because we are not always able to
+ * control the release of individual CPUs from the board firmware.
+ */
+volatile int hisi_pen_release = -1;
+
+/*
+ * Write hisi_write_pen_release in a way that is guaranteed to be visible to
+ * all observers, irrespective of whether they're taking part in coherency
+ * or not.  This is necessary for the hotplug code to work reliably.
+ */
+static void hisi_write_pen_release(int val)
+{
+	hisi_pen_release = val;
+	smp_wmb();
+	sync_cache_w(&hisi_pen_release);
+}
+
+/*
+ * hisi_lock exists to avoid running the loops_per_jiffy delay loop
+ * calibrations on the secondary CPU while the requesting CPU is using
+ * the limited-bandwidth bus - which affects the calibration value.
+ */
+static DEFINE_RAW_SPINLOCK(hisi_lock);
+
+static void hisi_pen_secondary_init(unsigned int cpu)
+{
+	/*
+	 * let the primary processor know we're out of the
+	 * pen, then head off into the C entry point
+	 */
+	hisi_write_pen_release(-1);
+
+	/*
+	 * Synchronise with the boot thread.
+	 */
+	raw_spin_lock(&hisi_lock);
+	raw_spin_unlock(&hisi_lock);
+}
+
+
 void hi3xxx_set_cpu_jump(int cpu, void *jump_addr)
 {
 	cpu = cpu_logical_map(cpu);
@@ -182,6 +224,92 @@ static const struct smp_operations hip01_smp_ops __initconst = {
 	.smp_boot_secondary     = hip01_boot_secondary,
 };
 
+
+static void hi3798_smp_prepare_cpus(unsigned int max_cpus)
+{
+	unsigned int i;
+	unsigned int l2ctlr;
+	unsigned int ncores;
+
+	asm ("mrc p15, 1, %0, c9, c0, 2\n" : "=r" (l2ctlr));
+	ncores = ((l2ctlr >> 24) & 0x3) + 1;
+
+	pr_info("SMP: %u cores detected\n", ncores);
+	if (ncores > max_cpus) {
+		pr_warn("SMP: %u cores greater than maximum (%u), clipping\n",
+			ncores, max_cpus);
+		ncores = max_cpus;
+	}
+	for (i = 0; i < ncores; i++)
+		set_cpu_possible(i, true);
+
+	/* Put the boot address in this magic register */
+	hix5hd2_set_scu_boot_addr(HIX5HD2_BOOT_ADDRESS,
+				  __pa_symbol(hisi_secondary_startup));
+}
+
+static int hi3798_boot_secondary(unsigned int cpu, struct task_struct *idle)
+{
+	unsigned long timeout;
+
+	/*
+	 * Set synchronisation state between this boot processor
+	 * and the secondary one
+	 */
+	raw_spin_lock(&hisi_lock);
+
+	hi3798_set_cpu(cpu, true);
+
+	/*
+	 * This is really belt and braces; we hold unintended secondary
+	 * CPUs in the holding pen until we're ready for them.  However,
+	 * since we haven't sent them a soft interrupt, they shouldn't
+	 * be there.
+	 */
+	hisi_write_pen_release(cpu);
+
+	/*
+	 * Send the secondary CPU a soft interrupt, thereby causing
+	 * the boot monitor to read the system wide flags register,
+	 * and branch to the address found there.
+	 */
+	arch_send_wakeup_ipi_mask(cpumask_of(cpu));
+
+	timeout = jiffies + (1 * HZ);
+	while (time_before(jiffies, timeout)) {
+		smp_rmb();
+		if (hisi_pen_release == -1)
+			break;
+
+		udelay(10);
+	}
+
+	/*
+	 * now the secondary core is starting up let it run its
+	 * calibrations, then wait for it to finish
+	 */
+	raw_spin_unlock(&hisi_lock);
+
+	return hisi_pen_release != -1 ? -ENOSYS : 0;
+}
+
+static int hi3798_cpu_kill(unsigned int cpu)
+{
+	hi3798_set_cpu(cpu, false);
+	return 1;
+}
+
+static const struct smp_operations hi3798_smp_ops __initconst = {
+	.smp_prepare_cpus	= hi3798_smp_prepare_cpus,
+	.smp_secondary_init	= hisi_pen_secondary_init,
+	.smp_boot_secondary	= hi3798_boot_secondary,
+#ifdef CONFIG_HOTPLUG_CPU
+	.cpu_die		= hi3798_cpu_die,
+	.cpu_kill		= hi3798_cpu_kill,
+#endif
+};
+
 CPU_METHOD_OF_DECLARE(hi3xxx_smp, "hisilicon,hi3620-smp", &hi3xxx_smp_ops);
 CPU_METHOD_OF_DECLARE(hix5hd2_smp, "hisilicon,hix5hd2-smp", &hix5hd2_smp_ops);
 CPU_METHOD_OF_DECLARE(hip01_smp, "hisilicon,hip01-smp", &hip01_smp_ops);
+CPU_METHOD_OF_DECLARE(hi3798_smp, "hisilicon,hi3798-smp", &hi3798_smp_ops);
