diff --git a/Documentation/devicetree/bindings/arm/rockchip/pmu.txt b/Documentation/devicetree/bindings/arm/rockchip/pmu.txt new file mode 100644 index 000000000000..3ee9b428b2f7 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/rockchip/pmu.txt @@ -0,0 +1,16 @@ +Rockchip power-management-unit: +------------------------------- + +The pmu is used to turn off and on different power domains of the SoCs +This includes the power to the CPU cores. + +Required node properties: +- compatible value : = "rockchip,rk3066-pmu"; +- reg : physical base address and the size of the registers window + +Example: + + pmu@20004000 { + compatible = "rockchip,rk3066-pmu"; + reg = <0x20004000 0x100>; + }; diff --git a/Documentation/devicetree/bindings/arm/rockchip/smp-sram.txt b/Documentation/devicetree/bindings/arm/rockchip/smp-sram.txt new file mode 100644 index 000000000000..d9416fb8db6f --- /dev/null +++ b/Documentation/devicetree/bindings/arm/rockchip/smp-sram.txt @@ -0,0 +1,30 @@ +Rockchip SRAM for smp bringup: +------------------------------ + +Rockchip's smp-capable SoCs use the first part of the sram for the bringup +of the cores. Once the core gets powered up it executes the code that is +residing at the very beginning of the sram. + +Therefore a reserved section sub-node has to be added to the mmio-sram +declaration. + +Required sub-node properties: +- compatible : should be "rockchip,rk3066-smp-sram" + +The rest of the properties should follow the generic mmio-sram discription +found in ../../misc/sram.txt + +Example: + + sram: sram@10080000 { + compatible = "mmio-sram"; + reg = <0x10080000 0x10000>; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + smp-sram@10080000 { + compatible = "rockchip,rk3066-smp-sram"; + reg = <0x10080000 0x50>; + }; + }; diff --git a/arch/arm/boot/dts/rk3066a.dtsi b/arch/arm/boot/dts/rk3066a.dtsi index be5d2b09a363..4d4dfbb59f4b 100644 --- a/arch/arm/boot/dts/rk3066a.dtsi +++ b/arch/arm/boot/dts/rk3066a.dtsi @@ -64,6 +64,19 @@ timer@2000e000 { clock-names = "timer", "pclk"; }; + sram: sram@10080000 { + compatible = "mmio-sram"; + reg = <0x10080000 0x10000>; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0 0x10080000 0x10000>; + + smp-sram@0 { + compatible = "rockchip,rk3066-smp-sram"; + reg = <0x0 0x50>; + }; + }; + pinctrl@20008000 { compatible = "rockchip,rk3066a-pinctrl"; reg = <0x20008000 0x150>; diff --git a/arch/arm/boot/dts/rk3188.dtsi b/arch/arm/boot/dts/rk3188.dtsi index 1a26b03b3649..bb36596ea205 100644 --- a/arch/arm/boot/dts/rk3188.dtsi +++ b/arch/arm/boot/dts/rk3188.dtsi @@ -60,6 +60,19 @@ local-timer@1013c600 { interrupts = ; }; + sram: sram@10080000 { + compatible = "mmio-sram"; + reg = <0x10080000 0x8000>; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0 0x10080000 0x8000>; + + smp-sram@0 { + compatible = "rockchip,rk3066-smp-sram"; + reg = <0x0 0x50>; + }; + }; + pinctrl@20008000 { compatible = "rockchip,rk3188-pinctrl"; reg = <0x20008000 0xa0>, diff --git a/arch/arm/boot/dts/rk3xxx.dtsi b/arch/arm/boot/dts/rk3xxx.dtsi index 0fcbcfd67de2..26e5a968d49d 100644 --- a/arch/arm/boot/dts/rk3xxx.dtsi +++ b/arch/arm/boot/dts/rk3xxx.dtsi @@ -26,6 +26,16 @@ soc { compatible = "simple-bus"; ranges; + scu@1013c000 { + compatible = "arm,cortex-a9-scu"; + reg = <0x1013c000 0x100>; + }; + + pmu@20004000 { + compatible = "rockchip,rk3066-pmu"; + reg = <0x20004000 0x100>; + }; + gic: interrupt-controller@1013d000 { compatible = "arm,cortex-a9-gic"; interrupt-controller; diff --git a/arch/arm/mach-rockchip/Kconfig b/arch/arm/mach-rockchip/Kconfig index cf073dea5784..133410e178a7 100644 --- a/arch/arm/mach-rockchip/Kconfig +++ b/arch/arm/mach-rockchip/Kconfig @@ -5,6 +5,7 @@ config ARCH_ROCKCHIP select ARCH_REQUIRE_GPIOLIB select ARM_GIC select CACHE_L2X0 + select HAVE_ARM_SCU if SMP select HAVE_ARM_TWD if SMP select HAVE_SMP select COMMON_CLK diff --git a/arch/arm/mach-rockchip/Makefile b/arch/arm/mach-rockchip/Makefile index 1547d4fc920a..4377a1436a98 100644 --- a/arch/arm/mach-rockchip/Makefile +++ b/arch/arm/mach-rockchip/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip.o +obj-$(CONFIG_SMP) += headsmp.o platsmp.o diff --git a/arch/arm/mach-rockchip/core.h b/arch/arm/mach-rockchip/core.h new file mode 100644 index 000000000000..e2e7c9dbb200 --- /dev/null +++ b/arch/arm/mach-rockchip/core.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2013 MundoReader S.L. + * Author: Heiko Stuebner + * + * 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. + */ + +extern char rockchip_secondary_trampoline; +extern char rockchip_secondary_trampoline_end; + +extern unsigned long rockchip_boot_fn; +extern void rockchip_secondary_startup(void); + +extern struct smp_operations rockchip_smp_ops; diff --git a/arch/arm/mach-rockchip/headsmp.S b/arch/arm/mach-rockchip/headsmp.S new file mode 100644 index 000000000000..73206e360e31 --- /dev/null +++ b/arch/arm/mach-rockchip/headsmp.S @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2013 MundoReader S.L. + * Author: Heiko Stuebner + * + * 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. + */ +#include +#include + +ENTRY(rockchip_secondary_startup) + bl v7_invalidate_l1 + b secondary_startup +ENDPROC(rockchip_secondary_startup) + +ENTRY(rockchip_secondary_trampoline) + ldr pc, 1f +ENDPROC(rockchip_secondary_trampoline) + .globl rockchip_boot_fn +rockchip_boot_fn: +1: .space 4 + +ENTRY(rockchip_secondary_trampoline_end) diff --git a/arch/arm/mach-rockchip/platsmp.c b/arch/arm/mach-rockchip/platsmp.c new file mode 100644 index 000000000000..dbfa5a26cfff --- /dev/null +++ b/arch/arm/mach-rockchip/platsmp.c @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2013 MundoReader S.L. + * Author: Heiko Stuebner + * + * 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. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "core.h" + +static void __iomem *scu_base_addr; +static void __iomem *sram_base_addr; +static int ncores; + +#define PMU_PWRDN_CON 0x08 +#define PMU_PWRDN_ST 0x0c + +#define PMU_PWRDN_SCU 4 + +static void __iomem *pmu_base_addr; + +static inline bool pmu_power_domain_is_on(int pd) +{ + return !(readl_relaxed(pmu_base_addr + PMU_PWRDN_ST) & BIT(pd)); +} + +static void pmu_set_power_domain(int pd, bool on) +{ + u32 val = readl_relaxed(pmu_base_addr + PMU_PWRDN_CON); + if (on) + val &= ~BIT(pd); + else + val |= BIT(pd); + writel(val, pmu_base_addr + PMU_PWRDN_CON); + + while (pmu_power_domain_is_on(pd) != on) { } +} + +/* + * Handling of CPU cores + */ + +static int __cpuinit rockchip_boot_secondary(unsigned int cpu, + struct task_struct *idle) +{ + if (!sram_base_addr || !pmu_base_addr) { + pr_err("%s: sram or pmu missing for cpu boot\n", __func__); + return -ENXIO; + } + + if (cpu >= ncores) { + pr_err("%s: cpu %d outside maximum number of cpus %d\n", + __func__, cpu, ncores); + return -ENXIO; + } + + /* start the core */ + pmu_set_power_domain(0 + cpu, true); + + return 0; +} + +/** + * rockchip_smp_prepare_sram - populate necessary sram block + * Starting cores execute the code residing at the start of the on-chip sram + * after power-on. Therefore make sure, this sram region is reserved and + * big enough. After this check, copy the trampoline code that directs the + * core to the real startup code in ram into the sram-region. + * @node: mmio-sram device node + */ +static int __init rockchip_smp_prepare_sram(struct device_node *node) +{ + unsigned int trampoline_sz = &rockchip_secondary_trampoline_end - + &rockchip_secondary_trampoline; + struct resource res; + unsigned int rsize; + int ret; + + ret = of_address_to_resource(node, 0, &res); + if (ret < 0) { + pr_err("%s: could not get address for node %s\n", + __func__, node->full_name); + return ret; + } + + rsize = resource_size(&res); + if (rsize < trampoline_sz) { + pr_err("%s: reserved block with size 0x%x is to small for trampoline size 0x%x\n", + __func__, rsize, trampoline_sz); + return -EINVAL; + } + + sram_base_addr = of_iomap(node, 0); + + /* set the boot function for the sram code */ + rockchip_boot_fn = virt_to_phys(rockchip_secondary_startup); + + /* copy the trampoline to sram, that runs during startup of the core */ + memcpy(sram_base_addr, &rockchip_secondary_trampoline, trampoline_sz); + flush_cache_all(); + outer_clean_range(0, trampoline_sz); + + dsb_sev(); + + return 0; +} + +static void __init rockchip_smp_prepare_cpus(unsigned int max_cpus) +{ + struct device_node *node; + unsigned int i; + + node = of_find_compatible_node(NULL, NULL, "arm,cortex-a9-scu"); + if (!node) { + pr_err("%s: missing scu\n", __func__); + return; + } + + scu_base_addr = of_iomap(node, 0); + if (!scu_base_addr) { + pr_err("%s: could not map scu registers\n", __func__); + return; + } + + node = of_find_compatible_node(NULL, NULL, "rockchip,rk3066-smp-sram"); + if (!node) { + pr_err("%s: could not find sram dt node\n", __func__); + return; + } + + if (rockchip_smp_prepare_sram(node)) + return; + + node = of_find_compatible_node(NULL, NULL, "rockchip,rk3066-pmu"); + if (!node) { + pr_err("%s: could not find sram dt node\n", __func__); + return; + } + + pmu_base_addr = of_iomap(node, 0); + if (!pmu_base_addr) { + pr_err("%s: could not map pmu registers\n", __func__); + return; + } + + /* enable the SCU power domain */ + pmu_set_power_domain(PMU_PWRDN_SCU, true); + + /* + * While the number of cpus is gathered from dt, also get the number + * of cores from the scu to verify this value when booting the cores. + */ + ncores = scu_get_core_count(scu_base_addr); + + scu_enable(scu_base_addr); + + /* Make sure that all cores except the first are really off */ + for (i = 1; i < ncores; i++) + pmu_set_power_domain(0 + i, false); +} + +struct smp_operations rockchip_smp_ops __initdata = { + .smp_prepare_cpus = rockchip_smp_prepare_cpus, + .smp_boot_secondary = rockchip_boot_secondary, +}; diff --git a/arch/arm/mach-rockchip/rockchip.c b/arch/arm/mach-rockchip/rockchip.c index 82c0b0709712..d211d6fa0d98 100644 --- a/arch/arm/mach-rockchip/rockchip.c +++ b/arch/arm/mach-rockchip/rockchip.c @@ -22,6 +22,7 @@ #include #include #include +#include "core.h" static void __init rockchip_dt_init(void) { @@ -38,6 +39,7 @@ static const char * const rockchip_board_dt_compat[] = { }; DT_MACHINE_START(ROCKCHIP_DT, "Rockchip Cortex-A9 (Device Tree)") + .smp = smp_ops(rockchip_smp_ops), .init_machine = rockchip_dt_init, .dt_compat = rockchip_board_dt_compat, MACHINE_END