diff options
Diffstat (limited to 'arch/arm/mach-vexpress')
| -rw-r--r-- | arch/arm/mach-vexpress/Kconfig | 103 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/Makefile | 10 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/Makefile.boot | 9 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/core.h | 7 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/ct-ca9x4.c | 103 | ||||
| -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 | 20 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/include/mach/clkdev.h | 15 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/include/mach/debug-macro.S | 43 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/include/mach/gpio.h | 1 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/include/mach/irqs.h | 2 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/include/mach/motherboard.h | 59 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/include/mach/timex.h | 23 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/include/mach/uncompress.h | 72 | ||||
| -rw-r--r-- | arch/arm/mach-vexpress/platsmp.c | 221 | ||||
| -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 | 479 |
20 files changed, 1542 insertions, 887 deletions
diff --git a/arch/arm/mach-vexpress/Kconfig b/arch/arm/mach-vexpress/Kconfig index cf8730d35e7..d8b9330f896 100644 --- a/arch/arm/mach-vexpress/Kconfig +++ b/arch/arm/mach-vexpress/Kconfig @@ -1,39 +1,27 @@ -menu "Versatile Express platform type" - depends on ARCH_VEXPRESS - -config ARCH_VEXPRESS_CORTEX_A5_A9_ERRATA - bool - select ARM_ERRATA_720789 - select ARM_ERRATA_751472 - 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 ARCH_VEXPRESS_CORTEX_A5_A9_ERRATA - select ARM_GIC - select CPU_V7 - select HAVE_SMP - select MIGHT_HAVE_CACHE_L2X0 - -config ARCH_VEXPRESS_DT - bool "Device Tree support for Versatile Express platforms" - select ARCH_VEXPRESS_CORTEX_A5_A9_ERRATA +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_PATCH_PHYS_VIRT - select AUTO_ZRELADDR - select CPU_V7 - select HAVE_SMP - select MIGHT_HAVE_CACHE_L2X0 - select USE_OF + 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 - New Versatile Express platforms require Flattened Device Tree to - be passed to the kernel. - This option enables support for systems using Cortex processor based ARM core and logic (FPGA) tiles on the Versatile Express motherboard, for example: @@ -49,7 +37,48 @@ config ARCH_VEXPRESS_DT platforms. The traditional (ATAGs) boot method is not usable on these boards with this option. - If your bootloader supports Flattened Device Tree based booting, - say Y here. +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. -endmenu +endif diff --git a/arch/arm/mach-vexpress/Makefile b/arch/arm/mach-vexpress/Makefile index 90551b9780a..fc649bc09d0 100644 --- a/arch/arm/mach-vexpress/Makefile +++ b/arch/arm/mach-vexpress/Makefile @@ -1,8 +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/Makefile.boot b/arch/arm/mach-vexpress/Makefile.boot deleted file mode 100644 index 909f85ebf5f..00000000000 --- a/arch/arm/mach-vexpress/Makefile.boot +++ /dev/null @@ -1,9 +0,0 @@ -# Those numbers are used only by the non-DT V2P-CA9 platform -# The DT-enabled ones require CONFIG_AUTO_ZRELADDR=y - zreladdr-y += 0x60008000 -params_phys-y := 0x60000100 -initrd_phys-y := 0x60800000 - -dtb-$(CONFIG_ARCH_VEXPRESS_DT) += vexpress-v2p-ca5s.dtb \ - vexpress-v2p-ca9.dtb \ - vexpress-v2p-ca15-tc1.dtb diff --git a/arch/arm/mach-vexpress/core.h b/arch/arm/mach-vexpress/core.h index a3a4980770b..152fad91b3a 100644 --- a/arch/arm/mach-vexpress/core.h +++ b/arch/arm/mach-vexpress/core.h @@ -4,4 +4,9 @@ /* 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 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 index c65cc3b462a..86150d7a2e7 100644 --- a/arch/arm/mach-vexpress/ct-ca9x4.c +++ b/arch/arm/mach-vexpress/ct-ca9x4.c @@ -9,11 +9,11 @@ #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/hardware/gic.h> -#include <asm/pmu.h> #include <asm/smp_scu.h> #include <asm/smp_twd.h> @@ -27,6 +27,7 @@ #include "core.h" #include <mach/motherboard.h> +#include <mach/irqs.h> #include <plat/clcd.h> @@ -44,6 +45,23 @@ 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); @@ -62,12 +80,7 @@ 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 void ct_ca9x4_clcd_enable(struct clcd_fb *fb) -{ - v2m_cfg_write(SYS_CFG_MUXFPGA | SYS_CFG_SITE_DB1, 0); - v2m_cfg_write(SYS_CFG_DVIMODE | SYS_CFG_SITE_DB1, 2); + ca9x4_l2_init(); } static int ct_ca9x4_clcd_setup(struct clcd_fb *fb) @@ -86,7 +99,6 @@ static struct clcd_board ct_ca9x4_clcd_data = { .caps = CLCD_CAP_5551 | CLCD_CAP_565, .check = clcdfb_check, .decode = clcdfb_decode, - .enable = ct_ca9x4_clcd_enable, .setup = ct_ca9x4_clcd_setup, .mmap = versatile_clcd_mmap_dma, .remove = versatile_clcd_remove_dma, @@ -104,46 +116,6 @@ static struct amba_device *ct_ca9x4_amba_devs[] __initdata = { &gpio_device, }; - -static long ct_round(struct clk *clk, unsigned long rate) -{ - return rate; -} - -static int ct_set(struct clk *clk, unsigned long rate) -{ - return v2m_cfg_write(SYS_CFG_OSC | SYS_CFG_SITE_DB1 | 1, rate); -} - -static const struct clk_ops osc1_clk_ops = { - .round = ct_round, - .set = ct_set, -}; - -static struct clk osc1_clk = { - .ops = &osc1_clk_ops, - .rate = 24000000, -}; - -static struct clk ct_sp804_clk = { - .rate = 1000000, -}; - -static struct clk_lookup lookups[] = { - { /* CLCD */ - .dev_id = "ct:clcd", - .clk = &osc1_clk, - }, { /* SP804 timers */ - .dev_id = "sp804", - .con_id = "ct-timer0", - .clk = &ct_sp804_clk, - }, { /* SP804 timers */ - .dev_id = "sp804", - .con_id = "ct-timer1", - .clk = &ct_sp804_clk, - }, -}; - static struct resource pmu_resources[] = { [0] = { .start = IRQ_CT_CA9X4_PMU_CPU0, @@ -169,34 +141,34 @@ static struct resource pmu_resources[] = { static struct platform_device pmu_device = { .name = "arm-pmu", - .id = ARM_PMU_DEVICE_CPU, + .id = -1, .num_resources = ARRAY_SIZE(pmu_resources), .resource = pmu_resources, }; -static void __init ct_ca9x4_init_early(void) -{ - clkdev_add_table(lookups, ARRAY_SIZE(lookups)); -} +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; -#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); + vexpress_syscfg_device_register(&osc1_device); } #ifdef CONFIG_SMP @@ -220,8 +192,6 @@ static void __init ct_ca9x4_init_cpu_map(void) for (i = 0; i < ncores; ++i) set_cpu_possible(i, true); - - set_smp_cross_call(gic_raise_softirq); } static void __init ct_ca9x4_smp_enable(unsigned int max_cpus) @@ -234,7 +204,6 @@ struct ct_desc ct_ca9x4_desc __initdata = { .id = V2M_CT_ID_CA9, .name = "CA9x4", .map_io = ct_ca9x4_map_io, - .init_early = ct_ca9x4_init_early, .init_irq = ct_ca9x4_init_irq, .init_tile = ct_ca9x4_init, #ifdef CONFIG_SMP 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 index c504a72b94d..f0ce6b8f5e7 100644 --- a/arch/arm/mach-vexpress/hotplug.c +++ b/arch/arm/mach-vexpress/hotplug.c @@ -12,17 +12,13 @@ #include <linux/errno.h> #include <linux/smp.h> -#include <asm/cacheflush.h> #include <asm/smp_plat.h> #include <asm/cp15.h> -extern volatile int pen_release; - static inline void cpu_enter_lowpower(void) { unsigned int v; - flush_cache_all(); asm volatile( "mcr p15, 0, %1, c7, c5, 0\n" " mcr p15, 0, %1, c7, c10, 4\n" @@ -84,17 +80,12 @@ static inline void platform_do_lowpower(unsigned int cpu, int *spurious) } } -int platform_cpu_kill(unsigned int cpu) -{ - return 1; -} - /* * platform-specific code to shutdown a CPU * * Called with IRQs disabled */ -void platform_cpu_die(unsigned int cpu) +void __ref vexpress_cpu_die(unsigned int cpu) { int spurious = 0; @@ -113,12 +104,3 @@ void platform_cpu_die(unsigned int cpu) if (spurious) pr_warn("CPU%u: %u spurious wakeup calls\n", cpu, spurious); } - -int platform_cpu_disable(unsigned int cpu) -{ - /* - * we don't allow CPU 0 to be shutdown (it is still too special - * e.g. clock tick interrupts) - */ - return cpu == 0 ? -EPERM : 0; -} diff --git a/arch/arm/mach-vexpress/include/mach/clkdev.h b/arch/arm/mach-vexpress/include/mach/clkdev.h deleted file mode 100644 index 3f8307d73ca..00000000000 --- a/arch/arm/mach-vexpress/include/mach/clkdev.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef __ASM_MACH_CLKDEV_H -#define __ASM_MACH_CLKDEV_H - -#include <plat/clock.h> - -struct clk { - const struct clk_ops *ops; - unsigned long rate; - const struct icst_params *params; -}; - -#define __clk_get(clk) ({ 1; }) -#define __clk_put(clk) do { } while (0) - -#endif diff --git a/arch/arm/mach-vexpress/include/mach/debug-macro.S b/arch/arm/mach-vexpress/include/mach/debug-macro.S deleted file mode 100644 index fa8224794e0..00000000000 --- a/arch/arm/mach-vexpress/include/mach/debug-macro.S +++ /dev/null @@ -1,43 +0,0 @@ -/* arch/arm/mach-realview/include/mach/debug-macro.S - * - * Debugging macro include header - * - * Copyright (C) 1994-1999 Russell King - * Moved from linux/arch/arm/kernel/debug.S by Ben Dooks - * - * 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. - */ - -#define DEBUG_LL_PHYS_BASE 0x10000000 -#define DEBUG_LL_UART_OFFSET 0x00009000 - -#define DEBUG_LL_PHYS_BASE_RS1 0x1c000000 -#define DEBUG_LL_UART_OFFSET_RS1 0x00090000 - -#define DEBUG_LL_VIRT_BASE 0xf8000000 - - .macro addruart,rp,rv,tmp - - @ Make an educated guess regarding the memory map: - @ - the original A9 core tile, which has MPCore peripherals - @ located at 0x1e000000, should use UART at 0x10009000 - @ - all other (RS1 complaint) tiles use UART mapped - @ at 0x1c090000 - mrc p15, 4, \tmp, c15, c0, 0 - cmp \tmp, #0x1e000000 - - @ Original memory map - moveq \rp, #DEBUG_LL_UART_OFFSET - orreq \rv, \rp, #DEBUG_LL_VIRT_BASE - orreq \rp, \rp, #DEBUG_LL_PHYS_BASE - - @ RS1 memory map - movne \rp, #DEBUG_LL_UART_OFFSET_RS1 - orrne \rv, \rp, #DEBUG_LL_VIRT_BASE - orrne \rp, \rp, #DEBUG_LL_PHYS_BASE_RS1 - - .endm - -#include <asm/hardware/debug-pl01x.S> diff --git a/arch/arm/mach-vexpress/include/mach/gpio.h b/arch/arm/mach-vexpress/include/mach/gpio.h deleted file mode 100644 index 40a8c178f10..00000000000 --- a/arch/arm/mach-vexpress/include/mach/gpio.h +++ /dev/null @@ -1 +0,0 @@ -/* empty */ diff --git a/arch/arm/mach-vexpress/include/mach/irqs.h b/arch/arm/mach-vexpress/include/mach/irqs.h index 4b10ee7657a..f8f7f782eb5 100644 --- a/arch/arm/mach-vexpress/include/mach/irqs.h +++ b/arch/arm/mach-vexpress/include/mach/irqs.h @@ -1,4 +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 index 31a92890893..68abc8b7278 100644 --- a/arch/arm/mach-vexpress/include/mach/motherboard.h +++ b/arch/arm/mach-vexpress/include/mach/motherboard.h @@ -39,31 +39,6 @@ #define V2M_CF (V2M_PA_CS7 + 0x0001a000) #define V2M_CLCD (V2M_PA_CS7 + 0x0001f000) -/* - * Offsets from SYSREGS base - */ -#define V2M_SYS_ID 0x000 -#define V2M_SYS_SW 0x004 -#define V2M_SYS_LED 0x008 -#define V2M_SYS_100HZ 0x024 -#define V2M_SYS_FLAGS 0x030 -#define V2M_SYS_FLAGSSET 0x030 -#define V2M_SYS_FLAGSCLR 0x034 -#define V2M_SYS_NVFLAGS 0x038 -#define V2M_SYS_NVFLAGSSET 0x038 -#define V2M_SYS_NVFLAGSCLR 0x03c -#define V2M_SYS_MCI 0x048 -#define V2M_SYS_FLASH 0x03c -#define V2M_SYS_CFGSW 0x058 -#define V2M_SYS_24MHZ 0x05c -#define V2M_SYS_MISC 0x060 -#define V2M_SYS_DMA 0x064 -#define V2M_SYS_PROCID0 0x084 -#define V2M_SYS_PROCID1 0x088 -#define V2M_SYS_CFGDATA 0x0a0 -#define V2M_SYS_CFGCTRL 0x0a4 -#define V2M_SYS_CFGSTAT 0x0a8 - /* * Interrupts. Those in {} are for AMBA devices @@ -89,40 +64,6 @@ /* - * Configuration - */ -#define SYS_CFG_START (1 << 31) -#define SYS_CFG_WRITE (1 << 30) -#define SYS_CFG_OSC (1 << 20) -#define SYS_CFG_VOLT (2 << 20) -#define SYS_CFG_AMP (3 << 20) -#define SYS_CFG_TEMP (4 << 20) -#define SYS_CFG_RESET (5 << 20) -#define SYS_CFG_SCC (6 << 20) -#define SYS_CFG_MUXFPGA (7 << 20) -#define SYS_CFG_SHUTDOWN (8 << 20) -#define SYS_CFG_REBOOT (9 << 20) -#define SYS_CFG_DVIMODE (11 << 20) -#define SYS_CFG_POWER (12 << 20) -#define SYS_CFG_SITE_MB (0 << 16) -#define SYS_CFG_SITE_DB1 (1 << 16) -#define SYS_CFG_SITE_DB2 (2 << 16) -#define SYS_CFG_STACK(n) ((n) << 12) - -#define SYS_CFG_ERR (1 << 1) -#define SYS_CFG_COMPLETE (1 << 0) - -int v2m_cfg_write(u32 devfn, u32 data); -int v2m_cfg_read(u32 devfn, u32 *data); -void v2m_flags_set(u32 data); - -/* - * Miscellaneous - */ -#define SYS_MISC_MASTERSITE (1 << 14) -#define SYS_PROCIDx_HBI_MASK 0xfff - -/* * Core tile IDs */ #define V2M_CT_ID_CA9 0x0c000191 diff --git a/arch/arm/mach-vexpress/include/mach/timex.h b/arch/arm/mach-vexpress/include/mach/timex.h deleted file mode 100644 index 00029bacd43..00000000000 --- a/arch/arm/mach-vexpress/include/mach/timex.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * arch/arm/mach-vexpress/include/mach/timex.h - * - * RealView architecture timex specifications - * - * Copyright (C) 2003 ARM Limited - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#define CLOCK_TICK_RATE (50000000 / 16) diff --git a/arch/arm/mach-vexpress/include/mach/uncompress.h b/arch/arm/mach-vexpress/include/mach/uncompress.h deleted file mode 100644 index 7dab5596b86..00000000000 --- a/arch/arm/mach-vexpress/include/mach/uncompress.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * arch/arm/mach-vexpress/include/mach/uncompress.h - * - * Copyright (C) 2003 ARM Limited - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#define AMBA_UART_DR(base) (*(volatile unsigned char *)((base) + 0x00)) -#define AMBA_UART_LCRH(base) (*(volatile unsigned char *)((base) + 0x2c)) -#define AMBA_UART_CR(base) (*(volatile unsigned char *)((base) + 0x30)) -#define AMBA_UART_FR(base) (*(volatile unsigned char *)((base) + 0x18)) - -#define UART_BASE 0x10009000 -#define UART_BASE_RS1 0x1c090000 - -static unsigned long get_uart_base(void) -{ - unsigned long mpcore_periph; - - /* - * Make an educated guess regarding the memory map: - * - the original A9 core tile, which has MPCore peripherals - * located at 0x1e000000, should use UART at 0x10009000 - * - all other (RS1 complaint) tiles use UART mapped - * at 0x1c090000 - */ - asm("mrc p15, 4, %0, c15, c0, 0" : "=r" (mpcore_periph)); - - if (mpcore_periph == 0x1e000000) - return UART_BASE; - else - return UART_BASE_RS1; -} - -/* - * This does not append a newline - */ -static inline void putc(int c) -{ - unsigned long base = get_uart_base(); - - while (AMBA_UART_FR(base) & (1 << 5)) - barrier(); - - AMBA_UART_DR(base) = c; -} - -static inline void flush(void) -{ - unsigned long base = get_uart_base(); - - while (AMBA_UART_FR(base) & (1 << 3)) - barrier(); -} - -/* - * nothing to do - */ -#define arch_decomp_setup() -#define arch_decomp_wdog() diff --git a/arch/arm/mach-vexpress/platsmp.c b/arch/arm/mach-vexpress/platsmp.c index 14ba1128ae8..a1f3804fd5a 100644 --- a/arch/arm/mach-vexpress/platsmp.c +++ b/arch/arm/mach-vexpress/platsmp.c @@ -12,180 +12,88 @@ #include <linux/errno.h> #include <linux/smp.h> #include <linux/io.h> -#include <linux/of_fdt.h> +#include <linux/of_address.h> +#include <linux/vexpress.h> +#include <asm/mcpm.h> #include <asm/smp_scu.h> -#include <asm/hardware/gic.h> #include <asm/mach/map.h> #include <mach/motherboard.h> -#include "core.h" - -extern void versatile_secondary_startup(void); - -#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 -}; +#include <plat/platsmp.h> -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; - __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; -} +#include "core.h" -void __init vexpress_dt_smp_map_io(void) +/* + * 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 (initial_boot_params) - WARN_ON(of_scan_flat_dt(vexpress_dt_find_scu, NULL)); + ct_desc->init_cpu_map(); } -static int __init vexpress_dt_cpus_num(unsigned long node, const char *uname, - int depth, void *data) +static void __init vexpress_smp_prepare_cpus(unsigned int max_cpus) { - 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; + /* + * Initialise the present map, which describes the set of CPUs + * actually populated at the present time. + */ + ct_desc->smp_enable(max_cpus); - return 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)); } -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); - - set_smp_cross_call(gic_raise_softirq); -} +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 +}; -static void __init vexpress_dt_smp_prepare_cpus(unsigned int max_cpus) +bool __init vexpress_smp_init_ops(void) { - 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; +#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; } -#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); -} +#if defined(CONFIG_OF) -#endif +static const struct of_device_id vexpress_smp_dt_scu_match[] __initconst = { + { .compatible = "arm,cortex-a5-scu", }, + { .compatible = "arm,cortex-a9-scu", }, + {} +}; -/* - * Initialise the CPU possible map early - this describes the CPUs - * which may be present or become present in the system. - */ -void __init smp_init_cpus(void) +static void __init vexpress_smp_dt_prepare_cpus(unsigned int max_cpus) { - if (ct_desc) - ct_desc->init_cpu_map(); - else - vexpress_dt_smp_init_cpus(); + struct device_node *scu = of_find_matching_node(NULL, + vexpress_smp_dt_scu_match); -} - -void __init platform_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); + if (scu) + scu_enable(of_iomap(scu, 0)); /* * Write the address of secondary startup into the @@ -193,5 +101,16 @@ void __init platform_smp_prepare_cpus(unsigned int max_cpus) * until it receives a soft interrupt, and then the * secondary CPU branches to this address. */ - v2m_flags_set(virt_to_phys(versatile_secondary_startup)); + 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 index fde26adaef3..6ff681a24ba 100644 --- a/arch/arm/mach-vexpress/v2m.c +++ b/arch/arm/mach-vexpress/v2m.c @@ -5,6 +5,7 @@ #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> @@ -15,26 +16,26 @@ #include <linux/smsc911x.h> #include <linux/spinlock.h> #include <linux/usb/isp1760.h> -#include <linux/clkdev.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/arch_timer.h> #include <asm/mach-types.h> #include <asm/sizes.h> -#include <asm/smp_twd.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/gic.h> #include <asm/hardware/timer-sp.h> -#include <asm/hardware/sp810.h> #include <mach/ct-ca9x4.h> #include <mach/motherboard.h> #include <plat/sched_clock.h> +#include <plat/platsmp.h> #include "core.h" @@ -53,100 +54,15 @@ static struct map_desc v2m_io_desc[] __initdata = { }, }; -static void __iomem *v2m_sysreg_base; - -static void __init v2m_sysctl_init(void __iomem *base) -{ - u32 scctrl; - - if (WARN_ON(!base)) - return; - - /* Select 1MHz TIMCLK as the reference clock for SP804 timers */ - scctrl = readl(base + SCCTRL); - scctrl |= SCCTRL_TIMEREN0SEL_TIMCLK; - scctrl |= SCCTRL_TIMEREN1SEL_TIMCLK; - writel(scctrl, base + SCCTRL); -} - static void __init v2m_sp804_init(void __iomem *base, unsigned int irq) { if (WARN_ON(!base || irq == NO_IRQ)) return; - writel(0, base + TIMER_1_BASE + TIMER_CTRL); - writel(0, base + TIMER_2_BASE + TIMER_CTRL); - sp804_clocksource_init(base + TIMER_2_BASE, "v2m-timer1"); sp804_clockevents_init(base + TIMER_1_BASE, irq, "v2m-timer0"); } -static void __init v2m_timer_init(void) -{ - v2m_sysctl_init(ioremap(V2M_SYSCTL, SZ_4K)); - v2m_sp804_init(ioremap(V2M_TIMER01, SZ_4K), IRQ_V2M_TIMER0); -} - -static struct sys_timer v2m_timer = { - .init = v2m_timer_init, -}; - - -static DEFINE_SPINLOCK(v2m_cfg_lock); - -int v2m_cfg_write(u32 devfn, u32 data) -{ - /* Configuration interface broken? */ - u32 val; - - printk("%s: writing %08x to %08x\n", __func__, data, devfn); - - devfn |= SYS_CFG_START | SYS_CFG_WRITE; - - spin_lock(&v2m_cfg_lock); - val = readl(v2m_sysreg_base + V2M_SYS_CFGSTAT); - writel(val & ~SYS_CFG_COMPLETE, v2m_sysreg_base + V2M_SYS_CFGSTAT); - - writel(data, v2m_sysreg_base + V2M_SYS_CFGDATA); - writel(devfn, v2m_sysreg_base + V2M_SYS_CFGCTRL); - - do { - val = readl(v2m_sysreg_base + V2M_SYS_CFGSTAT); - } while (val == 0); - spin_unlock(&v2m_cfg_lock); - - return !!(val & SYS_CFG_ERR); -} - -int v2m_cfg_read(u32 devfn, u32 *data) -{ - u32 val; - - devfn |= SYS_CFG_START; - - spin_lock(&v2m_cfg_lock); - writel(0, v2m_sysreg_base + V2M_SYS_CFGSTAT); - writel(devfn, v2m_sysreg_base + V2M_SYS_CFGCTRL); - - mb(); - - do { - cpu_relax(); - val = readl(v2m_sysreg_base + V2M_SYS_CFGSTAT); - } while (val == 0); - - *data = readl(v2m_sysreg_base + V2M_SYS_CFGDATA); - spin_unlock(&v2m_cfg_lock); - - return !!(val & SYS_CFG_ERR); -} - -void __init v2m_flags_set(u32 data) -{ - writel(~0, v2m_sysreg_base + V2M_SYS_FLAGSCLR); - writel(data, v2m_sysreg_base + V2M_SYS_FLAGSSET); -} - static struct resource v2m_pcie_i2c_resource = { .start = V2M_SERIAL_BUS_PCI, @@ -201,6 +117,11 @@ static struct platform_device v2m_eth_device = { .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, @@ -230,14 +151,8 @@ static struct platform_device v2m_usb_device = { .dev.platform_data = &v2m_usb_config, }; -static void v2m_flash_set_vpp(struct platform_device *pdev, int on) -{ - writel(on != 0, v2m_sysreg_base + V2M_SYS_FLASH); -} - static struct physmap_flash_data v2m_flash_data = { .width = 4, - .set_vpp = v2m_flash_set_vpp, }; static struct resource v2m_flash_resources[] = { @@ -284,14 +199,62 @@ static struct platform_device v2m_cf_device = { .dev.platform_data = &v2m_pata_data, }; -static unsigned int v2m_mmci_status(struct device *dev) -{ - return readl(v2m_sysreg_base + V2M_SYS_MCI) & (1 << 0); -} - static struct mmci_platform_data v2m_mmci_data = { .ocr_mask = MMC_VDD_32_33|MMC_VDD_33_34, - .status = v2m_mmci_status, + .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); @@ -318,100 +281,17 @@ static struct amba_device *v2m_amba_devs[] __initdata = { &rtc_device, }; - -static long v2m_osc_round(struct clk *clk, unsigned long rate) -{ - return rate; -} - -static int v2m_osc1_set(struct clk *clk, unsigned long rate) +static void __init v2m_timer_init(void) { - return v2m_cfg_write(SYS_CFG_OSC | SYS_CFG_SITE_MB | 1, rate); + vexpress_clk_init(ioremap(V2M_SYSCTL, SZ_4K)); + v2m_sp804_init(ioremap(V2M_TIMER01, SZ_4K), IRQ_V2M_TIMER0); } -static const struct clk_ops osc1_clk_ops = { - .round = v2m_osc_round, - .set = v2m_osc1_set, -}; - -static struct clk osc1_clk = { - .ops = &osc1_clk_ops, - .rate = 24000000, -}; - -static struct clk osc2_clk = { - .rate = 24000000, -}; - -static struct clk v2m_sp804_clk = { - .rate = 1000000, -}; - -static struct clk v2m_ref_clk = { - .rate = 32768, -}; - -static struct clk dummy_apb_pclk; - -static struct clk_lookup v2m_lookups[] = { - { /* AMBA bus clock */ - .con_id = "apb_pclk", - .clk = &dummy_apb_pclk, - }, { /* UART0 */ - .dev_id = "mb:uart0", - .clk = &osc2_clk, - }, { /* UART1 */ - .dev_id = "mb:uart1", - .clk = &osc2_clk, - }, { /* UART2 */ - .dev_id = "mb:uart2", - .clk = &osc2_clk, - }, { /* UART3 */ - .dev_id = "mb:uart3", - .clk = &osc2_clk, - }, { /* KMI0 */ - .dev_id = "mb:kmi0", - .clk = &osc2_clk, - }, { /* KMI1 */ - .dev_id = "mb:kmi1", - .clk = &osc2_clk, - }, { /* MMC0 */ - .dev_id = "mb:mmci", - .clk = &osc2_clk, - }, { /* CLCD */ - .dev_id = "mb:clcd", - .clk = &osc1_clk, - }, { /* SP805 WDT */ - .dev_id = "mb:wdt", - .clk = &v2m_ref_clk, - }, { /* SP804 timers */ - .dev_id = "sp804", - .con_id = "v2m-timer0", - .clk = &v2m_sp804_clk, - }, { /* SP804 timers */ - .dev_id = "sp804", - .con_id = "v2m-timer1", - .clk = &v2m_sp804_clk, - }, -}; - static void __init v2m_init_early(void) { - ct_desc->init_early(); - clkdev_add_table(v2m_lookups, ARRAY_SIZE(v2m_lookups)); - versatile_sched_clock_init(v2m_sysreg_base + V2M_SYS_24MHZ, 24000000); -} - -static void v2m_power_off(void) -{ - if (v2m_cfg_write(SYS_CFG_SHUTDOWN | SYS_CFG_SITE_MB, 0)) - printk(KERN_EMERG "Unable to shutdown\n"); -} - -static void v2m_restart(char str, const char *cmd) -{ - if (v2m_cfg_write(SYS_CFG_REBOOT | SYS_CFG_SITE_MB, 0)) - printk(KERN_EMERG "Unable to reboot\n"); + if (ct_desc->init_early) + ct_desc->init_early(); + versatile_sched_clock_init(vexpress_get_24mhz_clock_base(), 24000000); } struct ct_desc *ct_desc; @@ -428,7 +308,7 @@ static void __init v2m_populate_ct_desc(void) u32 current_tile_id; ct_desc = NULL; - current_tile_id = readl(v2m_sysreg_base + V2M_SYS_PROCID0) + current_tile_id = vexpress_get_procid(VEXPRESS_SITE_MASTER) & V2M_CT_ID_MASK; for (i = 0; i < ARRAY_SIZE(ct_descs) && !ct_desc; ++i) @@ -444,7 +324,7 @@ static void __init v2m_populate_ct_desc(void) static void __init v2m_map_io(void) { iotable_init(v2m_io_desc, ARRAY_SIZE(v2m_io_desc)); - v2m_sysreg_base = ioremap(V2M_SYSREGS, SZ_4K); + vexpress_sysreg_early_init(ioremap(V2M_SYSREGS, SZ_4K)); v2m_populate_ct_desc(); ct_desc->map_io(); } @@ -458,6 +338,10 @@ 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); @@ -468,224 +352,39 @@ static void __init v2m_init(void) for (i = 0; i < ARRAY_SIZE(v2m_amba_devs); i++) amba_device_register(v2m_amba_devs[i], &iomem_resource); - pm_power_off = v2m_power_off; + 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, - .timer = &v2m_timer, - .handle_irq = gic_handle_irq, + .init_time = v2m_timer_init, .init_machine = v2m_init, - .restart = v2m_restart, MACHINE_END -#if defined(CONFIG_ARCH_VEXPRESS_DT) - -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 -} - -static struct clk_lookup v2m_dt_lookups[] = { - { /* AMBA bus clock */ - .con_id = "apb_pclk", - .clk = &dummy_apb_pclk, - }, { /* SP804 timers */ - .dev_id = "sp804", - .con_id = "v2m-timer0", - .clk = &v2m_sp804_clk, - }, { /* SP804 timers */ - .dev_id = "sp804", - .con_id = "v2m-timer1", - .clk = &v2m_sp804_clk, - }, { /* PL180 MMCI */ - .dev_id = "mb:mmci", /* 10005000.mmci */ - .clk = &osc2_clk, - }, { /* PL050 KMI0 */ - .dev_id = "10006000.kmi", - .clk = &osc2_clk, - }, { /* PL050 KMI1 */ - .dev_id = "10007000.kmi", - .clk = &osc2_clk, - }, { /* PL011 UART0 */ - .dev_id = "10009000.uart", - .clk = &osc2_clk, - }, { /* PL011 UART1 */ - .dev_id = "1000a000.uart", - .clk = &osc2_clk, - }, { /* PL011 UART2 */ - .dev_id = "1000b000.uart", - .clk = &osc2_clk, - }, { /* PL011 UART3 */ - .dev_id = "1000c000.uart", - .clk = &osc2_clk, - }, { /* SP805 WDT */ - .dev_id = "1000f000.wdt", - .clk = &v2m_ref_clk, - }, { /* PL111 CLCD */ - .dev_id = "1001f000.clcd", - .clk = &osc1_clk, - }, - /* RS1 memory map */ - { /* PL180 MMCI */ - .dev_id = "mb:mmci", /* 1c050000.mmci */ - .clk = &osc2_clk, - }, { /* PL050 KMI0 */ - .dev_id = "1c060000.kmi", - .clk = &osc2_clk, - }, { /* PL050 KMI1 */ - .dev_id = "1c070000.kmi", - .clk = &osc2_clk, - }, { /* PL011 UART0 */ - .dev_id = "1c090000.uart", - .clk = &osc2_clk, - }, { /* PL011 UART1 */ - .dev_id = "1c0a0000.uart", - .clk = &osc2_clk, - }, { /* PL011 UART2 */ - .dev_id = "1c0b0000.uart", - .clk = &osc2_clk, - }, { /* PL011 UART3 */ - .dev_id = "1c0c0000.uart", - .clk = &osc2_clk, - }, { /* SP805 WDT */ - .dev_id = "1c0f0000.wdt", - .clk = &v2m_ref_clk, - }, { /* PL111 CLCD */ - .dev_id = "1c1f0000.clcd", - .clk = &osc1_clk, - }, -}; - -void __init v2m_dt_init_early(void) -{ - struct device_node *node; - u32 dt_hbi; - - node = of_find_compatible_node(NULL, NULL, "arm,vexpress-sysreg"); - v2m_sysreg_base = of_iomap(node, 0); - if (WARN_ON(!v2m_sysreg_base)) - return; - - /* Confirm board type against DT property, if available */ - if (of_property_read_u32(allnodes, "arm,hbi", &dt_hbi) == 0) { - u32 misc = readl(v2m_sysreg_base + V2M_SYS_MISC); - u32 id = readl(v2m_sysreg_base + (misc & SYS_MISC_MASTERSITE ? - V2M_SYS_PROCID1 : V2M_SYS_PROCID0)); - u32 hbi = id & SYS_PROCIDx_HBI_MASK; - - if (WARN_ON(dt_hbi != hbi)) - pr_warning("vexpress: DT HBI (%x) is not matching " - "hardware (%x)!\n", dt_hbi, hbi); - } - - clkdev_add_table(v2m_dt_lookups, ARRAY_SIZE(v2m_dt_lookups)); -} - -static struct of_device_id vexpress_irq_match[] __initdata = { - { .compatible = "arm,cortex-a9-gic", .data = gic_of_init, }, - {} -}; - -static void __init v2m_dt_init_irq(void) -{ - of_irq_init(vexpress_irq_match); -} - -static void __init v2m_dt_timer_init(void) -{ - struct device_node *node; - const char *path; - int err; - - node = of_find_compatible_node(NULL, NULL, "arm,sp810"); - v2m_sysctl_init(of_iomap(node, 0)); - - err = of_property_read_string(of_aliases, "arm,v2m_timer", &path); - if (WARN_ON(err)) - return; - node = of_find_node_by_path(path); - v2m_sp804_init(of_iomap(node, 0), irq_of_parse_and_map(node, 0)); - if (arch_timer_of_register() != 0) - twd_local_timer_of_register(); - - if (arch_timer_sched_clock_init() != 0) - versatile_sched_clock_init(v2m_sysreg_base + V2M_SYS_24MHZ, 24000000); -} - -static struct sys_timer v2m_dt_timer = { - .init = v2m_dt_timer_init, -}; - -static struct of_dev_auxdata v2m_dt_auxdata_lookup[] __initdata = { - OF_DEV_AUXDATA("arm,vexpress-flash", V2M_NOR0, "physmap-flash", - &v2m_flash_data), - OF_DEV_AUXDATA("arm,primecell", V2M_MMCI, "mb:mmci", &v2m_mmci_data), - /* RS1 memory map */ - OF_DEV_AUXDATA("arm,vexpress-flash", 0x08000000, "physmap-flash", - &v2m_flash_data), - OF_DEV_AUXDATA("arm,primecell", 0x1c050000, "mb:mmci", &v2m_mmci_data), - {} -}; - static void __init v2m_dt_init(void) { - l2x0_of_init(0x00400000, 0xfe0fffff); - of_platform_populate(NULL, of_default_bus_match_table, - v2m_dt_auxdata_lookup, NULL); - pm_power_off = v2m_power_off; + of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL); } -const static char *v2m_dt_match[] __initconst = { +static const char * const v2m_dt_match[] __initconst = { "arm,vexpress", NULL, }; DT_MACHINE_START(VEXPRESS_DT, "ARM-Versatile Express") .dt_compat = v2m_dt_match, - .map_io = v2m_dt_map_io, - .init_early = v2m_dt_init_early, - .init_irq = v2m_dt_init_irq, - .timer = &v2m_dt_timer, + .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, - .handle_irq = gic_handle_irq, - .restart = v2m_restart, MACHINE_END - -#endif |
