/* * linux/arch/arm/mach-sunxi/sun8i-mcpm.c * * Copyright(c) 2013-2015 Allwinnertech Co., Ltd. * http://www.allwinnertech.com * * Author: sunny * * allwinner sun8i platform multi-cluster power management operations. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_MCPM_WITH_ARISC_DVFS_SUPPORT /* sync with arisc module */ static bool arisc_ready; #endif #define is_arisc_ready() (arisc_ready) #define set_arisc_ready(x) (arisc_ready = (x)) #if defined(CONFIG_ARCH_SUN8IW6) static unsigned int soc_version; #define CORES_PER_CLUSTER (4) #elif defined(CONFIG_ARCH_SUN8IW9) #define CORES_PER_CLUSTER (3) #endif /* sun8i platform support two clusters, * cluster0 : cortex-a7, * cluster1 : cortex-a7. */ #define CLUSTER_0 0 #define CLUSTER_1 1 #define SUN8I_C0_CLSUTER_PWRUP_FREQ (600000) /* freq base on khz */ #define SUN8I_C1_CLSUTER_PWRUP_FREQ (600000) /* freq base on khz */ static unsigned int cluster_pll[MAX_CLUSTERS]; static unsigned int cluster_powerup_freq[MAX_CLUSTERS]; /* sunxi cluster and cpu power-management models */ static void __iomem *sun8i_prcm_base; static void __iomem *sun8i_cpuxcfg_base; static void __iomem *sun8i_cpuscfg_base; /* add by huangshr * to check cpu really power state*/ cpumask_t cpu_power_up_state_mask; /* end by huangshr*/ extern u32 cpu_cfg_readl(void __iomem *reg); extern void cpu_cfg_writel(u32 val, void __iomem *reg); int sun8i_is_wfi_mode(int pcpu, int pcluster) { return cpu_cfg_readl(sun8i_cpuxcfg_base + SUNXI_CLUSTER_CPU_STATUS(pcluster)) & (1 << (16 + pcpu)); } int sun8i_l2cache_is_wif_mode(int pcluster) { return cpu_cfg_readl(sun8i_cpuxcfg_base + SUNXI_CLUSTER_CPU_STATUS(pcluster)) & (1 << 0); } void sun8i_set_secondary_entry(unsigned long boot_addr) { sunxi_set_secondary_entry((void *)boot_addr); } static int sun8i_cpu_power_switch_set(unsigned int cluster, unsigned int cpu, bool enable) { if (enable) { if (0x00 == sunxi_smc_readl(sun8i_prcm_base + SUNXI_CPU_PWR_CLAMP(cluster, cpu))) { pr_debug("%s: power switch enable already\n", __func__); return 0; } /* de-active cpu power clamp */ sunxi_smc_writel(0xFE, sun8i_prcm_base + SUNXI_CPU_PWR_CLAMP(cluster, cpu)); udelay(20); sunxi_smc_writel(0xF8, sun8i_prcm_base + SUNXI_CPU_PWR_CLAMP(cluster, cpu)); udelay(10); sunxi_smc_writel(0xE0, sun8i_prcm_base + SUNXI_CPU_PWR_CLAMP(cluster, cpu)); udelay(10); #ifdef CONFIG_ARCH_SUN8IW6 sunxi_smc_writel(0xc0, sun8i_prcm_base + SUNXI_CPU_PWR_CLAMP(cluster, cpu)); udelay(10); #endif sunxi_smc_writel(0x80, sun8i_prcm_base + SUNXI_CPU_PWR_CLAMP(cluster, cpu)); udelay(10); sunxi_smc_writel(0x00, sun8i_prcm_base + SUNXI_CPU_PWR_CLAMP(cluster, cpu)); udelay(20); while (0x00 != sunxi_smc_readl(sun8i_prcm_base + SUNXI_CPU_PWR_CLAMP(cluster, cpu))) { ; } } else { if (0xFF == sunxi_smc_readl(sun8i_prcm_base + SUNXI_CPU_PWR_CLAMP(cluster, cpu))) { pr_debug("%s: power switch disable already\n", __func__); return 0; } sunxi_smc_writel(0xFF, sun8i_prcm_base + SUNXI_CPU_PWR_CLAMP(cluster, cpu)); udelay(30); while (0xFF != sunxi_smc_readl(sun8i_prcm_base + SUNXI_CPU_PWR_CLAMP(cluster, cpu))) { ; } } return 0; } int sun8i_cpu_power_set(unsigned int cluster, unsigned int cpu, bool enable) { unsigned int value; if (enable) { /* * power-up cpu core process */ pr_debug("sun8i power-up cluster-%d cpu-%d\n", cluster, cpu); cpumask_set_cpu(((cluster)*4 + cpu), &cpu_power_up_state_mask); /* assert cpu core reset */ value = cpu_cfg_readl(sun8i_cpuxcfg_base + SUNXI_CPU_RST_CTRL(cluster)); value &= (~(1<> 16) & 0xe) == 0xe)) { status = 1; } } else { if (((value >> 16) & 0xf) == 0xf) { status = 1; } } #elif defined(CONFIG_ARCH_SUN8IW9) if (((value >> 16) & 0x7) == 0x7) { status = 1; } #endif return status; } static int sun8i_mcpm_cpu_powerup(unsigned int cpu, unsigned int cluster) { sun8i_set_secondary_entry(virt_to_phys(mcpm_entry_point)); return sun8i_cpu_power_set(cluster, cpu, 1); } static int sun8i_mcpm_cluster_powerup(unsigned int cluster) { return sun8i_cluster_power_set(cluster, 1); } extern bool mcpm_cluster_unused(unsigned int cluster); static int sun8i_mcpm_wait_for_powerdown(unsigned int cpu, unsigned int cluster) { /* power-down cpu core */ sun8i_cpu_power_set(cluster, cpu, 0); #ifndef CONFIG_BL_SWITCHER /* if bL swithcer disable, the last-man should power-down cluster */ if ((mcpm_cluster_unused(cluster)) && (sun8i_cluster_power_status(cluster))) { sun8i_cluster_power_set(cluster, 0); } #endif return 0; } static void sun8i_mcpm_cluster_cache_disbale(void) { /* * Flush all cache levels for this cluster. * * Cluster1/Cluster0 can hit in the cache with SCTLR.C=0, so we don't need * a preliminary flush here for those CPUs. At least, that's * the theory -- without the extra flush, Linux explodes on * RTSM (maybe not needed anymore, to be investigated). */ flush_cache_all(); set_cr(get_cr() & ~CR_C); flush_cache_all(); /* * This is a harmless no-op. On platforms with a real * outer cache this might either be needed or not, * depending on where the outer cache sits. */ outer_flush_all(); /* Disable local coherency by clearing the ACTLR "SMP" bit: */ set_auxcr(get_auxcr() & ~(1 << 6)); } static void sun8i_mcpm_cpu_cache_disable(void) { /* * Flush the local CPU cache. * * Cluster1/Cluster0 can hit in the cache with SCTLR.C=0, * so we don't need * a preliminary flush here for those CPUs. At least, that's * the theory -- without the extra flush, Linux explodes on * RTSM (maybe not needed anymore, to be investigated). */ flush_cache_louis(); set_cr(get_cr() & ~CR_C); flush_cache_louis(); /* Disable local coherency by clearing the ACTLR "SMP" bit: */ set_auxcr(get_auxcr() & ~(1 << 6)); } static void sun8i_cpu_suspend_prepare(unsigned int cpu, unsigned int cluster) { return; } static void sun8i_cpu_powerdown_prepare(unsigned int cpu, unsigned int cluster) { return; } static void sun8i_cluster_powerdown_prepare(unsigned int cluster) { } static void sun8i_cpu_is_up(unsigned int cpu, unsigned int cluster) { } static void sun8i_cluster_is_up(unsigned int cluster) { } static const struct mcpm_platform_ops sun8i_mcpm_power_ops = { .cluster_powerup = sun8i_mcpm_cluster_powerup, .cpu_powerup = sun8i_mcpm_cpu_powerup, .wait_for_powerdown = sun8i_mcpm_wait_for_powerdown, .cluster_cache_disable = sun8i_mcpm_cluster_cache_disbale, .cpu_cache_disable = sun8i_mcpm_cpu_cache_disable, .cpu_suspend_prepare = sun8i_cpu_suspend_prepare, .cpu_powerdown_prepare = sun8i_cpu_powerdown_prepare, .cluster_powerdown_prepare = sun8i_cluster_powerdown_prepare, .cpu_is_up = sun8i_cpu_is_up, .cluster_is_up = sun8i_cluster_is_up, }; #ifdef CONFIG_MCPM_WITH_ARISC_DVFS_SUPPORT /* sun8i_arisc_notify_call - callback that gets triggered when arisc ready. */ static int sun8i_arisc_notify_call(struct notifier_block *nfb, unsigned long action, void *parg) { unsigned int mpidr, cpu, cluster; mpidr = read_cpuid_mpidr(); cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); switch (action) { case ARISC_INIT_READY: { unsigned int cpu; /* arisc ready now */ set_arisc_ready(1); /* power-off cluster-1 first */ sun8i_cluster_power_set((cluster == CLUSTER_0) ? CLUSTER_1 : CLUSTER_0, 0); /* power-up off-line cpus*/ for_each_present_cpu(cpu) { if (num_online_cpus() >= setup_max_cpus) { break; } if (!cpu_online(cpu)) { cpu_up(cpu); } } break; } } return NOTIFY_OK; } static struct notifier_block sun8i_arisc_notifier = { &sun8i_arisc_notify_call, NULL, 0 }; #endif static void __init sun8i_mcpm_boot_cpu_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 >= MAX_CPUS_PER_CLUSTER || cluster >= MAX_NR_CLUSTERS); cpumask_clear(&cpu_power_up_state_mask); cpumask_set_cpu((cluster*4 + cpu), &cpu_power_up_state_mask); } int sun8i_mcpm_cpu_map_init(void) { /* default cpu logical map */ cpu_logical_map(0) = 0x000; cpu_logical_map(1) = 0x001; cpu_logical_map(2) = 0x002; cpu_logical_map(3) = 0x003; cpu_logical_map(4) = 0x100; cpu_logical_map(5) = 0x101; cpu_logical_map(6) = 0x102; cpu_logical_map(7) = 0x103; return 0; } extern void sun8i_power_up_setup(unsigned int affinity_level); extern int __init cci_init(void); static int __init sun8i_mcpm_init(void) { int ret; /* initialize cci driver first */ cci_init(); /* initialize prcm and cpucfg model virtual base address */ sun8i_prcm_base = (void __iomem *)(SUNXI_R_PRCM_VBASE); sun8i_cpuxcfg_base = (void __iomem *)(SUNXI_CPUXCFG_VBASE); sun8i_cpuscfg_base = (void __iomem *)(SUNXI_R_CPUCFG_VBASE); sun8i_mcpm_boot_cpu_init(); ret = mcpm_platform_register(&sun8i_mcpm_power_ops); #ifndef CONFIG_SUNXI_TRUSTZONE if (!ret) { ret = mcpm_sync_init(sun8i_power_up_setup); } #endif /* set sun8i platform non-boot cpu startup entry. */ sun8i_set_secondary_entry(virt_to_phys(mcpm_entry_point)); /* initialize cluster0 and cluster1 cluster pll number */ cluster_pll[CLUSTER_0] = ARISC_DVFS_PLL1; cluster_pll[CLUSTER_1] = ARISC_DVFS_PLL2; /* initialize ca7 and ca15 cluster power-up freq as deafult */ cluster_powerup_freq[CLUSTER_0] = SUN8I_C0_CLSUTER_PWRUP_FREQ; cluster_powerup_freq[CLUSTER_1] = SUN8I_C1_CLSUTER_PWRUP_FREQ; #ifdef CONFIG_MCPM_WITH_ARISC_DVFS_SUPPORT /* register arisc ready notifier */ arisc_register_notifier(&sun8i_arisc_notifier); #endif #ifdef CONFIG_FPGA_V7_PLATFORM /* hard-encode by sunny to support sun8i 2big+2little, * we should use device-tree to config the cluster and cpu * topology information. * but sun8i not support device-tree now, * so I just hard-encode for temp debug. */ cpu_logical_map(0) = 0x000; cpu_logical_map(1) = 0x001; cpu_logical_map(2) = 0x100; cpu_logical_map(3) = 0x101; #endif #ifdef CONFIG_ARCH_SUN8IW6 soc_version = sunxi_get_soc_ver(); #endif return 0; } early_initcall(sun8i_mcpm_init);