diff options
Diffstat (limited to 'arch/arm/mach-vexpress')
| -rw-r--r-- | arch/arm/mach-vexpress/Kconfig | 81 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/Makefile | 18 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/core.h | 13 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/ct-ca9x4.c | 203 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/dcscb.c | 236 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/dcscb_setup.S | 38 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/hotplug.c | 106 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/include/mach/ct-ca9x4.h | 47 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/include/mach/hardware.h | 1 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/include/mach/irqs.h | 6 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/include/mach/motherboard.h | 88 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/include/mach/tc2.h | 10 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/platsmp.c | 225 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/tc2_pm.c | 277 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/tc2_pm_psci.c | 173 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/tc2_pm_setup.S | 68 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/v2m.c | 493 |
17 files changed, 2083 insertions, 0 deletions
diff --git a/arch/arm/mach-vexpress/Kconfig b/arch/arm/mach-vexpress/Kconfig new file mode 100644 index 00000000..39858ba0 --- /dev/null +++ b/arch/arm/mach-vexpress/Kconfig @@ -0,0 +1,81 @@ +config ARCH_VEXPRESS + bool "ARM Ltd. Versatile Express family" if ARCH_MULTI_V7 + select ARCH_HAS_CPUFREQ + select ARCH_HAS_OPP + select ARCH_REQUIRE_GPIOLIB + select ARCH_SUPPORTS_BIG_ENDIAN + select ARM_AMBA + select ARM_GIC + select ARM_TIMER_SP804 + select CLKDEV_LOOKUP + select COMMON_CLK + select COMMON_CLK_VERSATILE + select CPU_V7 + select GENERIC_CLOCKEVENTS + select HAVE_ARM_SCU if SMP + select HAVE_ARM_TWD if LOCAL_TIMERS + select HAVE_CLK + select HAVE_PATA_PLATFORM + select HAVE_SMP + select ICST + select MIGHT_HAVE_CACHE_L2X0 + select NO_IOPORT + select PLAT_VERSATILE + select PLAT_VERSATILE_CLCD + select POWER_RESET + select POWER_RESET_VEXPRESS + select POWER_SUPPLY + select REGULATOR_FIXED_VOLTAGE if REGULATOR + select VEXPRESS_CONFIG + help + This option enables support for systems using Cortex processor based + ARM core and logic (FPGA) tiles on the Versatile Express motherboard, + for example: + + - CoreTile Express A5x2 (V2P-CA5s) + - CoreTile Express A9x4 (V2P-CA9) + - CoreTile Express A15x2 (V2P-CA15) + - LogicTile Express 13MG (V2F-2XV6) with A5, A7, A9 or A15 SMMs + (Soft Macrocell Models) + - Versatile Express RTSMs (Models) + + You must boot using a Flattened Device Tree in order to use these + platforms. The traditional (ATAGs) boot method is not usable on + these boards with this option. + +menu "Versatile Express platform type" + depends on ARCH_VEXPRESS + +config ARCH_VEXPRESS_CORTEX_A5_A9_ERRATA + bool "Enable A5 and A9 only errata work-arounds" + default y + select ARM_ERRATA_720789 + select PL310_ERRATA_753970 if CACHE_PL310 + help + Provides common dependencies for Versatile Express platforms + based on Cortex-A5 and Cortex-A9 processors. In order to + build a working kernel, you must also enable relevant core + tile support or Flattened Device Tree based support options. + +config ARCH_VEXPRESS_CA9X4 + bool "Versatile Express Cortex-A9x4 tile" + select ARM_ERRATA_643719 + +config ARCH_VEXPRESS_DCSCB + bool "Dual Cluster System Control Block (DCSCB) support" + depends on MCPM + select ARM_CCI + help + Support for the Dual Cluster System Configuration Block (DCSCB). + This is needed to provide CPU and cluster power management + on RTSM implementing big.LITTLE. + +config ARCH_VEXPRESS_TC2 + bool "TC2 cluster management" + depends on MCPM + select VEXPRESS_SPC + select ARM_CCI + help + Support for CPU and cluster power management on TC2. + +endmenu diff --git a/arch/arm/mach-vexpress/Makefile b/arch/arm/mach-vexpress/Makefile new file mode 100644 index 00000000..14193dc7 --- /dev/null +++ b/arch/arm/mach-vexpress/Makefile @@ -0,0 +1,18 @@ +# +# Makefile for the linux kernel. +# +ccflags-$(CONFIG_ARCH_MULTIPLATFORM) := -I$(srctree)/$(src)/include \ + -I$(srctree)/arch/arm/plat-versatile/include + +obj-y := v2m.o +obj-$(CONFIG_ARCH_VEXPRESS_CA9X4) += ct-ca9x4.o +obj-$(CONFIG_ARCH_VEXPRESS_DCSCB) += dcscb.o dcscb_setup.o +CFLAGS_REMOVE_dcscb.o = -pg +obj-$(CONFIG_ARCH_VEXPRESS_TC2) += tc2_pm.o tc2_pm_setup.o +CFLAGS_REMOVE_tc2_pm.o = -pg +ifeq ($(CONFIG_ARCH_VEXPRESS_TC2),y) +obj-$(CONFIG_ARM_PSCI) += tc2_pm_psci.o +CFLAGS_REMOVE_tc2_pm_psci.o = -pg +endif +obj-$(CONFIG_SMP) += platsmp.o +obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o diff --git a/arch/arm/mach-vexpress/core.h b/arch/arm/mach-vexpress/core.h new file mode 100644 index 00000000..bde4374a --- /dev/null +++ b/arch/arm/mach-vexpress/core.h @@ -0,0 +1,13 @@ +/* 2MB large area for motherboard's peripherals static mapping */ +#define V2M_PERIPH 0xf8000000 + +/* Tile's peripherals static mappings should start here */ +#define V2T_PERIPH 0xf8200000 + +void vexpress_dt_smp_map_io(void); + +bool vexpress_smp_init_ops(void); + +extern struct smp_operations vexpress_smp_ops; + +extern void vexpress_cpu_die(unsigned int cpu); diff --git a/arch/arm/mach-vexpress/ct-ca9x4.c b/arch/arm/mach-vexpress/ct-ca9x4.c new file mode 100644 index 00000000..6f34497a --- /dev/null +++ b/arch/arm/mach-vexpress/ct-ca9x4.c @@ -0,0 +1,203 @@ +/* + * Versatile Express Core Tile Cortex A9x4 Support + */ +#include <linux/init.h> +#include <linux/gfp.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> +#include <linux/amba/bus.h> +#include <linux/amba/clcd.h> +#include <linux/clkdev.h> +#include <linux/vexpress.h> +#include <linux/irqchip/arm-gic.h> + +#include <asm/hardware/arm_timer.h> +#include <asm/hardware/cache-l2x0.h> +#include <asm/smp_scu.h> +#include <asm/smp_twd.h> + +#include <mach/ct-ca9x4.h> + +#include <asm/hardware/timer-sp.h> + +#include <asm/mach/map.h> +#include <asm/mach/time.h> + +#include "core.h" + +#include <mach/motherboard.h> +#include <mach/irqs.h> + +#include <plat/clcd.h> + +static struct map_desc ct_ca9x4_io_desc[] __initdata = { + { + .virtual = V2T_PERIPH, + .pfn = __phys_to_pfn(CT_CA9X4_MPIC), + .length = SZ_8K, + .type = MT_DEVICE, + }, +}; + +static void __init ct_ca9x4_map_io(void) +{ + iotable_init(ct_ca9x4_io_desc, ARRAY_SIZE(ct_ca9x4_io_desc)); +} + +#ifdef CONFIG_HAVE_ARM_TWD +static DEFINE_TWD_LOCAL_TIMER(twd_local_timer, A9_MPCORE_TWD, IRQ_LOCALTIMER); + +static void __init ca9x4_twd_init(void) +{ + int err = twd_local_timer_register(&twd_local_timer); + if (err) + pr_err("twd_local_timer_register failed %d\n", err); +} +#else +#define ca9x4_twd_init() do {} while(0) +#endif + +static void __init ct_ca9x4_init_irq(void) +{ + gic_init(0, 29, ioremap(A9_MPCORE_GIC_DIST, SZ_4K), + ioremap(A9_MPCORE_GIC_CPU, SZ_256)); + ca9x4_twd_init(); +} + +static int ct_ca9x4_clcd_setup(struct clcd_fb *fb) +{ + unsigned long framesize = 1024 * 768 * 2; + + fb->panel = versatile_clcd_get_panel("XVGA"); + if (!fb->panel) + return -EINVAL; + + return versatile_clcd_setup_dma(fb, framesize); +} + +static struct clcd_board ct_ca9x4_clcd_data = { + .name = "CT-CA9X4", + .caps = CLCD_CAP_5551 | CLCD_CAP_565, + .check = clcdfb_check, + .decode = clcdfb_decode, + .setup = ct_ca9x4_clcd_setup, + .mmap = versatile_clcd_mmap_dma, + .remove = versatile_clcd_remove_dma, +}; + +static AMBA_AHB_DEVICE(clcd, "ct:clcd", 0, CT_CA9X4_CLCDC, IRQ_CT_CA9X4_CLCDC, &ct_ca9x4_clcd_data); +static AMBA_APB_DEVICE(dmc, "ct:dmc", 0, CT_CA9X4_DMC, IRQ_CT_CA9X4_DMC, NULL); +static AMBA_APB_DEVICE(smc, "ct:smc", 0, CT_CA9X4_SMC, IRQ_CT_CA9X4_SMC, NULL); +static AMBA_APB_DEVICE(gpio, "ct:gpio", 0, CT_CA9X4_GPIO, IRQ_CT_CA9X4_GPIO, NULL); + +static struct amba_device *ct_ca9x4_amba_devs[] __initdata = { + &clcd_device, + &dmc_device, + &smc_device, + &gpio_device, +}; + +static struct resource pmu_resources[] = { + [0] = { + .start = IRQ_CT_CA9X4_PMU_CPU0, + .end = IRQ_CT_CA9X4_PMU_CPU0, + .flags = IORESOURCE_IRQ, + }, + [1] = { + .start = IRQ_CT_CA9X4_PMU_CPU1, + .end = IRQ_CT_CA9X4_PMU_CPU1, + .flags = IORESOURCE_IRQ, + }, + [2] = { + .start = IRQ_CT_CA9X4_PMU_CPU2, + .end = IRQ_CT_CA9X4_PMU_CPU2, + .flags = IORESOURCE_IRQ, + }, + [3] = { + .start = IRQ_CT_CA9X4_PMU_CPU3, + .end = IRQ_CT_CA9X4_PMU_CPU3, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device pmu_device = { + .name = "arm-pmu", + .id = -1, + .num_resources = ARRAY_SIZE(pmu_resources), + .resource = pmu_resources, +}; + +static struct platform_device osc1_device = { + .name = "vexpress-osc", + .id = 1, + .num_resources = 1, + .resource = (struct resource []) { + VEXPRESS_RES_FUNC(0xf, 1), + }, +}; + +static void __init ct_ca9x4_init(void) +{ + int i; + +#ifdef CONFIG_CACHE_L2X0 + void __iomem *l2x0_base = ioremap(CT_CA9X4_L2CC, SZ_4K); + + /* set RAM latencies to 1 cycle for this core tile. */ + writel(0, l2x0_base + L2X0_TAG_LATENCY_CTRL); + writel(0, l2x0_base + L2X0_DATA_LATENCY_CTRL); + + l2x0_init(l2x0_base, 0x00400000, 0xfe0fffff); +#endif + + for (i = 0; i < ARRAY_SIZE(ct_ca9x4_amba_devs); i++) + amba_device_register(ct_ca9x4_amba_devs[i], &iomem_resource); + + platform_device_register(&pmu_device); + platform_device_register(&osc1_device); + + WARN_ON(clk_register_clkdev(vexpress_osc_setup(&osc1_device.dev), + NULL, "ct:clcd")); +} + +#ifdef CONFIG_SMP +static void *ct_ca9x4_scu_base __initdata; + +static void __init ct_ca9x4_init_cpu_map(void) +{ + int i, ncores; + + ct_ca9x4_scu_base = ioremap(A9_MPCORE_SCU, SZ_128); + if (WARN_ON(!ct_ca9x4_scu_base)) + return; + + ncores = scu_get_core_count(ct_ca9x4_scu_base); + + if (ncores > nr_cpu_ids) { + pr_warn("SMP: %u cores greater than maximum (%u), clipping\n", + ncores, nr_cpu_ids); + ncores = nr_cpu_ids; + } + + for (i = 0; i < ncores; ++i) + set_cpu_possible(i, true); +} + +static void __init ct_ca9x4_smp_enable(unsigned int max_cpus) +{ + scu_enable(ct_ca9x4_scu_base); +} +#endif + +struct ct_desc ct_ca9x4_desc __initdata = { + .id = V2M_CT_ID_CA9, + .name = "CA9x4", + .map_io = ct_ca9x4_map_io, + .init_irq = ct_ca9x4_init_irq, + .init_tile = ct_ca9x4_init, +#ifdef CONFIG_SMP + .init_cpu_map = ct_ca9x4_init_cpu_map, + .smp_enable = ct_ca9x4_smp_enable, +#endif +}; diff --git a/arch/arm/mach-vexpress/dcscb.c b/arch/arm/mach-vexpress/dcscb.c new file mode 100644 index 00000000..b35700f8 --- /dev/null +++ b/arch/arm/mach-vexpress/dcscb.c @@ -0,0 +1,236 @@ +/* + * arch/arm/mach-vexpress/dcscb.c - Dual Cluster System Configuration Block + * + * Created by: Nicolas Pitre, May 2012 + * Copyright: (C) 2012-2013 Linaro Limited + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/spinlock.h> +#include <linux/errno.h> +#include <linux/of_address.h> +#include <linux/vexpress.h> +#include <linux/arm-cci.h> + +#include <asm/mcpm.h> +#include <asm/proc-fns.h> +#include <asm/cacheflush.h> +#include <asm/cputype.h> +#include <asm/cp15.h> +#include <asm/psci.h> + + +#define RST_HOLD0 0x0 +#define RST_HOLD1 0x4 +#define SYS_SWRESET 0x8 +#define RST_STAT0 0xc +#define RST_STAT1 0x10 +#define EAG_CFG_R 0x20 +#define EAG_CFG_W 0x24 +#define KFC_CFG_R 0x28 +#define KFC_CFG_W 0x2c +#define DCS_CFG_R 0x30 + +/* + * We can't use regular spinlocks. In the switcher case, it is possible + * for an outbound CPU to call power_down() while its inbound counterpart + * is already live using the same logical CPU number which trips lockdep + * debugging. + */ +static arch_spinlock_t dcscb_lock = __ARCH_SPIN_LOCK_UNLOCKED; + +static void __iomem *dcscb_base; +static int dcscb_use_count[4][2]; +static int dcscb_allcpus_mask[2]; + +static int dcscb_power_up(unsigned int cpu, unsigned int cluster) +{ + unsigned int rst_hold, cpumask = (1 << cpu); + unsigned int all_mask = dcscb_allcpus_mask[cluster]; + + pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); + if (cpu >= 4 || cluster >= 2) + return -EINVAL; + + /* + * Since this is called with IRQs enabled, and no arch_spin_lock_irq + * variant exists, we need to disable IRQs manually here. + */ + local_irq_disable(); + arch_spin_lock(&dcscb_lock); + + dcscb_use_count[cpu][cluster]++; + if (dcscb_use_count[cpu][cluster] == 1) { + rst_hold = readl_relaxed(dcscb_base + RST_HOLD0 + cluster * 4); + if (rst_hold & (1 << 8)) { + /* remove cluster reset and add individual CPU's reset */ + rst_hold &= ~(1 << 8); + rst_hold |= all_mask; + } + rst_hold &= ~(cpumask | (cpumask << 4)); + writel_relaxed(rst_hold, dcscb_base + RST_HOLD0 + cluster * 4); + } else if (dcscb_use_count[cpu][cluster] != 2) { + /* + * The only possible values are: + * 0 = CPU down + * 1 = CPU (still) up + * 2 = CPU requested to be up before it had a chance + * to actually make itself down. + * Any other value is a bug. + */ + BUG(); + } + + arch_spin_unlock(&dcscb_lock); + local_irq_enable(); + + return 0; +} + +static void dcscb_power_down(void) +{ + unsigned int mpidr, cpu, cluster, rst_hold, cpumask, all_mask; + bool last_man = false, skip_wfi = false; + + mpidr = read_cpuid_mpidr(); + cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + cpumask = (1 << cpu); + all_mask = dcscb_allcpus_mask[cluster]; + + pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); + BUG_ON(cpu >= 4 || cluster >= 2); + + __mcpm_cpu_going_down(cpu, cluster); + + arch_spin_lock(&dcscb_lock); + BUG_ON(__mcpm_cluster_state(cluster) != CLUSTER_UP); + dcscb_use_count[cpu][cluster]--; + if (dcscb_use_count[cpu][cluster] == 0) { + rst_hold = readl_relaxed(dcscb_base + RST_HOLD0 + cluster * 4); + rst_hold |= cpumask; + if (((rst_hold | (rst_hold >> 4)) & all_mask) == all_mask) { + rst_hold |= (1 << 8); + last_man = true; + } + writel_relaxed(rst_hold, dcscb_base + RST_HOLD0 + cluster * 4); + } else if (dcscb_use_count[cpu][cluster] == 1) { + /* + * A power_up request went ahead of us. + * Even if we do not want to shut this CPU down, + * the caller expects a certain state as if the WFI + * was aborted. So let's continue with cache cleaning. + */ + skip_wfi = true; + } else + BUG(); + + if (last_man && __mcpm_outbound_enter_critical(cpu, cluster)) { + arch_spin_unlock(&dcscb_lock); + + /* Flush all cache levels for this cluster. */ + v7_exit_coherency_flush(all); + + /* + * This is a harmless no-op. On platforms with a real + * outer cache this might either be needed or not, + * depending on where the outer cache sits. + */ + outer_flush_all(); + + /* + * Disable cluster-level coherency by masking + * incoming snoops and DVM messages: + */ + cci_disable_port_by_cpu(mpidr); + + __mcpm_outbound_leave_critical(cluster, CLUSTER_DOWN); + } else { + arch_spin_unlock(&dcscb_lock); + + /* Disable and flush the local CPU cache. */ + v7_exit_coherency_flush(louis); + } + + __mcpm_cpu_down(cpu, cluster); + + /* Now we are prepared for power-down, do it: */ + dsb(); + if (!skip_wfi) + wfi(); + + /* Not dead at this point? Let our caller cope. */ +} + +static const struct mcpm_platform_ops dcscb_power_ops = { + .power_up = dcscb_power_up, + .power_down = dcscb_power_down, +}; + +static void __init dcscb_usage_count_init(void) +{ + unsigned int mpidr, cpu, cluster; + + mpidr = read_cpuid_mpidr(); + cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + + pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); + BUG_ON(cpu >= 4 || cluster >= 2); + dcscb_use_count[cpu][cluster] = 1; +} + +extern void dcscb_power_up_setup(unsigned int affinity_level); + +static int __init dcscb_init(void) +{ + struct device_node *node; + unsigned int cfg; + int ret; + + ret = psci_probe(); + if (!ret) { + pr_debug("psci found. Aborting native init\n"); + return -ENODEV; + } + + if (!cci_probed()) + return -ENODEV; + + node = of_find_compatible_node(NULL, NULL, "arm,rtsm,dcscb"); + if (!node) + return -ENODEV; + dcscb_base = of_iomap(node, 0); + if (!dcscb_base) + return -EADDRNOTAVAIL; + cfg = readl_relaxed(dcscb_base + DCS_CFG_R); + dcscb_allcpus_mask[0] = (1 << (((cfg >> 16) >> (0 << 2)) & 0xf)) - 1; + dcscb_allcpus_mask[1] = (1 << (((cfg >> 16) >> (1 << 2)) & 0xf)) - 1; + dcscb_usage_count_init(); + + ret = mcpm_platform_register(&dcscb_power_ops); + if (!ret) + ret = mcpm_sync_init(dcscb_power_up_setup); + if (ret) { + iounmap(dcscb_base); + return ret; + } + + pr_info("VExpress DCSCB support installed\n"); + + /* + * Future entries into the kernel can now go + * through the cluster entry vectors. + */ + vexpress_flags_set(virt_to_phys(mcpm_entry_point)); + + return 0; +} + +early_initcall(dcscb_init); diff --git a/arch/arm/mach-vexpress/dcscb_setup.S b/arch/arm/mach-vexpress/dcscb_setup.S new file mode 100644 index 00000000..4bb7fbe0 --- /dev/null +++ b/arch/arm/mach-vexpress/dcscb_setup.S @@ -0,0 +1,38 @@ +/* + * arch/arm/include/asm/dcscb_setup.S + * + * Created by: Dave Martin, 2012-06-22 + * Copyright: (C) 2012-2013 Linaro Limited + * + * 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> + + +ENTRY(dcscb_power_up_setup) + + cmp r0, #0 @ check affinity level + beq 2f + +/* + * Enable cluster-level coherency, in preparation for turning on the MMU. + * The ACTLR SMP bit does not need to be set here, because cpu_resume() + * already restores that. + * + * A15/A7 may not require explicit L2 invalidation on reset, dependent + * on hardware integration decisions. + * For now, this code assumes that L2 is either already invalidated, + * or invalidation is not required. + */ + + b cci_enable_port_for_self + +2: @ Implementation-specific local CPU setup operations should go here, + @ if any. In this case, there is nothing to do. + + bx lr + +ENDPROC(dcscb_power_up_setup) diff --git a/arch/arm/mach-vexpress/hotplug.c b/arch/arm/mach-vexpress/hotplug.c new file mode 100644 index 00000000..f0ce6b8f --- /dev/null +++ b/arch/arm/mach-vexpress/hotplug.c @@ -0,0 +1,106 @@ +/* + * linux/arch/arm/mach-realview/hotplug.c + * + * Copyright (C) 2002 ARM Ltd. + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/smp.h> + +#include <asm/smp_plat.h> +#include <asm/cp15.h> + +static inline void cpu_enter_lowpower(void) +{ + unsigned int v; + + asm volatile( + "mcr p15, 0, %1, c7, c5, 0\n" + " mcr p15, 0, %1, c7, c10, 4\n" + /* + * Turn off coherency + */ + " mrc p15, 0, %0, c1, c0, 1\n" + " bic %0, %0, %3\n" + " mcr p15, 0, %0, c1, c0, 1\n" + " mrc p15, 0, %0, c1, c0, 0\n" + " bic %0, %0, %2\n" + " mcr p15, 0, %0, c1, c0, 0\n" + : "=&r" (v) + : "r" (0), "Ir" (CR_C), "Ir" (0x40) + : "cc"); +} + +static inline void cpu_leave_lowpower(void) +{ + unsigned int v; + + asm volatile( + "mrc p15, 0, %0, c1, c0, 0\n" + " orr %0, %0, %1\n" + " mcr p15, 0, %0, c1, c0, 0\n" + " mrc p15, 0, %0, c1, c0, 1\n" + " orr %0, %0, %2\n" + " mcr p15, 0, %0, c1, c0, 1\n" + : "=&r" (v) + : "Ir" (CR_C), "Ir" (0x40) + : "cc"); +} + +static inline void platform_do_lowpower(unsigned int cpu, int *spurious) +{ + /* + * there is no power-control hardware on this platform, so all + * we can do is put the core into WFI; this is safe as the calling + * code will have already disabled interrupts + */ + for (;;) { + wfi(); + + if (pen_release == cpu_logical_map(cpu)) { + /* + * OK, proper wakeup, we're done + */ + break; + } + + /* + * Getting here, means that we have come out of WFI without + * having been woken up - this shouldn't happen + * + * Just note it happening - when we're woken, we can report + * its occurrence. + */ + (*spurious)++; + } +} + +/* + * platform-specific code to shutdown a CPU + * + * Called with IRQs disabled + */ +void __ref vexpress_cpu_die(unsigned int cpu) +{ + int spurious = 0; + + /* + * we're ready for shutdown now, so do it + */ + cpu_enter_lowpower(); + platform_do_lowpower(cpu, &spurious); + + /* + * bring this CPU back into the world of cache + * coherency, and then restore interrupts + */ + cpu_leave_lowpower(); + + if (spurious) + pr_warn("CPU%u: %u spurious wakeup calls\n", cpu, spurious); +} diff --git a/arch/arm/mach-vexpress/include/mach/ct-ca9x4.h b/arch/arm/mach-vexpress/include/mach/ct-ca9x4.h new file mode 100644 index 00000000..84acf843 --- /dev/null +++ b/arch/arm/mach-vexpress/include/mach/ct-ca9x4.h @@ -0,0 +1,47 @@ +#ifndef __MACH_CT_CA9X4_H +#define __MACH_CT_CA9X4_H + +/* + * Physical base addresses + */ +#define CT_CA9X4_CLCDC (0x10020000) +#define CT_CA9X4_AXIRAM (0x10060000) +#define CT_CA9X4_DMC (0x100e0000) +#define CT_CA9X4_SMC (0x100e1000) +#define CT_CA9X4_SCC (0x100e2000) +#define CT_CA9X4_SP804_TIMER (0x100e4000) +#define CT_CA9X4_SP805_WDT (0x100e5000) +#define CT_CA9X4_TZPC (0x100e6000) +#define CT_CA9X4_GPIO (0x100e8000) +#define CT_CA9X4_FASTAXI (0x100e9000) +#define CT_CA9X4_SLOWAXI (0x100ea000) +#define CT_CA9X4_TZASC (0x100ec000) +#define CT_CA9X4_CORESIGHT (0x10200000) +#define CT_CA9X4_MPIC (0x1e000000) +#define CT_CA9X4_SYSTIMER (0x1e004000) +#define CT_CA9X4_SYSWDT (0x1e007000) +#define CT_CA9X4_L2CC (0x1e00a000) + +#define A9_MPCORE_SCU (CT_CA9X4_MPIC + 0x0000) +#define A9_MPCORE_GIC_CPU (CT_CA9X4_MPIC + 0x0100) +#define A9_MPCORE_GIT (CT_CA9X4_MPIC + 0x0200) +#define A9_MPCORE_TWD (CT_CA9X4_MPIC + 0x0600) +#define A9_MPCORE_GIC_DIST (CT_CA9X4_MPIC + 0x1000) + +/* + * Interrupts. Those in {} are for AMBA devices + */ +#define IRQ_CT_CA9X4_CLCDC { 76 } +#define IRQ_CT_CA9X4_DMC { 0 } +#define IRQ_CT_CA9X4_SMC { 77, 78 } +#define IRQ_CT_CA9X4_TIMER0 80 +#define IRQ_CT_CA9X4_TIMER1 81 +#define IRQ_CT_CA9X4_GPIO { 82 } +#define IRQ_CT_CA9X4_PMU_CPU0 92 +#define IRQ_CT_CA9X4_PMU_CPU1 93 +#define IRQ_CT_CA9X4_PMU_CPU2 94 +#define IRQ_CT_CA9X4_PMU_CPU3 95 + +extern struct ct_desc ct_ca9x4_desc; + +#endif diff --git a/arch/arm/mach-vexpress/include/mach/hardware.h b/arch/arm/mach-vexpress/include/mach/hardware.h new file mode 100644 index 00000000..40a8c178 --- /dev/null +++ b/arch/arm/mach-vexpress/include/mach/hardware.h @@ -0,0 +1 @@ +/* empty */ diff --git a/arch/arm/mach-vexpress/include/mach/irqs.h b/arch/arm/mach-vexpress/include/mach/irqs.h new file mode 100644 index 00000000..f8f7f782 --- /dev/null +++ b/arch/arm/mach-vexpress/include/mach/irqs.h @@ -0,0 +1,6 @@ +#define IRQ_LOCALTIMER 29 +#define IRQ_LOCALWDOG 30 + +#ifndef CONFIG_SPARSE_IRQ +#define NR_IRQS 256 +#endif diff --git a/arch/arm/mach-vexpress/include/mach/motherboard.h b/arch/arm/mach-vexpress/include/mach/motherboard.h new file mode 100644 index 00000000..68abc8b7 --- /dev/null +++ b/arch/arm/mach-vexpress/include/mach/motherboard.h @@ -0,0 +1,88 @@ +#ifndef __MACH_MOTHERBOARD_H +#define __MACH_MOTHERBOARD_H + +/* + * Physical addresses, offset from V2M_PA_CS0-3 + */ +#define V2M_NOR0 (V2M_PA_CS0) +#define V2M_NOR1 (V2M_PA_CS1) +#define V2M_SRAM (V2M_PA_CS2) +#define V2M_VIDEO_SRAM (V2M_PA_CS3 + 0x00000000) +#define V2M_LAN9118 (V2M_PA_CS3 + 0x02000000) +#define V2M_ISP1761 (V2M_PA_CS3 + 0x03000000) + +/* + * Physical addresses, offset from V2M_PA_CS7 + */ +#define V2M_SYSREGS (V2M_PA_CS7 + 0x00000000) +#define V2M_SYSCTL (V2M_PA_CS7 + 0x00001000) +#define V2M_SERIAL_BUS_PCI (V2M_PA_CS7 + 0x00002000) + +#define V2M_AACI (V2M_PA_CS7 + 0x00004000) +#define V2M_MMCI (V2M_PA_CS7 + 0x00005000) +#define V2M_KMI0 (V2M_PA_CS7 + 0x00006000) +#define V2M_KMI1 (V2M_PA_CS7 + 0x00007000) + +#define V2M_UART0 (V2M_PA_CS7 + 0x00009000) +#define V2M_UART1 (V2M_PA_CS7 + 0x0000a000) +#define V2M_UART2 (V2M_PA_CS7 + 0x0000b000) +#define V2M_UART3 (V2M_PA_CS7 + 0x0000c000) + +#define V2M_WDT (V2M_PA_CS7 + 0x0000f000) + +#define V2M_TIMER01 (V2M_PA_CS7 + 0x00011000) +#define V2M_TIMER23 (V2M_PA_CS7 + 0x00012000) + +#define V2M_SERIAL_BUS_DVI (V2M_PA_CS7 + 0x00016000) +#define V2M_RTC (V2M_PA_CS7 + 0x00017000) + +#define V2M_CF (V2M_PA_CS7 + 0x0001a000) +#define V2M_CLCD (V2M_PA_CS7 + 0x0001f000) + + +/* + * Interrupts. Those in {} are for AMBA devices + */ +#define IRQ_V2M_WDT { (32 + 0) } +#define IRQ_V2M_TIMER0 (32 + 2) +#define IRQ_V2M_TIMER1 (32 + 2) +#define IRQ_V2M_TIMER2 (32 + 3) +#define IRQ_V2M_TIMER3 (32 + 3) +#define IRQ_V2M_RTC { (32 + 4) } +#define IRQ_V2M_UART0 { (32 + 5) } +#define IRQ_V2M_UART1 { (32 + 6) } +#define IRQ_V2M_UART2 { (32 + 7) } +#define IRQ_V2M_UART3 { (32 + 8) } +#define IRQ_V2M_MMCI { (32 + 9), (32 + 10) } +#define IRQ_V2M_AACI { (32 + 11) } +#define IRQ_V2M_KMI0 { (32 + 12) } +#define IRQ_V2M_KMI1 { (32 + 13) } +#define IRQ_V2M_CLCD { (32 + 14) } +#define IRQ_V2M_LAN9118 (32 + 15) +#define IRQ_V2M_ISP1761 (32 + 16) +#define IRQ_V2M_PCIE (32 + 17) + + +/* + * Core tile IDs + */ +#define V2M_CT_ID_CA9 0x0c000191 +#define V2M_CT_ID_UNSUPPORTED 0xff000191 +#define V2M_CT_ID_MASK 0xff000fff + +struct ct_desc { + u32 id; + const char *name; + void (*map_io)(void); + void (*init_early)(void); + void (*init_irq)(void); + void (*init_tile)(void); +#ifdef CONFIG_SMP + void (*init_cpu_map)(void); + void (*smp_enable)(unsigned int); +#endif +}; + +extern struct ct_desc *ct_desc; + +#endif diff --git a/arch/arm/mach-vexpress/include/mach/tc2.h b/arch/arm/mach-vexpress/include/mach/tc2.h new file mode 100644 index 00000000..d3b5a222 --- /dev/null +++ b/arch/arm/mach-vexpress/include/mach/tc2.h @@ -0,0 +1,10 @@ +#ifndef __MACH_TC2_H +#define __MACH_TC2_H + +/* + * cpu and cluster limits + */ +#define TC2_MAX_CPUS 3 +#define TC2_MAX_CLUSTERS 2 + +#endif diff --git a/arch/arm/mach-vexpress/platsmp.c b/arch/arm/mach-vexpress/platsmp.c new file mode 100644 index 00000000..b4a5f0d8 --- /dev/null +++ b/arch/arm/mach-vexpress/platsmp.c @@ -0,0 +1,225 @@ +/* + * linux/arch/arm/mach-vexpress/platsmp.c + * + * Copyright (C) 2002 ARM Ltd. + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/smp.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_fdt.h> +#include <linux/vexpress.h> + +#include <asm/mcpm.h> +#include <asm/smp_scu.h> +#include <asm/mach/map.h> + +#include <mach/motherboard.h> + +#include <plat/platsmp.h> + +#include "core.h" + +#if defined(CONFIG_OF) + +static enum { + GENERIC_SCU, + CORTEX_A9_SCU, +} vexpress_dt_scu __initdata = GENERIC_SCU; + +static struct map_desc vexpress_dt_cortex_a9_scu_map __initdata = { + .virtual = V2T_PERIPH, + /* .pfn set in vexpress_dt_init_cortex_a9_scu() */ + .length = SZ_128, + .type = MT_DEVICE, +}; + +static void *vexpress_dt_cortex_a9_scu_base __initdata; + +const static char *vexpress_dt_cortex_a9_match[] __initconst = { + "arm,cortex-a5-scu", + "arm,cortex-a9-scu", + NULL +}; + +static int __init vexpress_dt_find_scu(unsigned long node, + const char *uname, int depth, void *data) +{ + if (of_flat_dt_match(node, vexpress_dt_cortex_a9_match)) { + phys_addr_t phys_addr; + const __be32 *reg = of_get_flat_dt_prop(node, "reg", NULL); + + if (WARN_ON(!reg)) + return -EINVAL; + + phys_addr = be32_to_cpup(reg); + vexpress_dt_scu = CORTEX_A9_SCU; + + vexpress_dt_cortex_a9_scu_map.pfn = __phys_to_pfn(phys_addr); + iotable_init(&vexpress_dt_cortex_a9_scu_map, 1); + vexpress_dt_cortex_a9_scu_base = ioremap(phys_addr, SZ_256); + if (WARN_ON(!vexpress_dt_cortex_a9_scu_base)) + return -EFAULT; + } + + return 0; +} + +void __init vexpress_dt_smp_map_io(void) +{ + if (initial_boot_params) + WARN_ON(of_scan_flat_dt(vexpress_dt_find_scu, NULL)); +} + +static int __init vexpress_dt_cpus_num(unsigned long node, const char *uname, + int depth, void *data) +{ + static int prev_depth = -1; + static int nr_cpus = -1; + + if (prev_depth > depth && nr_cpus > 0) + return nr_cpus; + + if (nr_cpus < 0 && strcmp(uname, "cpus") == 0) + nr_cpus = 0; + + if (nr_cpus >= 0) { + const char *device_type = of_get_flat_dt_prop(node, + "device_type", NULL); + + if (device_type && strcmp(device_type, "cpu") == 0) + nr_cpus++; + } + + prev_depth = depth; + + return 0; +} + +static void __init vexpress_dt_smp_init_cpus(void) +{ + int ncores = 0, i; + + switch (vexpress_dt_scu) { + case GENERIC_SCU: + ncores = of_scan_flat_dt(vexpress_dt_cpus_num, NULL); + break; + case CORTEX_A9_SCU: + ncores = scu_get_core_count(vexpress_dt_cortex_a9_scu_base); + break; + default: + WARN_ON(1); + break; + } + + if (ncores < 2) + return; + + if (ncores > nr_cpu_ids) { + pr_warn("SMP: %u cores greater than maximum (%u), clipping\n", + ncores, nr_cpu_ids); + ncores = nr_cpu_ids; + } + + for (i = 0; i < ncores; ++i) + set_cpu_possible(i, true); +} + +static void __init vexpress_dt_smp_prepare_cpus(unsigned int max_cpus) +{ + int i; + + switch (vexpress_dt_scu) { + case GENERIC_SCU: + for (i = 0; i < max_cpus; i++) + set_cpu_present(i, true); + break; + case CORTEX_A9_SCU: + scu_enable(vexpress_dt_cortex_a9_scu_base); + break; + default: + WARN_ON(1); + break; + } +} + +#else + +static void __init vexpress_dt_smp_init_cpus(void) +{ + WARN_ON(1); +} + +void __init vexpress_dt_smp_prepare_cpus(unsigned int max_cpus) +{ + WARN_ON(1); +} + +#endif + +/* + * Initialise the CPU possible map early - this describes the CPUs + * which may be present or become present in the system. + */ +static void __init vexpress_smp_init_cpus(void) +{ + if (ct_desc) + ct_desc->init_cpu_map(); + else + vexpress_dt_smp_init_cpus(); + +} + +static void __init vexpress_smp_prepare_cpus(unsigned int max_cpus) +{ + /* + * Initialise the present map, which describes the set of CPUs + * actually populated at the present time. + */ + if (ct_desc) + ct_desc->smp_enable(max_cpus); + else + vexpress_dt_smp_prepare_cpus(max_cpus); + + /* + * Write the address of secondary startup into the + * system-wide flags register. The boot monitor waits + * until it receives a soft interrupt, and then the + * secondary CPU branches to this address. + */ + vexpress_flags_set(virt_to_phys(versatile_secondary_startup)); +} + +struct smp_operations __initdata vexpress_smp_ops = { + .smp_init_cpus = vexpress_smp_init_cpus, + .smp_prepare_cpus = vexpress_smp_prepare_cpus, + .smp_secondary_init = versatile_secondary_init, + .smp_boot_secondary = versatile_boot_secondary, +#ifdef CONFIG_HOTPLUG_CPU + .cpu_die = vexpress_cpu_die, +#endif +}; + +bool __init vexpress_smp_init_ops(void) +{ +#ifdef CONFIG_MCPM + /* + * The best way to detect a multi-cluster configuration at the moment + * is to look for the presence of a CCI in the system. + * Override the default vexpress_smp_ops if so. + */ + struct device_node *node; + node = of_find_compatible_node(NULL, NULL, "arm,cci-400"); + if (node && of_device_is_available(node)) { + mcpm_smp_set_ops(); + return true; + } +#endif + return false; +} diff --git a/arch/arm/mach-vexpress/tc2_pm.c b/arch/arm/mach-vexpress/tc2_pm.c new file mode 100644 index 00000000..9fc264a3 --- /dev/null +++ b/arch/arm/mach-vexpress/tc2_pm.c @@ -0,0 +1,277 @@ +/* + * arch/arm/mach-vexpress/tc2_pm.c - TC2 power management support + * + * Created by: Nicolas Pitre, October 2012 + * Copyright: (C) 2012 Linaro Limited + * + * Some portions of this file were originally written by Achin Gupta + * Copyright: (C) 2012 ARM Limited + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/spinlock.h> +#include <linux/errno.h> +#include <linux/irqchip/arm-gic.h> + +#include <asm/mcpm.h> +#include <asm/proc-fns.h> +#include <asm/cacheflush.h> +#include <asm/cputype.h> +#include <asm/cp15.h> +#include <asm/psci.h> + +#include <mach/motherboard.h> +#include <mach/tc2.h> + +#include <linux/vexpress.h> +#include <linux/arm-cci.h> + +/* + * We can't use regular spinlocks. In the switcher case, it is possible + * for an outbound CPU to call power_down() after its inbound counterpart + * is already live using the same logical CPU number which trips lockdep + * debugging. + */ +static arch_spinlock_t tc2_pm_lock = __ARCH_SPIN_LOCK_UNLOCKED; + +static int tc2_pm_use_count[TC2_MAX_CPUS][TC2_MAX_CLUSTERS]; + +static int tc2_pm_power_up(unsigned int cpu, unsigned int cluster) +{ + pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); + if (cluster >= TC2_MAX_CLUSTERS || + cpu >= vexpress_spc_get_nb_cpus(cluster)) + return -EINVAL; + + /* + * Since this is called with IRQs enabled, and no arch_spin_lock_irq + * variant exists, we need to disable IRQs manually here. + */ + local_irq_disable(); + arch_spin_lock(&tc2_pm_lock); + + if (!tc2_pm_use_count[0][cluster] && + !tc2_pm_use_count[1][cluster] && + !tc2_pm_use_count[2][cluster]) + vexpress_spc_powerdown_enable(cluster, 0); + + tc2_pm_use_count[cpu][cluster]++; + if (tc2_pm_use_count[cpu][cluster] == 1) { + vexpress_spc_write_resume_reg(cluster, cpu, + virt_to_phys(mcpm_entry_point)); + vexpress_spc_set_cpu_wakeup_irq(cpu, cluster, 1); + } else if (tc2_pm_use_count[cpu][cluster] != 2) { + /* + * The only possible values are: + * 0 = CPU down + * 1 = CPU (still) up + * 2 = CPU requested to be up before it had a chance + * to actually make itself down. + * Any other value is a bug. + */ + BUG(); + } + + arch_spin_unlock(&tc2_pm_lock); + local_irq_enable(); + + return 0; +} + +static void tc2_pm_down(u64 residency) +{ + unsigned int mpidr, cpu, cluster; + bool last_man = false, skip_wfi = false; + + mpidr = read_cpuid_mpidr(); + cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + + pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); + BUG_ON(cluster >= TC2_MAX_CLUSTERS || + cpu >= vexpress_spc_get_nb_cpus(cluster)); + + __mcpm_cpu_going_down(cpu, cluster); + + arch_spin_lock(&tc2_pm_lock); + BUG_ON(__mcpm_cluster_state(cluster) != CLUSTER_UP); + tc2_pm_use_count[cpu][cluster]--; + if (tc2_pm_use_count[cpu][cluster] == 0) { + vexpress_spc_set_cpu_wakeup_irq(cpu, cluster, 1); + if (!tc2_pm_use_count[0][cluster] && + !tc2_pm_use_count[1][cluster] && + !tc2_pm_use_count[2][cluster] && + (!residency || residency > 5000)) { + vexpress_spc_powerdown_enable(cluster, 1); + vexpress_spc_set_global_wakeup_intr(1); + last_man = true; + } + } else if (tc2_pm_use_count[cpu][cluster] == 1) { + /* + * A power_up request went ahead of us. + * Even if we do not want to shut this CPU down, + * the caller expects a certain state as if the WFI + * was aborted. So let's continue with cache cleaning. + */ + skip_wfi = true; + } else + BUG(); + + /* + * If the CPU is committed to power down, make sure + * the power controller will be in charge of waking it + * up upon IRQ, ie IRQ lines are cut from GIC CPU IF + * to the CPU by disabling the GIC CPU IF to prevent wfi + * from completing execution behind power controller back + */ + if (!skip_wfi) + gic_cpu_if_down(); + + if (last_man && __mcpm_outbound_enter_critical(cpu, cluster)) { + arch_spin_unlock(&tc2_pm_lock); + + if (read_cpuid_part_number() == ARM_CPU_PART_CORTEX_A15) { + /* + * On the Cortex-A15 we need to disable + * L2 prefetching before flushing the cache. + */ + asm volatile( + "mcr p15, 1, %0, c15, c0, 3 \n\t" + "isb \n\t" + "dsb " + : : "r" (0x400) ); + } + + v7_exit_coherency_flush(all); + + cci_disable_port_by_cpu(mpidr); + + __mcpm_outbound_leave_critical(cluster, CLUSTER_DOWN); + } else { + /* + * If last man then undo any setup done previously. + */ + if (last_man) { + vexpress_spc_powerdown_enable(cluster, 0); + vexpress_spc_set_global_wakeup_intr(0); + } + + arch_spin_unlock(&tc2_pm_lock); + + v7_exit_coherency_flush(louis); + } + + __mcpm_cpu_down(cpu, cluster); + + /* Now we are prepared for power-down, do it: */ + if (!skip_wfi) + wfi(); + + /* Not dead at this point? Let our caller cope. */ +} + +static void tc2_pm_power_down(void) +{ + tc2_pm_down(0); +} + +static void tc2_pm_suspend(u64 residency) +{ + extern void tc2_resume(void); + unsigned int mpidr, cpu, cluster; + + mpidr = read_cpuid_mpidr(); + cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + vexpress_spc_write_resume_reg(cluster, cpu, + virt_to_phys(tc2_resume)); + + tc2_pm_down(residency); +} + +static void tc2_pm_powered_up(void) +{ + unsigned int mpidr, cpu, cluster; + unsigned long flags; + + mpidr = read_cpuid_mpidr(); + cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + + pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); + BUG_ON(cluster >= TC2_MAX_CLUSTERS || + cpu >= vexpress_spc_get_nb_cpus(cluster)); + + local_irq_save(flags); + arch_spin_lock(&tc2_pm_lock); + + if (!tc2_pm_use_count[0][cluster] && + !tc2_pm_use_count[1][cluster] && + !tc2_pm_use_count[2][cluster]) { + vexpress_spc_powerdown_enable(cluster, 0); + vexpress_spc_set_global_wakeup_intr(0); + } + + if (!tc2_pm_use_count[cpu][cluster]) + tc2_pm_use_count[cpu][cluster] = 1; + + vexpress_spc_set_cpu_wakeup_irq(cpu, cluster, 0); + vexpress_spc_write_resume_reg(cluster, cpu, 0); + + arch_spin_unlock(&tc2_pm_lock); + local_irq_restore(flags); +} + +static const struct mcpm_platform_ops tc2_pm_power_ops = { + .power_up = tc2_pm_power_up, + .power_down = tc2_pm_power_down, + .suspend = tc2_pm_suspend, + .powered_up = tc2_pm_powered_up, +}; + +static void __init tc2_pm_usage_count_init(void) +{ + unsigned int mpidr, cpu, cluster; + + mpidr = read_cpuid_mpidr(); + cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + + pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); + BUG_ON(cluster >= TC2_MAX_CLUSTERS || + cpu >= vexpress_spc_get_nb_cpus(cluster)); + + tc2_pm_use_count[cpu][cluster] = 1; +} + +extern void tc2_pm_power_up_setup(unsigned int affinity_level); + +static int __init tc2_pm_init(void) +{ + int ret; + + ret = psci_probe(); + if (!ret) { + pr_debug("psci found. Aborting native init\n"); + return -ENODEV; + } + + if (!vexpress_spc_check_loaded()) + return -ENODEV; + + tc2_pm_usage_count_init(); + + ret = mcpm_platform_register(&tc2_pm_power_ops); + if (!ret) + ret = mcpm_sync_init(tc2_pm_power_up_setup); + if (!ret) + pr_info("TC2 power management initialized\n"); + return ret; +} + +early_initcall(tc2_pm_init); diff --git a/arch/arm/mach-vexpress/tc2_pm_psci.c b/arch/arm/mach-vexpress/tc2_pm_psci.c new file mode 100644 index 00000000..c2fdc22e --- /dev/null +++ b/arch/arm/mach-vexpress/tc2_pm_psci.c @@ -0,0 +1,173 @@ +/* + * arch/arm/mach-vexpress/tc2_pm_psci.c - TC2 PSCI support + * + * Created by: Achin Gupta, December 2012 + * Copyright: (C) 2012 ARM Limited + * + * Some portions of this file were originally written by Nicolas Pitre + * Copyright: (C) 2012 Linaro Limited + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/spinlock.h> +#include <linux/errno.h> + +#include <asm/mcpm.h> +#include <asm/proc-fns.h> +#include <asm/cacheflush.h> +#include <asm/psci.h> +#include <asm/atomic.h> +#include <asm/cputype.h> +#include <asm/cp15.h> + +#include <mach/motherboard.h> +#include <mach/tc2.h> + +#include <linux/vexpress.h> + +/* + * Platform specific state id understood by the firmware and used to + * program the power controller + */ +#define PSCI_POWER_STATE_ID 0 + +static atomic_t tc2_pm_use_count[TC2_MAX_CPUS][TC2_MAX_CLUSTERS]; + +static int tc2_pm_psci_power_up(unsigned int cpu, unsigned int cluster) +{ + unsigned int mpidr = (cluster << 8) | cpu; + int ret = 0; + + BUG_ON(!psci_ops.cpu_on); + + switch (atomic_inc_return(&tc2_pm_use_count[cpu][cluster])) { + case 1: + /* + * This is a request to power up a cpu that linux thinks has + * been powered down. Retries are needed if the firmware has + * seen the power down request as yet. + */ + do + ret = psci_ops.cpu_on(mpidr, + virt_to_phys(mcpm_entry_point)); + while (ret == -EAGAIN); + + return ret; + case 2: + /* This power up request has overtaken a power down request */ + return ret; + default: + /* Any other value is a bug */ + BUG(); + } +} + +static void tc2_pm_psci_power_down(void) +{ + struct psci_power_state power_state; + unsigned int mpidr, cpu, cluster; + + mpidr = read_cpuid_mpidr(); + cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + + BUG_ON(!psci_ops.cpu_off); + + switch (atomic_dec_return(&tc2_pm_use_count[cpu][cluster])) { + case 1: + /* + * Overtaken by a power up. Flush caches, exit coherency, + * return & fake a reset + */ + set_cr(get_cr() & ~CR_C); + + flush_cache_louis(); + + asm volatile ("clrex"); + set_auxcr(get_auxcr() & ~(1 << 6)); + + return; + case 0: + /* A normal request to possibly power down the cluster */ + power_state.id = PSCI_POWER_STATE_ID; + power_state.type = PSCI_POWER_STATE_TYPE_POWER_DOWN; + power_state.affinity_level = PSCI_POWER_STATE_AFFINITY_LEVEL1; + + psci_ops.cpu_off(power_state); + + /* On success this function never returns */ + default: + /* Any other value is a bug */ + BUG(); + } +} + +static void tc2_pm_psci_suspend(u64 unused) +{ + struct psci_power_state power_state; + + BUG_ON(!psci_ops.cpu_suspend); + + /* On TC2 always attempt to power down the cluster */ + power_state.id = PSCI_POWER_STATE_ID; + power_state.type = PSCI_POWER_STATE_TYPE_POWER_DOWN; + power_state.affinity_level = PSCI_POWER_STATE_AFFINITY_LEVEL1; + + psci_ops.cpu_suspend(power_state, virt_to_phys(mcpm_entry_point)); + + /* On success this function never returns */ + BUG(); +} + +static const struct mcpm_platform_ops tc2_pm_power_ops = { + .power_up = tc2_pm_psci_power_up, + .power_down = tc2_pm_psci_power_down, + .suspend = tc2_pm_psci_suspend, +}; + +static void __init tc2_pm_usage_count_init(void) +{ + unsigned int mpidr, cpu, cluster; + + mpidr = read_cpuid_mpidr(); + cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + + pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); + BUG_ON(cluster >= TC2_MAX_CLUSTERS || + cpu >= vexpress_spc_get_nb_cpus(cluster)); + + atomic_set(&tc2_pm_use_count[cpu][cluster], 1); +} + +static int __init tc2_pm_psci_init(void) +{ + int ret; + + ret = psci_probe(); + if (ret) { + pr_debug("psci not found. Aborting psci init\n"); + return -ENODEV; + } + + if (!vexpress_spc_check_loaded()) { + pr_debug("spc not found. Aborting psci init\n"); + return -ENODEV; + } + + tc2_pm_usage_count_init(); + + ret = mcpm_platform_register(&tc2_pm_power_ops); + if (!ret) + ret = mcpm_sync_init(NULL); + if (!ret) + pr_info("TC2 power management initialized\n"); + return ret; +} + +early_initcall(tc2_pm_psci_init); diff --git a/arch/arm/mach-vexpress/tc2_pm_setup.S b/arch/arm/mach-vexpress/tc2_pm_setup.S new file mode 100644 index 00000000..a18dafee --- /dev/null +++ b/arch/arm/mach-vexpress/tc2_pm_setup.S @@ -0,0 +1,68 @@ +/* + * arch/arm/include/asm/tc2_pm_setup.S + * + * Created by: Nicolas Pitre, October 2012 + ( (based on dcscb_setup.S by Dave Martin) + * Copyright: (C) 2012 Linaro Limited + * + * 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 <asm/mcpm.h> + + +#define SPC_PHYS_BASE 0x7FFF0000 +#define SPC_WAKE_INT_STAT 0xb2c + +#define SNOOP_CTL_A15 0x404 +#define SNOOP_CTL_A7 0x504 + +#define A15_SNOOP_MASK (0x3 << 7) +#define A7_SNOOP_MASK (0x1 << 13) + +#define A15_BX_ADDR0 0xB68 + + +ENTRY(tc2_resume) + mrc p15, 0, r0, c0, c0, 5 + ubfx r1, r0, #0, #4 @ r1 = cpu + ubfx r2, r0, #8, #4 @ r2 = cluster + add r1, r1, r2, lsl #2 @ r1 = index of CPU in WAKE_INT_STAT + ldr r3, =SPC_PHYS_BASE + SPC_WAKE_INT_STAT + ldr r3, [r3] + lsr r3, r1 + tst r3, #1 + wfieq @ if no pending IRQ reenters wfi + b mcpm_entry_point +ENDPROC(tc2_resume) + +/* + * Enable cluster-level coherency, in preparation for turning on the MMU. + * The ACTLR SMP bit does not need to be set here, because cpu_resume() + * already restores that. + */ + +ENTRY(tc2_pm_power_up_setup) + + cmp r0, #0 + beq 2f + + b cci_enable_port_for_self + +2: @ Clear the BX addr register + ldr r3, =SPC_PHYS_BASE + A15_BX_ADDR0 + mrc p15, 0, r0, c0, c0, 5 @ MPIDR + ubfx r1, r0, #8, #4 @ cluster + ubfx r0, r0, #0, #4 @ cpu + add r3, r3, r1, lsl #4 + mov r1, #0 + str r1, [r3, r0, lsl #2] + dsb + + bx lr + +ENDPROC(tc2_pm_power_up_setup) diff --git a/arch/arm/mach-vexpress/v2m.c b/arch/arm/mach-vexpress/v2m.c new file mode 100644 index 00000000..057f99b6 --- /dev/null +++ b/arch/arm/mach-vexpress/v2m.c @@ -0,0 +1,493 @@ +/* + * Versatile Express V2M Motherboard Support + */ +#include <linux/clocksource.h> +#include <linux/device.h> +#include <linux/amba/bus.h> +#include <linux/amba/mmci.h> +#include <linux/io.h> +#include <linux/clocksource.h> +#include <linux/smp.h> +#include <linux/init.h> +#include <linux/irqchip.h> +#include <linux/memblock.h> +#include <linux/of_address.h> +#include <linux/of_fdt.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/ata_platform.h> +#include <linux/smsc911x.h> +#include <linux/spinlock.h> +#include <linux/usb/isp1760.h> +#include <linux/mtd/physmap.h> +#include <linux/regulator/fixed.h> +#include <linux/regulator/machine.h> +#include <linux/vexpress.h> +#include <linux/clk-provider.h> +#include <linux/clkdev.h> + +#include <asm/mach-types.h> +#include <asm/sizes.h> +#include <asm/mach/arch.h> +#include <asm/mach/map.h> +#include <asm/mach/time.h> +#include <asm/hardware/arm_timer.h> +#include <asm/hardware/cache-l2x0.h> +#include <asm/hardware/timer-sp.h> + +#include <mach/ct-ca9x4.h> +#include <mach/motherboard.h> + +#include <plat/sched_clock.h> +#include <plat/platsmp.h> + +#include "core.h" + +#define V2M_PA_CS0 0x40000000 +#define V2M_PA_CS1 0x44000000 +#define V2M_PA_CS2 0x48000000 +#define V2M_PA_CS3 0x4c000000 +#define V2M_PA_CS7 0x10000000 + +static struct map_desc v2m_io_desc[] __initdata = { + { + .virtual = V2M_PERIPH, + .pfn = __phys_to_pfn(V2M_PA_CS7), + .length = SZ_128K, + .type = MT_DEVICE, + }, +}; + +static void __init v2m_sp804_init(void __iomem *base, unsigned int irq) +{ + if (WARN_ON(!base || irq == NO_IRQ)) + return; + + sp804_clocksource_init(base + TIMER_2_BASE, "v2m-timer1"); + sp804_clockevents_init(base + TIMER_1_BASE, irq, "v2m-timer0"); +} + + +static struct resource v2m_pcie_i2c_resource = { + .start = V2M_SERIAL_BUS_PCI, + .end = V2M_SERIAL_BUS_PCI + SZ_4K - 1, + .flags = IORESOURCE_MEM, +}; + +static struct platform_device v2m_pcie_i2c_device = { + .name = "versatile-i2c", + .id = 0, + .num_resources = 1, + .resource = &v2m_pcie_i2c_resource, +}; + +static struct resource v2m_ddc_i2c_resource = { + .start = V2M_SERIAL_BUS_DVI, + .end = V2M_SERIAL_BUS_DVI + SZ_4K - 1, + .flags = IORESOURCE_MEM, +}; + +static struct platform_device v2m_ddc_i2c_device = { + .name = "versatile-i2c", + .id = 1, + .num_resources = 1, + .resource = &v2m_ddc_i2c_resource, +}; + +static struct resource v2m_eth_resources[] = { + { + .start = V2M_LAN9118, + .end = V2M_LAN9118 + SZ_64K - 1, + .flags = IORESOURCE_MEM, + }, { + .start = IRQ_V2M_LAN9118, + .end = IRQ_V2M_LAN9118, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct smsc911x_platform_config v2m_eth_config = { + .flags = SMSC911X_USE_32BIT, + .irq_polarity = SMSC911X_IRQ_POLARITY_ACTIVE_HIGH, + .irq_type = SMSC911X_IRQ_TYPE_PUSH_PULL, + .phy_interface = PHY_INTERFACE_MODE_MII, +}; + +static struct platform_device v2m_eth_device = { + .name = "smsc911x", + .id = -1, + .resource = v2m_eth_resources, + .num_resources = ARRAY_SIZE(v2m_eth_resources), + .dev.platform_data = &v2m_eth_config, +}; + +static struct regulator_consumer_supply v2m_eth_supplies[] = { + REGULATOR_SUPPLY("vddvario", "smsc911x"), + REGULATOR_SUPPLY("vdd33a", "smsc911x"), +}; + +static struct resource v2m_usb_resources[] = { + { + .start = V2M_ISP1761, + .end = V2M_ISP1761 + SZ_128K - 1, + .flags = IORESOURCE_MEM, + }, { + .start = IRQ_V2M_ISP1761, + .end = IRQ_V2M_ISP1761, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct isp1760_platform_data v2m_usb_config = { + .is_isp1761 = true, + .bus_width_16 = false, + .port1_otg = true, + .analog_oc = false, + .dack_polarity_high = false, + .dreq_polarity_high = false, +}; + +static struct platform_device v2m_usb_device = { + .name = "isp1760", + .id = -1, + .resource = v2m_usb_resources, + .num_resources = ARRAY_SIZE(v2m_usb_resources), + .dev.platform_data = &v2m_usb_config, +}; + +static struct physmap_flash_data v2m_flash_data = { + .width = 4, +}; + +static struct resource v2m_flash_resources[] = { + { + .start = V2M_NOR0, + .end = V2M_NOR0 + SZ_64M - 1, + .flags = IORESOURCE_MEM, + }, { + .start = V2M_NOR1, + .end = V2M_NOR1 + SZ_64M - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device v2m_flash_device = { + .name = "physmap-flash", + .id = -1, + .resource = v2m_flash_resources, + .num_resources = ARRAY_SIZE(v2m_flash_resources), + .dev.platform_data = &v2m_flash_data, +}; + +static struct pata_platform_info v2m_pata_data = { + .ioport_shift = 2, +}; + +static struct resource v2m_pata_resources[] = { + { + .start = V2M_CF, + .end = V2M_CF + 0xff, + .flags = IORESOURCE_MEM, + }, { + .start = V2M_CF + 0x100, + .end = V2M_CF + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device v2m_cf_device = { + .name = "pata_platform", + .id = -1, + .resource = v2m_pata_resources, + .num_resources = ARRAY_SIZE(v2m_pata_resources), + .dev.platform_data = &v2m_pata_data, +}; + +static struct mmci_platform_data v2m_mmci_data = { + .ocr_mask = MMC_VDD_32_33|MMC_VDD_33_34, + .gpio_wp = VEXPRESS_GPIO_MMC_WPROT, + .gpio_cd = VEXPRESS_GPIO_MMC_CARDIN, +}; + +static struct resource v2m_sysreg_resources[] = { + { + .start = V2M_SYSREGS, + .end = V2M_SYSREGS + 0xfff, + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device v2m_sysreg_device = { + .name = "vexpress-sysreg", + .id = -1, + .resource = v2m_sysreg_resources, + .num_resources = ARRAY_SIZE(v2m_sysreg_resources), +}; + +static struct platform_device v2m_muxfpga_device = { + .name = "vexpress-muxfpga", + .id = 0, + .num_resources = 1, + .resource = (struct resource []) { + VEXPRESS_RES_FUNC(0, 7), + } +}; + +static struct platform_device v2m_shutdown_device = { + .name = "vexpress-shutdown", + .id = 0, + .num_resources = 1, + .resource = (struct resource []) { + VEXPRESS_RES_FUNC(0, 8), + } +}; + +static struct platform_device v2m_reboot_device = { + .name = "vexpress-reboot", + .id = 0, + .num_resources = 1, + .resource = (struct resource []) { + VEXPRESS_RES_FUNC(0, 9), + } +}; + +static struct platform_device v2m_dvimode_device = { + .name = "vexpress-dvimode", + .id = 0, + .num_resources = 1, + .resource = (struct resource []) { + VEXPRESS_RES_FUNC(0, 11), + } +}; + +static AMBA_APB_DEVICE(aaci, "mb:aaci", 0, V2M_AACI, IRQ_V2M_AACI, NULL); +static AMBA_APB_DEVICE(mmci, "mb:mmci", 0, V2M_MMCI, IRQ_V2M_MMCI, &v2m_mmci_data); +static AMBA_APB_DEVICE(kmi0, "mb:kmi0", 0, V2M_KMI0, IRQ_V2M_KMI0, NULL); +static AMBA_APB_DEVICE(kmi1, "mb:kmi1", 0, V2M_KMI1, IRQ_V2M_KMI1, NULL); +static AMBA_APB_DEVICE(uart0, "mb:uart0", 0, V2M_UART0, IRQ_V2M_UART0, NULL); +static AMBA_APB_DEVICE(uart1, "mb:uart1", 0, V2M_UART1, IRQ_V2M_UART1, NULL); +static AMBA_APB_DEVICE(uart2, "mb:uart2", 0, V2M_UART2, IRQ_V2M_UART2, NULL); +static AMBA_APB_DEVICE(uart3, "mb:uart3", 0, V2M_UART3, IRQ_V2M_UART3, NULL); +static AMBA_APB_DEVICE(wdt, "mb:wdt", 0, V2M_WDT, IRQ_V2M_WDT, NULL); +static AMBA_APB_DEVICE(rtc, "mb:rtc", 0, V2M_RTC, IRQ_V2M_RTC, NULL); + +static struct amba_device *v2m_amba_devs[] __initdata = { + &aaci_device, + &mmci_device, + &kmi0_device, + &kmi1_device, + &uart0_device, + &uart1_device, + &uart2_device, + &uart3_device, + &wdt_device, + &rtc_device, +}; + +static void __init v2m_timer_init(void) +{ + vexpress_clk_init(ioremap(V2M_SYSCTL, SZ_4K)); + v2m_sp804_init(ioremap(V2M_TIMER01, SZ_4K), IRQ_V2M_TIMER0); +} + +static void __init v2m_init_early(void) +{ + if (ct_desc->init_early) + ct_desc->init_early(); + versatile_sched_clock_init(vexpress_get_24mhz_clock_base(), 24000000); +} + +struct ct_desc *ct_desc; + +static struct ct_desc *ct_descs[] __initdata = { +#ifdef CONFIG_ARCH_VEXPRESS_CA9X4 + &ct_ca9x4_desc, +#endif +}; + +static void __init v2m_populate_ct_desc(void) +{ + int i; + u32 current_tile_id; + + ct_desc = NULL; + current_tile_id = vexpress_get_procid(VEXPRESS_SITE_MASTER) + & V2M_CT_ID_MASK; + + for (i = 0; i < ARRAY_SIZE(ct_descs) && !ct_desc; ++i) + if (ct_descs[i]->id == current_tile_id) + ct_desc = ct_descs[i]; + + if (!ct_desc) + panic("vexpress: this kernel does not support core tile ID 0x%08x when booting via ATAGs.\n" + "You may need a device tree blob or a different kernel to boot on this board.\n", + current_tile_id); +} + +static void __init v2m_map_io(void) +{ + iotable_init(v2m_io_desc, ARRAY_SIZE(v2m_io_desc)); + vexpress_sysreg_early_init(ioremap(V2M_SYSREGS, SZ_4K)); + v2m_populate_ct_desc(); + ct_desc->map_io(); +} + +static void __init v2m_init_irq(void) +{ + ct_desc->init_irq(); +} + +static void __init v2m_init(void) +{ + int i; + + regulator_register_fixed(0, v2m_eth_supplies, + ARRAY_SIZE(v2m_eth_supplies)); + + platform_device_register(&v2m_muxfpga_device); + platform_device_register(&v2m_shutdown_device); + platform_device_register(&v2m_reboot_device); + platform_device_register(&v2m_dvimode_device); + + platform_device_register(&v2m_sysreg_device); + platform_device_register(&v2m_pcie_i2c_device); + platform_device_register(&v2m_ddc_i2c_device); + platform_device_register(&v2m_flash_device); + platform_device_register(&v2m_cf_device); + platform_device_register(&v2m_eth_device); + platform_device_register(&v2m_usb_device); + + for (i = 0; i < ARRAY_SIZE(v2m_amba_devs); i++) + amba_device_register(v2m_amba_devs[i], &iomem_resource); + + ct_desc->init_tile(); +} + +MACHINE_START(VEXPRESS, "ARM-Versatile Express") + .atag_offset = 0x100, + .smp = smp_ops(vexpress_smp_ops), + .map_io = v2m_map_io, + .init_early = v2m_init_early, + .init_irq = v2m_init_irq, + .init_time = v2m_timer_init, + .init_machine = v2m_init, +MACHINE_END + +static void __init v2m_dt_hdlcd_init(void) +{ + struct device_node *node; + int len, na, ns; + const __be32 *prop; + phys_addr_t fb_base, fb_size; + + node = of_find_compatible_node(NULL, NULL, "arm,hdlcd"); + if (!node) + return; + + na = of_n_addr_cells(node); + ns = of_n_size_cells(node); + + prop = of_get_property(node, "framebuffer", &len); + if (WARN_ON(!prop || len < (na + ns) * sizeof(*prop))) + return; + + fb_base = of_read_number(prop, na); + fb_size = of_read_number(prop + na, ns); + + if (WARN_ON(memblock_remove(fb_base, fb_size))) + return; +}; + +static struct map_desc v2m_rs1_io_desc __initdata = { + .virtual = V2M_PERIPH, + .pfn = __phys_to_pfn(0x1c000000), + .length = SZ_2M, + .type = MT_DEVICE, +}; + +static int __init v2m_dt_scan_memory_map(unsigned long node, const char *uname, + int depth, void *data) +{ + const char **map = data; + + if (strcmp(uname, "motherboard") != 0) + return 0; + + *map = of_get_flat_dt_prop(node, "arm,v2m-memory-map", NULL); + + return 1; +} + +void __init v2m_dt_map_io(void) +{ + const char *map = NULL; + + of_scan_flat_dt(v2m_dt_scan_memory_map, &map); + + if (map && strcmp(map, "rs1") == 0) + iotable_init(&v2m_rs1_io_desc, 1); + else + iotable_init(v2m_io_desc, ARRAY_SIZE(v2m_io_desc)); + +#if defined(CONFIG_SMP) + vexpress_dt_smp_map_io(); +#endif +} + +void __init v2m_dt_init_early(void) +{ + u32 dt_hbi; + + vexpress_sysreg_of_early_init(); + + /* Confirm board type against DT property, if available */ + if (of_property_read_u32(of_allnodes, "arm,hbi", &dt_hbi) == 0) { + u32 hbi = vexpress_get_hbi(VEXPRESS_SITE_MASTER); + + if (WARN_ON(dt_hbi != hbi)) + pr_warning("vexpress: DT HBI (%x) is not matching " + "hardware (%x)!\n", dt_hbi, hbi); + } + + v2m_dt_hdlcd_init(); +} + +static void __init v2m_dt_timer_init(void) +{ + of_clk_init(NULL); + + clocksource_of_init(); + + versatile_sched_clock_init(vexpress_get_24mhz_clock_base(), + 24000000); +} + +static const struct of_device_id v2m_dt_bus_match[] __initconst = { + { .compatible = "simple-bus", }, + { .compatible = "arm,amba-bus", }, + { .compatible = "arm,vexpress,config-bus", }, + {} +}; + +static void __init v2m_dt_init(void) +{ + l2x0_of_init(0x00400000, 0xfe0fffff); + of_platform_populate(NULL, v2m_dt_bus_match, NULL, NULL); +} + +static const char * const v2m_dt_match[] __initconst = { + "arm,vexpress", + NULL, +}; + +DT_MACHINE_START(VEXPRESS_DT, "ARM-Versatile Express") + .dt_compat = v2m_dt_match, + .smp = smp_ops(vexpress_smp_ops), + .smp_init = smp_init_ops(vexpress_smp_init_ops), + .map_io = v2m_dt_map_io, + .init_early = v2m_dt_init_early, + .init_irq = irqchip_init, + .init_time = v2m_dt_timer_init, + .init_machine = v2m_dt_init, +MACHINE_END |
