diff options
Diffstat (limited to 'arch/arm/mach-vexpress')
| -rw-r--r-- | arch/arm/mach-vexpress/Kconfig | 84 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/Makefile | 18 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/core.h | 12 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/ct-ca9x4.c | 213 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/dcscb.c | 237 | ||||
| -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/platsmp.c | 116 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/spc.c | 584 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/spc.h | 25 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/tc2_pm.c | 378 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/v2m.c | 390 |
16 files changed, 2343 insertions, 0 deletions
diff --git a/arch/arm/mach-vexpress/Kconfig b/arch/arm/mach-vexpress/Kconfig new file mode 100644 index 00000000000..d8b9330f896 --- /dev/null +++ b/arch/arm/mach-vexpress/Kconfig @@ -0,0 +1,84 @@ +menuconfig ARCH_VEXPRESS + bool "ARM Ltd. Versatile Express family" if ARCH_MULTI_V7 + select ARCH_REQUIRE_GPIOLIB + select ARCH_SUPPORTS_BIG_ENDIAN + select ARM_AMBA + select ARM_GIC + select ARM_GLOBAL_TIMER + select ARM_TIMER_SP804 + select COMMON_CLK_VERSATILE + select HAVE_ARM_SCU if SMP + select HAVE_ARM_TWD if SMP + select HAVE_PATA_PLATFORM + select ICST + select NO_IOPORT_MAP + 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 + select VEXPRESS_SYSCFG + select MFD_VEXPRESS_SYSREG + 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. + +if 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_L2X0 + 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" + +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_SPC + bool "Versatile Express Serial Power Controller (SPC)" + select ARCH_HAS_OPP + select PM_OPP + help + The TC2 (A15x2 A7x3) versatile express core tile integrates a logic + block called Serial Power Controller (SPC) that provides the interface + between the dual cluster test-chip and the M3 microcontroller that + carries out power management. + +config ARCH_VEXPRESS_TC2_PM + bool "Versatile Express TC2 power management" + depends on MCPM + select ARM_CCI + select ARCH_VEXPRESS_SPC + help + Support for CPU and cluster power management on Versatile Express + with a TC2 (A15x2 A7x3) big.LITTLE core tile. + +endif diff --git a/arch/arm/mach-vexpress/Makefile b/arch/arm/mach-vexpress/Makefile new file mode 100644 index 00000000000..fc649bc09d0 --- /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_dcscb.o += -march=armv7-a +CFLAGS_REMOVE_dcscb.o = -pg +obj-$(CONFIG_ARCH_VEXPRESS_SPC) += spc.o +CFLAGS_REMOVE_spc.o = -pg +obj-$(CONFIG_ARCH_VEXPRESS_TC2_PM) += tc2_pm.o +CFLAGS_tc2_pm.o += -march=armv7-a +CFLAGS_REMOVE_tc2_pm.o = -pg +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 00000000000..152fad91b3a --- /dev/null +++ b/arch/arm/mach-vexpress/core.h @@ -0,0 +1,12 @@ +/* 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 + +bool vexpress_smp_init_ops(void); + +extern struct smp_operations vexpress_smp_ops; +extern struct smp_operations vexpress_smp_dt_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 00000000000..86150d7a2e7 --- /dev/null +++ b/arch/arm/mach-vexpress/ct-ca9x4.c @@ -0,0 +1,213 @@ +/* + * 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)); +} + +static void __init ca9x4_l2_init(void) +{ +#ifdef CONFIG_CACHE_L2X0 + void __iomem *l2x0_base = ioremap(CT_CA9X4_L2CC, SZ_4K); + + if (l2x0_base) { + /* set RAM latencies to 1 cycle for this core tile. */ + writel(0, l2x0_base + L310_TAG_LATENCY_CTRL); + writel(0, l2x0_base + L310_DATA_LATENCY_CTRL); + + l2x0_init(l2x0_base, 0x00400000, 0xfe0fffff); + } else { + pr_err("L2C: unable to map L2 cache controller\n"); + } +#endif +} + +#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(); + ca9x4_l2_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 clk_lookup osc1_lookup = { + .dev_id = "ct:clcd", +}; + +static struct platform_device osc1_device = { + .name = "vexpress-osc", + .id = 1, + .num_resources = 1, + .resource = (struct resource []) { + VEXPRESS_RES_FUNC(0xf, 1), + }, + .dev.platform_data = &osc1_lookup, +}; + +static void __init ct_ca9x4_init(void) +{ + int i; + + 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); + vexpress_syscfg_device_register(&osc1_device); +} + +#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 00000000000..30b993399ed --- /dev/null +++ b/arch/arm/mach-vexpress/dcscb.c @@ -0,0 +1,237 @@ +/* + * 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> + + +#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; + + pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); + if (cpu >= 4 || cluster >= 2) + return -EINVAL; + + all_mask = dcscb_allcpus_mask[cluster]; + + /* + * 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); + + pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); + BUG_ON(cpu >= 4 || cluster >= 2); + + all_mask = dcscb_allcpus_mask[cluster]; + + __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); + + /* + * A full outer cache flush could be needed at this point + * on platforms with such a cache, depending on where the + * outer cache sits. In some cases the notion of a "last + * cluster standing" would need to be implemented if the + * outer cache is shared across clusters. In any case, when + * the outer cache needs flushing, there is no concurrent + * access to the cache controller to worry about and no + * special locking besides what is already provided by the + * MCPM state machinery is needed. + */ + + /* + * 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; + + 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 00000000000..4bb7fbe0f62 --- /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 00000000000..f0ce6b8f5e7 --- /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 00000000000..84acf8439d4 --- /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 00000000000..40a8c178f10 --- /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 00000000000..f8f7f782eb5 --- /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 00000000000..68abc8b7278 --- /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/platsmp.c b/arch/arm/mach-vexpress/platsmp.c new file mode 100644 index 00000000000..a1f3804fd5a --- /dev/null +++ b/arch/arm/mach-vexpress/platsmp.c @@ -0,0 +1,116 @@ +/* + * 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_address.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" + +/* + * 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) +{ + ct_desc->init_cpu_map(); +} + +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. + */ + ct_desc->smp_enable(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; +} + +#if defined(CONFIG_OF) + +static const struct of_device_id vexpress_smp_dt_scu_match[] __initconst = { + { .compatible = "arm,cortex-a5-scu", }, + { .compatible = "arm,cortex-a9-scu", }, + {} +}; + +static void __init vexpress_smp_dt_prepare_cpus(unsigned int max_cpus) +{ + struct device_node *scu = of_find_matching_node(NULL, + vexpress_smp_dt_scu_match); + + if (scu) + scu_enable(of_iomap(scu, 0)); + + /* + * 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_dt_ops = { + .smp_prepare_cpus = vexpress_smp_dt_prepare_cpus, + .smp_secondary_init = versatile_secondary_init, + .smp_boot_secondary = versatile_boot_secondary, +#ifdef CONFIG_HOTPLUG_CPU + .cpu_die = vexpress_cpu_die, +#endif +}; + +#endif diff --git a/arch/arm/mach-vexpress/spc.c b/arch/arm/mach-vexpress/spc.c new file mode 100644 index 00000000000..2c2754e79cb --- /dev/null +++ b/arch/arm/mach-vexpress/spc.c @@ -0,0 +1,584 @@ +/* + * Versatile Express Serial Power Controller (SPC) support + * + * Copyright (C) 2013 ARM Ltd. + * + * Authors: Sudeep KarkadaNagesha <sudeep.karkadanagesha@arm.com> + * Achin Gupta <achin.gupta@arm.com> + * Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> + * + * 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. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk-provider.h> +#include <linux/clkdev.h> +#include <linux/cpu.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/pm_opp.h> +#include <linux/slab.h> +#include <linux/semaphore.h> + +#include <asm/cacheflush.h> + +#define SPCLOG "vexpress-spc: " + +#define PERF_LVL_A15 0x00 +#define PERF_REQ_A15 0x04 +#define PERF_LVL_A7 0x08 +#define PERF_REQ_A7 0x0c +#define COMMS 0x10 +#define COMMS_REQ 0x14 +#define PWC_STATUS 0x18 +#define PWC_FLAG 0x1c + +/* SPC wake-up IRQs status and mask */ +#define WAKE_INT_MASK 0x24 +#define WAKE_INT_RAW 0x28 +#define WAKE_INT_STAT 0x2c +/* SPC power down registers */ +#define A15_PWRDN_EN 0x30 +#define A7_PWRDN_EN 0x34 +/* SPC per-CPU mailboxes */ +#define A15_BX_ADDR0 0x68 +#define A7_BX_ADDR0 0x78 + +/* SPC CPU/cluster reset statue */ +#define STANDBYWFI_STAT 0x3c +#define STANDBYWFI_STAT_A15_CPU_MASK(cpu) (1 << (cpu)) +#define STANDBYWFI_STAT_A7_CPU_MASK(cpu) (1 << (3 + (cpu))) + +/* SPC system config interface registers */ +#define SYSCFG_WDATA 0x70 +#define SYSCFG_RDATA 0x74 + +/* A15/A7 OPP virtual register base */ +#define A15_PERFVAL_BASE 0xC10 +#define A7_PERFVAL_BASE 0xC30 + +/* Config interface control bits */ +#define SYSCFG_START (1 << 31) +#define SYSCFG_SCC (6 << 20) +#define SYSCFG_STAT (14 << 20) + +/* wake-up interrupt masks */ +#define GBL_WAKEUP_INT_MSK (0x3 << 10) + +/* TC2 static dual-cluster configuration */ +#define MAX_CLUSTERS 2 + +/* + * Even though the SPC takes max 3-5 ms to complete any OPP/COMMS + * operation, the operation could start just before jiffie is about + * to be incremented. So setting timeout value of 20ms = 2jiffies@100Hz + */ +#define TIMEOUT_US 20000 + +#define MAX_OPPS 8 +#define CA15_DVFS 0 +#define CA7_DVFS 1 +#define SPC_SYS_CFG 2 +#define STAT_COMPLETE(type) ((1 << 0) << (type << 2)) +#define STAT_ERR(type) ((1 << 1) << (type << 2)) +#define RESPONSE_MASK(type) (STAT_COMPLETE(type) | STAT_ERR(type)) + +struct ve_spc_opp { + unsigned long freq; + unsigned long u_volt; +}; + +struct ve_spc_drvdata { + void __iomem *baseaddr; + /* + * A15s cluster identifier + * It corresponds to A15 processors MPIDR[15:8] bitfield + */ + u32 a15_clusid; + uint32_t cur_rsp_mask; + uint32_t cur_rsp_stat; + struct semaphore sem; + struct completion done; + struct ve_spc_opp *opps[MAX_CLUSTERS]; + int num_opps[MAX_CLUSTERS]; +}; + +static struct ve_spc_drvdata *info; + +static inline bool cluster_is_a15(u32 cluster) +{ + return cluster == info->a15_clusid; +} + +/** + * ve_spc_global_wakeup_irq() + * + * Function to set/clear global wakeup IRQs. Not protected by locking since + * it might be used in code paths where normal cacheable locks are not + * working. Locking must be provided by the caller to ensure atomicity. + * + * @set: if true, global wake-up IRQs are set, if false they are cleared + */ +void ve_spc_global_wakeup_irq(bool set) +{ + u32 reg; + + reg = readl_relaxed(info->baseaddr + WAKE_INT_MASK); + + if (set) + reg |= GBL_WAKEUP_INT_MSK; + else + reg &= ~GBL_WAKEUP_INT_MSK; + + writel_relaxed(reg, info->baseaddr + WAKE_INT_MASK); +} + +/** + * ve_spc_cpu_wakeup_irq() + * + * Function to set/clear per-CPU wake-up IRQs. Not protected by locking since + * it might be used in code paths where normal cacheable locks are not + * working. Locking must be provided by the caller to ensure atomicity. + * + * @cluster: mpidr[15:8] bitfield describing cluster affinity level + * @cpu: mpidr[7:0] bitfield describing cpu affinity level + * @set: if true, wake-up IRQs are set, if false they are cleared + */ +void ve_spc_cpu_wakeup_irq(u32 cluster, u32 cpu, bool set) +{ + u32 mask, reg; + + if (cluster >= MAX_CLUSTERS) + return; + + mask = 1 << cpu; + + if (!cluster_is_a15(cluster)) + mask <<= 4; + + reg = readl_relaxed(info->baseaddr + WAKE_INT_MASK); + + if (set) + reg |= mask; + else + reg &= ~mask; + + writel_relaxed(reg, info->baseaddr + WAKE_INT_MASK); +} + +/** + * ve_spc_set_resume_addr() - set the jump address used for warm boot + * + * @cluster: mpidr[15:8] bitfield describing cluster affinity level + * @cpu: mpidr[7:0] bitfield describing cpu affinity level + * @addr: physical resume address + */ +void ve_spc_set_resume_addr(u32 cluster, u32 cpu, u32 addr) +{ + void __iomem *baseaddr; + + if (cluster >= MAX_CLUSTERS) + return; + + if (cluster_is_a15(cluster)) + baseaddr = info->baseaddr + A15_BX_ADDR0 + (cpu << 2); + else + baseaddr = info->baseaddr + A7_BX_ADDR0 + (cpu << 2); + + writel_relaxed(addr, baseaddr); +} + +/** + * ve_spc_powerdown() + * + * Function to enable/disable cluster powerdown. Not protected by locking + * since it might be used in code paths where normal cacheable locks are not + * working. Locking must be provided by the caller to ensure atomicity. + * + * @cluster: mpidr[15:8] bitfield describing cluster affinity level + * @enable: if true enables powerdown, if false disables it + */ +void ve_spc_powerdown(u32 cluster, bool enable) +{ + u32 pwdrn_reg; + + if (cluster >= MAX_CLUSTERS) + return; + + pwdrn_reg = cluster_is_a15(cluster) ? A15_PWRDN_EN : A7_PWRDN_EN; + writel_relaxed(enable, info->baseaddr + pwdrn_reg); +} + +static u32 standbywfi_cpu_mask(u32 cpu, u32 cluster) +{ + return cluster_is_a15(cluster) ? + STANDBYWFI_STAT_A15_CPU_MASK(cpu) + : STANDBYWFI_STAT_A7_CPU_MASK(cpu); +} + +/** + * ve_spc_cpu_in_wfi(u32 cpu, u32 cluster) + * + * @cpu: mpidr[7:0] bitfield describing CPU affinity level within cluster + * @cluster: mpidr[15:8] bitfield describing cluster affinity level + * + * @return: non-zero if and only if the specified CPU is in WFI + * + * Take care when interpreting the result of this function: a CPU might + * be in WFI temporarily due to idle, and is not necessarily safely + * parked. + */ +int ve_spc_cpu_in_wfi(u32 cpu, u32 cluster) +{ + int ret; + u32 mask = standbywfi_cpu_mask(cpu, cluster); + + if (cluster >= MAX_CLUSTERS) + return 1; + + ret = readl_relaxed(info->baseaddr + STANDBYWFI_STAT); + + pr_debug("%s: PCFGREG[0x%X] = 0x%08X, mask = 0x%X\n", + __func__, STANDBYWFI_STAT, ret, mask); + + return ret & mask; +} + +static int ve_spc_get_performance(int cluster, u32 *freq) +{ + struct ve_spc_opp *opps = info->opps[cluster]; + u32 perf_cfg_reg = 0; + u32 perf; + + perf_cfg_reg = cluster_is_a15(cluster) ? PERF_LVL_A15 : PERF_LVL_A7; + + perf = readl_relaxed(info->baseaddr + perf_cfg_reg); + if (perf >= info->num_opps[cluster]) + return -EINVAL; + + opps += perf; + *freq = opps->freq; + + return 0; +} + +/* find closest match to given frequency in OPP table */ +static int ve_spc_round_performance(int cluster, u32 freq) +{ + int idx, max_opp = info->num_opps[cluster]; + struct ve_spc_opp *opps = info->opps[cluster]; + u32 fmin = 0, fmax = ~0, ftmp; + + freq /= 1000; /* OPP entries in kHz */ + for (idx = 0; idx < max_opp; idx++, opps++) { + ftmp = opps->freq; + if (ftmp >= freq) { + if (ftmp <= fmax) + fmax = ftmp; + } else { + if (ftmp >= fmin) + fmin = ftmp; + } + } + if (fmax != ~0) + return fmax * 1000; + else + return fmin * 1000; +} + +static int ve_spc_find_performance_index(int cluster, u32 freq) +{ + int idx, max_opp = info->num_opps[cluster]; + struct ve_spc_opp *opps = info->opps[cluster]; + + for (idx = 0; idx < max_opp; idx++, opps++) + if (opps->freq == freq) + break; + return (idx == max_opp) ? -EINVAL : idx; +} + +static int ve_spc_waitforcompletion(int req_type) +{ + int ret = wait_for_completion_interruptible_timeout( + &info->done, usecs_to_jiffies(TIMEOUT_US)); + if (ret == 0) + ret = -ETIMEDOUT; + else if (ret > 0) + ret = info->cur_rsp_stat & STAT_COMPLETE(req_type) ? 0 : -EIO; + return ret; +} + +static int ve_spc_set_performance(int cluster, u32 freq) +{ + u32 perf_cfg_reg, perf_stat_reg; + int ret, perf, req_type; + + if (cluster_is_a15(cluster)) { + req_type = CA15_DVFS; + perf_cfg_reg = PERF_LVL_A15; + perf_stat_reg = PERF_REQ_A15; + } else { + req_type = CA7_DVFS; + perf_cfg_reg = PERF_LVL_A7; + perf_stat_reg = PERF_REQ_A7; + } + + perf = ve_spc_find_performance_index(cluster, freq); + + if (perf < 0) + return perf; + + if (down_timeout(&info->sem, usecs_to_jiffies(TIMEOUT_US))) + return -ETIME; + + init_completion(&info->done); + info->cur_rsp_mask = RESPONSE_MASK(req_type); + + writel(perf, info->baseaddr + perf_cfg_reg); + ret = ve_spc_waitforcompletion(req_type); + + info->cur_rsp_mask = 0; + up(&info->sem); + + return ret; +} + +static int ve_spc_read_sys_cfg(int func, int offset, uint32_t *data) +{ + int ret; + + if (down_timeout(&info->sem, usecs_to_jiffies(TIMEOUT_US))) + return -ETIME; + + init_completion(&info->done); + info->cur_rsp_mask = RESPONSE_MASK(SPC_SYS_CFG); + + /* Set the control value */ + writel(SYSCFG_START | func | offset >> 2, info->baseaddr + COMMS); + ret = ve_spc_waitforcompletion(SPC_SYS_CFG); + + if (ret == 0) + *data = readl(info->baseaddr + SYSCFG_RDATA); + + info->cur_rsp_mask = 0; + up(&info->sem); + + return ret; +} + +static irqreturn_t ve_spc_irq_handler(int irq, void *data) +{ + struct ve_spc_drvdata *drv_data = data; + uint32_t status = readl_relaxed(drv_data->baseaddr + PWC_STATUS); + + if (info->cur_rsp_mask & status) { + info->cur_rsp_stat = status; + complete(&drv_data->done); + } + + return IRQ_HANDLED; +} + +/* + * +--------------------------+ + * | 31 20 | 19 0 | + * +--------------------------+ + * | m_volt | freq(kHz) | + * +--------------------------+ + */ +#define MULT_FACTOR 20 +#define VOLT_SHIFT 20 +#define FREQ_MASK (0xFFFFF) +static int ve_spc_populate_opps(uint32_t cluster) +{ + uint32_t data = 0, off, ret, idx; + struct ve_spc_opp *opps; + + opps = kzalloc(sizeof(*opps) * MAX_OPPS, GFP_KERNEL); + if (!opps) + return -ENOMEM; + + info->opps[cluster] = opps; + + off = cluster_is_a15(cluster) ? A15_PERFVAL_BASE : A7_PERFVAL_BASE; + for (idx = 0; idx < MAX_OPPS; idx++, off += 4, opps++) { + ret = ve_spc_read_sys_cfg(SYSCFG_SCC, off, &data); + if (!ret) { + opps->freq = (data & FREQ_MASK) * MULT_FACTOR; + opps->u_volt = (data >> VOLT_SHIFT) * 1000; + } else { + break; + } + } + info->num_opps[cluster] = idx; + + return ret; +} + +static int ve_init_opp_table(struct device *cpu_dev) +{ + int cluster = topology_physical_package_id(cpu_dev->id); + int idx, ret = 0, max_opp = info->num_opps[cluster]; + struct ve_spc_opp *opps = info->opps[cluster]; + + for (idx = 0; idx < max_opp; idx++, opps++) { + ret = dev_pm_opp_add(cpu_dev, opps->freq * 1000, opps->u_volt); + if (ret) { + dev_warn(cpu_dev, "failed to add opp %lu %lu\n", + opps->freq, opps->u_volt); + return ret; + } + } + return ret; +} + +int __init ve_spc_init(void __iomem *baseaddr, u32 a15_clusid, int irq) +{ + int ret; + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + pr_err(SPCLOG "unable to allocate mem\n"); + return -ENOMEM; + } + + info->baseaddr = baseaddr; + info->a15_clusid = a15_clusid; + + if (irq <= 0) { + pr_err(SPCLOG "Invalid IRQ %d\n", irq); + kfree(info); + return -EINVAL; + } + + init_completion(&info->done); + + readl_relaxed(info->baseaddr + PWC_STATUS); + + ret = request_irq(irq, ve_spc_irq_handler, IRQF_TRIGGER_HIGH + | IRQF_ONESHOT, "vexpress-spc", info); + if (ret) { + pr_err(SPCLOG "IRQ %d request failed\n", irq); + kfree(info); + return -ENODEV; + } + + sema_init(&info->sem, 1); + /* + * Multi-cluster systems may need this data when non-coherent, during + * cluster power-up/power-down. Make sure driver info reaches main + * memory. + */ + sync_cache_w(info); + sync_cache_w(&info); + + return 0; +} + +struct clk_spc { + struct clk_hw hw; + int cluster; +}; + +#define to_clk_spc(spc) container_of(spc, struct clk_spc, hw) +static unsigned long spc_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_spc *spc = to_clk_spc(hw); + u32 freq; + + if (ve_spc_get_performance(spc->cluster, &freq)) + return -EIO; + + return freq * 1000; +} + +static long spc_round_rate(struct clk_hw *hw, unsigned long drate, + unsigned long *parent_rate) +{ + struct clk_spc *spc = to_clk_spc(hw); + + return ve_spc_round_performance(spc->cluster, drate); +} + +static int spc_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_spc *spc = to_clk_spc(hw); + + return ve_spc_set_performance(spc->cluster, rate / 1000); +} + +static struct clk_ops clk_spc_ops = { + .recalc_rate = spc_recalc_rate, + .round_rate = spc_round_rate, + .set_rate = spc_set_rate, +}; + +static struct clk *ve_spc_clk_register(struct device *cpu_dev) +{ + struct clk_init_data init; + struct clk_spc *spc; + + spc = kzalloc(sizeof(*spc), GFP_KERNEL); + if (!spc) { + pr_err("could not allocate spc clk\n"); + return ERR_PTR(-ENOMEM); + } + + spc->hw.init = &init; + spc->cluster = topology_physical_package_id(cpu_dev->id); + + init.name = dev_name(cpu_dev); + init.ops = &clk_spc_ops; + init.flags = CLK_IS_ROOT | CLK_GET_RATE_NOCACHE; + init.num_parents = 0; + + return devm_clk_register(cpu_dev, &spc->hw); +} + +static int __init ve_spc_clk_init(void) +{ + int cpu; + struct clk *clk; + + if (!info) + return 0; /* Continue only if SPC is initialised */ + + if (ve_spc_populate_opps(0) || ve_spc_populate_opps(1)) { + pr_err("failed to build OPP table\n"); + return -ENODEV; + } + + for_each_possible_cpu(cpu) { + struct device *cpu_dev = get_cpu_device(cpu); + if (!cpu_dev) { + pr_warn("failed to get cpu%d device\n", cpu); + continue; + } + clk = ve_spc_clk_register(cpu_dev); + if (IS_ERR(clk)) { + pr_warn("failed to register cpu%d clock\n", cpu); + continue; + } + if (clk_register_clkdev(clk, NULL, dev_name(cpu_dev))) { + pr_warn("failed to register cpu%d clock lookup\n", cpu); + continue; + } + + if (ve_init_opp_table(cpu_dev)) + pr_warn("failed to initialise cpu%d opp table\n", cpu); + } + + platform_device_register_simple("vexpress-spc-cpufreq", -1, NULL, 0); + return 0; +} +module_init(ve_spc_clk_init); diff --git a/arch/arm/mach-vexpress/spc.h b/arch/arm/mach-vexpress/spc.h new file mode 100644 index 00000000000..793d065243b --- /dev/null +++ b/arch/arm/mach-vexpress/spc.h @@ -0,0 +1,25 @@ +/* + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Copyright (C) 2012 ARM Limited + */ + + +#ifndef __SPC_H_ +#define __SPC_H_ + +int __init ve_spc_init(void __iomem *base, u32 a15_clusid, int irq); +void ve_spc_global_wakeup_irq(bool set); +void ve_spc_cpu_wakeup_irq(u32 cluster, u32 cpu, bool set); +void ve_spc_set_resume_addr(u32 cluster, u32 cpu, u32 addr); +void ve_spc_powerdown(u32 cluster, bool enable); +int ve_spc_cpu_in_wfi(u32 cpu, u32 cluster); + +#endif diff --git a/arch/arm/mach-vexpress/tc2_pm.c b/arch/arm/mach-vexpress/tc2_pm.c new file mode 100644 index 00000000000..b743a0ae02c --- /dev/null +++ b/arch/arm/mach-vexpress/tc2_pm.c @@ -0,0 +1,378 @@ +/* + * arch/arm/mach-vexpress/tc2_pm.c - TC2 power management support + * + * Created by: Nicolas Pitre, October 2012 + * Copyright: (C) 2012-2013 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/delay.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/of_address.h> +#include <linux/of_irq.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 <linux/arm-cci.h> + +#include "spc.h" + +/* SCC conf registers */ +#define RESET_CTRL 0x018 +#define RESET_A15_NCORERESET(cpu) (1 << (2 + (cpu))) +#define RESET_A7_NCORERESET(cpu) (1 << (16 + (cpu))) + +#define A15_CONF 0x400 +#define A7_CONF 0x500 +#define SYS_INFO 0x700 +#define SPC_BASE 0xb00 + +static void __iomem *scc; + +/* + * 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; + +#define TC2_CLUSTERS 2 +#define TC2_MAX_CPUS_PER_CLUSTER 3 + +static unsigned int tc2_nr_cpus[TC2_CLUSTERS]; + +/* Keep per-cpu usage count to cope with unordered up/down requests */ +static int tc2_pm_use_count[TC2_MAX_CPUS_PER_CLUSTER][TC2_CLUSTERS]; + +#define tc2_cluster_unused(cluster) \ + (!tc2_pm_use_count[0][cluster] && \ + !tc2_pm_use_count[1][cluster] && \ + !tc2_pm_use_count[2][cluster]) + +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_CLUSTERS || cpu >= tc2_nr_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_cluster_unused(cluster)) + ve_spc_powerdown(cluster, false); + + tc2_pm_use_count[cpu][cluster]++; + if (tc2_pm_use_count[cpu][cluster] == 1) { + ve_spc_set_resume_addr(cluster, cpu, + virt_to_phys(mcpm_entry_point)); + ve_spc_cpu_wakeup_irq(cluster, cpu, true); + } 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_CLUSTERS || cpu >= TC2_MAX_CPUS_PER_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) { + ve_spc_cpu_wakeup_irq(cluster, cpu, true); + if (tc2_cluster_unused(cluster)) { + ve_spc_powerdown(cluster, true); + ve_spc_global_wakeup_irq(true); + 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) { + ve_spc_powerdown(cluster, false); + ve_spc_global_wakeup_irq(false); + } + + 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 int tc2_core_in_reset(unsigned int cpu, unsigned int cluster) +{ + u32 mask = cluster ? + RESET_A7_NCORERESET(cpu) + : RESET_A15_NCORERESET(cpu); + + return !(readl_relaxed(scc + RESET_CTRL) & mask); +} + +#define POLL_MSEC 10 +#define TIMEOUT_MSEC 1000 + +static int tc2_pm_wait_for_powerdown(unsigned int cpu, unsigned int cluster) +{ + unsigned tries; + + pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); + BUG_ON(cluster >= TC2_CLUSTERS || cpu >= TC2_MAX_CPUS_PER_CLUSTER); + + for (tries = 0; tries < TIMEOUT_MSEC / POLL_MSEC; ++tries) { + /* + * Only examine the hardware state if the target CPU has + * caught up at least as far as tc2_pm_down(): + */ + if (ACCESS_ONCE(tc2_pm_use_count[cpu][cluster]) == 0) { + pr_debug("%s(cpu=%u, cluster=%u): RESET_CTRL = 0x%08X\n", + __func__, cpu, cluster, + readl_relaxed(scc + RESET_CTRL)); + + /* + * We need the CPU to reach WFI, but the power + * controller may put the cluster in reset and + * power it off as soon as that happens, before + * we have a chance to see STANDBYWFI. + * + * So we need to check for both conditions: + */ + if (tc2_core_in_reset(cpu, cluster) || + ve_spc_cpu_in_wfi(cpu, cluster)) + return 0; /* success: the CPU is halted */ + } + + /* Otherwise, wait and retry: */ + msleep(POLL_MSEC); + } + + return -ETIMEDOUT; /* timeout */ +} + +static void tc2_pm_suspend(u64 residency) +{ + unsigned int mpidr, cpu, cluster; + + mpidr = read_cpuid_mpidr(); + cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + ve_spc_set_resume_addr(cluster, cpu, virt_to_phys(mcpm_entry_point)); + 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_CLUSTERS || cpu >= TC2_MAX_CPUS_PER_CLUSTER); + + local_irq_save(flags); + arch_spin_lock(&tc2_pm_lock); + + if (tc2_cluster_unused(cluster)) { + ve_spc_powerdown(cluster, false); + ve_spc_global_wakeup_irq(false); + } + + if (!tc2_pm_use_count[cpu][cluster]) + tc2_pm_use_count[cpu][cluster] = 1; + + ve_spc_cpu_wakeup_irq(cluster, cpu, false); + ve_spc_set_resume_addr(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, + .wait_for_powerdown = tc2_pm_wait_for_powerdown, + .suspend = tc2_pm_suspend, + .powered_up = tc2_pm_powered_up, +}; + +static bool __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); + if (cluster >= TC2_CLUSTERS || cpu >= tc2_nr_cpus[cluster]) { + pr_err("%s: boot CPU is out of bound!\n", __func__); + return false; + } + tc2_pm_use_count[cpu][cluster] = 1; + return true; +} + +/* + * Enable cluster-level coherency, in preparation for turning on the MMU. + */ +static void __naked tc2_pm_power_up_setup(unsigned int affinity_level) +{ + asm volatile (" \n" +" cmp r0, #1 \n" +" bxne lr \n" +" b cci_enable_port_for_self "); +} + +static int __init tc2_pm_init(void) +{ + int ret, irq; + u32 a15_cluster_id, a7_cluster_id, sys_info; + struct device_node *np; + + /* + * The power management-related features are hidden behind + * SCC registers. We need to extract runtime information like + * cluster ids and number of CPUs really available in clusters. + */ + np = of_find_compatible_node(NULL, NULL, + "arm,vexpress-scc,v2p-ca15_a7"); + scc = of_iomap(np, 0); + if (!scc) + return -ENODEV; + + a15_cluster_id = readl_relaxed(scc + A15_CONF) & 0xf; + a7_cluster_id = readl_relaxed(scc + A7_CONF) & 0xf; + if (a15_cluster_id >= TC2_CLUSTERS || a7_cluster_id >= TC2_CLUSTERS) + return -EINVAL; + + sys_info = readl_relaxed(scc + SYS_INFO); + tc2_nr_cpus[a15_cluster_id] = (sys_info >> 16) & 0xf; + tc2_nr_cpus[a7_cluster_id] = (sys_info >> 20) & 0xf; + + irq = irq_of_parse_and_map(np, 0); + + /* + * A subset of the SCC registers is also used to communicate + * with the SPC (power controller). We need to be able to + * drive it very early in the boot process to power up + * processors, so we initialize the SPC driver here. + */ + ret = ve_spc_init(scc + SPC_BASE, a15_cluster_id, irq); + if (ret) + return ret; + + if (!cci_probed()) + return -ENODEV; + + if (!tc2_pm_usage_count_init()) + return -EINVAL; + + ret = mcpm_platform_register(&tc2_pm_power_ops); + if (!ret) { + mcpm_sync_init(tc2_pm_power_up_setup); + pr_info("TC2 power management initialized\n"); + } + return ret; +} + +early_initcall(tc2_pm_init); diff --git a/arch/arm/mach-vexpress/v2m.c b/arch/arm/mach-vexpress/v2m.c new file mode 100644 index 00000000000..6ff681a24ba --- /dev/null +++ b/arch/arm/mach-vexpress/v2m.c @@ -0,0 +1,390 @@ +/* + * Versatile Express V2M Motherboard Support + */ +#include <linux/device.h> +#include <linux/amba/bus.h> +#include <linux/amba/mmci.h> +#include <linux/io.h> +#include <linux/smp.h> +#include <linux/init.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/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, + .status = vexpress_get_mci_cardin, + .gpio_cd = -1, + .gpio_wp = -1, +}; + +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_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); + + vexpress_syscfg_device_register(&v2m_muxfpga_device); + vexpress_syscfg_device_register(&v2m_shutdown_device); + vexpress_syscfg_device_register(&v2m_reboot_device); + vexpress_syscfg_device_register(&v2m_dvimode_device); + + 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_init(void) +{ + of_platform_populate(NULL, of_default_bus_match_table, 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, + .l2c_aux_val = 0x00400000, + .l2c_aux_mask = 0xfe0fffff, + .smp = smp_ops(vexpress_smp_dt_ops), + .smp_init = smp_init_ops(vexpress_smp_init_ops), + .init_machine = v2m_dt_init, +MACHINE_END |
