diff options
-rw-r--r-- | Documentation/devicetree/bindings/memory-controllers/synopsys.txt | 11 | ||||
-rw-r--r-- | arch/arm/boot/dts/zynq-7000.dtsi | 5 | ||||
-rw-r--r-- | arch/arm/mach-zynq/Makefile | 3 | ||||
-rw-r--r-- | arch/arm/mach-zynq/common.c | 11 | ||||
-rw-r--r-- | arch/arm/mach-zynq/common.h | 16 | ||||
-rw-r--r-- | arch/arm/mach-zynq/hotplug.c | 47 | ||||
-rw-r--r-- | arch/arm/mach-zynq/platsmp.c | 41 | ||||
-rw-r--r-- | arch/arm/mach-zynq/pm.c | 83 | ||||
-rw-r--r-- | arch/arm/mach-zynq/slcr.c | 43 | ||||
-rw-r--r-- | drivers/cpuidle/cpuidle-zynq.c | 10 |
10 files changed, 207 insertions, 63 deletions
diff --git a/Documentation/devicetree/bindings/memory-controllers/synopsys.txt b/Documentation/devicetree/bindings/memory-controllers/synopsys.txt new file mode 100644 index 0000000..f9c6454 --- /dev/null +++ b/Documentation/devicetree/bindings/memory-controllers/synopsys.txt @@ -0,0 +1,11 @@ +Binding for Synopsys IntelliDDR Multi Protocol Memory Controller + +Required properties: + - compatible: Should be 'xlnx,zynq-ddrc-a05' + - reg: Base address and size of the controllers memory area + +Example: + memory-controller@f8006000 { + compatible = "xlnx,zynq-ddrc-a05"; + reg = <0xf8006000 0x1000>; + }; diff --git a/arch/arm/boot/dts/zynq-7000.dtsi b/arch/arm/boot/dts/zynq-7000.dtsi index 6cc83d4..587cadc 100644 --- a/arch/arm/boot/dts/zynq-7000.dtsi +++ b/arch/arm/boot/dts/zynq-7000.dtsi @@ -146,6 +146,11 @@ cache-level = <2>; }; + memory-controller@f8006000 { + compatible = "xlnx,zynq-ddrc-a05"; + reg = <0xf8006000 0x1000>; + } ; + uart0: serial@e0000000 { compatible = "xlnx,xuartps", "cdns,uart-r1p8"; status = "disabled"; diff --git a/arch/arm/mach-zynq/Makefile b/arch/arm/mach-zynq/Makefile index 1b25d92..c85fb3f 100644 --- a/arch/arm/mach-zynq/Makefile +++ b/arch/arm/mach-zynq/Makefile @@ -3,8 +3,7 @@ # # Common support -obj-y := common.o slcr.o +obj-y := common.o slcr.o pm.o CFLAGS_REMOVE_hotplug.o =-march=armv6k CFLAGS_hotplug.o =-Wa,-march=armv7-a -mcpu=cortex-a9 -obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o obj-$(CONFIG_SMP) += headsmp.o platsmp.o diff --git a/arch/arm/mach-zynq/common.c b/arch/arm/mach-zynq/common.c index 31a6fa4..613c476 100644 --- a/arch/arm/mach-zynq/common.c +++ b/arch/arm/mach-zynq/common.c @@ -98,6 +98,12 @@ static int __init zynq_get_revision(void) return revision; } +static void __init zynq_init_late(void) +{ + zynq_core_pm_init(); + zynq_pm_late_init(); +} + /** * zynq_init_machine - System specific initialization, intended to be * called from board specific initialization. @@ -198,12 +204,13 @@ static const char * const zynq_dt_match[] = { DT_MACHINE_START(XILINX_EP107, "Xilinx Zynq Platform") /* 64KB way size, 8-way associativity, parity disabled */ - .l2c_aux_val = 0x02000000, - .l2c_aux_mask = 0xf0ffffff, + .l2c_aux_val = 0x00000000, + .l2c_aux_mask = 0xffffffff, .smp = smp_ops(zynq_smp_ops), .map_io = zynq_map_io, .init_irq = zynq_irq_init, .init_machine = zynq_init_machine, + .init_late = zynq_init_late, .init_time = zynq_timer_init, .dt_compat = zynq_dt_match, .reserve = zynq_memory_init, diff --git a/arch/arm/mach-zynq/common.h b/arch/arm/mach-zynq/common.h index f652f0a..2bc7127 100644 --- a/arch/arm/mach-zynq/common.h +++ b/arch/arm/mach-zynq/common.h @@ -24,6 +24,8 @@ extern int zynq_early_slcr_init(void); extern void zynq_slcr_system_reset(void); extern void zynq_slcr_cpu_stop(int cpu); extern void zynq_slcr_cpu_start(int cpu); +extern bool zynq_slcr_cpu_state_read(int cpu); +extern void zynq_slcr_cpu_state_write(int cpu, bool die); extern u32 zynq_slcr_get_device_id(void); #ifdef CONFIG_SMP @@ -37,7 +39,17 @@ extern struct smp_operations zynq_smp_ops __initdata; extern void __iomem *zynq_scu_base; -/* Hotplug */ -extern void zynq_platform_cpu_die(unsigned int cpu); +void zynq_pm_late_init(void); + +static inline void zynq_core_pm_init(void) +{ + /* A9 clock gating */ + asm volatile ("mrc p15, 0, r12, c15, c0, 0\n" + "orr r12, r12, #1\n" + "mcr p15, 0, r12, c15, c0, 0\n" + : /* no outputs */ + : /* no inputs */ + : "r12"); +} #endif diff --git a/arch/arm/mach-zynq/hotplug.c b/arch/arm/mach-zynq/hotplug.c index 5052c70..b685c89 100644 --- a/arch/arm/mach-zynq/hotplug.c +++ b/arch/arm/mach-zynq/hotplug.c @@ -10,50 +10,5 @@ * 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/proc-fns.h> -#include <asm/cacheflush.h> -#include <asm/cp15.h> -#include "common.h" - -static inline void zynq_cpu_enter_lowpower(void) -{ - unsigned int v; - - flush_cache_all(); - asm volatile( - " mcr p15, 0, %1, c7, c5, 0\n" - " dsb\n" - /* - * Turn off coherency - */ - " mrc p15, 0, %0, c1, c0, 1\n" - " bic %0, %0, #0x40\n" - " mcr p15, 0, %0, c1, c0, 1\n" - " mrc p15, 0, %0, c1, c0, 0\n" - " bic %0, %0, %2\n" - " mcr p15, 0, %0, c1, c0, 0\n" - : "=&r" (v) - : "r" (0), "Ir" (CR_C) - : "cc"); -} - -/* - * platform-specific code to shutdown a CPU - * - * Called with IRQs disabled - */ -void zynq_platform_cpu_die(unsigned int cpu) -{ - zynq_cpu_enter_lowpower(); - - /* - * 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 (;;) - cpu_do_idle(); -} diff --git a/arch/arm/mach-zynq/platsmp.c b/arch/arm/mach-zynq/platsmp.c index abc82ef..52d768f 100644 --- a/arch/arm/mach-zynq/platsmp.c +++ b/arch/arm/mach-zynq/platsmp.c @@ -112,20 +112,59 @@ static void __init zynq_smp_prepare_cpus(unsigned int max_cpus) scu_enable(zynq_scu_base); } +/** + * zynq_secondary_init - Initialize secondary CPU cores + * @cpu: CPU that is initialized + * + * This function is in the hotplug path. Don't move it into the + * init section!! + */ +static void zynq_secondary_init(unsigned int cpu) +{ + zynq_core_pm_init(); +} + #ifdef CONFIG_HOTPLUG_CPU static int zynq_cpu_kill(unsigned cpu) { + unsigned long timeout = jiffies + msecs_to_jiffies(50); + + while (zynq_slcr_cpu_state_read(cpu)) + if (time_after(jiffies, timeout)) + return 0; + zynq_slcr_cpu_stop(cpu); return 1; } + +/** + * zynq_cpu_die - Let a CPU core die + * @cpu: Dying CPU + * + * Platform-specific code to shutdown a CPU. + * Called with IRQs disabled on the dying CPU. + */ +static void zynq_cpu_die(unsigned int cpu) +{ + zynq_slcr_cpu_state_write(cpu, true); + + /* + * 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 (;;) + cpu_do_idle(); +} #endif struct smp_operations zynq_smp_ops __initdata = { .smp_init_cpus = zynq_smp_init_cpus, .smp_prepare_cpus = zynq_smp_prepare_cpus, .smp_boot_secondary = zynq_boot_secondary, + .smp_secondary_init = zynq_secondary_init, #ifdef CONFIG_HOTPLUG_CPU - .cpu_die = zynq_platform_cpu_die, + .cpu_die = zynq_cpu_die, .cpu_kill = zynq_cpu_kill, #endif }; diff --git a/arch/arm/mach-zynq/pm.c b/arch/arm/mach-zynq/pm.c new file mode 100644 index 0000000..911fcf8 --- /dev/null +++ b/arch/arm/mach-zynq/pm.c @@ -0,0 +1,83 @@ +/* + * Zynq power management + * + * Copyright (C) 2012 - 2014 Xilinx + * + * Sören Brinkmann <soren.brinkmann@xilinx.com> + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/io.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include "common.h" + +/* register offsets */ +#define DDRC_CTRL_REG1_OFFS 0x60 +#define DDRC_DRAM_PARAM_REG3_OFFS 0x20 + +/* bitfields */ +#define DDRC_CLOCKSTOP_MASK BIT(23) +#define DDRC_SELFREFRESH_MASK BIT(12) + +static void __iomem *ddrc_base; + +/** + * zynq_pm_ioremap() - Create IO mappings + * @comp: DT compatible string + * Return: Pointer to the mapped memory or NULL. + * + * Remap the memory region for a compatible DT node. + */ +static void __iomem *zynq_pm_ioremap(const char *comp) +{ + struct device_node *np; + void __iomem *base = NULL; + + np = of_find_compatible_node(NULL, NULL, comp); + if (np) { + base = of_iomap(np, 0); + of_node_put(np); + } else { + pr_warn("%s: no compatible node found for '%s'\n", __func__, + comp); + } + + return base; +} + +/** + * zynq_pm_late_init() - Power management init + * + * Initialization of power management related featurs and infrastructure. + */ +void __init zynq_pm_late_init(void) +{ + u32 reg; + + ddrc_base = zynq_pm_ioremap("xlnx,zynq-ddrc-a05"); + if (!ddrc_base) { + pr_warn("%s: Unable to map DDRC IO memory.\n", __func__); + } else { + /* + * Enable DDRC clock stop feature. The HW takes care of + * entering/exiting the correct mode depending + * on activity state. + */ + reg = readl(ddrc_base + DDRC_DRAM_PARAM_REG3_OFFS); + reg |= DDRC_CLOCKSTOP_MASK; + writel(reg, ddrc_base + DDRC_DRAM_PARAM_REG3_OFFS); + } +} diff --git a/arch/arm/mach-zynq/slcr.c b/arch/arm/mach-zynq/slcr.c index c43a2d1..d4cb50c 100644 --- a/arch/arm/mach-zynq/slcr.c +++ b/arch/arm/mach-zynq/slcr.c @@ -138,6 +138,8 @@ void zynq_slcr_cpu_start(int cpu) zynq_slcr_write(reg, SLCR_A9_CPU_RST_CTRL_OFFSET); reg &= ~(SLCR_A9_CPU_CLKSTOP << cpu); zynq_slcr_write(reg, SLCR_A9_CPU_RST_CTRL_OFFSET); + + zynq_slcr_cpu_state_write(cpu, false); } /** @@ -154,8 +156,47 @@ void zynq_slcr_cpu_stop(int cpu) } /** - * zynq_slcr_init - Regular slcr driver init + * zynq_slcr_cpu_state - Read/write cpu state + * @cpu: cpu number * + * SLCR_REBOOT_STATUS save upper 2 bits (31/30 cpu states for cpu0 and cpu1) + * 0 means cpu is running, 1 cpu is going to die. + * + * Return: true if cpu is running, false if cpu is going to die + */ +bool zynq_slcr_cpu_state_read(int cpu) +{ + u32 state; + + state = readl(zynq_slcr_base + SLCR_REBOOT_STATUS_OFFSET); + state &= 1 << (31 - cpu); + + return !state; +} + +/** + * zynq_slcr_cpu_state - Read/write cpu state + * @cpu: cpu number + * @die: cpu state - true if cpu is going to die + * + * SLCR_REBOOT_STATUS save upper 2 bits (31/30 cpu states for cpu0 and cpu1) + * 0 means cpu is running, 1 cpu is going to die. + */ +void zynq_slcr_cpu_state_write(int cpu, bool die) +{ + u32 state, mask; + + state = readl(zynq_slcr_base + SLCR_REBOOT_STATUS_OFFSET); + mask = 1 << (31 - cpu); + if (die) + state |= mask; + else + state &= ~mask; + writel(state, zynq_slcr_base + SLCR_REBOOT_STATUS_OFFSET); +} + +/** + * zynq_slcr_init - Regular slcr driver init * Return: 0 on success, negative errno otherwise. * * Called early during boot from platform code to remap SLCR area. diff --git a/drivers/cpuidle/cpuidle-zynq.c b/drivers/cpuidle/cpuidle-zynq.c index aded759..c61b8b2 100644 --- a/drivers/cpuidle/cpuidle-zynq.c +++ b/drivers/cpuidle/cpuidle-zynq.c @@ -26,7 +26,6 @@ */ #include <linux/init.h> -#include <linux/cpu_pm.h> #include <linux/cpuidle.h> #include <linux/platform_device.h> #include <asm/proc-fns.h> @@ -38,15 +37,9 @@ static int zynq_enter_idle(struct cpuidle_device *dev, struct cpuidle_driver *drv, int index) { - /* Devices must be stopped here */ - cpu_pm_enter(); - /* Add code for DDR self refresh start */ cpu_do_idle(); - /* Add code for DDR self refresh stop */ - cpu_pm_exit(); - return index; } @@ -59,8 +52,7 @@ static struct cpuidle_driver zynq_idle_driver = { .enter = zynq_enter_idle, .exit_latency = 10, .target_residency = 10000, - .flags = CPUIDLE_FLAG_TIME_VALID | - CPUIDLE_FLAG_TIMER_STOP, + .flags = CPUIDLE_FLAG_TIME_VALID, .name = "RAM_SR", .desc = "WFI and RAM Self Refresh", }, |