@@ -75,6 +75,7 @@ config ARCH_R8A7779
config ARCH_R8A7790
bool "R-Car H2 (R8A77900)"
select ARCH_RCAR_GEN2
+ select SHMOBILE_MCPM if MCPM
select I2C
config ARCH_R8A7791
@@ -96,6 +97,14 @@ config ARCH_SH73A0
select ARCH_RMOBILE
select RENESAS_INTC_IRQPIN
+config SHMOBILE_MCPM
+ bool "Shmobile Multi-Cluster PM support"
+ default n
+ depends on MCPM
+ select ARM_CCI400_PORT_CTRL
+ help
+ This is needed to provide CPU and cluster power management.
+
comment "Renesas ARM SoCs System Configuration"
endif
@@ -48,6 +48,7 @@ obj-$(CONFIG_CPU_FREQ) += cpufreq.o
obj-$(CONFIG_PM_RCAR) += pm-rcar.o
obj-$(CONFIG_PM_RMOBILE) += pm-rmobile.o
obj-$(CONFIG_ARCH_RCAR_GEN2) += pm-rcar-gen2.o
+obj-$(CONFIG_SHMOBILE_MCPM) += mcpm-shmobile.o
# Board objects
ifndef CONFIG_ARCH_SHMOBILE_MULTI
@@ -22,6 +22,12 @@ struct clk;
extern int shmobile_clk_init(void);
extern struct platform_suspend_ops shmobile_suspend_ops;
+#if defined(CONFIG_SHMOBILE_MCPM)
+extern bool sh_mcpm_probed(void);
+#else
+static inline bool sh_mcpm_probed(void) { return false; }
+#endif
+
#ifdef CONFIG_SUSPEND
int shmobile_suspend_init(void);
void shmobile_smp_apmu_suspend_init(void);
new file mode 100644
@@ -0,0 +1,155 @@
+/*
+ * SMP support for SoCs with APMU
+ *
+ * Copyright (C) 2015 Renesas Electronics Corporation
+ *
+ * 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 <asm/cp15.h>
+#include <asm/cputype.h>
+#include <asm/smp_plat.h>
+#include <asm/cacheflush.h>
+
+#include <linux/arm-cci.h>
+#include <linux/of.h>
+
+#include "common.h"
+#include "platsmp-apmu.h"
+
+static bool sh_mcpm_probe_ok;
+
+static inline unsigned int pcpu_to_cpu(unsigned int pcpu, unsigned int pcluster)
+{
+ unsigned int mpidr = 0;
+
+ mpidr = pcpu | (pcluster << MPIDR_LEVEL_BITS);
+
+ return get_logical_index(mpidr);
+}
+
+bool sh_mcpm_probed(void)
+{
+ return sh_mcpm_probe_ok;
+}
+
+static void __naked sh_mcpm_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 sh_mcpm_cpu_powerup(unsigned int pcpu, unsigned int pcluster)
+{
+ int cpu = pcpu_to_cpu(pcpu, pcluster);
+
+ apmu_power_on(cpu);
+
+ return 0;
+}
+
+static int sh_mcpm_cluster_powerup(unsigned int cluster)
+{
+ return 0;
+}
+
+static void sh_mcpm_cpu_powerdown_prepare(unsigned int pcpu,
+ unsigned int pcluster)
+{
+ int cpu = pcpu_to_cpu(pcpu, pcluster);
+
+ apmu_power_off(cpu);
+}
+
+static void sh_mcpm_cluster_powerdown_prepare(unsigned int pcluster)
+{
+}
+
+static void sh_mcpm_cpu_cache_disable(void)
+{
+ v7_exit_coherency_flush(louis);
+}
+
+static void sh_mcpm_cluster_cache_disable(void)
+{
+ if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A15) {
+ /* disable L2 prefetching on the Cortex-A15 */
+ 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(read_cpuid_mpidr());
+}
+
+static void sh_mcpm_cpu_is_up(unsigned int pcpu, unsigned int pcluster)
+{
+}
+
+static int sh_mcpm_wait_for_powerdown(unsigned int pcpu, unsigned int pcluster)
+{
+ int cpu = pcpu_to_cpu(pcpu, pcluster);
+
+ apmu_power_off_poll(cpu);
+
+ return 0;
+}
+
+static const struct mcpm_platform_ops sh_mcpm_power_ops = {
+ .cpu_powerup = sh_mcpm_cpu_powerup,
+ .cluster_powerup = sh_mcpm_cluster_powerup,
+ .cpu_powerdown_prepare = sh_mcpm_cpu_powerdown_prepare,
+ .cluster_powerdown_prepare = sh_mcpm_cluster_powerdown_prepare,
+ .cpu_cache_disable = sh_mcpm_cpu_cache_disable,
+ .cluster_cache_disable = sh_mcpm_cluster_cache_disable,
+ .wait_for_powerdown = sh_mcpm_wait_for_powerdown,
+ .cpu_is_up = sh_mcpm_cpu_is_up,
+};
+
+static int __init sh_mcpm_init(void)
+{
+ struct device_node *node;
+ int ret = 0;
+ int i;
+
+ sh_mcpm_probe_ok = false;
+
+ /* Register boot function as boot cpu did not call power up
+ * but will call power down on idle.
+ */
+
+ node = of_find_compatible_node(NULL, NULL, "arm,cci-400");
+ if (!node && !of_device_is_available(node)) {
+ pr_err("cci-400 node not found!");
+ return -ENODEV;
+ }
+
+ if (!cci_probed())
+ return -ENODEV;
+
+ ret = mcpm_platform_register(&sh_mcpm_power_ops);
+ if (!ret)
+ ret = mcpm_sync_init(sh_mcpm_power_up_setup);
+ if (!ret)
+ ret = mcpm_loopback(sh_mcpm_cluster_cache_disable); /* CCI on */
+ if (ret) {
+ pr_err("mcpm could not be stared. %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < num_possible_cpus(); i++)
+ shmobile_smp_hook(i,
+ virt_to_phys(mcpm_entry_point), 0);
+
+ mcpm_smp_set_ops();
+ sh_mcpm_probe_ok = true;
+
+ return 0;
+}
+early_initcall(sh_mcpm_init);
@@ -46,7 +46,7 @@ int __maybe_unused apmu_power_on(unsigned int cpu)
if (!p)
return -EINVAL;
- if (pcluster != boot_pcluster) {
+ if (!sh_mcpm_probed() && (pcluster != boot_pcluster)) {
pr_err("Requested to boot cpu %d on non-boot cluster!\n", cpu);
return -EINVAL;
}