|
|
@@ -0,0 +1,635 @@
|
|
|
+From a0ad1511d5805b95ac4c454d7904c670a1696055 Mon Sep 17 00:00:00 2001
|
|
|
+From: Kapil Hali <[email protected]>
|
|
|
+Date: Wed, 14 Oct 2015 13:47:00 -0400
|
|
|
+Subject: [PATCH] ARM: BCM: Add SMP support for Broadcom NSP
|
|
|
+
|
|
|
+Add SMP support for Broadcom's Northstar Plus SoC,
|
|
|
+cpu enable method and pen_release procedures. This
|
|
|
+changes also consolidates iProc family's - BCM NSP
|
|
|
+and BCM Kona, SMP handling in a common file.
|
|
|
+
|
|
|
+Northstar Plus SoC is based on ARM Cortex-A9
|
|
|
+revision r3p0 which requires configuration for ARM
|
|
|
+Errata 764369 for SMP. This change adds the needed
|
|
|
+configuration option.
|
|
|
+
|
|
|
+Signed-off-by: Kapil Hali <[email protected]>
|
|
|
+---
|
|
|
+ arch/arm/mach-bcm/Makefile | 2 +-
|
|
|
+ arch/arm/mach-bcm/bcm_nsp.h | 19 +++
|
|
|
+ arch/arm/mach-bcm/headsmp.S | 37 +++++
|
|
|
+ arch/arm/mach-bcm/kona_smp.c | 202 ---------------------------
|
|
|
+ arch/arm/mach-bcm/platsmp.c | 326 +++++++++++++++++++++++++++++++++++++++++++
|
|
|
+ 5 files changed, 383 insertions(+), 203 deletions(-)
|
|
|
+ create mode 100644 arch/arm/mach-bcm/bcm_nsp.h
|
|
|
+ create mode 100644 arch/arm/mach-bcm/headsmp.S
|
|
|
+ delete mode 100644 arch/arm/mach-bcm/kona_smp.c
|
|
|
+ create mode 100644 arch/arm/mach-bcm/platsmp.c
|
|
|
+
|
|
|
+--- a/arch/arm/mach-bcm/Makefile
|
|
|
++++ b/arch/arm/mach-bcm/Makefile
|
|
|
+@@ -20,7 +20,7 @@ obj-$(CONFIG_ARCH_BCM_281XX) += board_bc
|
|
|
+ obj-$(CONFIG_ARCH_BCM_21664) += board_bcm21664.o
|
|
|
+
|
|
|
+ # BCM281XX and BCM21664 SMP support
|
|
|
+-obj-$(CONFIG_ARCH_BCM_MOBILE_SMP) += kona_smp.o
|
|
|
++obj-$(CONFIG_ARCH_BCM_MOBILE_SMP) += platsmp.o
|
|
|
+
|
|
|
+ # BCM281XX and BCM21664 L2 cache control
|
|
|
+ obj-$(CONFIG_ARCH_BCM_MOBILE_L2_CACHE) += kona_l2_cache.o
|
|
|
+--- /dev/null
|
|
|
++++ b/arch/arm/mach-bcm/bcm_nsp.h
|
|
|
+@@ -0,0 +1,19 @@
|
|
|
++/*
|
|
|
++ * Copyright (C) 2015 Broadcom Corporation
|
|
|
++ *
|
|
|
++ * This program is free software; you can redistribute it and/or
|
|
|
++ * modify it under the terms of the GNU General Public License as
|
|
|
++ * published by the Free Software Foundation version 2.
|
|
|
++ *
|
|
|
++ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
|
|
|
++ * kind, whether express or implied; without even the implied warranty
|
|
|
++ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
++ * GNU General Public License for more details.
|
|
|
++ */
|
|
|
++
|
|
|
++#ifndef __BCM_NSP_H
|
|
|
++#define __BCM_NSP_H
|
|
|
++
|
|
|
++extern void nsp_secondary_startup(void);
|
|
|
++
|
|
|
++#endif /* __BCM_NSP_H */
|
|
|
+--- /dev/null
|
|
|
++++ b/arch/arm/mach-bcm/headsmp.S
|
|
|
+@@ -0,0 +1,37 @@
|
|
|
++/*
|
|
|
++ * Copyright (C) 2015 Broadcom Corporation
|
|
|
++ *
|
|
|
++ * This program is free software; you can redistribute it and/or
|
|
|
++ * modify it under the terms of the GNU General Public License as
|
|
|
++ * published by the Free Software Foundation version 2.
|
|
|
++ *
|
|
|
++ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
|
|
|
++ * kind, whether express or implied; without even the implied warranty
|
|
|
++ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
++ * GNU General Public License for more details.
|
|
|
++ */
|
|
|
++
|
|
|
++#include <linux/linkage.h>
|
|
|
++
|
|
|
++/*
|
|
|
++ * iProc specific entry point for secondary CPUs. This provides
|
|
|
++ * a "holding pen" into which all secondary cores are held until
|
|
|
++ * we are ready for them to initialise.
|
|
|
++ */
|
|
|
++ENTRY(nsp_secondary_startup)
|
|
|
++ 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
|
|
|
++
|
|
|
++ b secondary_startup
|
|
|
++
|
|
|
++1: .long .
|
|
|
++ .long pen_release
|
|
|
++
|
|
|
++ENDPROC(nsp_secondary_startup)
|
|
|
+--- a/arch/arm/mach-bcm/kona_smp.c
|
|
|
++++ /dev/null
|
|
|
+@@ -1,202 +0,0 @@
|
|
|
+-/*
|
|
|
+- * Copyright (C) 2014 Broadcom Corporation
|
|
|
+- * Copyright 2014 Linaro Limited
|
|
|
+- *
|
|
|
+- * This program is free software; you can redistribute it and/or
|
|
|
+- * modify it under the terms of the GNU General Public License as
|
|
|
+- * published by the Free Software Foundation version 2.
|
|
|
+- *
|
|
|
+- * This program is distributed "as is" WITHOUT ANY WARRANTY of any
|
|
|
+- * kind, whether express or implied; without even the implied warranty
|
|
|
+- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
+- * GNU General Public License for more details.
|
|
|
+- */
|
|
|
+-
|
|
|
+-#include <linux/init.h>
|
|
|
+-#include <linux/errno.h>
|
|
|
+-#include <linux/io.h>
|
|
|
+-#include <linux/of.h>
|
|
|
+-#include <linux/sched.h>
|
|
|
+-
|
|
|
+-#include <asm/smp.h>
|
|
|
+-#include <asm/smp_plat.h>
|
|
|
+-#include <asm/smp_scu.h>
|
|
|
+-
|
|
|
+-/* Size of mapped Cortex A9 SCU address space */
|
|
|
+-#define CORTEX_A9_SCU_SIZE 0x58
|
|
|
+-
|
|
|
+-#define SECONDARY_TIMEOUT_NS NSEC_PER_MSEC /* 1 msec (in nanoseconds) */
|
|
|
+-#define BOOT_ADDR_CPUID_MASK 0x3
|
|
|
+-
|
|
|
+-/* Name of device node property defining secondary boot register location */
|
|
|
+-#define OF_SECONDARY_BOOT "secondary-boot-reg"
|
|
|
+-
|
|
|
+-/* I/O address of register used to coordinate secondary core startup */
|
|
|
+-static u32 secondary_boot;
|
|
|
+-
|
|
|
+-/*
|
|
|
+- * 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;
|
|
|
+-
|
|
|
+- 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);
|
|
|
+-
|
|
|
+- iounmap(scu_base); /* That's the last we'll need of this */
|
|
|
+-
|
|
|
+- return 0;
|
|
|
+-}
|
|
|
+-
|
|
|
+-static void __init bcm_smp_prepare_cpus(unsigned int max_cpus)
|
|
|
+-{
|
|
|
+- static cpumask_t only_cpu_0 = { CPU_BITS_CPU0 };
|
|
|
+- struct device_node *node;
|
|
|
+- int ret;
|
|
|
+-
|
|
|
+- BUG_ON(secondary_boot); /* We're called only once */
|
|
|
+-
|
|
|
+- /*
|
|
|
+- * This function is only called via smp_ops->smp_prepare_cpu().
|
|
|
+- * That only happens if a "/cpus" device tree node exists
|
|
|
+- * and has an "enable-method" property that selects the SMP
|
|
|
+- * operations defined herein.
|
|
|
+- */
|
|
|
+- node = of_find_node_by_path("/cpus");
|
|
|
+- BUG_ON(!node);
|
|
|
+-
|
|
|
+- /*
|
|
|
+- * Our secondary enable method requires a "secondary-boot-reg"
|
|
|
+- * property to specify a register address used to request the
|
|
|
+- * ROM code boot a secondary code. If we have any trouble
|
|
|
+- * getting this we fall back to uniprocessor mode.
|
|
|
+- */
|
|
|
+- if (of_property_read_u32(node, OF_SECONDARY_BOOT, &secondary_boot)) {
|
|
|
+- pr_err("%s: missing/invalid " OF_SECONDARY_BOOT " property\n",
|
|
|
+- node->name);
|
|
|
+- ret = -ENOENT; /* Arrange to disable SMP */
|
|
|
+- goto out;
|
|
|
+- }
|
|
|
+-
|
|
|
+- /*
|
|
|
+- * Enable the SCU on Cortex A9 based SoCs. If -ENOENT is
|
|
|
+- * returned, the SoC reported a uniprocessor configuration.
|
|
|
+- * We bail on any other error.
|
|
|
+- */
|
|
|
+- ret = scu_a9_enable();
|
|
|
+-out:
|
|
|
+- of_node_put(node);
|
|
|
+- if (ret) {
|
|
|
+- /* Update the CPU present map to reflect uniprocessor mode */
|
|
|
+- BUG_ON(ret != -ENOENT);
|
|
|
+- pr_warn("disabling SMP\n");
|
|
|
+- init_cpu_present(&only_cpu_0);
|
|
|
+- }
|
|
|
+-}
|
|
|
+-
|
|
|
+-/*
|
|
|
+- * The ROM code has the secondary cores looping, waiting for an event.
|
|
|
+- * When an event occurs each core examines the bottom two bits of the
|
|
|
+- * secondary boot register. When a core finds those bits contain its
|
|
|
+- * own core id, it performs initialization, including computing its boot
|
|
|
+- * address by clearing the boot register value's bottom two bits. The
|
|
|
+- * core signals that it is beginning its execution by writing its boot
|
|
|
+- * address back to the secondary boot register, and finally jumps to
|
|
|
+- * that address.
|
|
|
+- *
|
|
|
+- * So to start a core executing we need to:
|
|
|
+- * - Encode the (hardware) CPU id with the bottom bits of the secondary
|
|
|
+- * start address.
|
|
|
+- * - Write that value into the secondary boot register.
|
|
|
+- * - Generate an event to wake up the secondary CPU(s).
|
|
|
+- * - Wait for the secondary boot register to be re-written, which
|
|
|
+- * indicates the secondary core has started.
|
|
|
+- */
|
|
|
+-static int bcm_boot_secondary(unsigned int cpu, struct task_struct *idle)
|
|
|
+-{
|
|
|
+- void __iomem *boot_reg;
|
|
|
+- phys_addr_t boot_func;
|
|
|
+- u64 start_clock;
|
|
|
+- u32 cpu_id;
|
|
|
+- u32 boot_val;
|
|
|
+- bool timeout = false;
|
|
|
+-
|
|
|
+- cpu_id = cpu_logical_map(cpu);
|
|
|
+- if (cpu_id & ~BOOT_ADDR_CPUID_MASK) {
|
|
|
+- pr_err("bad cpu id (%u > %u)\n", cpu_id, BOOT_ADDR_CPUID_MASK);
|
|
|
+- return -EINVAL;
|
|
|
+- }
|
|
|
+-
|
|
|
+- if (!secondary_boot) {
|
|
|
+- pr_err("required secondary boot register not specified\n");
|
|
|
+- return -EINVAL;
|
|
|
+- }
|
|
|
+-
|
|
|
+- boot_reg = ioremap_nocache((phys_addr_t)secondary_boot, sizeof(u32));
|
|
|
+- if (!boot_reg) {
|
|
|
+- pr_err("unable to map boot register for cpu %u\n", cpu_id);
|
|
|
+- return -ENOSYS;
|
|
|
+- }
|
|
|
+-
|
|
|
+- /*
|
|
|
+- * Secondary cores will start in secondary_startup(),
|
|
|
+- * defined in "arch/arm/kernel/head.S"
|
|
|
+- */
|
|
|
+- boot_func = virt_to_phys(secondary_startup);
|
|
|
+- BUG_ON(boot_func & BOOT_ADDR_CPUID_MASK);
|
|
|
+- BUG_ON(boot_func > (phys_addr_t)U32_MAX);
|
|
|
+-
|
|
|
+- /* The core to start is encoded in the low bits */
|
|
|
+- boot_val = (u32)boot_func | cpu_id;
|
|
|
+- writel_relaxed(boot_val, boot_reg);
|
|
|
+-
|
|
|
+- sev();
|
|
|
+-
|
|
|
+- /* The low bits will be cleared once the core has started */
|
|
|
+- start_clock = local_clock();
|
|
|
+- while (!timeout && readl_relaxed(boot_reg) == boot_val)
|
|
|
+- timeout = local_clock() - start_clock > SECONDARY_TIMEOUT_NS;
|
|
|
+-
|
|
|
+- iounmap(boot_reg);
|
|
|
+-
|
|
|
+- if (!timeout)
|
|
|
+- return 0;
|
|
|
+-
|
|
|
+- pr_err("timeout waiting for cpu %u to start\n", cpu_id);
|
|
|
+-
|
|
|
+- return -ENOSYS;
|
|
|
+-}
|
|
|
+-
|
|
|
+-static struct smp_operations bcm_smp_ops __initdata = {
|
|
|
+- .smp_prepare_cpus = bcm_smp_prepare_cpus,
|
|
|
+- .smp_boot_secondary = bcm_boot_secondary,
|
|
|
+-};
|
|
|
+-CPU_METHOD_OF_DECLARE(bcm_smp_bcm281xx, "brcm,bcm11351-cpu-method",
|
|
|
+- &bcm_smp_ops);
|
|
|
+--- /dev/null
|
|
|
++++ b/arch/arm/mach-bcm/platsmp.c
|
|
|
+@@ -0,0 +1,326 @@
|
|
|
++/*
|
|
|
++ * Copyright (C) 2014-2015 Broadcom Corporation
|
|
|
++ * Copyright 2014 Linaro Limited
|
|
|
++ *
|
|
|
++ * This program is free software; you can redistribute it and/or
|
|
|
++ * modify it under the terms of the GNU General Public License as
|
|
|
++ * published by the Free Software Foundation version 2.
|
|
|
++ *
|
|
|
++ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
|
|
|
++ * kind, whether express or implied; without even the implied warranty
|
|
|
++ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
++ * GNU General Public License for more details.
|
|
|
++ */
|
|
|
++
|
|
|
++#include <linux/cpumask.h>
|
|
|
++#include <linux/delay.h>
|
|
|
++#include <linux/errno.h>
|
|
|
++#include <linux/init.h>
|
|
|
++#include <linux/io.h>
|
|
|
++#include <linux/jiffies.h>
|
|
|
++#include <linux/of.h>
|
|
|
++#include <linux/sched.h>
|
|
|
++#include <linux/smp.h>
|
|
|
++
|
|
|
++#include <asm/cacheflush.h>
|
|
|
++#include <asm/smp.h>
|
|
|
++#include <asm/smp_plat.h>
|
|
|
++#include <asm/smp_scu.h>
|
|
|
++
|
|
|
++#include "bcm_nsp.h"
|
|
|
++
|
|
|
++/* Size of mapped Cortex A9 SCU address space */
|
|
|
++#define CORTEX_A9_SCU_SIZE 0x58
|
|
|
++
|
|
|
++#define SECONDARY_TIMEOUT_NS NSEC_PER_MSEC /* 1 msec (in nanoseconds) */
|
|
|
++#define BOOT_ADDR_CPUID_MASK 0x3
|
|
|
++
|
|
|
++/* Name of device node property defining secondary boot register location */
|
|
|
++#define OF_SECONDARY_BOOT "secondary-boot-reg"
|
|
|
++
|
|
|
++/* I/O address of register used to coordinate secondary core startup */
|
|
|
++static u32 secondary_boot;
|
|
|
++
|
|
|
++static DEFINE_SPINLOCK(boot_lock);
|
|
|
++
|
|
|
++/*
|
|
|
++ * 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 write_pen_release(int val)
|
|
|
++{
|
|
|
++ pen_release = val;
|
|
|
++ /*
|
|
|
++ * Ensure write to pen_release is visible to the other cores,
|
|
|
++ * here - primary core
|
|
|
++ */
|
|
|
++ smp_wmb();
|
|
|
++ sync_cache_w(&pen_release);
|
|
|
++}
|
|
|
++
|
|
|
++/*
|
|
|
++ * 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;
|
|
|
++
|
|
|
++ 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);
|
|
|
++
|
|
|
++ iounmap(scu_base); /* That's the last we'll need of this */
|
|
|
++
|
|
|
++ return 0;
|
|
|
++}
|
|
|
++
|
|
|
++static int nsp_write_lut(void (*secondary_startup) (void))
|
|
|
++{
|
|
|
++ void __iomem *sku_rom_lut;
|
|
|
++ phys_addr_t secondary_startup_phy;
|
|
|
++
|
|
|
++ if (!secondary_boot) {
|
|
|
++ pr_warn("required secondary boot register not specified\n");
|
|
|
++ return -EINVAL;
|
|
|
++ }
|
|
|
++
|
|
|
++ sku_rom_lut = ioremap_nocache((phys_addr_t)secondary_boot,
|
|
|
++ sizeof(secondary_boot));
|
|
|
++ if (!sku_rom_lut) {
|
|
|
++ pr_warn("unable to ioremap SKU-ROM LUT register\n");
|
|
|
++ return -ENOMEM;
|
|
|
++ }
|
|
|
++
|
|
|
++ secondary_startup_phy = virt_to_phys(secondary_startup);
|
|
|
++ BUG_ON(secondary_startup_phy > (phys_addr_t)U32_MAX);
|
|
|
++
|
|
|
++ writel_relaxed(secondary_startup_phy, sku_rom_lut);
|
|
|
++ /*
|
|
|
++ * Ensure the write is visible to the secondary core.
|
|
|
++ */
|
|
|
++ smp_wmb();
|
|
|
++
|
|
|
++ iounmap(sku_rom_lut);
|
|
|
++
|
|
|
++ return 0;
|
|
|
++}
|
|
|
++
|
|
|
++static void nsp_secondary_init(unsigned int cpu)
|
|
|
++{
|
|
|
++ /*
|
|
|
++ * Let the primary cpu know we are out of holding pen.
|
|
|
++ */
|
|
|
++ write_pen_release(-1);
|
|
|
++
|
|
|
++ /*
|
|
|
++ * Synchronise with the boot thread.
|
|
|
++ */
|
|
|
++ spin_lock(&boot_lock);
|
|
|
++ spin_unlock(&boot_lock);
|
|
|
++}
|
|
|
++
|
|
|
++static void __init bcm_smp_prepare_cpus(unsigned int max_cpus)
|
|
|
++{
|
|
|
++ static cpumask_t only_cpu_0 = { CPU_BITS_CPU0 };
|
|
|
++ struct device_node *node;
|
|
|
++ int ret;
|
|
|
++
|
|
|
++ BUG_ON(secondary_boot); /* We're called only once */
|
|
|
++
|
|
|
++ /*
|
|
|
++ * This function is only called via smp_ops->smp_prepare_cpu().
|
|
|
++ * That only happens if a "/cpus" device tree node exists
|
|
|
++ * and has an "enable-method" property that selects the SMP
|
|
|
++ * operations defined herein.
|
|
|
++ */
|
|
|
++ node = of_find_node_by_path("/cpus");
|
|
|
++ BUG_ON(!node);
|
|
|
++
|
|
|
++ /*
|
|
|
++ * Our secondary enable method requires a "secondary-boot-reg"
|
|
|
++ * property to specify a register address used to request the
|
|
|
++ * ROM code boot a secondary core. If we have any trouble
|
|
|
++ * getting this we fall back to uniprocessor mode.
|
|
|
++ */
|
|
|
++ if (of_property_read_u32(node, OF_SECONDARY_BOOT, &secondary_boot)) {
|
|
|
++ pr_warn("%s: missing/invalid " OF_SECONDARY_BOOT " property\n",
|
|
|
++ node->name);
|
|
|
++ ret = -ENOENT; /* Arrange to disable SMP */
|
|
|
++ goto out;
|
|
|
++ }
|
|
|
++
|
|
|
++ /*
|
|
|
++ * Enable the SCU on Cortex A9 based SoCs. If -ENOENT is
|
|
|
++ * returned, the SoC reported a uniprocessor configuration.
|
|
|
++ * We bail on any other error.
|
|
|
++ */
|
|
|
++ ret = scu_a9_enable();
|
|
|
++out:
|
|
|
++ of_node_put(node);
|
|
|
++ if (ret) {
|
|
|
++ /* Update the CPU present map to reflect uniprocessor mode */
|
|
|
++ pr_warn("disabling SMP\n");
|
|
|
++ init_cpu_present(&only_cpu_0);
|
|
|
++ }
|
|
|
++}
|
|
|
++
|
|
|
++/*
|
|
|
++ * The ROM code has the secondary cores looping, waiting for an event.
|
|
|
++ * When an event occurs each core examines the bottom two bits of the
|
|
|
++ * secondary boot register. When a core finds those bits contain its
|
|
|
++ * own core id, it performs initialization, including computing its boot
|
|
|
++ * address by clearing the boot register value's bottom two bits. The
|
|
|
++ * core signals that it is beginning its execution by writing its boot
|
|
|
++ * address back to the secondary boot register, and finally jumps to
|
|
|
++ * that address.
|
|
|
++ *
|
|
|
++ * So to start a core executing we need to:
|
|
|
++ * - Encode the (hardware) CPU id with the bottom bits of the secondary
|
|
|
++ * start address.
|
|
|
++ * - Write that value into the secondary boot register.
|
|
|
++ * - Generate an event to wake up the secondary CPU(s).
|
|
|
++ * - Wait for the secondary boot register to be re-written, which
|
|
|
++ * indicates the secondary core has started.
|
|
|
++ */
|
|
|
++static int kona_boot_secondary(unsigned int cpu, struct task_struct *idle)
|
|
|
++{
|
|
|
++ void __iomem *boot_reg;
|
|
|
++ phys_addr_t boot_func;
|
|
|
++ u64 start_clock;
|
|
|
++ u32 cpu_id;
|
|
|
++ u32 boot_val;
|
|
|
++ bool timeout = false;
|
|
|
++
|
|
|
++ cpu_id = cpu_logical_map(cpu);
|
|
|
++ if (cpu_id & ~BOOT_ADDR_CPUID_MASK) {
|
|
|
++ pr_err("bad cpu id (%u > %u)\n", cpu_id, BOOT_ADDR_CPUID_MASK);
|
|
|
++ return -EINVAL;
|
|
|
++ }
|
|
|
++
|
|
|
++ if (!secondary_boot) {
|
|
|
++ pr_err("required secondary boot register not specified\n");
|
|
|
++ return -EINVAL;
|
|
|
++ }
|
|
|
++
|
|
|
++ boot_reg = ioremap_nocache((phys_addr_t)secondary_boot, sizeof(u32));
|
|
|
++ if (!boot_reg) {
|
|
|
++ pr_err("unable to map boot register for cpu %u\n", cpu_id);
|
|
|
++ return -ENOMEM;
|
|
|
++ }
|
|
|
++
|
|
|
++ /*
|
|
|
++ * Secondary cores will start in secondary_startup(),
|
|
|
++ * defined in "arch/arm/kernel/head.S"
|
|
|
++ */
|
|
|
++ boot_func = virt_to_phys(secondary_startup);
|
|
|
++ BUG_ON(boot_func & BOOT_ADDR_CPUID_MASK);
|
|
|
++ BUG_ON(boot_func > (phys_addr_t)U32_MAX);
|
|
|
++
|
|
|
++ /* The core to start is encoded in the low bits */
|
|
|
++ boot_val = (u32)boot_func | cpu_id;
|
|
|
++ writel_relaxed(boot_val, boot_reg);
|
|
|
++
|
|
|
++ sev();
|
|
|
++
|
|
|
++ /* The low bits will be cleared once the core has started */
|
|
|
++ start_clock = local_clock();
|
|
|
++ while (!timeout && readl_relaxed(boot_reg) == boot_val)
|
|
|
++ timeout = local_clock() - start_clock > SECONDARY_TIMEOUT_NS;
|
|
|
++
|
|
|
++ iounmap(boot_reg);
|
|
|
++
|
|
|
++ if (!timeout)
|
|
|
++ return 0;
|
|
|
++
|
|
|
++ pr_err("timeout waiting for cpu %u to start\n", cpu_id);
|
|
|
++
|
|
|
++ return -ENXIO;
|
|
|
++}
|
|
|
++
|
|
|
++static int nsp_boot_secondary(unsigned int cpu, struct task_struct *idle)
|
|
|
++{
|
|
|
++ unsigned long timeout;
|
|
|
++ int ret;
|
|
|
++
|
|
|
++ /*
|
|
|
++ * After wake up, secondary core branches to the startup
|
|
|
++ * address programmed at SKU ROM LUT location.
|
|
|
++ */
|
|
|
++ ret = nsp_write_lut(nsp_secondary_startup);
|
|
|
++ if (ret) {
|
|
|
++ pr_err("unable to write startup addr to SKU ROM LUT\n");
|
|
|
++ goto out;
|
|
|
++ }
|
|
|
++
|
|
|
++ /*
|
|
|
++ * The secondary processor is waiting to be released from
|
|
|
++ * the holding pen - release it, then wait for it to flag
|
|
|
++ * that it has been released by resetting pen_release.
|
|
|
++ */
|
|
|
++ spin_lock(&boot_lock);
|
|
|
++
|
|
|
++ write_pen_release(cpu_logical_map(cpu));
|
|
|
++ /*
|
|
|
++ * Send an Event to wake up the secondary core which is in
|
|
|
++ * WFE state. Updated pen_release should also be visible to
|
|
|
++ * the secondary core.
|
|
|
++ */
|
|
|
++ dsb_sev();
|
|
|
++
|
|
|
++ timeout = jiffies + (1 * HZ);
|
|
|
++ while (time_before(jiffies, timeout)) {
|
|
|
++ /* Make sure loads on other CPU is visible */
|
|
|
++ smp_rmb();
|
|
|
++ if (pen_release == -1)
|
|
|
++ break;
|
|
|
++
|
|
|
++ udelay(10);
|
|
|
++ }
|
|
|
++
|
|
|
++ spin_unlock(&boot_lock);
|
|
|
++
|
|
|
++ ret = pen_release != -1 ? -ENXIO : 0;
|
|
|
++
|
|
|
++out:
|
|
|
++ return ret;
|
|
|
++}
|
|
|
++
|
|
|
++static struct smp_operations bcm_smp_ops __initdata = {
|
|
|
++ .smp_prepare_cpus = bcm_smp_prepare_cpus,
|
|
|
++ .smp_boot_secondary = kona_boot_secondary,
|
|
|
++};
|
|
|
++CPU_METHOD_OF_DECLARE(bcm_smp_bcm281xx, "brcm,bcm11351-cpu-method",
|
|
|
++ &bcm_smp_ops);
|
|
|
++
|
|
|
++struct smp_operations nsp_smp_ops __initdata = {
|
|
|
++ .smp_prepare_cpus = bcm_smp_prepare_cpus,
|
|
|
++ .smp_secondary_init = nsp_secondary_init,
|
|
|
++ .smp_boot_secondary = nsp_boot_secondary,
|
|
|
++};
|
|
|
++CPU_METHOD_OF_DECLARE(bcm_smp_nsp, "brcm,bcm-nsp-smp", &nsp_smp_ops);
|