diff options
Diffstat (limited to 'arch/arm/mach-bcm')
-rw-r--r-- | arch/arm/mach-bcm/Kconfig | 256 | ||||
-rw-r--r-- | arch/arm/mach-bcm/Makefile | 62 | ||||
-rw-r--r-- | arch/arm/mach-bcm/bcm2711.c | 25 | ||||
-rw-r--r-- | arch/arm/mach-bcm/bcm63xx_pmb.c | 215 | ||||
-rw-r--r-- | arch/arm/mach-bcm/bcm63xx_smp.c | 169 | ||||
-rw-r--r-- | arch/arm/mach-bcm/bcm63xx_smp.h | 9 | ||||
-rw-r--r-- | arch/arm/mach-bcm/bcm_5301x.c | 50 | ||||
-rw-r--r-- | arch/arm/mach-bcm/bcm_cygnus.c | 15 | ||||
-rw-r--r-- | arch/arm/mach-bcm/bcm_hr2.c | 15 | ||||
-rw-r--r-- | arch/arm/mach-bcm/bcm_kona_smc.c | 169 | ||||
-rw-r--r-- | arch/arm/mach-bcm/bcm_kona_smc.h | 22 | ||||
-rw-r--r-- | arch/arm/mach-bcm/bcm_nsp.c | 15 | ||||
-rw-r--r-- | arch/arm/mach-bcm/board_bcm21664.c | 21 | ||||
-rw-r--r-- | arch/arm/mach-bcm/board_bcm23550.c | 15 | ||||
-rw-r--r-- | arch/arm/mach-bcm/board_bcm281xx.c | 63 | ||||
-rw-r--r-- | arch/arm/mach-bcm/board_bcm2835.c | 29 | ||||
-rw-r--r-- | arch/arm/mach-bcm/brcmstb.c | 41 | ||||
-rw-r--r-- | arch/arm/mach-bcm/kona_l2_cache.c | 38 | ||||
-rw-r--r-- | arch/arm/mach-bcm/kona_l2_cache.h | 8 | ||||
-rw-r--r-- | arch/arm/mach-bcm/platsmp-brcmstb.c | 363 | ||||
-rw-r--r-- | arch/arm/mach-bcm/platsmp.c | 339 | ||||
-rw-r--r-- | arch/arm/mach-bcm/platsmp.h | 6 |
22 files changed, 1945 insertions, 0 deletions
diff --git a/arch/arm/mach-bcm/Kconfig b/arch/arm/mach-bcm/Kconfig new file mode 100644 index 000000000..8789d93a7 --- /dev/null +++ b/arch/arm/mach-bcm/Kconfig @@ -0,0 +1,256 @@ +# SPDX-License-Identifier: GPL-2.0 +menuconfig ARCH_BCM + bool "Broadcom SoC Support" + depends on ARCH_MULTI_V6_V7 + help + This enables support for Broadcom ARM based SoC chips + +if ARCH_BCM + +comment "IPROC architected SoCs" + +config ARCH_BCM_IPROC + bool + select ARM_GIC + select CACHE_L2X0 + select HAVE_ARM_SCU if SMP + select HAVE_ARM_TWD if SMP + select ARM_GLOBAL_TIMER + select CLKSRC_MMIO + select GPIOLIB + select ARM_AMBA + select PINCTRL + help + This enables support for systems based on Broadcom IPROC architected SoCs. + The IPROC complex contains one or more ARM CPUs along with common + core peripherals. Application specific SoCs are created by adding a + uArchitecture containing peripherals outside of the IPROC complex. + Currently supported SoCs are Cygnus. + +config ARCH_BCM_CYGNUS + bool "Broadcom Cygnus Support" + depends on ARCH_MULTI_V7 + select ARCH_BCM_IPROC + help + Enable support for the Cygnus family, + which includes the following variants: + BCM11300, BCM11320, BCM11350, BCM11360, + BCM58300, BCM58302, BCM58303, BCM58305. + +config ARCH_BCM_HR2 + bool "Broadcom Hurricane 2 SoC support" + depends on ARCH_MULTI_V7 + select ARCH_BCM_IPROC + help + Enable support for the Hurricane 2 family, + which includes the following variants: + BCM53342, BCM53343, BCM53344, BCM53346. + +config ARCH_BCM_NSP + bool "Broadcom Northstar Plus SoC Support" + depends on ARCH_MULTI_V7 + select ARCH_BCM_IPROC + select ARM_ERRATA_754322 + select ARM_ERRATA_775420 + select ARM_ERRATA_764369 if SMP + select ARM_TIMER_SP804 + help + Support for Broadcom Northstar Plus SoC. + Broadcom Northstar Plus family of SoCs are used for switching control + and management applications as well as residential router/gateway + applications. The SoC features dual core Cortex A9 ARM CPUs, + integrating several peripheral interfaces including multiple Gigabit + Ethernet PHYs, DDR3 memory, PCIE Gen-2, USB 2.0 and USB 3.0, serial and + NAND flash, SATA and several other IO controllers. + +config ARCH_BCM_5301X + bool "Broadcom BCM470X / BCM5301X ARM SoC" + depends on ARCH_MULTI_V7 + select ARCH_BCM_IPROC + select ARM_ERRATA_754322 + select ARM_ERRATA_775420 + select ARM_ERRATA_764369 if SMP + + help + Support for Broadcom BCM470X and BCM5301X SoCs with ARM CPU cores. + + This is a network SoC line mostly used in home routers and + wifi access points, its internal name is Northstar. + This includes the following SoC: BCM53010, BCM53011, BCM53012, + BCM53014, BCM53015, BCM53016, BCM53017, BCM53018, BCM4707, + BCM4708 and BCM4709. + + Do not confuse this with the BCM4760 which is a totally + different SoC or with the older BCM47XX and BCM53XX based + network SoC using a MIPS CPU, they are supported by arch/mips/bcm47xx + +comment "KONA architected SoCs" + +config ARCH_BCM_MOBILE + bool + select GPIOLIB + select ARM_ERRATA_754322 + select ARM_ERRATA_775420 + select ARM_GIC + select GPIO_BCM_KONA + select TICK_ONESHOT + select HAVE_ARM_ARCH_TIMER + select PINCTRL + select ARCH_BCM_MOBILE_SMP if SMP + select BCM_KONA_TIMER + help + This enables support for systems based on Broadcom mobile SoCs. + +config ARCH_BCM_281XX + bool "Broadcom BCM281XX SoC family" + depends on ARCH_MULTI_V7 + select ARCH_BCM_MOBILE + help + Enable support for the BCM281XX family, which includes + BCM11130, BCM11140, BCM11351, BCM28145 and BCM28155 + variants. + +config ARCH_BCM_21664 + bool "Broadcom BCM21664 SoC family" + depends on ARCH_MULTI_V7 + select ARCH_BCM_MOBILE + help + Enable support for the BCM21664 family, which includes + BCM21663 and BCM21664 variants. + +config ARCH_BCM_23550 + bool "Broadcom BCM23550 SoC" + depends on ARCH_MULTI_V7 + select ARCH_BCM_MOBILE + help + Enable support for the BCM23550. + +config ARCH_BCM_MOBILE_L2_CACHE + bool "Broadcom mobile SoC level 2 cache support" + depends on ARCH_BCM_281XX || ARCH_BCM_21664 + default y + select CACHE_L2X0 + select ARCH_BCM_MOBILE_SMC + +config ARCH_BCM_MOBILE_SMC + bool + depends on ARCH_BCM_MOBILE + +config ARCH_BCM_MOBILE_SMP + bool + depends on ARCH_BCM_MOBILE + select HAVE_ARM_SCU + select ARM_ERRATA_764369 + help + SMP support for the BCM281XX, BCM21664 and BCM23550 SoC families. + Provided as an option so SMP support for SoCs of this type + can be disabled for an SMP-enabled kernel. + +comment "Other Architectures" + +config ARCH_BCM2835 + bool "Broadcom BCM2835 family" + depends on ARCH_MULTI_V6 || ARCH_MULTI_V7 + select GPIOLIB + select ARM_AMBA + select ARM_ERRATA_411920 if ARCH_MULTI_V6 + select ARM_GIC if ARCH_MULTI_V7 + select ZONE_DMA if ARCH_MULTI_V7 + select ARM_TIMER_SP804 + select HAVE_ARM_ARCH_TIMER if ARCH_MULTI_V7 + select BCM2835_TIMER + select PINCTRL + select PINCTRL_BCM2835 + select MFD_CORE + help + This enables support for the Broadcom BCM2711 and BCM283x SoCs. + This SoC is used in the Raspberry Pi and Roku 2 devices. + +config ARCH_BCM_53573 + bool "Broadcom BCM53573 SoC series support" + depends on ARCH_MULTI_V7 + select ARCH_BCM_IPROC + select HAVE_ARM_ARCH_TIMER + help + BCM53573 series is set of SoCs using ARM Cortex-A7 CPUs with wireless + embedded in the chipset. + This SoC line is mostly used in home routers and is some cheaper + alternative for Northstar family. + + The base chip is BCM53573 and there are some packaging modifications + like BCM47189 and BCM47452. + +config ARCH_BRCMSTB + bool "Broadcom BCM7XXX based boards" + depends on ARCH_MULTI_V7 + select ARCH_HAS_RESET_CONTROLLER + select ARM_AMBA + select ARM_GIC + select ARM_ERRATA_798181 if SMP + select HAVE_ARM_ARCH_TIMER + select ZONE_DMA if ARM_LPAE + select SOC_BRCMSTB + select SOC_BUS + select PINCTRL + help + Say Y if you intend to run the kernel on a Broadcom ARM-based STB + chipset. + + This enables support for Broadcom ARM-based set-top box chipsets, + including the 7445 family of chips. + +menuconfig ARCH_BCMBCA + bool "Broadcom Broadband Carrier Access (BCA) origin SoC" + depends on ARCH_MULTI_V7 + select ARM_AMBA + select ARM_GIC + select HAVE_ARM_ARCH_TIMER + help + Say Y if you intend to run the kernel on a Broadcom Broadband ARM-based + BCA chipset. + + This enables support for Broadcom BCA ARM-based broadband chipsets, + including the DSL, PON and Wireless family of chips. + +comment "BCMBCA sub platforms" + +if ARCH_BCMBCA + +config ARCH_BCMBCA_CORTEXA7 + bool "Cortex-A7 SoCs" + help + Say Y if you intend to run the kernel on a Broadcom Broadband ARM A7 + based chipset. + + This enables support for Broadcom BCA ARM A7 broadband chipsets, + including various DSL, PON and Wireless family of chips. + +config ARCH_BCMBCA_CORTEXA9 + bool "Cortex-A9 SoCS" + select ARM_ERRATA_754322 + select ARM_ERRATA_764369 if SMP + select ARCH_HAS_RESET_CONTROLLER + select ARM_GLOBAL_TIMER + select CACHE_L2X0 + select HAVE_ARM_TWD if SMP + select HAVE_ARM_SCU if SMP + help + Say Y if you intend to run the kernel on a Broadcom Broadband ARM A9 + based BCA chipset. + + This enables support for Broadcom BCA ARM A9 broadband chipset. Currently + only DSL chip BCM63138. + +config ARCH_BCMBCA_BRAHMAB15 + bool "Brahma-B15 SoCs" + select ARM_ERRATA_798181 if SMP + help + Say Y if you intend to run the kernel on a Broadcom Broadband ARM B15 + based BCA chipset. + + This enables support for Broadcom BCA ARM B15 broadband chipset. Currently + only DSL chip BCM63148. + +endif + +endif diff --git a/arch/arm/mach-bcm/Makefile b/arch/arm/mach-bcm/Makefile new file mode 100644 index 000000000..2e523f29e --- /dev/null +++ b/arch/arm/mach-bcm/Makefile @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright (C) 2012-2015 Broadcom Corporation +# + +# Cygnus +obj-$(CONFIG_ARCH_BCM_CYGNUS) += bcm_cygnus.o + +# Hurricane 2 +obj-$(CONFIG_ARCH_BCM_HR2) += bcm_hr2.o + +# Northstar Plus +obj-$(CONFIG_ARCH_BCM_NSP) += bcm_nsp.o + +ifeq ($(CONFIG_ARCH_BCM_NSP),y) +obj-$(CONFIG_SMP) += platsmp.o +endif + +# BCM281XX +obj-$(CONFIG_ARCH_BCM_281XX) += board_bcm281xx.o + +# BCM21664 +obj-$(CONFIG_ARCH_BCM_21664) += board_bcm21664.o + +# BCM23550 +obj-$(CONFIG_ARCH_BCM_23550) += board_bcm23550.o + +# BCM281XX, BCM21664 and BCM23550 SMP support +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 + +# Support for secure monitor traps +obj-$(CONFIG_ARCH_BCM_MOBILE_SMC) += bcm_kona_smc.o +CFLAGS_REMOVE_bcm_kona_smc.o += $(CC_FLAGS_FTRACE) + +# BCM2835 +ifeq ($(CONFIG_ARCH_BCM2835),y) +obj-y += board_bcm2835.o +obj-y += bcm2711.o +ifeq ($(CONFIG_ARM),y) +obj-$(CONFIG_SMP) += platsmp.o +endif +endif + +# BCM5301X +obj-$(CONFIG_ARCH_BCM_5301X) += bcm_5301x.o +ifeq ($(CONFIG_ARCH_BCM_5301X),y) +obj-$(CONFIG_SMP) += platsmp.o +endif + +ifeq ($(CONFIG_ARCH_BRCMSTB),y) +CFLAGS_platsmp-brcmstb.o += -march=armv7-a +obj-y += brcmstb.o +obj-$(CONFIG_SMP) += platsmp-brcmstb.o +endif + +# BCMBCA +ifeq ($(CONFIG_ARCH_BCMBCA),y) +obj-$(CONFIG_SMP) += bcm63xx_smp.o bcm63xx_pmb.o +endif diff --git a/arch/arm/mach-bcm/bcm2711.c b/arch/arm/mach-bcm/bcm2711.c new file mode 100644 index 000000000..fa0300d8c --- /dev/null +++ b/arch/arm/mach-bcm/bcm2711.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2019 Stefan Wahren + */ + +#include <linux/of_address.h> + +#include <asm/mach/arch.h> + +#include "platsmp.h" + +static const char * const bcm2711_compat[] = { +#ifdef CONFIG_ARCH_MULTI_V7 + "brcm,bcm2711", +#endif + NULL +}; + +DT_MACHINE_START(BCM2711, "BCM2711") +#ifdef CONFIG_ZONE_DMA + .dma_zone_size = SZ_1G, +#endif + .dt_compat = bcm2711_compat, + .smp = smp_ops(bcm2836_smp_ops), +MACHINE_END diff --git a/arch/arm/mach-bcm/bcm63xx_pmb.c b/arch/arm/mach-bcm/bcm63xx_pmb.c new file mode 100644 index 000000000..003f1472a --- /dev/null +++ b/arch/arm/mach-bcm/bcm63xx_pmb.c @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Broadcom BCM63138 PMB initialization for secondary CPU(s) + * + * Copyright (C) 2015 Broadcom Corporation + * Author: Florian Fainelli <f.fainelli@gmail.com> + */ +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/spinlock.h> +#include <linux/reset/bcm63xx_pmb.h> +#include <linux/of.h> +#include <linux/of_address.h> + +#include "bcm63xx_smp.h" + +/* ARM Control register definitions */ +#define CORE_PWR_CTRL_SHIFT 0 +#define CORE_PWR_CTRL_MASK 0x3 +#define PLL_PWR_ON BIT(8) +#define PLL_LDO_PWR_ON BIT(9) +#define PLL_CLAMP_ON BIT(10) +#define CPU_RESET_N(x) BIT(13 + (x)) +#define NEON_RESET_N BIT(15) +#define PWR_CTRL_STATUS_SHIFT 28 +#define PWR_CTRL_STATUS_MASK 0x3 +#define PWR_DOWN_SHIFT 30 +#define PWR_DOWN_MASK 0x3 + +/* CPU Power control register definitions */ +#define MEM_PWR_OK BIT(0) +#define MEM_PWR_ON BIT(1) +#define MEM_CLAMP_ON BIT(2) +#define MEM_PWR_OK_STATUS BIT(4) +#define MEM_PWR_ON_STATUS BIT(5) +#define MEM_PDA_SHIFT 8 +#define MEM_PDA_MASK 0xf +#define MEM_PDA_CPU_MASK 0x1 +#define MEM_PDA_NEON_MASK 0xf +#define CLAMP_ON BIT(15) +#define PWR_OK_SHIFT 16 +#define PWR_OK_MASK 0xf +#define PWR_ON_SHIFT 20 +#define PWR_CPU_MASK 0x03 +#define PWR_NEON_MASK 0x01 +#define PWR_ON_MASK 0xf +#define PWR_OK_STATUS_SHIFT 24 +#define PWR_OK_STATUS_MASK 0xf +#define PWR_ON_STATUS_SHIFT 28 +#define PWR_ON_STATUS_MASK 0xf + +#define ARM_CONTROL 0x30 +#define ARM_PWR_CONTROL_BASE 0x34 +#define ARM_PWR_CONTROL(x) (ARM_PWR_CONTROL_BASE + (x) * 0x4) +#define ARM_NEON_L2 0x3c + +/* Perform a value write, then spin until the value shifted by + * shift is seen, masked with mask and is different from cond. + */ +static int bpcm_wr_rd_mask(void __iomem *master, + unsigned int addr, u32 off, u32 *val, + u32 shift, u32 mask, u32 cond) +{ + int ret; + + ret = bpcm_wr(master, addr, off, *val); + if (ret) + return ret; + + do { + ret = bpcm_rd(master, addr, off, val); + if (ret) + return ret; + + cpu_relax(); + } while (((*val >> shift) & mask) != cond); + + return ret; +} + +/* Global lock to serialize accesses to the PMB registers while we + * are bringing up the secondary CPU + */ +static DEFINE_SPINLOCK(pmb_lock); + +static int bcm63xx_pmb_get_resources(struct device_node *dn, + void __iomem **base, + unsigned int *cpu, + unsigned int *addr) +{ + struct of_phandle_args args; + int ret; + + *cpu = of_get_cpu_hwid(dn, 0); + if (*cpu == ~0U) { + pr_err("CPU is missing a reg node\n"); + return -ENODEV; + } + + ret = of_parse_phandle_with_args(dn, "resets", "#reset-cells", + 0, &args); + if (ret) { + pr_err("CPU is missing a resets phandle\n"); + return ret; + } + + if (args.args_count != 2) { + pr_err("reset-controller does not conform to reset-cells\n"); + return -EINVAL; + } + + *base = of_iomap(args.np, 0); + if (!*base) { + pr_err("failed remapping PMB register\n"); + return -ENOMEM; + } + + /* We do not need the number of zones */ + *addr = args.args[0]; + + return 0; +} + +int bcm63xx_pmb_power_on_cpu(struct device_node *dn) +{ + void __iomem *base; + unsigned int cpu, addr; + unsigned long flags; + u32 val, ctrl; + int ret; + + ret = bcm63xx_pmb_get_resources(dn, &base, &cpu, &addr); + if (ret) + return ret; + + /* We would not know how to enable a third and greater CPU */ + WARN_ON(cpu > 1); + + spin_lock_irqsave(&pmb_lock, flags); + + /* Check if the CPU is already on and save the ARM_CONTROL register + * value since we will use it later for CPU de-assert once done with + * the CPU-specific power sequence + */ + ret = bpcm_rd(base, addr, ARM_CONTROL, &ctrl); + if (ret) + goto out; + + if (ctrl & CPU_RESET_N(cpu)) { + pr_info("PMB: CPU%d is already powered on\n", cpu); + ret = 0; + goto out; + } + + /* Power on PLL */ + ret = bpcm_rd(base, addr, ARM_PWR_CONTROL(cpu), &val); + if (ret) + goto out; + + val |= (PWR_CPU_MASK << PWR_ON_SHIFT); + + ret = bpcm_wr_rd_mask(base, addr, ARM_PWR_CONTROL(cpu), &val, + PWR_ON_STATUS_SHIFT, PWR_CPU_MASK, PWR_CPU_MASK); + if (ret) + goto out; + + val |= (PWR_CPU_MASK << PWR_OK_SHIFT); + + ret = bpcm_wr_rd_mask(base, addr, ARM_PWR_CONTROL(cpu), &val, + PWR_OK_STATUS_SHIFT, PWR_CPU_MASK, PWR_CPU_MASK); + if (ret) + goto out; + + val &= ~CLAMP_ON; + + ret = bpcm_wr(base, addr, ARM_PWR_CONTROL(cpu), val); + if (ret) + goto out; + + /* Power on CPU<N> RAM */ + val &= ~(MEM_PDA_MASK << MEM_PDA_SHIFT); + + ret = bpcm_wr(base, addr, ARM_PWR_CONTROL(cpu), val); + if (ret) + goto out; + + val |= MEM_PWR_ON; + + ret = bpcm_wr_rd_mask(base, addr, ARM_PWR_CONTROL(cpu), &val, + 0, MEM_PWR_ON_STATUS, MEM_PWR_ON_STATUS); + if (ret) + goto out; + + val |= MEM_PWR_OK; + + ret = bpcm_wr_rd_mask(base, addr, ARM_PWR_CONTROL(cpu), &val, + 0, MEM_PWR_OK_STATUS, MEM_PWR_OK_STATUS); + if (ret) + goto out; + + val &= ~MEM_CLAMP_ON; + + ret = bpcm_wr(base, addr, ARM_PWR_CONTROL(cpu), val); + if (ret) + goto out; + + /* De-assert CPU reset */ + ctrl |= CPU_RESET_N(cpu); + + ret = bpcm_wr(base, addr, ARM_CONTROL, ctrl); +out: + spin_unlock_irqrestore(&pmb_lock, flags); + iounmap(base); + return ret; +} diff --git a/arch/arm/mach-bcm/bcm63xx_smp.c b/arch/arm/mach-bcm/bcm63xx_smp.c new file mode 100644 index 000000000..641e1f8fc --- /dev/null +++ b/arch/arm/mach-bcm/bcm63xx_smp.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Broadcom BCM63138 DSL SoCs SMP support code + * + * Copyright (C) 2015, Broadcom Corporation + */ + +#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, 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 = __pa_symbol(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); + of_node_put(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; + } +} + +static const struct smp_operations bcm63138_smp_ops __initconst = { + .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); diff --git a/arch/arm/mach-bcm/bcm63xx_smp.h b/arch/arm/mach-bcm/bcm63xx_smp.h new file mode 100644 index 000000000..4e742604a --- /dev/null +++ b/arch/arm/mach-bcm/bcm63xx_smp.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __BCM63XX_SMP_H +#define __BCM63XX_SMP_H + +struct device_node; + +extern int bcm63xx_pmb_power_on_cpu(struct device_node *dn); + +#endif /* __BCM63XX_SMP_H */ diff --git a/arch/arm/mach-bcm/bcm_5301x.c b/arch/arm/mach-bcm/bcm_5301x.c new file mode 100644 index 000000000..fe067f6ce --- /dev/null +++ b/arch/arm/mach-bcm/bcm_5301x.c @@ -0,0 +1,50 @@ +/* + * Broadcom BCM470X / BCM5301X ARM platform code. + * + * Copyright 2013 Hauke Mehrtens <hauke@hauke-m.de> + * + * Licensed under the GNU/GPL. See COPYING for details. + */ +#include <linux/of_platform.h> +#include <asm/hardware/cache-l2x0.h> + +#include <asm/mach/arch.h> +#include <asm/siginfo.h> +#include <asm/signal.h> + +#define FSR_EXTERNAL (1 << 12) +#define FSR_READ (0 << 10) +#define FSR_IMPRECISE 0x0406 + +static const char *const bcm5301x_dt_compat[] __initconst = { + "brcm,bcm4708", + NULL, +}; + +static int bcm5301x_abort_handler(unsigned long addr, unsigned int fsr, + struct pt_regs *regs) +{ + /* + * We want to ignore aborts forwarded from the PCIe bus that are + * expected and shouldn't really be passed by the PCIe controller. + * The biggest disadvantage is the same FSR code may be reported when + * reading non-existing APB register and we shouldn't ignore that. + */ + if (fsr == (FSR_EXTERNAL | FSR_READ | FSR_IMPRECISE)) + return 0; + + return 1; +} + +static void __init bcm5301x_init_early(void) +{ + hook_fault_code(16 + 6, bcm5301x_abort_handler, SIGBUS, BUS_OBJERR, + "imprecise external abort"); +} + +DT_MACHINE_START(BCM5301X, "BCM5301X") + .l2c_aux_val = 0, + .l2c_aux_mask = ~0, + .dt_compat = bcm5301x_dt_compat, + .init_early = bcm5301x_init_early, +MACHINE_END diff --git a/arch/arm/mach-bcm/bcm_cygnus.c b/arch/arm/mach-bcm/bcm_cygnus.c new file mode 100644 index 000000000..2469b66cc --- /dev/null +++ b/arch/arm/mach-bcm/bcm_cygnus.c @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2014 Broadcom Corporation + +#include <asm/mach/arch.h> + +static const char * const bcm_cygnus_dt_compat[] __initconst = { + "brcm,cygnus", + NULL, +}; + +DT_MACHINE_START(BCM_CYGNUS_DT, "Broadcom Cygnus SoC") + .l2c_aux_val = 0, + .l2c_aux_mask = ~0, + .dt_compat = bcm_cygnus_dt_compat, +MACHINE_END diff --git a/arch/arm/mach-bcm/bcm_hr2.c b/arch/arm/mach-bcm/bcm_hr2.c new file mode 100644 index 000000000..a19cc206e --- /dev/null +++ b/arch/arm/mach-bcm/bcm_hr2.c @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2017 Broadcom + +#include <asm/mach/arch.h> + +static const char * const bcm_hr2_dt_compat[] __initconst = { + "brcm,hr2", + NULL, +}; + +DT_MACHINE_START(BCM_HR2_DT, "Broadcom Hurricane 2 SoC") + .l2c_aux_val = 0, + .l2c_aux_mask = ~0, + .dt_compat = bcm_hr2_dt_compat, +MACHINE_END diff --git a/arch/arm/mach-bcm/bcm_kona_smc.c b/arch/arm/mach-bcm/bcm_kona_smc.c new file mode 100644 index 000000000..185335843 --- /dev/null +++ b/arch/arm/mach-bcm/bcm_kona_smc.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2013 Broadcom Corporation +#include <linux/smp.h> +#include <linux/io.h> +#include <linux/ioport.h> + +#include <asm/cacheflush.h> +#include <linux/of_address.h> + +#include "bcm_kona_smc.h" + +static u32 bcm_smc_buffer_phys; /* physical address */ +static void __iomem *bcm_smc_buffer; /* virtual address */ + +struct bcm_kona_smc_data { + unsigned service_id; + unsigned arg0; + unsigned arg1; + unsigned arg2; + unsigned arg3; + unsigned result; +}; + +static const struct of_device_id bcm_kona_smc_ids[] __initconst = { + {.compatible = "brcm,kona-smc"}, + {.compatible = "bcm,kona-smc"}, /* deprecated name */ + {}, +}; + +/* Map in the args buffer area */ +int __init bcm_kona_smc_init(void) +{ + struct device_node *node; + const __be32 *prop_val; + u64 prop_size = 0; + unsigned long buffer_size; + u32 buffer_phys; + + /* Read buffer addr and size from the device tree node */ + node = of_find_matching_node(NULL, bcm_kona_smc_ids); + if (!node) + return -ENODEV; + + prop_val = of_get_address(node, 0, &prop_size, NULL); + of_node_put(node); + if (!prop_val) + return -EINVAL; + + /* We assume space for four 32-bit arguments */ + if (prop_size < 4 * sizeof(u32) || prop_size > (u64)ULONG_MAX) + return -EINVAL; + buffer_size = (unsigned long)prop_size; + + buffer_phys = be32_to_cpup(prop_val); + if (!buffer_phys) + return -EINVAL; + + bcm_smc_buffer = ioremap(buffer_phys, buffer_size); + if (!bcm_smc_buffer) + return -ENOMEM; + bcm_smc_buffer_phys = buffer_phys; + + pr_info("Kona Secure API initialized\n"); + + return 0; +} + +/* + * int bcm_kona_do_smc(u32 service_id, u32 buffer_addr) + * + * Only core 0 can run the secure monitor code. If an "smc" request + * is initiated on a different core it must be redirected to core 0 + * for execution. We rely on the caller to handle this. + * + * Each "smc" request supplies a service id and the address of a + * buffer containing parameters related to the service to be + * performed. A flags value defines the behavior of the level 2 + * cache and interrupt handling while the secure monitor executes. + * + * Parameters to the "smc" request are passed in r4-r6 as follows: + * r4 service id + * r5 flags (SEC_ROM_*) + * r6 physical address of buffer with other parameters + * + * Execution of an "smc" request produces two distinct results. + * + * First, the secure monitor call itself (regardless of the specific + * service request) can succeed, or can produce an error. When an + * "smc" request completes this value is found in r12; it should + * always be SEC_EXIT_NORMAL. + * + * In addition, the particular service performed produces a result. + * The values that should be expected depend on the service. We + * therefore return this value to the caller, so it can handle the + * request result appropriately. This result value is found in r0 + * when the "smc" request completes. + */ +static int bcm_kona_do_smc(u32 service_id, u32 buffer_phys) +{ + register u32 ip asm("ip"); /* Also called r12 */ + register u32 r0 asm("r0"); + register u32 r4 asm("r4"); + register u32 r5 asm("r5"); + register u32 r6 asm("r6"); + + r4 = service_id; + r5 = 0x3; /* Keep IRQ and FIQ off in SM */ + r6 = buffer_phys; + + asm volatile ( + /* Make sure we got the registers we want */ + __asmeq("%0", "ip") + __asmeq("%1", "r0") + __asmeq("%2", "r4") + __asmeq("%3", "r5") + __asmeq("%4", "r6") + ".arch_extension sec\n" + " smc #0\n" + : "=r" (ip), "=r" (r0) + : "r" (r4), "r" (r5), "r" (r6) + : "r1", "r2", "r3", "r7", "lr"); + + BUG_ON(ip != SEC_EXIT_NORMAL); + + return r0; +} + +/* __bcm_kona_smc() should only run on CPU 0, with pre-emption disabled */ +static void __bcm_kona_smc(void *info) +{ + struct bcm_kona_smc_data *data = info; + u32 __iomem *args = bcm_smc_buffer; + + BUG_ON(smp_processor_id() != 0); + BUG_ON(!args); + + /* Copy the four 32 bit argument values into the bounce area */ + writel_relaxed(data->arg0, args++); + writel_relaxed(data->arg1, args++); + writel_relaxed(data->arg2, args++); + writel(data->arg3, args); + + /* Flush caches for input data passed to Secure Monitor */ + flush_cache_all(); + + /* Trap into Secure Monitor and record the request result */ + data->result = bcm_kona_do_smc(data->service_id, bcm_smc_buffer_phys); +} + +unsigned bcm_kona_smc(unsigned service_id, unsigned arg0, unsigned arg1, + unsigned arg2, unsigned arg3) +{ + struct bcm_kona_smc_data data; + + data.service_id = service_id; + data.arg0 = arg0; + data.arg1 = arg1; + data.arg2 = arg2; + data.arg3 = arg3; + data.result = 0; + + /* + * Due to a limitation of the secure monitor, we must use the SMP + * infrastructure to forward all secure monitor calls to Core 0. + */ + smp_call_function_single(0, __bcm_kona_smc, &data, 1); + + return data.result; +} diff --git a/arch/arm/mach-bcm/bcm_kona_smc.h b/arch/arm/mach-bcm/bcm_kona_smc.h new file mode 100644 index 000000000..17f3811fa --- /dev/null +++ b/arch/arm/mach-bcm/bcm_kona_smc.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2013 Broadcom Corporation */ + +#ifndef BCM_KONA_SMC_H +#define BCM_KONA_SMC_H + +#include <linux/types.h> + +/* Broadcom Secure Service API service IDs, return codes, and exit codes */ +#define SSAPI_ENABLE_L2_CACHE 0x01000002 +#define SEC_ROM_RET_OK 0x00000001 +#define SEC_EXIT_NORMAL 0x1 + +extern int __init bcm_kona_smc_init(void); + +extern unsigned bcm_kona_smc(unsigned service_id, + unsigned arg0, + unsigned arg1, + unsigned arg2, + unsigned arg3); + +#endif /* BCM_KONA_SMC_H */ diff --git a/arch/arm/mach-bcm/bcm_nsp.c b/arch/arm/mach-bcm/bcm_nsp.c new file mode 100644 index 000000000..65ed9f8a5 --- /dev/null +++ b/arch/arm/mach-bcm/bcm_nsp.c @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2015 Broadcom Corporation + +#include <asm/mach/arch.h> + +static const char *const bcm_nsp_dt_compat[] __initconst = { + "brcm,nsp", + NULL, +}; + +DT_MACHINE_START(NSP_DT, "Broadcom Northstar Plus SoC") + .l2c_aux_val = 0, + .l2c_aux_mask = ~0, + .dt_compat = bcm_nsp_dt_compat, +MACHINE_END diff --git a/arch/arm/mach-bcm/board_bcm21664.c b/arch/arm/mach-bcm/board_bcm21664.c new file mode 100644 index 000000000..9ce9ed092 --- /dev/null +++ b/arch/arm/mach-bcm/board_bcm21664.c @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2014 Broadcom Corporation + +#include <asm/mach/arch.h> + +#include "kona_l2_cache.h" + +static void __init bcm21664_init(void) +{ + kona_l2_cache_init(); +} + +static const char * const bcm21664_dt_compat[] = { + "brcm,bcm21664", + NULL, +}; + +DT_MACHINE_START(BCM21664_DT, "BCM21664 Broadcom Application Processor") + .init_machine = bcm21664_init, + .dt_compat = bcm21664_dt_compat, +MACHINE_END diff --git a/arch/arm/mach-bcm/board_bcm23550.c b/arch/arm/mach-bcm/board_bcm23550.c new file mode 100644 index 000000000..dd6e9cb78 --- /dev/null +++ b/arch/arm/mach-bcm/board_bcm23550.c @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2016 Broadcom + +#include <linux/of_platform.h> + +#include <asm/mach/arch.h> + +static const char * const bcm23550_dt_compat[] = { + "brcm,bcm23550", + NULL, +}; + +DT_MACHINE_START(BCM23550_DT, "BCM23550 Broadcom Application Processor") + .dt_compat = bcm23550_dt_compat, +MACHINE_END diff --git a/arch/arm/mach-bcm/board_bcm281xx.c b/arch/arm/mach-bcm/board_bcm281xx.c new file mode 100644 index 000000000..91f6d633e --- /dev/null +++ b/arch/arm/mach-bcm/board_bcm281xx.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2012-2014 Broadcom Corporation + +#include <linux/clocksource.h> +#include <linux/of_address.h> + +#include <asm/mach/arch.h> + +#include "kona_l2_cache.h" + +#define SECWDOG_OFFSET 0x00000000 +#define SECWDOG_RESERVED_MASK 0xe2000000 +#define SECWDOG_WD_LOAD_FLAG_MASK 0x10000000 +#define SECWDOG_EN_MASK 0x08000000 +#define SECWDOG_SRSTEN_MASK 0x04000000 +#define SECWDOG_CLKS_SHIFT 20 +#define SECWDOG_COUNT_SHIFT 0 + +static void bcm281xx_restart(enum reboot_mode mode, const char *cmd) +{ + uint32_t val; + void __iomem *base; + struct device_node *np_wdog; + + np_wdog = of_find_compatible_node(NULL, NULL, "brcm,kona-wdt"); + if (!np_wdog) { + pr_emerg("Couldn't find brcm,kona-wdt\n"); + return; + } + base = of_iomap(np_wdog, 0); + of_node_put(np_wdog); + if (!base) { + pr_emerg("Couldn't map brcm,kona-wdt\n"); + return; + } + + /* Enable watchdog with short timeout (244us). */ + val = readl(base + SECWDOG_OFFSET); + val &= SECWDOG_RESERVED_MASK | SECWDOG_WD_LOAD_FLAG_MASK; + val |= SECWDOG_EN_MASK | SECWDOG_SRSTEN_MASK | + (0x15 << SECWDOG_CLKS_SHIFT) | + (0x8 << SECWDOG_COUNT_SHIFT); + writel(val, base + SECWDOG_OFFSET); + + /* Wait for reset */ + while (1); +} + +static void __init bcm281xx_init(void) +{ + kona_l2_cache_init(); +} + +static const char * const bcm281xx_dt_compat[] = { + "brcm,bcm11351", /* Have to use the first number upstreamed */ + NULL, +}; + +DT_MACHINE_START(BCM281XX_DT, "BCM281xx Broadcom Application Processor") + .init_machine = bcm281xx_init, + .restart = bcm281xx_restart, + .dt_compat = bcm281xx_dt_compat, +MACHINE_END diff --git a/arch/arm/mach-bcm/board_bcm2835.c b/arch/arm/mach-bcm/board_bcm2835.c new file mode 100644 index 000000000..bfc556f76 --- /dev/null +++ b/arch/arm/mach-bcm/board_bcm2835.c @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2010 Broadcom + */ + +#include <linux/init.h> +#include <linux/irqchip.h> +#include <linux/of_address.h> + +#include <asm/mach/arch.h> +#include <asm/mach/map.h> + +#include "platsmp.h" + +static const char * const bcm2835_compat[] = { +#ifdef CONFIG_ARCH_MULTI_V6 + "brcm,bcm2835", +#endif +#ifdef CONFIG_ARCH_MULTI_V7 + "brcm,bcm2836", + "brcm,bcm2837", +#endif + NULL +}; + +DT_MACHINE_START(BCM2835, "BCM2835") + .dt_compat = bcm2835_compat, + .smp = smp_ops(bcm2836_smp_ops), +MACHINE_END diff --git a/arch/arm/mach-bcm/brcmstb.c b/arch/arm/mach-bcm/brcmstb.c new file mode 100644 index 000000000..2e3385ced --- /dev/null +++ b/arch/arm/mach-bcm/brcmstb.c @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2013-2014 Broadcom Corporation + +#include <linux/init.h> +#include <linux/irqchip.h> +#include <linux/of_platform.h> + +#include <asm/mach-types.h> +#include <asm/mach/arch.h> + +/* + * Storage for debug-macro.S's state. + * + * This must be in .data not .bss so that it gets initialized each time the + * kernel is loaded. The data is declared here rather than debug-macro.S so + * that multiple inclusions of debug-macro.S point at the same data. + */ +u32 brcmstb_uart_config[3] = { + /* Debug UART initialization required */ + 1, + /* Debug UART physical address */ + 0, + /* Debug UART virtual address */ + 0, +}; + +static void __init brcmstb_init_irq(void) +{ + irqchip_init(); +} + +static const char *const brcmstb_match[] __initconst = { + "brcm,bcm7445", + "brcm,brcmstb", + NULL +}; + +DT_MACHINE_START(BRCMSTB, "Broadcom STB (Flattened Device Tree)") + .dt_compat = brcmstb_match, + .init_irq = brcmstb_init_irq, +MACHINE_END diff --git a/arch/arm/mach-bcm/kona_l2_cache.c b/arch/arm/mach-bcm/kona_l2_cache.c new file mode 100644 index 000000000..457c66e1d --- /dev/null +++ b/arch/arm/mach-bcm/kona_l2_cache.c @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2012-2014 Broadcom Corporation + + +#include <linux/init.h> +#include <linux/printk.h> +#include <asm/hardware/cache-l2x0.h> + +#include "bcm_kona_smc.h" +#include "kona_l2_cache.h" + +void __init kona_l2_cache_init(void) +{ + unsigned int result; + int ret; + + ret = bcm_kona_smc_init(); + if (ret) { + pr_info("Secure API not available (%d). Skipping L2 init.\n", + ret); + return; + } + + result = bcm_kona_smc(SSAPI_ENABLE_L2_CACHE, 0, 0, 0, 0); + if (result != SEC_ROM_RET_OK) { + pr_err("Secure Monitor call failed (%u)! Skipping L2 init.\n", + result); + return; + } + + /* + * The aux_val and aux_mask have no effect since L2 cache is already + * enabled. Pass 0s for aux_val and 1s for aux_mask for default value. + */ + ret = l2x0_of_init(0, ~0); + if (ret) + pr_err("Couldn't enable L2 cache: %d\n", ret); +} diff --git a/arch/arm/mach-bcm/kona_l2_cache.h b/arch/arm/mach-bcm/kona_l2_cache.h new file mode 100644 index 000000000..77c5dc033 --- /dev/null +++ b/arch/arm/mach-bcm/kona_l2_cache.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2012-2014 Broadcom Corporation */ + +#ifdef CONFIG_ARCH_BCM_MOBILE_L2_CACHE +void kona_l2_cache_init(void); +#else +#define kona_l2_cache_init() ((void)0) +#endif diff --git a/arch/arm/mach-bcm/platsmp-brcmstb.c b/arch/arm/mach-bcm/platsmp-brcmstb.c new file mode 100644 index 000000000..8989299eb --- /dev/null +++ b/arch/arm/mach-bcm/platsmp-brcmstb.c @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Broadcom STB CPU SMP and hotplug support for ARM + * + * Copyright (C) 2013-2014 Broadcom Corporation + */ + +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/jiffies.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/printk.h> +#include <linux/regmap.h> +#include <linux/smp.h> +#include <linux/mfd/syscon.h> + +#include <asm/cacheflush.h> +#include <asm/cp15.h> +#include <asm/mach-types.h> +#include <asm/smp_plat.h> + +enum { + ZONE_MAN_CLKEN_MASK = BIT(0), + ZONE_MAN_RESET_CNTL_MASK = BIT(1), + ZONE_MAN_MEM_PWR_MASK = BIT(4), + ZONE_RESERVED_1_MASK = BIT(5), + ZONE_MAN_ISO_CNTL_MASK = BIT(6), + ZONE_MANUAL_CONTROL_MASK = BIT(7), + ZONE_PWR_DN_REQ_MASK = BIT(9), + ZONE_PWR_UP_REQ_MASK = BIT(10), + ZONE_BLK_RST_ASSERT_MASK = BIT(12), + ZONE_PWR_OFF_STATE_MASK = BIT(25), + ZONE_PWR_ON_STATE_MASK = BIT(26), + ZONE_DPG_PWR_STATE_MASK = BIT(28), + ZONE_MEM_PWR_STATE_MASK = BIT(29), + ZONE_RESET_STATE_MASK = BIT(31), + CPU0_PWR_ZONE_CTRL_REG = 1, + CPU_RESET_CONFIG_REG = 2, +}; + +static void __iomem *cpubiuctrl_block; +static void __iomem *hif_cont_block; +static u32 cpu0_pwr_zone_ctrl_reg; +static u32 cpu_rst_cfg_reg; +static u32 hif_cont_reg; + +#ifdef CONFIG_HOTPLUG_CPU +/* + * We must quiesce a dying CPU before it can be killed by the boot CPU. Because + * one or more cache may be disabled, we must flush to ensure coherency. We + * cannot use traditional completion structures or spinlocks as they rely on + * coherency. + */ +static DEFINE_PER_CPU_ALIGNED(int, per_cpu_sw_state); + +static int per_cpu_sw_state_rd(u32 cpu) +{ + sync_cache_r(SHIFT_PERCPU_PTR(&per_cpu_sw_state, per_cpu_offset(cpu))); + return per_cpu(per_cpu_sw_state, cpu); +} + +static void per_cpu_sw_state_wr(u32 cpu, int val) +{ + dmb(); + per_cpu(per_cpu_sw_state, cpu) = val; + sync_cache_w(SHIFT_PERCPU_PTR(&per_cpu_sw_state, per_cpu_offset(cpu))); +} +#else +static inline void per_cpu_sw_state_wr(u32 cpu, int val) { } +#endif + +static void __iomem *pwr_ctrl_get_base(u32 cpu) +{ + void __iomem *base = cpubiuctrl_block + cpu0_pwr_zone_ctrl_reg; + base += (cpu_logical_map(cpu) * 4); + return base; +} + +static u32 pwr_ctrl_rd(u32 cpu) +{ + void __iomem *base = pwr_ctrl_get_base(cpu); + return readl_relaxed(base); +} + +static void pwr_ctrl_set(unsigned int cpu, u32 val, u32 mask) +{ + void __iomem *base = pwr_ctrl_get_base(cpu); + writel((readl(base) & mask) | val, base); +} + +static void pwr_ctrl_clr(unsigned int cpu, u32 val, u32 mask) +{ + void __iomem *base = pwr_ctrl_get_base(cpu); + writel((readl(base) & mask) & ~val, base); +} + +#define POLL_TMOUT_MS 500 +static int pwr_ctrl_wait_tmout(unsigned int cpu, u32 set, u32 mask) +{ + const unsigned long timeo = jiffies + msecs_to_jiffies(POLL_TMOUT_MS); + u32 tmp; + + do { + tmp = pwr_ctrl_rd(cpu) & mask; + if (!set == !tmp) + return 0; + } while (time_before(jiffies, timeo)); + + tmp = pwr_ctrl_rd(cpu) & mask; + if (!set == !tmp) + return 0; + + return -ETIMEDOUT; +} + +static void cpu_rst_cfg_set(u32 cpu, int set) +{ + u32 val; + val = readl_relaxed(cpubiuctrl_block + cpu_rst_cfg_reg); + if (set) + val |= BIT(cpu_logical_map(cpu)); + else + val &= ~BIT(cpu_logical_map(cpu)); + writel_relaxed(val, cpubiuctrl_block + cpu_rst_cfg_reg); +} + +static void cpu_set_boot_addr(u32 cpu, unsigned long boot_addr) +{ + const int reg_ofs = cpu_logical_map(cpu) * 8; + writel_relaxed(0, hif_cont_block + hif_cont_reg + reg_ofs); + writel_relaxed(boot_addr, hif_cont_block + hif_cont_reg + 4 + reg_ofs); +} + +static void brcmstb_cpu_boot(u32 cpu) +{ + /* Mark this CPU as "up" */ + per_cpu_sw_state_wr(cpu, 1); + + /* + * Set the reset vector to point to the secondary_startup + * routine + */ + cpu_set_boot_addr(cpu, __pa_symbol(secondary_startup)); + + /* Unhalt the cpu */ + cpu_rst_cfg_set(cpu, 0); +} + +static void brcmstb_cpu_power_on(u32 cpu) +{ + /* + * The secondary cores power was cut, so we must go through + * power-on initialization. + */ + pwr_ctrl_set(cpu, ZONE_MAN_ISO_CNTL_MASK, 0xffffff00); + pwr_ctrl_set(cpu, ZONE_MANUAL_CONTROL_MASK, -1); + pwr_ctrl_set(cpu, ZONE_RESERVED_1_MASK, -1); + + pwr_ctrl_set(cpu, ZONE_MAN_MEM_PWR_MASK, -1); + + if (pwr_ctrl_wait_tmout(cpu, 1, ZONE_MEM_PWR_STATE_MASK)) + panic("ZONE_MEM_PWR_STATE_MASK set timeout"); + + pwr_ctrl_set(cpu, ZONE_MAN_CLKEN_MASK, -1); + + if (pwr_ctrl_wait_tmout(cpu, 1, ZONE_DPG_PWR_STATE_MASK)) + panic("ZONE_DPG_PWR_STATE_MASK set timeout"); + + pwr_ctrl_clr(cpu, ZONE_MAN_ISO_CNTL_MASK, -1); + pwr_ctrl_set(cpu, ZONE_MAN_RESET_CNTL_MASK, -1); +} + +static int brcmstb_cpu_get_power_state(u32 cpu) +{ + int tmp = pwr_ctrl_rd(cpu); + return (tmp & ZONE_RESET_STATE_MASK) ? 0 : 1; +} + +#ifdef CONFIG_HOTPLUG_CPU + +static void brcmstb_cpu_die(u32 cpu) +{ + v7_exit_coherency_flush(all); + + per_cpu_sw_state_wr(cpu, 0); + + /* Sit and wait to die */ + wfi(); + + /* We should never get here... */ + while (1) + ; +} + +static int brcmstb_cpu_kill(u32 cpu) +{ + /* + * Ordinarily, the hardware forbids power-down of CPU0 (which is good + * because it is the boot CPU), but this is not true when using BPCM + * manual mode. Consequently, we must avoid turning off CPU0 here to + * ensure that TI2C master reset will work. + */ + if (cpu == 0) { + pr_warn("SMP: refusing to power off CPU0\n"); + return 1; + } + + while (per_cpu_sw_state_rd(cpu)) + ; + + pwr_ctrl_set(cpu, ZONE_MANUAL_CONTROL_MASK, -1); + pwr_ctrl_clr(cpu, ZONE_MAN_RESET_CNTL_MASK, -1); + pwr_ctrl_clr(cpu, ZONE_MAN_CLKEN_MASK, -1); + pwr_ctrl_set(cpu, ZONE_MAN_ISO_CNTL_MASK, -1); + pwr_ctrl_clr(cpu, ZONE_MAN_MEM_PWR_MASK, -1); + + if (pwr_ctrl_wait_tmout(cpu, 0, ZONE_MEM_PWR_STATE_MASK)) + panic("ZONE_MEM_PWR_STATE_MASK clear timeout"); + + pwr_ctrl_clr(cpu, ZONE_RESERVED_1_MASK, -1); + + if (pwr_ctrl_wait_tmout(cpu, 0, ZONE_DPG_PWR_STATE_MASK)) + panic("ZONE_DPG_PWR_STATE_MASK clear timeout"); + + /* Flush pipeline before resetting CPU */ + mb(); + + /* Assert reset on the CPU */ + cpu_rst_cfg_set(cpu, 1); + + return 1; +} + +#endif /* CONFIG_HOTPLUG_CPU */ + +static int __init setup_hifcpubiuctrl_regs(struct device_node *np) +{ + int rc = 0; + char *name; + struct device_node *syscon_np = NULL; + + name = "syscon-cpu"; + + syscon_np = of_parse_phandle(np, name, 0); + if (!syscon_np) { + pr_err("can't find phandle %s\n", name); + rc = -EINVAL; + goto cleanup; + } + + cpubiuctrl_block = of_iomap(syscon_np, 0); + if (!cpubiuctrl_block) { + pr_err("iomap failed for cpubiuctrl_block\n"); + rc = -EINVAL; + goto cleanup; + } + + rc = of_property_read_u32_index(np, name, CPU0_PWR_ZONE_CTRL_REG, + &cpu0_pwr_zone_ctrl_reg); + if (rc) { + pr_err("failed to read 1st entry from %s property (%d)\n", name, + rc); + rc = -EINVAL; + goto cleanup; + } + + rc = of_property_read_u32_index(np, name, CPU_RESET_CONFIG_REG, + &cpu_rst_cfg_reg); + if (rc) { + pr_err("failed to read 2nd entry from %s property (%d)\n", name, + rc); + rc = -EINVAL; + goto cleanup; + } + +cleanup: + of_node_put(syscon_np); + return rc; +} + +static int __init setup_hifcont_regs(struct device_node *np) +{ + int rc = 0; + char *name; + struct device_node *syscon_np = NULL; + + name = "syscon-cont"; + + syscon_np = of_parse_phandle(np, name, 0); + if (!syscon_np) { + pr_err("can't find phandle %s\n", name); + rc = -EINVAL; + goto cleanup; + } + + hif_cont_block = of_iomap(syscon_np, 0); + if (!hif_cont_block) { + pr_err("iomap failed for hif_cont_block\n"); + rc = -EINVAL; + goto cleanup; + } + + /* Offset is at top of hif_cont_block */ + hif_cont_reg = 0; + +cleanup: + of_node_put(syscon_np); + return rc; +} + +static void __init brcmstb_cpu_ctrl_setup(unsigned int max_cpus) +{ + int rc; + struct device_node *np; + char *name; + + name = "brcm,brcmstb-smpboot"; + np = of_find_compatible_node(NULL, NULL, name); + if (!np) { + pr_err("can't find compatible node %s\n", name); + return; + } + + rc = setup_hifcpubiuctrl_regs(np); + if (rc) + goto out_put_node; + + rc = setup_hifcont_regs(np); + if (rc) + goto out_put_node; + +out_put_node: + of_node_put(np); +} + +static int brcmstb_boot_secondary(unsigned int cpu, struct task_struct *idle) +{ + /* Missing the brcm,brcmstb-smpboot DT node? */ + if (!cpubiuctrl_block || !hif_cont_block) + return -ENODEV; + + /* Bring up power to the core if necessary */ + if (brcmstb_cpu_get_power_state(cpu) == 0) + brcmstb_cpu_power_on(cpu); + + brcmstb_cpu_boot(cpu); + + return 0; +} + +static const struct smp_operations brcmstb_smp_ops __initconst = { + .smp_prepare_cpus = brcmstb_cpu_ctrl_setup, + .smp_boot_secondary = brcmstb_boot_secondary, +#ifdef CONFIG_HOTPLUG_CPU + .cpu_kill = brcmstb_cpu_kill, + .cpu_die = brcmstb_cpu_die, +#endif +}; + +CPU_METHOD_OF_DECLARE(brcmstb_smp, "brcm,brahma-b15", &brcmstb_smp_ops); diff --git a/arch/arm/mach-bcm/platsmp.c b/arch/arm/mach-bcm/platsmp.c new file mode 100644 index 000000000..c9db2a900 --- /dev/null +++ b/arch/arm/mach-bcm/platsmp.c @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2014-2015 Broadcom Corporation + * Copyright 2014 Linaro Limited + */ + +#include <linux/cpumask.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/irqchip/irq-bcm2836.h> +#include <linux/jiffies.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/sched.h> +#include <linux/sched/clock.h> +#include <linux/smp.h> + +#include <asm/cacheflush.h> +#include <asm/smp.h> +#include <asm/smp_plat.h> +#include <asm/smp_scu.h> + +#include "platsmp.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" +#define MPIDR_CPUID_BITMASK 0x3 + +/* + * 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 u32 secondary_boot_addr_for(unsigned int cpu) +{ + u32 secondary_boot_addr = 0; + struct device_node *cpu_node = of_get_cpu_node(cpu, NULL); + + if (!cpu_node) { + pr_err("Failed to find device tree node for CPU%u\n", cpu); + return 0; + } + + if (of_property_read_u32(cpu_node, + OF_SECONDARY_BOOT, + &secondary_boot_addr)) + pr_err("required secondary boot register not specified for CPU%u\n", + cpu); + + of_node_put(cpu_node); + + return secondary_boot_addr; +} + +static int nsp_write_lut(unsigned int cpu) +{ + void __iomem *sku_rom_lut; + phys_addr_t secondary_startup_phy; + const u32 secondary_boot_addr = secondary_boot_addr_for(cpu); + + if (!secondary_boot_addr) + return -EINVAL; + + sku_rom_lut = ioremap((phys_addr_t)secondary_boot_addr, + sizeof(phys_addr_t)); + if (!sku_rom_lut) { + pr_warn("unable to ioremap SKU-ROM LUT register for cpu %u\n", cpu); + return -ENOMEM; + } + + secondary_startup_phy = __pa_symbol(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 __init bcm_smp_prepare_cpus(unsigned int max_cpus) +{ + const cpumask_t only_cpu_0 = { CPU_BITS_CPU0 }; + + /* Enable the SCU on Cortex A9 based SoCs */ + if (scu_a9_enable()) { + /* Update the CPU present map to reflect uniprocessor mode */ + pr_warn("failed to enable A9 SCU - 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; + const u32 secondary_boot_addr = secondary_boot_addr_for(cpu); + + 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_addr) + return -EINVAL; + + boot_reg = ioremap((phys_addr_t)secondary_boot_addr, + sizeof(phys_addr_t)); + 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 = __pa_symbol(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; +} + +/* Cluster Dormant Control command to bring CPU into a running state */ +#define CDC_CMD 6 +#define CDC_CMD_OFFSET 0 +#define CDC_CMD_REG(cpu) (CDC_CMD_OFFSET + 4*(cpu)) + +/* + * BCM23550 has a Cluster Dormant Control block that keeps the core in + * idle state. A command needs to be sent to the block to bring the CPU + * into running state. + */ +static int bcm23550_boot_secondary(unsigned int cpu, struct task_struct *idle) +{ + void __iomem *cdc_base; + struct device_node *dn; + char *name; + int ret; + + /* Make sure a CDC node exists before booting the + * secondary core. + */ + name = "brcm,bcm23550-cdc"; + dn = of_find_compatible_node(NULL, NULL, name); + if (!dn) { + pr_err("unable to find cdc node\n"); + return -ENODEV; + } + + cdc_base = of_iomap(dn, 0); + of_node_put(dn); + + if (!cdc_base) { + pr_err("unable to remap cdc base register\n"); + return -ENOMEM; + } + + /* Boot the secondary core */ + ret = kona_boot_secondary(cpu, idle); + if (ret) + goto out; + + /* Bring this CPU to RUN state so that nIRQ nFIQ + * signals are unblocked. + */ + writel_relaxed(CDC_CMD, cdc_base + CDC_CMD_REG(cpu)); + +out: + iounmap(cdc_base); + + return ret; +} + +static int nsp_boot_secondary(unsigned int cpu, struct task_struct *idle) +{ + int ret; + + /* + * After wake up, secondary core branches to the startup + * address programmed at SKU ROM LUT location. + */ + ret = nsp_write_lut(cpu); + if (ret) { + pr_err("unable to write startup addr to SKU ROM LUT\n"); + goto out; + } + + /* Send a CPU wakeup interrupt to the secondary core */ + arch_send_wakeup_ipi_mask(cpumask_of(cpu)); + +out: + return ret; +} + +static int bcm2836_boot_secondary(unsigned int cpu, struct task_struct *idle) +{ + void __iomem *intc_base; + struct device_node *dn; + char *name; + + name = "brcm,bcm2836-l1-intc"; + dn = of_find_compatible_node(NULL, NULL, name); + if (!dn) { + pr_err("unable to find intc node\n"); + return -ENODEV; + } + + intc_base = of_iomap(dn, 0); + of_node_put(dn); + + if (!intc_base) { + pr_err("unable to remap intc base register\n"); + return -ENOMEM; + } + + writel(virt_to_phys(secondary_startup), + intc_base + LOCAL_MAILBOX3_SET0 + 16 * cpu); + + dsb(sy); + sev(); + + iounmap(intc_base); + + return 0; +} + +static const struct smp_operations kona_smp_ops __initconst = { + .smp_prepare_cpus = bcm_smp_prepare_cpus, + .smp_boot_secondary = kona_boot_secondary, +}; +CPU_METHOD_OF_DECLARE(bcm_smp_bcm281xx, "brcm,bcm11351-cpu-method", + &kona_smp_ops); + +static const struct smp_operations bcm23550_smp_ops __initconst = { + .smp_boot_secondary = bcm23550_boot_secondary, +}; +CPU_METHOD_OF_DECLARE(bcm_smp_bcm23550, "brcm,bcm23550", + &bcm23550_smp_ops); + +static const struct smp_operations nsp_smp_ops __initconst = { + .smp_prepare_cpus = bcm_smp_prepare_cpus, + .smp_boot_secondary = nsp_boot_secondary, +}; +CPU_METHOD_OF_DECLARE(bcm_smp_nsp, "brcm,bcm-nsp-smp", &nsp_smp_ops); + +const struct smp_operations bcm2836_smp_ops __initconst = { + .smp_boot_secondary = bcm2836_boot_secondary, +}; +CPU_METHOD_OF_DECLARE(bcm_smp_bcm2836, "brcm,bcm2836-smp", &bcm2836_smp_ops); diff --git a/arch/arm/mach-bcm/platsmp.h b/arch/arm/mach-bcm/platsmp.h new file mode 100644 index 000000000..e65bffad1 --- /dev/null +++ b/arch/arm/mach-bcm/platsmp.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2017 Stefan Wahren <stefan.wahren@i2se.com> + */ + +extern const struct smp_operations bcm2836_smp_ops; |