diff options
Diffstat (limited to 'arch/arm/mach-hisi')
-rw-r--r-- | arch/arm/mach-hisi/Kconfig | 53 | ||||
-rw-r--r-- | arch/arm/mach-hisi/Makefile | 9 | ||||
-rw-r--r-- | arch/arm/mach-hisi/core.h | 23 | ||||
-rw-r--r-- | arch/arm/mach-hisi/headsmp.S | 16 | ||||
-rw-r--r-- | arch/arm/mach-hisi/hisilicon.c | 84 | ||||
-rw-r--r-- | arch/arm/mach-hisi/hotplug.c | 291 | ||||
-rw-r--r-- | arch/arm/mach-hisi/platmcpm.c | 386 | ||||
-rw-r--r-- | arch/arm/mach-hisi/platsmp.c | 186 |
8 files changed, 1048 insertions, 0 deletions
diff --git a/arch/arm/mach-hisi/Kconfig b/arch/arm/mach-hisi/Kconfig new file mode 100644 index 000000000..83061ad0e --- /dev/null +++ b/arch/arm/mach-hisi/Kconfig @@ -0,0 +1,53 @@ +config ARCH_HISI + bool "Hisilicon SoC Support" + depends on ARCH_MULTI_V7 + select ARM_AMBA + select ARM_GIC + select ARM_TIMER_SP804 + select POWER_RESET + select POWER_RESET_HISI + select POWER_SUPPLY + +if ARCH_HISI + +menu "Hisilicon platform type" + +config ARCH_HI3xxx + bool "Hisilicon Hi36xx family" if ARCH_MULTI_V7 + select CACHE_L2X0 + select HAVE_ARM_SCU if SMP + select HAVE_ARM_TWD if SMP + select PINCTRL + select PINCTRL_SINGLE + help + Support for Hisilicon Hi36xx SoC family + +config ARCH_HIP01 + bool "Hisilicon HIP01 family" if ARCH_MULTI_V7 + select HAVE_ARM_SCU if SMP + select HAVE_ARM_TWD if SMP + select ARM_GLOBAL_TIMER + help + Support for Hisilicon HIP01 SoC family + +config ARCH_HIP04 + bool "Hisilicon HiP04 Cortex A15 family" if ARCH_MULTI_V7 + select ARM_ERRATA_798181 if SMP + select HAVE_ARM_ARCH_TIMER + select MCPM if SMP + select MCPM_QUAD_CLUSTER if SMP + help + Support for Hisilicon HiP04 SoC family + +config ARCH_HIX5HD2 + bool "Hisilicon X5HD2 family" if ARCH_MULTI_V7 + select CACHE_L2X0 + select HAVE_ARM_SCU if SMP + select HAVE_ARM_TWD if SMP + select PINCTRL + select PINCTRL_SINGLE + help + Support for Hisilicon HIX5HD2 SoC family +endmenu + +endif diff --git a/arch/arm/mach-hisi/Makefile b/arch/arm/mach-hisi/Makefile new file mode 100644 index 000000000..6b7b3033d --- /dev/null +++ b/arch/arm/mach-hisi/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for Hisilicon processors family +# + +CFLAGS_platmcpm.o := -march=armv7-a + +obj-y += hisilicon.o +obj-$(CONFIG_MCPM) += platmcpm.o +obj-$(CONFIG_SMP) += platsmp.o hotplug.o headsmp.o diff --git a/arch/arm/mach-hisi/core.h b/arch/arm/mach-hisi/core.h new file mode 100644 index 000000000..92a682d8e --- /dev/null +++ b/arch/arm/mach-hisi/core.h @@ -0,0 +1,23 @@ +#ifndef __HISILICON_CORE_H +#define __HISILICON_CORE_H + +#include <linux/reboot.h> + +extern void hi3xxx_set_cpu_jump(int cpu, void *jump_addr); +extern int hi3xxx_get_cpu_jump(int cpu); +extern void secondary_startup(void); +extern struct smp_operations hi3xxx_smp_ops; + +extern void hi3xxx_cpu_die(unsigned int cpu); +extern int hi3xxx_cpu_kill(unsigned int cpu); +extern void hi3xxx_set_cpu(int cpu, bool enable); + +extern void hisi_secondary_startup(void); +extern struct smp_operations hix5hd2_smp_ops; +extern void hix5hd2_set_cpu(int cpu, bool enable); +extern void hix5hd2_cpu_die(unsigned int cpu); + +extern struct smp_operations hip01_smp_ops; +extern void hip01_set_cpu(int cpu, bool enable); +extern void hip01_cpu_die(unsigned int cpu); +#endif diff --git a/arch/arm/mach-hisi/headsmp.S b/arch/arm/mach-hisi/headsmp.S new file mode 100644 index 000000000..81e35b159 --- /dev/null +++ b/arch/arm/mach-hisi/headsmp.S @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2014 Hisilicon Limited. + * Copyright (c) 2014 Linaro Ltd. + * + * 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/linkage.h> +#include <linux/init.h> + + __CPUINIT + +ENTRY(hisi_secondary_startup) + bl v7_invalidate_l1 + b secondary_startup diff --git a/arch/arm/mach-hisi/hisilicon.c b/arch/arm/mach-hisi/hisilicon.c new file mode 100644 index 000000000..c6bd7c7bd --- /dev/null +++ b/arch/arm/mach-hisi/hisilicon.c @@ -0,0 +1,84 @@ +/* + * (Hisilicon's SoC based) flattened device tree enabled machine + * + * Copyright (c) 2012-2013 Hisilicon Ltd. + * Copyright (c) 2012-2013 Linaro Ltd. + * + * Author: Haojian Zhuang <haojian.zhuang@linaro.org> + * + * 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/clk-provider.h> +#include <linux/clocksource.h> +#include <linux/irqchip.h> + +#include <asm/mach/arch.h> +#include <asm/mach/map.h> + +#define HI3620_SYSCTRL_PHYS_BASE 0xfc802000 +#define HI3620_SYSCTRL_VIRT_BASE 0xfe802000 + +/* + * This table is only for optimization. Since ioremap() could always share + * the same mapping if it's defined as static IO mapping. + * + * Without this table, system could also work. The cost is some virtual address + * spaces wasted since ioremap() may be called multi times for the same + * IO space. + */ +static struct map_desc hi3620_io_desc[] __initdata = { + { + /* sysctrl */ + .pfn = __phys_to_pfn(HI3620_SYSCTRL_PHYS_BASE), + .virtual = HI3620_SYSCTRL_VIRT_BASE, + .length = 0x1000, + .type = MT_DEVICE, + }, +}; + +static void __init hi3620_map_io(void) +{ + debug_ll_io_init(); + iotable_init(hi3620_io_desc, ARRAY_SIZE(hi3620_io_desc)); +} + +static const char *const hi3xxx_compat[] __initconst = { + "hisilicon,hi3620-hi4511", + NULL, +}; + +DT_MACHINE_START(HI3620, "Hisilicon Hi3620 (Flattened Device Tree)") + .map_io = hi3620_map_io, + .dt_compat = hi3xxx_compat, +MACHINE_END + +static const char *const hix5hd2_compat[] __initconst = { + "hisilicon,hix5hd2", + NULL, +}; + +DT_MACHINE_START(HIX5HD2_DT, "Hisilicon HIX5HD2 (Flattened Device Tree)") + .dt_compat = hix5hd2_compat, +MACHINE_END + +static const char *const hip04_compat[] __initconst = { + "hisilicon,hip04-d01", + NULL, +}; + +DT_MACHINE_START(HIP04, "Hisilicon HiP04 (Flattened Device Tree)") + .dt_compat = hip04_compat, +MACHINE_END + +static const char *const hip01_compat[] __initconst = { + "hisilicon,hip01", + "hisilicon,hip01-ca9x2", + NULL, +}; + +DT_MACHINE_START(HIP01, "Hisilicon HIP01 (Flattened Device Tree)") + .dt_compat = hip01_compat, +MACHINE_END diff --git a/arch/arm/mach-hisi/hotplug.c b/arch/arm/mach-hisi/hotplug.c new file mode 100644 index 000000000..a129aae72 --- /dev/null +++ b/arch/arm/mach-hisi/hotplug.c @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2013 Linaro Ltd. + * Copyright (c) 2013 Hisilicon Limited. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include <linux/cpu.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <asm/cacheflush.h> +#include <asm/smp_plat.h> +#include "core.h" + +/* Sysctrl registers in Hi3620 SoC */ +#define SCISOEN 0xc0 +#define SCISODIS 0xc4 +#define SCPERPWREN 0xd0 +#define SCPERPWRDIS 0xd4 +#define SCCPUCOREEN 0xf4 +#define SCCPUCOREDIS 0xf8 +#define SCPERCTRL0 0x200 +#define SCCPURSTEN 0x410 +#define SCCPURSTDIS 0x414 + +/* + * bit definition in SCISOEN/SCPERPWREN/... + * + * CPU2_ISO_CTRL (1 << 5) + * CPU3_ISO_CTRL (1 << 6) + * ... + */ +#define CPU2_ISO_CTRL (1 << 5) + +/* + * bit definition in SCPERCTRL0 + * + * CPU0_WFI_MASK_CFG (1 << 28) + * CPU1_WFI_MASK_CFG (1 << 29) + * ... + */ +#define CPU0_WFI_MASK_CFG (1 << 28) + +/* + * bit definition in SCCPURSTEN/... + * + * CPU0_SRST_REQ_EN (1 << 0) + * CPU1_SRST_REQ_EN (1 << 1) + * ... + */ +#define CPU0_HPM_SRST_REQ_EN (1 << 22) +#define CPU0_DBG_SRST_REQ_EN (1 << 12) +#define CPU0_NEON_SRST_REQ_EN (1 << 4) +#define CPU0_SRST_REQ_EN (1 << 0) + +#define HIX5HD2_PERI_CRG20 0x50 +#define CRG20_CPU1_RESET (1 << 17) + +#define HIX5HD2_PERI_PMC0 0x1000 +#define PMC0_CPU1_WAIT_MTCOMS_ACK (1 << 8) +#define PMC0_CPU1_PMC_ENABLE (1 << 7) +#define PMC0_CPU1_POWERDOWN (1 << 3) + +#define HIP01_PERI9 0x50 +#define PERI9_CPU1_RESET (1 << 1) + +enum { + HI3620_CTRL, + ERROR_CTRL, +}; + +static void __iomem *ctrl_base; +static int id; + +static void set_cpu_hi3620(int cpu, bool enable) +{ + u32 val = 0; + + if (enable) { + /* MTCMOS set */ + if ((cpu == 2) || (cpu == 3)) + writel_relaxed(CPU2_ISO_CTRL << (cpu - 2), + ctrl_base + SCPERPWREN); + udelay(100); + + /* Enable core */ + writel_relaxed(0x01 << cpu, ctrl_base + SCCPUCOREEN); + + /* unreset */ + val = CPU0_DBG_SRST_REQ_EN | CPU0_NEON_SRST_REQ_EN + | CPU0_SRST_REQ_EN; + writel_relaxed(val << cpu, ctrl_base + SCCPURSTDIS); + /* reset */ + val |= CPU0_HPM_SRST_REQ_EN; + writel_relaxed(val << cpu, ctrl_base + SCCPURSTEN); + + /* ISO disable */ + if ((cpu == 2) || (cpu == 3)) + writel_relaxed(CPU2_ISO_CTRL << (cpu - 2), + ctrl_base + SCISODIS); + udelay(1); + + /* WFI Mask */ + val = readl_relaxed(ctrl_base + SCPERCTRL0); + val &= ~(CPU0_WFI_MASK_CFG << cpu); + writel_relaxed(val, ctrl_base + SCPERCTRL0); + + /* Unreset */ + val = CPU0_DBG_SRST_REQ_EN | CPU0_NEON_SRST_REQ_EN + | CPU0_SRST_REQ_EN | CPU0_HPM_SRST_REQ_EN; + writel_relaxed(val << cpu, ctrl_base + SCCPURSTDIS); + } else { + /* wfi mask */ + val = readl_relaxed(ctrl_base + SCPERCTRL0); + val |= (CPU0_WFI_MASK_CFG << cpu); + writel_relaxed(val, ctrl_base + SCPERCTRL0); + + /* disable core*/ + writel_relaxed(0x01 << cpu, ctrl_base + SCCPUCOREDIS); + + if ((cpu == 2) || (cpu == 3)) { + /* iso enable */ + writel_relaxed(CPU2_ISO_CTRL << (cpu - 2), + ctrl_base + SCISOEN); + udelay(1); + } + + /* reset */ + val = CPU0_DBG_SRST_REQ_EN | CPU0_NEON_SRST_REQ_EN + | CPU0_SRST_REQ_EN | CPU0_HPM_SRST_REQ_EN; + writel_relaxed(val << cpu, ctrl_base + SCCPURSTEN); + + if ((cpu == 2) || (cpu == 3)) { + /* MTCMOS unset */ + writel_relaxed(CPU2_ISO_CTRL << (cpu - 2), + ctrl_base + SCPERPWRDIS); + udelay(100); + } + } +} + +static int hi3xxx_hotplug_init(void) +{ + struct device_node *node; + + node = of_find_compatible_node(NULL, NULL, "hisilicon,sysctrl"); + if (node) { + ctrl_base = of_iomap(node, 0); + id = HI3620_CTRL; + return 0; + } + id = ERROR_CTRL; + return -ENOENT; +} + +void hi3xxx_set_cpu(int cpu, bool enable) +{ + if (!ctrl_base) { + if (hi3xxx_hotplug_init() < 0) + return; + } + + if (id == HI3620_CTRL) + set_cpu_hi3620(cpu, enable); +} + +static bool hix5hd2_hotplug_init(void) +{ + struct device_node *np; + + np = of_find_compatible_node(NULL, NULL, "hisilicon,cpuctrl"); + if (np) { + ctrl_base = of_iomap(np, 0); + return true; + } + return false; +} + +void hix5hd2_set_cpu(int cpu, bool enable) +{ + u32 val = 0; + + if (!ctrl_base) + if (!hix5hd2_hotplug_init()) + BUG(); + + if (enable) { + /* power on cpu1 */ + val = readl_relaxed(ctrl_base + HIX5HD2_PERI_PMC0); + val &= ~(PMC0_CPU1_WAIT_MTCOMS_ACK | PMC0_CPU1_POWERDOWN); + val |= PMC0_CPU1_PMC_ENABLE; + writel_relaxed(val, ctrl_base + HIX5HD2_PERI_PMC0); + /* unreset */ + val = readl_relaxed(ctrl_base + HIX5HD2_PERI_CRG20); + val &= ~CRG20_CPU1_RESET; + writel_relaxed(val, ctrl_base + HIX5HD2_PERI_CRG20); + } else { + /* power down cpu1 */ + val = readl_relaxed(ctrl_base + HIX5HD2_PERI_PMC0); + val |= PMC0_CPU1_PMC_ENABLE | PMC0_CPU1_POWERDOWN; + val &= ~PMC0_CPU1_WAIT_MTCOMS_ACK; + writel_relaxed(val, ctrl_base + HIX5HD2_PERI_PMC0); + + /* reset */ + val = readl_relaxed(ctrl_base + HIX5HD2_PERI_CRG20); + val |= CRG20_CPU1_RESET; + writel_relaxed(val, ctrl_base + HIX5HD2_PERI_CRG20); + } +} + +void hip01_set_cpu(int cpu, bool enable) +{ + unsigned int temp; + struct device_node *np; + + if (!ctrl_base) { + np = of_find_compatible_node(NULL, NULL, "hisilicon,hip01-sysctrl"); + if (np) + ctrl_base = of_iomap(np, 0); + else + BUG(); + } + + if (enable) { + /* reset on CPU1 */ + temp = readl_relaxed(ctrl_base + HIP01_PERI9); + temp |= PERI9_CPU1_RESET; + writel_relaxed(temp, ctrl_base + HIP01_PERI9); + + udelay(50); + + /* unreset on CPU1 */ + temp = readl_relaxed(ctrl_base + HIP01_PERI9); + temp &= ~PERI9_CPU1_RESET; + writel_relaxed(temp, ctrl_base + HIP01_PERI9); + } +} + +static inline void cpu_enter_lowpower(void) +{ + unsigned int v; + + flush_cache_all(); + + /* + * Turn off coherency and L1 D-cache + */ + asm volatile( + " mrc p15, 0, %0, c1, c0, 1\n" + " bic %0, %0, #0x40\n" + " mcr p15, 0, %0, c1, c0, 1\n" + " mrc p15, 0, %0, c1, c0, 0\n" + " bic %0, %0, #0x04\n" + " mcr p15, 0, %0, c1, c0, 0\n" + : "=&r" (v) + : "r" (0) + : "cc"); +} + +#ifdef CONFIG_HOTPLUG_CPU +void hi3xxx_cpu_die(unsigned int cpu) +{ + cpu_enter_lowpower(); + hi3xxx_set_cpu_jump(cpu, phys_to_virt(0)); + cpu_do_idle(); + + /* We should have never returned from idle */ + panic("cpu %d unexpectedly exit from shutdown\n", cpu); +} + +int hi3xxx_cpu_kill(unsigned int cpu) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(50); + + while (hi3xxx_get_cpu_jump(cpu)) + if (time_after(jiffies, timeout)) + return 0; + hi3xxx_set_cpu(cpu, false); + return 1; +} + +void hix5hd2_cpu_die(unsigned int cpu) +{ + flush_cache_all(); + hix5hd2_set_cpu(cpu, false); +} +#endif diff --git a/arch/arm/mach-hisi/platmcpm.c b/arch/arm/mach-hisi/platmcpm.c new file mode 100644 index 000000000..280f3f14f --- /dev/null +++ b/arch/arm/mach-hisi/platmcpm.c @@ -0,0 +1,386 @@ +/* + * Copyright (c) 2013-2014 Linaro Ltd. + * Copyright (c) 2013-2014 Hisilicon Limited. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/memblock.h> +#include <linux/of_address.h> + +#include <asm/cputype.h> +#include <asm/cp15.h> +#include <asm/mcpm.h> + +#include "core.h" + +/* bits definition in SC_CPU_RESET_REQ[x]/SC_CPU_RESET_DREQ[x] + * 1 -- unreset; 0 -- reset + */ +#define CORE_RESET_BIT(x) (1 << x) +#define NEON_RESET_BIT(x) (1 << (x + 4)) +#define CORE_DEBUG_RESET_BIT(x) (1 << (x + 9)) +#define CLUSTER_L2_RESET_BIT (1 << 8) +#define CLUSTER_DEBUG_RESET_BIT (1 << 13) + +/* + * bits definition in SC_CPU_RESET_STATUS[x] + * 1 -- reset status; 0 -- unreset status + */ +#define CORE_RESET_STATUS(x) (1 << x) +#define NEON_RESET_STATUS(x) (1 << (x + 4)) +#define CORE_DEBUG_RESET_STATUS(x) (1 << (x + 9)) +#define CLUSTER_L2_RESET_STATUS (1 << 8) +#define CLUSTER_DEBUG_RESET_STATUS (1 << 13) +#define CORE_WFI_STATUS(x) (1 << (x + 16)) +#define CORE_WFE_STATUS(x) (1 << (x + 20)) +#define CORE_DEBUG_ACK(x) (1 << (x + 24)) + +#define SC_CPU_RESET_REQ(x) (0x520 + (x << 3)) /* reset */ +#define SC_CPU_RESET_DREQ(x) (0x524 + (x << 3)) /* unreset */ +#define SC_CPU_RESET_STATUS(x) (0x1520 + (x << 3)) + +#define FAB_SF_MODE 0x0c +#define FAB_SF_INVLD 0x10 + +/* bits definition in FB_SF_INVLD */ +#define FB_SF_INVLD_START (1 << 8) + +#define HIP04_MAX_CLUSTERS 4 +#define HIP04_MAX_CPUS_PER_CLUSTER 4 + +#define POLL_MSEC 10 +#define TIMEOUT_MSEC 1000 + +static void __iomem *sysctrl, *fabric; +static int hip04_cpu_table[HIP04_MAX_CLUSTERS][HIP04_MAX_CPUS_PER_CLUSTER]; +static DEFINE_SPINLOCK(boot_lock); +static u32 fabric_phys_addr; +/* + * [0]: bootwrapper physical address + * [1]: bootwrapper size + * [2]: relocation address + * [3]: relocation size + */ +static u32 hip04_boot_method[4]; + +static bool hip04_cluster_is_down(unsigned int cluster) +{ + int i; + + for (i = 0; i < HIP04_MAX_CPUS_PER_CLUSTER; i++) + if (hip04_cpu_table[cluster][i]) + return false; + return true; +} + +static void hip04_set_snoop_filter(unsigned int cluster, unsigned int on) +{ + unsigned long data; + + if (!fabric) + BUG(); + data = readl_relaxed(fabric + FAB_SF_MODE); + if (on) + data |= 1 << cluster; + else + data &= ~(1 << cluster); + writel_relaxed(data, fabric + FAB_SF_MODE); + do { + cpu_relax(); + } while (data != readl_relaxed(fabric + FAB_SF_MODE)); +} + +static int hip04_mcpm_power_up(unsigned int cpu, unsigned int cluster) +{ + unsigned long data; + void __iomem *sys_dreq, *sys_status; + + if (!sysctrl) + return -ENODEV; + if (cluster >= HIP04_MAX_CLUSTERS || cpu >= HIP04_MAX_CPUS_PER_CLUSTER) + return -EINVAL; + + spin_lock_irq(&boot_lock); + + if (hip04_cpu_table[cluster][cpu]) + goto out; + + sys_dreq = sysctrl + SC_CPU_RESET_DREQ(cluster); + sys_status = sysctrl + SC_CPU_RESET_STATUS(cluster); + if (hip04_cluster_is_down(cluster)) { + data = CLUSTER_DEBUG_RESET_BIT; + writel_relaxed(data, sys_dreq); + do { + cpu_relax(); + data = readl_relaxed(sys_status); + } while (data & CLUSTER_DEBUG_RESET_STATUS); + } + + data = CORE_RESET_BIT(cpu) | NEON_RESET_BIT(cpu) | \ + CORE_DEBUG_RESET_BIT(cpu); + writel_relaxed(data, sys_dreq); + do { + cpu_relax(); + } while (data == readl_relaxed(sys_status)); + /* + * We may fail to power up core again without this delay. + * It's not mentioned in document. It's found by test. + */ + udelay(20); +out: + hip04_cpu_table[cluster][cpu]++; + spin_unlock_irq(&boot_lock); + + return 0; +} + +static void hip04_mcpm_power_down(void) +{ + unsigned int mpidr, cpu, cluster; + bool skip_wfi = false, last_man = false; + + mpidr = read_cpuid_mpidr(); + cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + + __mcpm_cpu_going_down(cpu, cluster); + + spin_lock(&boot_lock); + BUG_ON(__mcpm_cluster_state(cluster) != CLUSTER_UP); + hip04_cpu_table[cluster][cpu]--; + if (hip04_cpu_table[cluster][cpu] == 1) { + /* A power_up request went ahead of us. */ + skip_wfi = true; + } else if (hip04_cpu_table[cluster][cpu] > 1) { + pr_err("Cluster %d CPU%d boots multiple times\n", cluster, cpu); + BUG(); + } + + last_man = hip04_cluster_is_down(cluster); + if (last_man && __mcpm_outbound_enter_critical(cpu, cluster)) { + spin_unlock(&boot_lock); + /* Since it's Cortex A15, disable L2 prefetching. */ + asm volatile( + "mcr p15, 1, %0, c15, c0, 3 \n\t" + "isb \n\t" + "dsb " + : : "r" (0x400) ); + v7_exit_coherency_flush(all); + hip04_set_snoop_filter(cluster, 0); + __mcpm_outbound_leave_critical(cluster, CLUSTER_DOWN); + } else { + spin_unlock(&boot_lock); + v7_exit_coherency_flush(louis); + } + + __mcpm_cpu_down(cpu, cluster); + + if (!skip_wfi) + wfi(); +} + +static int hip04_mcpm_wait_for_powerdown(unsigned int cpu, unsigned int cluster) +{ + unsigned int data, tries, count; + int ret = -ETIMEDOUT; + + BUG_ON(cluster >= HIP04_MAX_CLUSTERS || + cpu >= HIP04_MAX_CPUS_PER_CLUSTER); + + count = TIMEOUT_MSEC / POLL_MSEC; + spin_lock_irq(&boot_lock); + for (tries = 0; tries < count; tries++) { + if (hip04_cpu_table[cluster][cpu]) { + ret = -EBUSY; + goto err; + } + cpu_relax(); + data = readl_relaxed(sysctrl + SC_CPU_RESET_STATUS(cluster)); + if (data & CORE_WFI_STATUS(cpu)) + break; + spin_unlock_irq(&boot_lock); + /* Wait for clean L2 when the whole cluster is down. */ + msleep(POLL_MSEC); + spin_lock_irq(&boot_lock); + } + if (tries >= count) + goto err; + data = CORE_RESET_BIT(cpu) | NEON_RESET_BIT(cpu) | \ + CORE_DEBUG_RESET_BIT(cpu); + writel_relaxed(data, sysctrl + SC_CPU_RESET_REQ(cluster)); + for (tries = 0; tries < count; tries++) { + cpu_relax(); + data = readl_relaxed(sysctrl + SC_CPU_RESET_STATUS(cluster)); + if (data & CORE_RESET_STATUS(cpu)) + break; + } + if (tries >= count) + goto err; + spin_unlock_irq(&boot_lock); + return 0; +err: + spin_unlock_irq(&boot_lock); + return ret; +} + +static void hip04_mcpm_powered_up(void) +{ + unsigned int mpidr, cpu, cluster; + + mpidr = read_cpuid_mpidr(); + cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + + spin_lock(&boot_lock); + if (!hip04_cpu_table[cluster][cpu]) + hip04_cpu_table[cluster][cpu] = 1; + spin_unlock(&boot_lock); +} + +static void __naked hip04_mcpm_power_up_setup(unsigned int affinity_level) +{ + asm volatile (" \n" +" cmp r0, #0 \n" +" bxeq lr \n" + /* calculate fabric phys address */ +" adr r2, 2f \n" +" ldmia r2, {r1, r3} \n" +" sub r0, r2, r1 \n" +" ldr r2, [r0, r3] \n" + /* get cluster id from MPIDR */ +" mrc p15, 0, r0, c0, c0, 5 \n" +" ubfx r1, r0, #8, #8 \n" + /* 1 << cluster id */ +" mov r0, #1 \n" +" mov r3, r0, lsl r1 \n" +" ldr r0, [r2, #"__stringify(FAB_SF_MODE)"] \n" +" tst r0, r3 \n" +" bxne lr \n" +" orr r1, r0, r3 \n" +" str r1, [r2, #"__stringify(FAB_SF_MODE)"] \n" +"1: ldr r0, [r2, #"__stringify(FAB_SF_MODE)"] \n" +" tst r0, r3 \n" +" beq 1b \n" +" bx lr \n" + +" .align 2 \n" +"2: .word . \n" +" .word fabric_phys_addr \n" + ); +} + +static const struct mcpm_platform_ops hip04_mcpm_ops = { + .power_up = hip04_mcpm_power_up, + .power_down = hip04_mcpm_power_down, + .wait_for_powerdown = hip04_mcpm_wait_for_powerdown, + .powered_up = hip04_mcpm_powered_up, +}; + +static bool __init hip04_cpu_table_init(void) +{ + unsigned int mpidr, cpu, cluster; + + mpidr = read_cpuid_mpidr(); + cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + + if (cluster >= HIP04_MAX_CLUSTERS || + cpu >= HIP04_MAX_CPUS_PER_CLUSTER) { + pr_err("%s: boot CPU is out of bound!\n", __func__); + return false; + } + hip04_set_snoop_filter(cluster, 1); + hip04_cpu_table[cluster][cpu] = 1; + return true; +} + +static int __init hip04_mcpm_init(void) +{ + struct device_node *np, *np_sctl, *np_fab; + struct resource fab_res; + void __iomem *relocation; + int ret = -ENODEV; + + np = of_find_compatible_node(NULL, NULL, "hisilicon,hip04-bootwrapper"); + if (!np) + goto err; + ret = of_property_read_u32_array(np, "boot-method", + &hip04_boot_method[0], 4); + if (ret) + goto err; + np_sctl = of_find_compatible_node(NULL, NULL, "hisilicon,sysctrl"); + if (!np_sctl) + goto err; + np_fab = of_find_compatible_node(NULL, NULL, "hisilicon,hip04-fabric"); + if (!np_fab) + goto err; + + ret = memblock_reserve(hip04_boot_method[0], hip04_boot_method[1]); + if (ret) + goto err; + + relocation = ioremap(hip04_boot_method[2], hip04_boot_method[3]); + if (!relocation) { + pr_err("failed to map relocation space\n"); + ret = -ENOMEM; + goto err_reloc; + } + sysctrl = of_iomap(np_sctl, 0); + if (!sysctrl) { + pr_err("failed to get sysctrl base\n"); + ret = -ENOMEM; + goto err_sysctrl; + } + ret = of_address_to_resource(np_fab, 0, &fab_res); + if (ret) { + pr_err("failed to get fabric base phys\n"); + goto err_fabric; + } + fabric_phys_addr = fab_res.start; + sync_cache_w(&fabric_phys_addr); + fabric = of_iomap(np_fab, 0); + if (!fabric) { + pr_err("failed to get fabric base\n"); + ret = -ENOMEM; + goto err_fabric; + } + + if (!hip04_cpu_table_init()) { + ret = -EINVAL; + goto err_table; + } + ret = mcpm_platform_register(&hip04_mcpm_ops); + if (ret) { + goto err_table; + } + + /* + * Fill the instruction address that is used after secondary core + * out of reset. + */ + writel_relaxed(hip04_boot_method[0], relocation); + writel_relaxed(0xa5a5a5a5, relocation + 4); /* magic number */ + writel_relaxed(virt_to_phys(mcpm_entry_point), relocation + 8); + writel_relaxed(0, relocation + 12); + iounmap(relocation); + + mcpm_sync_init(hip04_mcpm_power_up_setup); + mcpm_smp_set_ops(); + pr_info("HiP04 MCPM initialized\n"); + return ret; +err_table: + iounmap(fabric); +err_fabric: + iounmap(sysctrl); +err_sysctrl: + iounmap(relocation); +err_reloc: + memblock_free(hip04_boot_method[0], hip04_boot_method[1]); +err: + return ret; +} +early_initcall(hip04_mcpm_init); diff --git a/arch/arm/mach-hisi/platsmp.c b/arch/arm/mach-hisi/platsmp.c new file mode 100644 index 000000000..8880c8e8b --- /dev/null +++ b/arch/arm/mach-hisi/platsmp.c @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2013 Linaro Ltd. + * Copyright (c) 2013 Hisilicon Limited. + * Based on arch/arm/mach-vexpress/platsmp.c, Copyright (C) 2002 ARM Ltd. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ +#include <linux/smp.h> +#include <linux/io.h> +#include <linux/of_address.h> +#include <linux/delay.h> + +#include <asm/cacheflush.h> +#include <asm/smp_plat.h> +#include <asm/smp_scu.h> +#include <asm/mach/map.h> + +#include "core.h" + +#define HIX5HD2_BOOT_ADDRESS 0xffff0000 + +static void __iomem *ctrl_base; + +void hi3xxx_set_cpu_jump(int cpu, void *jump_addr) +{ + cpu = cpu_logical_map(cpu); + if (!cpu || !ctrl_base) + return; + writel_relaxed(virt_to_phys(jump_addr), ctrl_base + ((cpu - 1) << 2)); +} + +int hi3xxx_get_cpu_jump(int cpu) +{ + cpu = cpu_logical_map(cpu); + if (!cpu || !ctrl_base) + return 0; + return readl_relaxed(ctrl_base + ((cpu - 1) << 2)); +} + +static void __init hisi_enable_scu_a9(void) +{ + unsigned long base = 0; + void __iomem *scu_base = NULL; + + if (scu_a9_has_base()) { + base = scu_a9_get_base(); + scu_base = ioremap(base, SZ_4K); + if (!scu_base) { + pr_err("ioremap(scu_base) failed\n"); + return; + } + scu_enable(scu_base); + iounmap(scu_base); + } +} + +static void __init hi3xxx_smp_prepare_cpus(unsigned int max_cpus) +{ + struct device_node *np = NULL; + u32 offset = 0; + + hisi_enable_scu_a9(); + if (!ctrl_base) { + np = of_find_compatible_node(NULL, NULL, "hisilicon,sysctrl"); + if (!np) { + pr_err("failed to find hisilicon,sysctrl node\n"); + return; + } + ctrl_base = of_iomap(np, 0); + if (!ctrl_base) { + pr_err("failed to map address\n"); + return; + } + if (of_property_read_u32(np, "smp-offset", &offset) < 0) { + pr_err("failed to find smp-offset property\n"); + return; + } + ctrl_base += offset; + } +} + +static int hi3xxx_boot_secondary(unsigned int cpu, struct task_struct *idle) +{ + hi3xxx_set_cpu(cpu, true); + hi3xxx_set_cpu_jump(cpu, secondary_startup); + arch_send_wakeup_ipi_mask(cpumask_of(cpu)); + return 0; +} + +struct smp_operations hi3xxx_smp_ops __initdata = { + .smp_prepare_cpus = hi3xxx_smp_prepare_cpus, + .smp_boot_secondary = hi3xxx_boot_secondary, +#ifdef CONFIG_HOTPLUG_CPU + .cpu_die = hi3xxx_cpu_die, + .cpu_kill = hi3xxx_cpu_kill, +#endif +}; + +static void __init hisi_common_smp_prepare_cpus(unsigned int max_cpus) +{ + hisi_enable_scu_a9(); +} + +void hix5hd2_set_scu_boot_addr(phys_addr_t start_addr, phys_addr_t jump_addr) +{ + void __iomem *virt; + + virt = ioremap(start_addr, PAGE_SIZE); + + writel_relaxed(0xe51ff004, virt); /* ldr pc, [rc, #-4] */ + writel_relaxed(jump_addr, virt + 4); /* pc jump phy address */ + iounmap(virt); +} + +static int hix5hd2_boot_secondary(unsigned int cpu, struct task_struct *idle) +{ + phys_addr_t jumpaddr; + + jumpaddr = virt_to_phys(hisi_secondary_startup); + hix5hd2_set_scu_boot_addr(HIX5HD2_BOOT_ADDRESS, jumpaddr); + hix5hd2_set_cpu(cpu, true); + arch_send_wakeup_ipi_mask(cpumask_of(cpu)); + return 0; +} + + +struct smp_operations hix5hd2_smp_ops __initdata = { + .smp_prepare_cpus = hisi_common_smp_prepare_cpus, + .smp_boot_secondary = hix5hd2_boot_secondary, +#ifdef CONFIG_HOTPLUG_CPU + .cpu_die = hix5hd2_cpu_die, +#endif +}; + + +#define SC_SCTL_REMAP_CLR 0x00000100 +#define HIP01_BOOT_ADDRESS 0x80000000 +#define REG_SC_CTRL 0x000 + +void hip01_set_boot_addr(phys_addr_t start_addr, phys_addr_t jump_addr) +{ + void __iomem *virt; + + virt = phys_to_virt(start_addr); + + writel_relaxed(0xe51ff004, virt); + writel_relaxed(jump_addr, virt + 4); +} + +static int hip01_boot_secondary(unsigned int cpu, struct task_struct *idle) +{ + phys_addr_t jumpaddr; + unsigned int remap_reg_value = 0; + struct device_node *node; + + + jumpaddr = virt_to_phys(hisi_secondary_startup); + hip01_set_boot_addr(HIP01_BOOT_ADDRESS, jumpaddr); + + node = of_find_compatible_node(NULL, NULL, "hisilicon,hip01-sysctrl"); + if (WARN_ON(!node)) + return -1; + ctrl_base = of_iomap(node, 0); + + /* set the secondary core boot from DDR */ + remap_reg_value = readl_relaxed(ctrl_base + REG_SC_CTRL); + barrier(); + remap_reg_value |= SC_SCTL_REMAP_CLR; + barrier(); + writel_relaxed(remap_reg_value, ctrl_base + REG_SC_CTRL); + + hip01_set_cpu(cpu, true); + + return 0; +} + +struct smp_operations hip01_smp_ops __initdata = { + .smp_prepare_cpus = hisi_common_smp_prepare_cpus, + .smp_boot_secondary = hip01_boot_secondary, +}; + +CPU_METHOD_OF_DECLARE(hi3xxx_smp, "hisilicon,hi3620-smp", &hi3xxx_smp_ops); +CPU_METHOD_OF_DECLARE(hix5hd2_smp, "hisilicon,hix5hd2-smp", &hix5hd2_smp_ops); +CPU_METHOD_OF_DECLARE(hip01_smp, "hisilicon,hip01-smp", &hip01_smp_ops); |