162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2013 Imagination Technologies
462306a36Sopenharmony_ci * Author: Paul Burton <paul.burton@mips.com>
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <linux/cpu.h>
862306a36Sopenharmony_ci#include <linux/delay.h>
962306a36Sopenharmony_ci#include <linux/io.h>
1062306a36Sopenharmony_ci#include <linux/sched/task_stack.h>
1162306a36Sopenharmony_ci#include <linux/sched/hotplug.h>
1262306a36Sopenharmony_ci#include <linux/slab.h>
1362306a36Sopenharmony_ci#include <linux/smp.h>
1462306a36Sopenharmony_ci#include <linux/types.h>
1562306a36Sopenharmony_ci#include <linux/irq.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include <asm/bcache.h>
1862306a36Sopenharmony_ci#include <asm/mips-cps.h>
1962306a36Sopenharmony_ci#include <asm/mips_mt.h>
2062306a36Sopenharmony_ci#include <asm/mipsregs.h>
2162306a36Sopenharmony_ci#include <asm/pm-cps.h>
2262306a36Sopenharmony_ci#include <asm/r4kcache.h>
2362306a36Sopenharmony_ci#include <asm/smp.h>
2462306a36Sopenharmony_ci#include <asm/smp-cps.h>
2562306a36Sopenharmony_ci#include <asm/time.h>
2662306a36Sopenharmony_ci#include <asm/uasm.h>
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistatic DECLARE_BITMAP(core_power, NR_CPUS);
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistruct core_boot_config *mips_cps_core_bootcfg;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cistatic unsigned __init core_vpe_count(unsigned int cluster, unsigned core)
3362306a36Sopenharmony_ci{
3462306a36Sopenharmony_ci	return min(smp_max_threads, mips_cps_numvps(cluster, core));
3562306a36Sopenharmony_ci}
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_cistatic void __init cps_smp_setup(void)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	unsigned int nclusters, ncores, nvpes, core_vpes;
4062306a36Sopenharmony_ci	unsigned long core_entry;
4162306a36Sopenharmony_ci	int cl, c, v;
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	/* Detect & record VPE topology */
4462306a36Sopenharmony_ci	nvpes = 0;
4562306a36Sopenharmony_ci	nclusters = mips_cps_numclusters();
4662306a36Sopenharmony_ci	pr_info("%s topology ", cpu_has_mips_r6 ? "VP" : "VPE");
4762306a36Sopenharmony_ci	for (cl = 0; cl < nclusters; cl++) {
4862306a36Sopenharmony_ci		if (cl > 0)
4962306a36Sopenharmony_ci			pr_cont(",");
5062306a36Sopenharmony_ci		pr_cont("{");
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci		ncores = mips_cps_numcores(cl);
5362306a36Sopenharmony_ci		for (c = 0; c < ncores; c++) {
5462306a36Sopenharmony_ci			core_vpes = core_vpe_count(cl, c);
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci			if (c > 0)
5762306a36Sopenharmony_ci				pr_cont(",");
5862306a36Sopenharmony_ci			pr_cont("%u", core_vpes);
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci			/* Use the number of VPEs in cluster 0 core 0 for smp_num_siblings */
6162306a36Sopenharmony_ci			if (!cl && !c)
6262306a36Sopenharmony_ci				smp_num_siblings = core_vpes;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci			for (v = 0; v < min_t(int, core_vpes, NR_CPUS - nvpes); v++) {
6562306a36Sopenharmony_ci				cpu_set_cluster(&cpu_data[nvpes + v], cl);
6662306a36Sopenharmony_ci				cpu_set_core(&cpu_data[nvpes + v], c);
6762306a36Sopenharmony_ci				cpu_set_vpe_id(&cpu_data[nvpes + v], v);
6862306a36Sopenharmony_ci			}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci			nvpes += core_vpes;
7162306a36Sopenharmony_ci		}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci		pr_cont("}");
7462306a36Sopenharmony_ci	}
7562306a36Sopenharmony_ci	pr_cont(" total %u\n", nvpes);
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	/* Indicate present CPUs (CPU being synonymous with VPE) */
7862306a36Sopenharmony_ci	for (v = 0; v < min_t(unsigned, nvpes, NR_CPUS); v++) {
7962306a36Sopenharmony_ci		set_cpu_possible(v, cpu_cluster(&cpu_data[v]) == 0);
8062306a36Sopenharmony_ci		set_cpu_present(v, cpu_cluster(&cpu_data[v]) == 0);
8162306a36Sopenharmony_ci		__cpu_number_map[v] = v;
8262306a36Sopenharmony_ci		__cpu_logical_map[v] = v;
8362306a36Sopenharmony_ci	}
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	/* Set a coherent default CCA (CWB) */
8662306a36Sopenharmony_ci	change_c0_config(CONF_CM_CMASK, 0x5);
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	/* Core 0 is powered up (we're running on it) */
8962306a36Sopenharmony_ci	bitmap_set(core_power, 0, 1);
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	/* Initialise core 0 */
9262306a36Sopenharmony_ci	mips_cps_core_init();
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	/* Make core 0 coherent with everything */
9562306a36Sopenharmony_ci	write_gcr_cl_coherence(0xff);
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	if (mips_cm_revision() >= CM_REV_CM3) {
9862306a36Sopenharmony_ci		core_entry = CKSEG1ADDR((unsigned long)mips_cps_core_entry);
9962306a36Sopenharmony_ci		write_gcr_bev_base(core_entry);
10062306a36Sopenharmony_ci	}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci#ifdef CONFIG_MIPS_MT_FPAFF
10362306a36Sopenharmony_ci	/* If we have an FPU, enroll ourselves in the FPU-full mask */
10462306a36Sopenharmony_ci	if (cpu_has_fpu)
10562306a36Sopenharmony_ci		cpumask_set_cpu(0, &mt_fpu_cpumask);
10662306a36Sopenharmony_ci#endif /* CONFIG_MIPS_MT_FPAFF */
10762306a36Sopenharmony_ci}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_cistatic void __init cps_prepare_cpus(unsigned int max_cpus)
11062306a36Sopenharmony_ci{
11162306a36Sopenharmony_ci	unsigned ncores, core_vpes, c, cca;
11262306a36Sopenharmony_ci	bool cca_unsuitable, cores_limited;
11362306a36Sopenharmony_ci	u32 *entry_code;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	mips_mt_set_cpuoptions();
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	/* Detect whether the CCA is unsuited to multi-core SMP */
11862306a36Sopenharmony_ci	cca = read_c0_config() & CONF_CM_CMASK;
11962306a36Sopenharmony_ci	switch (cca) {
12062306a36Sopenharmony_ci	case 0x4: /* CWBE */
12162306a36Sopenharmony_ci	case 0x5: /* CWB */
12262306a36Sopenharmony_ci		/* The CCA is coherent, multi-core is fine */
12362306a36Sopenharmony_ci		cca_unsuitable = false;
12462306a36Sopenharmony_ci		break;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	default:
12762306a36Sopenharmony_ci		/* CCA is not coherent, multi-core is not usable */
12862306a36Sopenharmony_ci		cca_unsuitable = true;
12962306a36Sopenharmony_ci	}
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	/* Warn the user if the CCA prevents multi-core */
13262306a36Sopenharmony_ci	cores_limited = false;
13362306a36Sopenharmony_ci	if (cca_unsuitable || cpu_has_dc_aliases) {
13462306a36Sopenharmony_ci		for_each_present_cpu(c) {
13562306a36Sopenharmony_ci			if (cpus_are_siblings(smp_processor_id(), c))
13662306a36Sopenharmony_ci				continue;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci			set_cpu_present(c, false);
13962306a36Sopenharmony_ci			cores_limited = true;
14062306a36Sopenharmony_ci		}
14162306a36Sopenharmony_ci	}
14262306a36Sopenharmony_ci	if (cores_limited)
14362306a36Sopenharmony_ci		pr_warn("Using only one core due to %s%s%s\n",
14462306a36Sopenharmony_ci			cca_unsuitable ? "unsuitable CCA" : "",
14562306a36Sopenharmony_ci			(cca_unsuitable && cpu_has_dc_aliases) ? " & " : "",
14662306a36Sopenharmony_ci			cpu_has_dc_aliases ? "dcache aliasing" : "");
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	/*
14962306a36Sopenharmony_ci	 * Patch the start of mips_cps_core_entry to provide:
15062306a36Sopenharmony_ci	 *
15162306a36Sopenharmony_ci	 * s0 = kseg0 CCA
15262306a36Sopenharmony_ci	 */
15362306a36Sopenharmony_ci	entry_code = (u32 *)&mips_cps_core_entry;
15462306a36Sopenharmony_ci	uasm_i_addiu(&entry_code, 16, 0, cca);
15562306a36Sopenharmony_ci	UASM_i_LA(&entry_code, 17, (long)mips_gcr_base);
15662306a36Sopenharmony_ci	BUG_ON((void *)entry_code > (void *)&mips_cps_core_entry_patch_end);
15762306a36Sopenharmony_ci	blast_dcache_range((unsigned long)&mips_cps_core_entry,
15862306a36Sopenharmony_ci			   (unsigned long)entry_code);
15962306a36Sopenharmony_ci	bc_wback_inv((unsigned long)&mips_cps_core_entry,
16062306a36Sopenharmony_ci		     (void *)entry_code - (void *)&mips_cps_core_entry);
16162306a36Sopenharmony_ci	__sync();
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	/* Allocate core boot configuration structs */
16462306a36Sopenharmony_ci	ncores = mips_cps_numcores(0);
16562306a36Sopenharmony_ci	mips_cps_core_bootcfg = kcalloc(ncores, sizeof(*mips_cps_core_bootcfg),
16662306a36Sopenharmony_ci					GFP_KERNEL);
16762306a36Sopenharmony_ci	if (!mips_cps_core_bootcfg) {
16862306a36Sopenharmony_ci		pr_err("Failed to allocate boot config for %u cores\n", ncores);
16962306a36Sopenharmony_ci		goto err_out;
17062306a36Sopenharmony_ci	}
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	/* Allocate VPE boot configuration structs */
17362306a36Sopenharmony_ci	for (c = 0; c < ncores; c++) {
17462306a36Sopenharmony_ci		core_vpes = core_vpe_count(0, c);
17562306a36Sopenharmony_ci		mips_cps_core_bootcfg[c].vpe_config = kcalloc(core_vpes,
17662306a36Sopenharmony_ci				sizeof(*mips_cps_core_bootcfg[c].vpe_config),
17762306a36Sopenharmony_ci				GFP_KERNEL);
17862306a36Sopenharmony_ci		if (!mips_cps_core_bootcfg[c].vpe_config) {
17962306a36Sopenharmony_ci			pr_err("Failed to allocate %u VPE boot configs\n",
18062306a36Sopenharmony_ci			       core_vpes);
18162306a36Sopenharmony_ci			goto err_out;
18262306a36Sopenharmony_ci		}
18362306a36Sopenharmony_ci	}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	/* Mark this CPU as booted */
18662306a36Sopenharmony_ci	atomic_set(&mips_cps_core_bootcfg[cpu_core(&current_cpu_data)].vpe_mask,
18762306a36Sopenharmony_ci		   1 << cpu_vpe_id(&current_cpu_data));
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	return;
19062306a36Sopenharmony_cierr_out:
19162306a36Sopenharmony_ci	/* Clean up allocations */
19262306a36Sopenharmony_ci	if (mips_cps_core_bootcfg) {
19362306a36Sopenharmony_ci		for (c = 0; c < ncores; c++)
19462306a36Sopenharmony_ci			kfree(mips_cps_core_bootcfg[c].vpe_config);
19562306a36Sopenharmony_ci		kfree(mips_cps_core_bootcfg);
19662306a36Sopenharmony_ci		mips_cps_core_bootcfg = NULL;
19762306a36Sopenharmony_ci	}
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	/* Effectively disable SMP by declaring CPUs not present */
20062306a36Sopenharmony_ci	for_each_possible_cpu(c) {
20162306a36Sopenharmony_ci		if (c == 0)
20262306a36Sopenharmony_ci			continue;
20362306a36Sopenharmony_ci		set_cpu_present(c, false);
20462306a36Sopenharmony_ci	}
20562306a36Sopenharmony_ci}
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_cistatic void boot_core(unsigned int core, unsigned int vpe_id)
20862306a36Sopenharmony_ci{
20962306a36Sopenharmony_ci	u32 stat, seq_state;
21062306a36Sopenharmony_ci	unsigned timeout;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	/* Select the appropriate core */
21362306a36Sopenharmony_ci	mips_cm_lock_other(0, core, 0, CM_GCR_Cx_OTHER_BLOCK_LOCAL);
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	/* Set its reset vector */
21662306a36Sopenharmony_ci	write_gcr_co_reset_base(CKSEG1ADDR((unsigned long)mips_cps_core_entry));
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	/* Ensure its coherency is disabled */
21962306a36Sopenharmony_ci	write_gcr_co_coherence(0);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	/* Start it with the legacy memory map and exception base */
22262306a36Sopenharmony_ci	write_gcr_co_reset_ext_base(CM_GCR_Cx_RESET_EXT_BASE_UEB);
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	/* Ensure the core can access the GCRs */
22562306a36Sopenharmony_ci	set_gcr_access(1 << core);
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	if (mips_cpc_present()) {
22862306a36Sopenharmony_ci		/* Reset the core */
22962306a36Sopenharmony_ci		mips_cpc_lock_other(core);
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci		if (mips_cm_revision() >= CM_REV_CM3) {
23262306a36Sopenharmony_ci			/* Run only the requested VP following the reset */
23362306a36Sopenharmony_ci			write_cpc_co_vp_stop(0xf);
23462306a36Sopenharmony_ci			write_cpc_co_vp_run(1 << vpe_id);
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci			/*
23762306a36Sopenharmony_ci			 * Ensure that the VP_RUN register is written before the
23862306a36Sopenharmony_ci			 * core leaves reset.
23962306a36Sopenharmony_ci			 */
24062306a36Sopenharmony_ci			wmb();
24162306a36Sopenharmony_ci		}
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci		write_cpc_co_cmd(CPC_Cx_CMD_RESET);
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci		timeout = 100;
24662306a36Sopenharmony_ci		while (true) {
24762306a36Sopenharmony_ci			stat = read_cpc_co_stat_conf();
24862306a36Sopenharmony_ci			seq_state = stat & CPC_Cx_STAT_CONF_SEQSTATE;
24962306a36Sopenharmony_ci			seq_state >>= __ffs(CPC_Cx_STAT_CONF_SEQSTATE);
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci			/* U6 == coherent execution, ie. the core is up */
25262306a36Sopenharmony_ci			if (seq_state == CPC_Cx_STAT_CONF_SEQSTATE_U6)
25362306a36Sopenharmony_ci				break;
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci			/* Delay a little while before we start warning */
25662306a36Sopenharmony_ci			if (timeout) {
25762306a36Sopenharmony_ci				timeout--;
25862306a36Sopenharmony_ci				mdelay(10);
25962306a36Sopenharmony_ci				continue;
26062306a36Sopenharmony_ci			}
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci			pr_warn("Waiting for core %u to start... STAT_CONF=0x%x\n",
26362306a36Sopenharmony_ci				core, stat);
26462306a36Sopenharmony_ci			mdelay(1000);
26562306a36Sopenharmony_ci		}
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci		mips_cpc_unlock_other();
26862306a36Sopenharmony_ci	} else {
26962306a36Sopenharmony_ci		/* Take the core out of reset */
27062306a36Sopenharmony_ci		write_gcr_co_reset_release(0);
27162306a36Sopenharmony_ci	}
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	mips_cm_unlock_other();
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	/* The core is now powered up */
27662306a36Sopenharmony_ci	bitmap_set(core_power, core, 1);
27762306a36Sopenharmony_ci}
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_cistatic void remote_vpe_boot(void *dummy)
28062306a36Sopenharmony_ci{
28162306a36Sopenharmony_ci	unsigned core = cpu_core(&current_cpu_data);
28262306a36Sopenharmony_ci	struct core_boot_config *core_cfg = &mips_cps_core_bootcfg[core];
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	mips_cps_boot_vpes(core_cfg, cpu_vpe_id(&current_cpu_data));
28562306a36Sopenharmony_ci}
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_cistatic int cps_boot_secondary(int cpu, struct task_struct *idle)
28862306a36Sopenharmony_ci{
28962306a36Sopenharmony_ci	unsigned core = cpu_core(&cpu_data[cpu]);
29062306a36Sopenharmony_ci	unsigned vpe_id = cpu_vpe_id(&cpu_data[cpu]);
29162306a36Sopenharmony_ci	struct core_boot_config *core_cfg = &mips_cps_core_bootcfg[core];
29262306a36Sopenharmony_ci	struct vpe_boot_config *vpe_cfg = &core_cfg->vpe_config[vpe_id];
29362306a36Sopenharmony_ci	unsigned long core_entry;
29462306a36Sopenharmony_ci	unsigned int remote;
29562306a36Sopenharmony_ci	int err;
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci	/* We don't yet support booting CPUs in other clusters */
29862306a36Sopenharmony_ci	if (cpu_cluster(&cpu_data[cpu]) != cpu_cluster(&raw_current_cpu_data))
29962306a36Sopenharmony_ci		return -ENOSYS;
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ci	vpe_cfg->pc = (unsigned long)&smp_bootstrap;
30262306a36Sopenharmony_ci	vpe_cfg->sp = __KSTK_TOS(idle);
30362306a36Sopenharmony_ci	vpe_cfg->gp = (unsigned long)task_thread_info(idle);
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci	atomic_or(1 << cpu_vpe_id(&cpu_data[cpu]), &core_cfg->vpe_mask);
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	preempt_disable();
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci	if (!test_bit(core, core_power)) {
31062306a36Sopenharmony_ci		/* Boot a VPE on a powered down core */
31162306a36Sopenharmony_ci		boot_core(core, vpe_id);
31262306a36Sopenharmony_ci		goto out;
31362306a36Sopenharmony_ci	}
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci	if (cpu_has_vp) {
31662306a36Sopenharmony_ci		mips_cm_lock_other(0, core, vpe_id, CM_GCR_Cx_OTHER_BLOCK_LOCAL);
31762306a36Sopenharmony_ci		core_entry = CKSEG1ADDR((unsigned long)mips_cps_core_entry);
31862306a36Sopenharmony_ci		write_gcr_co_reset_base(core_entry);
31962306a36Sopenharmony_ci		mips_cm_unlock_other();
32062306a36Sopenharmony_ci	}
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci	if (!cpus_are_siblings(cpu, smp_processor_id())) {
32362306a36Sopenharmony_ci		/* Boot a VPE on another powered up core */
32462306a36Sopenharmony_ci		for (remote = 0; remote < NR_CPUS; remote++) {
32562306a36Sopenharmony_ci			if (!cpus_are_siblings(cpu, remote))
32662306a36Sopenharmony_ci				continue;
32762306a36Sopenharmony_ci			if (cpu_online(remote))
32862306a36Sopenharmony_ci				break;
32962306a36Sopenharmony_ci		}
33062306a36Sopenharmony_ci		if (remote >= NR_CPUS) {
33162306a36Sopenharmony_ci			pr_crit("No online CPU in core %u to start CPU%d\n",
33262306a36Sopenharmony_ci				core, cpu);
33362306a36Sopenharmony_ci			goto out;
33462306a36Sopenharmony_ci		}
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci		err = smp_call_function_single(remote, remote_vpe_boot,
33762306a36Sopenharmony_ci					       NULL, 1);
33862306a36Sopenharmony_ci		if (err)
33962306a36Sopenharmony_ci			panic("Failed to call remote CPU\n");
34062306a36Sopenharmony_ci		goto out;
34162306a36Sopenharmony_ci	}
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci	BUG_ON(!cpu_has_mipsmt && !cpu_has_vp);
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ci	/* Boot a VPE on this core */
34662306a36Sopenharmony_ci	mips_cps_boot_vpes(core_cfg, vpe_id);
34762306a36Sopenharmony_ciout:
34862306a36Sopenharmony_ci	preempt_enable();
34962306a36Sopenharmony_ci	return 0;
35062306a36Sopenharmony_ci}
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_cistatic void cps_init_secondary(void)
35362306a36Sopenharmony_ci{
35462306a36Sopenharmony_ci	int core = cpu_core(&current_cpu_data);
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci	/* Disable MT - we only want to run 1 TC per VPE */
35762306a36Sopenharmony_ci	if (cpu_has_mipsmt)
35862306a36Sopenharmony_ci		dmt();
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_ci	if (mips_cm_revision() >= CM_REV_CM3) {
36162306a36Sopenharmony_ci		unsigned int ident = read_gic_vl_ident();
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci		/*
36462306a36Sopenharmony_ci		 * Ensure that our calculation of the VP ID matches up with
36562306a36Sopenharmony_ci		 * what the GIC reports, otherwise we'll have configured
36662306a36Sopenharmony_ci		 * interrupts incorrectly.
36762306a36Sopenharmony_ci		 */
36862306a36Sopenharmony_ci		BUG_ON(ident != mips_cm_vp_id(smp_processor_id()));
36962306a36Sopenharmony_ci	}
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci	if (core > 0 && !read_gcr_cl_coherence())
37262306a36Sopenharmony_ci		pr_warn("Core %u is not in coherent domain\n", core);
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci	if (cpu_has_veic)
37562306a36Sopenharmony_ci		clear_c0_status(ST0_IM);
37662306a36Sopenharmony_ci	else
37762306a36Sopenharmony_ci		change_c0_status(ST0_IM, STATUSF_IP2 | STATUSF_IP3 |
37862306a36Sopenharmony_ci					 STATUSF_IP4 | STATUSF_IP5 |
37962306a36Sopenharmony_ci					 STATUSF_IP6 | STATUSF_IP7);
38062306a36Sopenharmony_ci}
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_cistatic void cps_smp_finish(void)
38362306a36Sopenharmony_ci{
38462306a36Sopenharmony_ci	write_c0_compare(read_c0_count() + (8 * mips_hpt_frequency / HZ));
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci#ifdef CONFIG_MIPS_MT_FPAFF
38762306a36Sopenharmony_ci	/* If we have an FPU, enroll ourselves in the FPU-full mask */
38862306a36Sopenharmony_ci	if (cpu_has_fpu)
38962306a36Sopenharmony_ci		cpumask_set_cpu(smp_processor_id(), &mt_fpu_cpumask);
39062306a36Sopenharmony_ci#endif /* CONFIG_MIPS_MT_FPAFF */
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci	local_irq_enable();
39362306a36Sopenharmony_ci}
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci#if defined(CONFIG_HOTPLUG_CPU) || defined(CONFIG_KEXEC)
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_cienum cpu_death {
39862306a36Sopenharmony_ci	CPU_DEATH_HALT,
39962306a36Sopenharmony_ci	CPU_DEATH_POWER,
40062306a36Sopenharmony_ci};
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_cistatic void cps_shutdown_this_cpu(enum cpu_death death)
40362306a36Sopenharmony_ci{
40462306a36Sopenharmony_ci	unsigned int cpu, core, vpe_id;
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_ci	cpu = smp_processor_id();
40762306a36Sopenharmony_ci	core = cpu_core(&cpu_data[cpu]);
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	if (death == CPU_DEATH_HALT) {
41062306a36Sopenharmony_ci		vpe_id = cpu_vpe_id(&cpu_data[cpu]);
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ci		pr_debug("Halting core %d VP%d\n", core, vpe_id);
41362306a36Sopenharmony_ci		if (cpu_has_mipsmt) {
41462306a36Sopenharmony_ci			/* Halt this TC */
41562306a36Sopenharmony_ci			write_c0_tchalt(TCHALT_H);
41662306a36Sopenharmony_ci			instruction_hazard();
41762306a36Sopenharmony_ci		} else if (cpu_has_vp) {
41862306a36Sopenharmony_ci			write_cpc_cl_vp_stop(1 << vpe_id);
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_ci			/* Ensure that the VP_STOP register is written */
42162306a36Sopenharmony_ci			wmb();
42262306a36Sopenharmony_ci		}
42362306a36Sopenharmony_ci	} else {
42462306a36Sopenharmony_ci		if (IS_ENABLED(CONFIG_HOTPLUG_CPU)) {
42562306a36Sopenharmony_ci			pr_debug("Gating power to core %d\n", core);
42662306a36Sopenharmony_ci			/* Power down the core */
42762306a36Sopenharmony_ci			cps_pm_enter_state(CPS_PM_POWER_GATED);
42862306a36Sopenharmony_ci		}
42962306a36Sopenharmony_ci	}
43062306a36Sopenharmony_ci}
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci#ifdef CONFIG_KEXEC
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_cistatic void cps_kexec_nonboot_cpu(void)
43562306a36Sopenharmony_ci{
43662306a36Sopenharmony_ci	if (cpu_has_mipsmt || cpu_has_vp)
43762306a36Sopenharmony_ci		cps_shutdown_this_cpu(CPU_DEATH_HALT);
43862306a36Sopenharmony_ci	else
43962306a36Sopenharmony_ci		cps_shutdown_this_cpu(CPU_DEATH_POWER);
44062306a36Sopenharmony_ci}
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_ci#endif /* CONFIG_KEXEC */
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci#endif /* CONFIG_HOTPLUG_CPU || CONFIG_KEXEC */
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_ci#ifdef CONFIG_HOTPLUG_CPU
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_cistatic int cps_cpu_disable(void)
44962306a36Sopenharmony_ci{
45062306a36Sopenharmony_ci	unsigned cpu = smp_processor_id();
45162306a36Sopenharmony_ci	struct core_boot_config *core_cfg;
45262306a36Sopenharmony_ci
45362306a36Sopenharmony_ci	if (!cps_pm_support_state(CPS_PM_POWER_GATED))
45462306a36Sopenharmony_ci		return -EINVAL;
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	core_cfg = &mips_cps_core_bootcfg[cpu_core(&current_cpu_data)];
45762306a36Sopenharmony_ci	atomic_sub(1 << cpu_vpe_id(&current_cpu_data), &core_cfg->vpe_mask);
45862306a36Sopenharmony_ci	smp_mb__after_atomic();
45962306a36Sopenharmony_ci	set_cpu_online(cpu, false);
46062306a36Sopenharmony_ci	calculate_cpu_foreign_map();
46162306a36Sopenharmony_ci	irq_migrate_all_off_this_cpu();
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_ci	return 0;
46462306a36Sopenharmony_ci}
46562306a36Sopenharmony_ci
46662306a36Sopenharmony_cistatic unsigned cpu_death_sibling;
46762306a36Sopenharmony_cistatic enum cpu_death cpu_death;
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_civoid play_dead(void)
47062306a36Sopenharmony_ci{
47162306a36Sopenharmony_ci	unsigned int cpu;
47262306a36Sopenharmony_ci
47362306a36Sopenharmony_ci	local_irq_disable();
47462306a36Sopenharmony_ci	idle_task_exit();
47562306a36Sopenharmony_ci	cpu = smp_processor_id();
47662306a36Sopenharmony_ci	cpu_death = CPU_DEATH_POWER;
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_ci	pr_debug("CPU%d going offline\n", cpu);
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_ci	if (cpu_has_mipsmt || cpu_has_vp) {
48162306a36Sopenharmony_ci		/* Look for another online VPE within the core */
48262306a36Sopenharmony_ci		for_each_online_cpu(cpu_death_sibling) {
48362306a36Sopenharmony_ci			if (!cpus_are_siblings(cpu, cpu_death_sibling))
48462306a36Sopenharmony_ci				continue;
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_ci			/*
48762306a36Sopenharmony_ci			 * There is an online VPE within the core. Just halt
48862306a36Sopenharmony_ci			 * this TC and leave the core alone.
48962306a36Sopenharmony_ci			 */
49062306a36Sopenharmony_ci			cpu_death = CPU_DEATH_HALT;
49162306a36Sopenharmony_ci			break;
49262306a36Sopenharmony_ci		}
49362306a36Sopenharmony_ci	}
49462306a36Sopenharmony_ci
49562306a36Sopenharmony_ci	cpuhp_ap_report_dead();
49662306a36Sopenharmony_ci
49762306a36Sopenharmony_ci	cps_shutdown_this_cpu(cpu_death);
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_ci	/* This should never be reached */
50062306a36Sopenharmony_ci	panic("Failed to offline CPU %u", cpu);
50162306a36Sopenharmony_ci}
50262306a36Sopenharmony_ci
50362306a36Sopenharmony_cistatic void wait_for_sibling_halt(void *ptr_cpu)
50462306a36Sopenharmony_ci{
50562306a36Sopenharmony_ci	unsigned cpu = (unsigned long)ptr_cpu;
50662306a36Sopenharmony_ci	unsigned vpe_id = cpu_vpe_id(&cpu_data[cpu]);
50762306a36Sopenharmony_ci	unsigned halted;
50862306a36Sopenharmony_ci	unsigned long flags;
50962306a36Sopenharmony_ci
51062306a36Sopenharmony_ci	do {
51162306a36Sopenharmony_ci		local_irq_save(flags);
51262306a36Sopenharmony_ci		settc(vpe_id);
51362306a36Sopenharmony_ci		halted = read_tc_c0_tchalt();
51462306a36Sopenharmony_ci		local_irq_restore(flags);
51562306a36Sopenharmony_ci	} while (!(halted & TCHALT_H));
51662306a36Sopenharmony_ci}
51762306a36Sopenharmony_ci
51862306a36Sopenharmony_cistatic void cps_cpu_die(unsigned int cpu) { }
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_cistatic void cps_cleanup_dead_cpu(unsigned cpu)
52162306a36Sopenharmony_ci{
52262306a36Sopenharmony_ci	unsigned core = cpu_core(&cpu_data[cpu]);
52362306a36Sopenharmony_ci	unsigned int vpe_id = cpu_vpe_id(&cpu_data[cpu]);
52462306a36Sopenharmony_ci	ktime_t fail_time;
52562306a36Sopenharmony_ci	unsigned stat;
52662306a36Sopenharmony_ci	int err;
52762306a36Sopenharmony_ci
52862306a36Sopenharmony_ci	/*
52962306a36Sopenharmony_ci	 * Now wait for the CPU to actually offline. Without doing this that
53062306a36Sopenharmony_ci	 * offlining may race with one or more of:
53162306a36Sopenharmony_ci	 *
53262306a36Sopenharmony_ci	 *   - Onlining the CPU again.
53362306a36Sopenharmony_ci	 *   - Powering down the core if another VPE within it is offlined.
53462306a36Sopenharmony_ci	 *   - A sibling VPE entering a non-coherent state.
53562306a36Sopenharmony_ci	 *
53662306a36Sopenharmony_ci	 * In the non-MT halt case (ie. infinite loop) the CPU is doing nothing
53762306a36Sopenharmony_ci	 * with which we could race, so do nothing.
53862306a36Sopenharmony_ci	 */
53962306a36Sopenharmony_ci	if (cpu_death == CPU_DEATH_POWER) {
54062306a36Sopenharmony_ci		/*
54162306a36Sopenharmony_ci		 * Wait for the core to enter a powered down or clock gated
54262306a36Sopenharmony_ci		 * state, the latter happening when a JTAG probe is connected
54362306a36Sopenharmony_ci		 * in which case the CPC will refuse to power down the core.
54462306a36Sopenharmony_ci		 */
54562306a36Sopenharmony_ci		fail_time = ktime_add_ms(ktime_get(), 2000);
54662306a36Sopenharmony_ci		do {
54762306a36Sopenharmony_ci			mips_cm_lock_other(0, core, 0, CM_GCR_Cx_OTHER_BLOCK_LOCAL);
54862306a36Sopenharmony_ci			mips_cpc_lock_other(core);
54962306a36Sopenharmony_ci			stat = read_cpc_co_stat_conf();
55062306a36Sopenharmony_ci			stat &= CPC_Cx_STAT_CONF_SEQSTATE;
55162306a36Sopenharmony_ci			stat >>= __ffs(CPC_Cx_STAT_CONF_SEQSTATE);
55262306a36Sopenharmony_ci			mips_cpc_unlock_other();
55362306a36Sopenharmony_ci			mips_cm_unlock_other();
55462306a36Sopenharmony_ci
55562306a36Sopenharmony_ci			if (stat == CPC_Cx_STAT_CONF_SEQSTATE_D0 ||
55662306a36Sopenharmony_ci			    stat == CPC_Cx_STAT_CONF_SEQSTATE_D2 ||
55762306a36Sopenharmony_ci			    stat == CPC_Cx_STAT_CONF_SEQSTATE_U2)
55862306a36Sopenharmony_ci				break;
55962306a36Sopenharmony_ci
56062306a36Sopenharmony_ci			/*
56162306a36Sopenharmony_ci			 * The core ought to have powered down, but didn't &
56262306a36Sopenharmony_ci			 * now we don't really know what state it's in. It's
56362306a36Sopenharmony_ci			 * likely that its _pwr_up pin has been wired to logic
56462306a36Sopenharmony_ci			 * 1 & it powered back up as soon as we powered it
56562306a36Sopenharmony_ci			 * down...
56662306a36Sopenharmony_ci			 *
56762306a36Sopenharmony_ci			 * The best we can do is warn the user & continue in
56862306a36Sopenharmony_ci			 * the hope that the core is doing nothing harmful &
56962306a36Sopenharmony_ci			 * might behave properly if we online it later.
57062306a36Sopenharmony_ci			 */
57162306a36Sopenharmony_ci			if (WARN(ktime_after(ktime_get(), fail_time),
57262306a36Sopenharmony_ci				 "CPU%u hasn't powered down, seq. state %u\n",
57362306a36Sopenharmony_ci				 cpu, stat))
57462306a36Sopenharmony_ci				break;
57562306a36Sopenharmony_ci		} while (1);
57662306a36Sopenharmony_ci
57762306a36Sopenharmony_ci		/* Indicate the core is powered off */
57862306a36Sopenharmony_ci		bitmap_clear(core_power, core, 1);
57962306a36Sopenharmony_ci	} else if (cpu_has_mipsmt) {
58062306a36Sopenharmony_ci		/*
58162306a36Sopenharmony_ci		 * Have a CPU with access to the offlined CPUs registers wait
58262306a36Sopenharmony_ci		 * for its TC to halt.
58362306a36Sopenharmony_ci		 */
58462306a36Sopenharmony_ci		err = smp_call_function_single(cpu_death_sibling,
58562306a36Sopenharmony_ci					       wait_for_sibling_halt,
58662306a36Sopenharmony_ci					       (void *)(unsigned long)cpu, 1);
58762306a36Sopenharmony_ci		if (err)
58862306a36Sopenharmony_ci			panic("Failed to call remote sibling CPU\n");
58962306a36Sopenharmony_ci	} else if (cpu_has_vp) {
59062306a36Sopenharmony_ci		do {
59162306a36Sopenharmony_ci			mips_cm_lock_other(0, core, vpe_id, CM_GCR_Cx_OTHER_BLOCK_LOCAL);
59262306a36Sopenharmony_ci			stat = read_cpc_co_vp_running();
59362306a36Sopenharmony_ci			mips_cm_unlock_other();
59462306a36Sopenharmony_ci		} while (stat & (1 << vpe_id));
59562306a36Sopenharmony_ci	}
59662306a36Sopenharmony_ci}
59762306a36Sopenharmony_ci
59862306a36Sopenharmony_ci#endif /* CONFIG_HOTPLUG_CPU */
59962306a36Sopenharmony_ci
60062306a36Sopenharmony_cistatic const struct plat_smp_ops cps_smp_ops = {
60162306a36Sopenharmony_ci	.smp_setup		= cps_smp_setup,
60262306a36Sopenharmony_ci	.prepare_cpus		= cps_prepare_cpus,
60362306a36Sopenharmony_ci	.boot_secondary		= cps_boot_secondary,
60462306a36Sopenharmony_ci	.init_secondary		= cps_init_secondary,
60562306a36Sopenharmony_ci	.smp_finish		= cps_smp_finish,
60662306a36Sopenharmony_ci	.send_ipi_single	= mips_smp_send_ipi_single,
60762306a36Sopenharmony_ci	.send_ipi_mask		= mips_smp_send_ipi_mask,
60862306a36Sopenharmony_ci#ifdef CONFIG_HOTPLUG_CPU
60962306a36Sopenharmony_ci	.cpu_disable		= cps_cpu_disable,
61062306a36Sopenharmony_ci	.cpu_die		= cps_cpu_die,
61162306a36Sopenharmony_ci	.cleanup_dead_cpu	= cps_cleanup_dead_cpu,
61262306a36Sopenharmony_ci#endif
61362306a36Sopenharmony_ci#ifdef CONFIG_KEXEC
61462306a36Sopenharmony_ci	.kexec_nonboot_cpu	= cps_kexec_nonboot_cpu,
61562306a36Sopenharmony_ci#endif
61662306a36Sopenharmony_ci};
61762306a36Sopenharmony_ci
61862306a36Sopenharmony_cibool mips_cps_smp_in_use(void)
61962306a36Sopenharmony_ci{
62062306a36Sopenharmony_ci	extern const struct plat_smp_ops *mp_ops;
62162306a36Sopenharmony_ci	return mp_ops == &cps_smp_ops;
62262306a36Sopenharmony_ci}
62362306a36Sopenharmony_ci
62462306a36Sopenharmony_ciint register_cps_smp_ops(void)
62562306a36Sopenharmony_ci{
62662306a36Sopenharmony_ci	if (!mips_cm_present()) {
62762306a36Sopenharmony_ci		pr_warn("MIPS CPS SMP unable to proceed without a CM\n");
62862306a36Sopenharmony_ci		return -ENODEV;
62962306a36Sopenharmony_ci	}
63062306a36Sopenharmony_ci
63162306a36Sopenharmony_ci	/* check we have a GIC - we need one for IPIs */
63262306a36Sopenharmony_ci	if (!(read_gcr_gic_status() & CM_GCR_GIC_STATUS_EX)) {
63362306a36Sopenharmony_ci		pr_warn("MIPS CPS SMP unable to proceed without a GIC\n");
63462306a36Sopenharmony_ci		return -ENODEV;
63562306a36Sopenharmony_ci	}
63662306a36Sopenharmony_ci
63762306a36Sopenharmony_ci	register_smp_ops(&cps_smp_ops);
63862306a36Sopenharmony_ci	return 0;
63962306a36Sopenharmony_ci}
640