213 lines
4.9 KiB
C
Executable File
213 lines
4.9 KiB
C
Executable File
/**
|
|
NVT SMP(Multi-Core) source file
|
|
It's used to do secondary core init.
|
|
@file platsmp.c
|
|
@ingroup
|
|
@note
|
|
Copyright Novatek Microelectronics Corp. 2017. All rights reserved.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License version 2 as
|
|
published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/device.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/io.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/of_address.h>
|
|
|
|
#include <asm/cacheflush.h>
|
|
#include <asm/smp_plat.h>
|
|
#include <asm/smp_scu.h>
|
|
|
|
#include <mach/nvt-io.h>
|
|
|
|
#include "common.h"
|
|
|
|
#ifdef CONFIG_OPTEE
|
|
#include <linux/arm-smccc.h>
|
|
#include <mach/smc_func_id/optee_smc.h>
|
|
#else
|
|
static DEFINE_SPINLOCK(boot_lock);
|
|
#endif
|
|
|
|
extern void nvt_secondary_startup(void);
|
|
|
|
/*
|
|
* Initialise the CPU possible map early - this describes the CPUs
|
|
* which may be present or become present in the system.
|
|
*/
|
|
static const struct of_device_id nvt_smp_dt_scu_match[] __initconst = {
|
|
{ .compatible = "arm,cortex-a9-scu", },
|
|
{}
|
|
};
|
|
|
|
#ifdef CONFIG_OPTEE
|
|
static void set_secondary_entry(u32 release_phys)
|
|
{
|
|
}
|
|
static void nvt_secondary_init(unsigned int cpu)
|
|
{
|
|
}
|
|
static int nvt_boot_secondary(unsigned int cpu, struct task_struct *idle)
|
|
{
|
|
struct arm_smccc_res res = {0};
|
|
|
|
// REF: generic_boot_core_release(size_t core_idx, paddr_t entry)
|
|
arm_smccc_smc(OPTEE_SMC_BOOT_SECONDARY,
|
|
cpu, /* core_idx */
|
|
0,
|
|
virt_to_phys(secondary_startup), /* jump entry */
|
|
0, 0, 0, 0,
|
|
&res);
|
|
|
|
if (OPTEE_SMC_RETURN_OK == res.a0) {
|
|
printk("OPTEE_SMC_RETURN_OK, return 0\n");
|
|
return 0;
|
|
} else {
|
|
printk("res.a0 = %ld, return -ENOSYS\n", res.a0);
|
|
return -ENOSYS;
|
|
}
|
|
}
|
|
#else //#ifdef CONFIG_OPTEE
|
|
|
|
/*
|
|
* Write the address of secondary startup into the
|
|
* system register. The nvt_secondary_startup start to check
|
|
* the pen_release value.
|
|
*/
|
|
static void set_secondary_entry(u32 release_phys)
|
|
{
|
|
void __iomem *virt = ioremap_cache(release_phys, sizeof(u32 *));
|
|
|
|
writel(virt_to_phys(nvt_secondary_startup), virt);
|
|
//printk("release_phys=0x%x, virt=0x%x, read data=0x%x\n", release_phys, virt, readl(virt));
|
|
|
|
smp_wmb();
|
|
#ifdef CONFIG_OUTER_CACHE
|
|
outer_flush_range(release_phys, release_phys + 4);
|
|
#else
|
|
__cpuc_flush_dcache_area(virt, sizeof(u32));
|
|
#endif
|
|
iounmap(virt);
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
* The kernel L2 cache is ready. So call the outer_clean_range API.
|
|
*/
|
|
static void write_pen_release(int val)
|
|
{
|
|
pen_release = val;
|
|
smp_wmb();
|
|
sync_cache_w(&pen_release);
|
|
}
|
|
|
|
static void nvt_secondary_init(unsigned int cpu)
|
|
{
|
|
/*
|
|
* let the primary processor know we're out of the
|
|
* pen, then head off into the C entry point
|
|
*/
|
|
write_pen_release(-1);
|
|
|
|
/*
|
|
* Synchronise with the boot thread.
|
|
*/
|
|
spin_lock(&boot_lock);
|
|
spin_unlock(&boot_lock);
|
|
}
|
|
|
|
static int nvt_boot_secondary(unsigned int cpu, struct task_struct *idle)
|
|
{
|
|
unsigned long timeout;
|
|
|
|
/*
|
|
* Set synchronisation state between this boot processor
|
|
* and the secondary one
|
|
*/
|
|
spin_lock(&boot_lock);
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
write_pen_release(cpu_logical_map(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 (pen_release == -1) {
|
|
break;
|
|
}
|
|
|
|
udelay(10);
|
|
}
|
|
|
|
if (pen_release != -1)
|
|
printk("pen_release: %d\n\t", pen_release);
|
|
|
|
/*
|
|
* now the secondary core is starting up let it run its
|
|
* calibrations, then wait for it to finish
|
|
*/
|
|
spin_unlock(&boot_lock);
|
|
|
|
return pen_release != -1 ? -ENOSYS : 0;
|
|
}
|
|
#endif //#ifdef CONFIG_OPTEE
|
|
|
|
static void __init nvt_ca9_smp_prepare_cpus(unsigned int max_cpus)
|
|
{
|
|
struct device_node *scu = of_find_matching_node(NULL, nvt_smp_dt_scu_match);
|
|
int cpu_count = 0;
|
|
int cpu;
|
|
|
|
if (scu)
|
|
scu_enable(of_iomap(scu, 0));
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
struct device_node *np;
|
|
u32 release_phys;
|
|
|
|
np = of_get_cpu_node(cpu, NULL);
|
|
if (!np)
|
|
continue;
|
|
if (of_property_read_u32(np, "cpu-release-addr"
|
|
, &release_phys))
|
|
continue;
|
|
|
|
if (cpu_count < max_cpus) {
|
|
set_cpu_present(cpu, true);
|
|
cpu_count++;
|
|
}
|
|
|
|
if (release_phys != 0) {
|
|
set_secondary_entry(release_phys);
|
|
}
|
|
}
|
|
}
|
|
|
|
struct smp_operations nvt_ca9_smp_ops __initdata = {
|
|
.smp_prepare_cpus = nvt_ca9_smp_prepare_cpus,
|
|
.smp_secondary_init = nvt_secondary_init,
|
|
.smp_boot_secondary = nvt_boot_secondary,
|
|
#ifdef CONFIG_HOTPLUG_CPU
|
|
.cpu_die = nvt_cpu_die,
|
|
#endif
|
|
};
|