@@ -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
@@ -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
new file mode 100644
@@ -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)
@@ -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
@@ -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);