bcm63xx_smp.c 4.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
/*
 * Broadcom BCM63138 DSL SoCs SMP support code
 *
 * Copyright (C) 2015, Broadcom Corporation
 *
 * Licensed under the terms of the GPLv2
 */

#include <linux/delay.h>
#include <linux/init.h>
#include <linux/smp.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>

#include <asm/cacheflush.h>
#include <asm/smp_scu.h>
#include <asm/smp_plat.h>
#include <asm/vfp.h>

#include "bcm63xx_smp.h"

/* Size of mapped Cortex A9 SCU address space */
#define CORTEX_A9_SCU_SIZE	0x58

/*
 * Enable the Cortex A9 Snoop Control Unit
 *
 * By the time this is called we already know there are multiple
 * cores present.  We assume we're running on a Cortex A9 processor,
 * so any trouble getting the base address register or getting the
 * SCU base is a problem.
 *
 * Return 0 if successful or an error code otherwise.
 */
static int __init scu_a9_enable(void)
{
	unsigned long config_base;
	void __iomem *scu_base;
	unsigned int i, ncores;

	if (!scu_a9_has_base()) {
		pr_err("no configuration base address register!\n");
		return -ENXIO;
	}

	/* Config base address register value is zero for uniprocessor */
	config_base = scu_a9_get_base();
	if (!config_base) {
		pr_err("hardware reports only one core\n");
		return -ENOENT;
	}

	scu_base = ioremap((phys_addr_t)config_base, CORTEX_A9_SCU_SIZE);
	if (!scu_base) {
		pr_err("failed to remap config base (%lu/%u) for SCU\n",
			config_base, CORTEX_A9_SCU_SIZE);
		return -ENOMEM;
	}

	scu_enable(scu_base);

	ncores = scu_base ? scu_get_core_count(scu_base) : 1;

	if (ncores > nr_cpu_ids) {
		pr_warn("SMP: %u cores greater than maximum (%u), clipping\n",
				ncores, nr_cpu_ids);
		ncores = nr_cpu_ids;
	}

	/* The BCM63138 SoC has two Cortex-A9 CPUs, CPU0 features a complete
	 * and fully functional VFP unit that can be used, but CPU1 does not.
	 * Since we will not be able to trap kernel-mode NEON to force
	 * migration to CPU0, just do not advertise VFP support at all.
	 *
	 * This will make vfp_init bail out and do not attempt to use VFP at
	 * all, for kernel-mode NEON, we do not want to introduce any
	 * conditionals in hot-paths, so we just restrict the system to UP.
	 */
#ifdef CONFIG_VFP
	if (ncores > 1) {
		pr_warn("SMP: secondary CPUs lack VFP unit, disabling VFP\n");
		vfp_disable();

#ifdef CONFIG_KERNEL_MODE_NEON
		WARN(1, "SMP: kernel-mode NEON enabled, restricting to UP\n");
		ncores = 1;
#endif
	}
#endif

	for (i = 0; i < ncores; i++)
		set_cpu_possible(i, true);

	iounmap(scu_base);	/* That's the last we'll need of this */

	return 0;
}

static const struct of_device_id bcm63138_bootlut_ids[] = {
	{ .compatible = "brcm,bcm63138-bootlut", },
	{ /* sentinel */ },
};

#define BOOTLUT_RESET_VECT	0x20

static int bcm63138_smp_boot_secondary(unsigned int cpu,
				       struct task_struct *idle)
{
	void __iomem *bootlut_base;
	struct device_node *dn;
	int ret = 0;
	u32 val;

	dn = of_find_matching_node(NULL, bcm63138_bootlut_ids);
	if (!dn) {
		pr_err("SMP: unable to find bcm63138 boot LUT node\n");
		return -ENODEV;
	}

	bootlut_base = of_iomap(dn, 0);
	of_node_put(dn);

	if (!bootlut_base) {
		pr_err("SMP: unable to remap boot LUT base register\n");
		return -ENOMEM;
	}

	/* Locate the secondary CPU node */
	dn = of_get_cpu_node(cpu_logical_map(cpu), NULL);
	if (!dn) {
		pr_err("SMP: failed to locate secondary CPU%d node\n", cpu);
		ret = -ENODEV;
		goto out;
	}

	/* Write the secondary init routine to the BootLUT reset vector */
	val = virt_to_phys(bcm63138_secondary_startup);
	writel_relaxed(val, bootlut_base + BOOTLUT_RESET_VECT);

	/* Power up the core, will jump straight to its reset vector when we
	 * return
	 */
	ret = bcm63xx_pmb_power_on_cpu(dn);
	if (ret)
		goto out;
out:
	iounmap(bootlut_base);

	return ret;
}

static void __init bcm63138_smp_prepare_cpus(unsigned int max_cpus)
{
	int ret;

	ret = scu_a9_enable();
	if (ret) {
		pr_warn("SMP: Cortex-A9 SCU setup failed\n");
		return;
	}
}

struct smp_operations bcm63138_smp_ops __initdata = {
	.smp_prepare_cpus	= bcm63138_smp_prepare_cpus,
	.smp_boot_secondary	= bcm63138_smp_boot_secondary,
};

CPU_METHOD_OF_DECLARE(bcm63138_smp, "brcm,bcm63138", &bcm63138_smp_ops);