18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * Power Management Service Unit(PMSU) support for Armada 370/XP platforms. 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Copyright (C) 2012 Marvell 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * Yehuda Yitschak <yehuday@marvell.com> 78c2ecf20Sopenharmony_ci * Gregory Clement <gregory.clement@free-electrons.com> 88c2ecf20Sopenharmony_ci * Thomas Petazzoni <thomas.petazzoni@free-electrons.com> 98c2ecf20Sopenharmony_ci * 108c2ecf20Sopenharmony_ci * This file is licensed under the terms of the GNU General Public 118c2ecf20Sopenharmony_ci * License version 2. This program is licensed "as is" without any 128c2ecf20Sopenharmony_ci * warranty of any kind, whether express or implied. 138c2ecf20Sopenharmony_ci * 148c2ecf20Sopenharmony_ci * The Armada 370 and Armada XP SOCs have a power management service 158c2ecf20Sopenharmony_ci * unit which is responsible for powering down and waking up CPUs and 168c2ecf20Sopenharmony_ci * other SOC units 178c2ecf20Sopenharmony_ci */ 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "mvebu-pmsu: " fmt 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#include <linux/clk.h> 228c2ecf20Sopenharmony_ci#include <linux/cpu_pm.h> 238c2ecf20Sopenharmony_ci#include <linux/delay.h> 248c2ecf20Sopenharmony_ci#include <linux/init.h> 258c2ecf20Sopenharmony_ci#include <linux/io.h> 268c2ecf20Sopenharmony_ci#include <linux/kernel.h> 278c2ecf20Sopenharmony_ci#include <linux/mbus.h> 288c2ecf20Sopenharmony_ci#include <linux/mvebu-pmsu.h> 298c2ecf20Sopenharmony_ci#include <linux/of_address.h> 308c2ecf20Sopenharmony_ci#include <linux/of_device.h> 318c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 328c2ecf20Sopenharmony_ci#include <linux/resource.h> 338c2ecf20Sopenharmony_ci#include <linux/slab.h> 348c2ecf20Sopenharmony_ci#include <linux/smp.h> 358c2ecf20Sopenharmony_ci#include <asm/cacheflush.h> 368c2ecf20Sopenharmony_ci#include <asm/cp15.h> 378c2ecf20Sopenharmony_ci#include <asm/smp_scu.h> 388c2ecf20Sopenharmony_ci#include <asm/smp_plat.h> 398c2ecf20Sopenharmony_ci#include <asm/suspend.h> 408c2ecf20Sopenharmony_ci#include <asm/tlbflush.h> 418c2ecf20Sopenharmony_ci#include "common.h" 428c2ecf20Sopenharmony_ci#include "pmsu.h" 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci#define PMSU_BASE_OFFSET 0x100 458c2ecf20Sopenharmony_ci#define PMSU_REG_SIZE 0x1000 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci/* PMSU MP registers */ 488c2ecf20Sopenharmony_ci#define PMSU_CONTROL_AND_CONFIG(cpu) ((cpu * 0x100) + 0x104) 498c2ecf20Sopenharmony_ci#define PMSU_CONTROL_AND_CONFIG_DFS_REQ BIT(18) 508c2ecf20Sopenharmony_ci#define PMSU_CONTROL_AND_CONFIG_PWDDN_REQ BIT(16) 518c2ecf20Sopenharmony_ci#define PMSU_CONTROL_AND_CONFIG_L2_PWDDN BIT(20) 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci#define PMSU_CPU_POWER_DOWN_CONTROL(cpu) ((cpu * 0x100) + 0x108) 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci#define PMSU_CPU_POWER_DOWN_DIS_SNP_Q_SKIP BIT(0) 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci#define PMSU_STATUS_AND_MASK(cpu) ((cpu * 0x100) + 0x10c) 588c2ecf20Sopenharmony_ci#define PMSU_STATUS_AND_MASK_CPU_IDLE_WAIT BIT(16) 598c2ecf20Sopenharmony_ci#define PMSU_STATUS_AND_MASK_SNP_Q_EMPTY_WAIT BIT(17) 608c2ecf20Sopenharmony_ci#define PMSU_STATUS_AND_MASK_IRQ_WAKEUP BIT(20) 618c2ecf20Sopenharmony_ci#define PMSU_STATUS_AND_MASK_FIQ_WAKEUP BIT(21) 628c2ecf20Sopenharmony_ci#define PMSU_STATUS_AND_MASK_DBG_WAKEUP BIT(22) 638c2ecf20Sopenharmony_ci#define PMSU_STATUS_AND_MASK_IRQ_MASK BIT(24) 648c2ecf20Sopenharmony_ci#define PMSU_STATUS_AND_MASK_FIQ_MASK BIT(25) 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci#define PMSU_EVENT_STATUS_AND_MASK(cpu) ((cpu * 0x100) + 0x120) 678c2ecf20Sopenharmony_ci#define PMSU_EVENT_STATUS_AND_MASK_DFS_DONE BIT(1) 688c2ecf20Sopenharmony_ci#define PMSU_EVENT_STATUS_AND_MASK_DFS_DONE_MASK BIT(17) 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci#define PMSU_BOOT_ADDR_REDIRECT_OFFSET(cpu) ((cpu * 0x100) + 0x124) 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci/* PMSU fabric registers */ 738c2ecf20Sopenharmony_ci#define L2C_NFABRIC_PM_CTL 0x4 748c2ecf20Sopenharmony_ci#define L2C_NFABRIC_PM_CTL_PWR_DOWN BIT(20) 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci/* PMSU delay registers */ 778c2ecf20Sopenharmony_ci#define PMSU_POWERDOWN_DELAY 0xF04 788c2ecf20Sopenharmony_ci#define PMSU_POWERDOWN_DELAY_PMU BIT(1) 798c2ecf20Sopenharmony_ci#define PMSU_POWERDOWN_DELAY_MASK 0xFFFE 808c2ecf20Sopenharmony_ci#define PMSU_DFLT_ARMADA38X_DELAY 0x64 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci/* CA9 MPcore SoC Control registers */ 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci#define MPCORE_RESET_CTL 0x64 858c2ecf20Sopenharmony_ci#define MPCORE_RESET_CTL_L2 BIT(0) 868c2ecf20Sopenharmony_ci#define MPCORE_RESET_CTL_DEBUG BIT(16) 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci#define SRAM_PHYS_BASE 0xFFFF0000 898c2ecf20Sopenharmony_ci#define BOOTROM_BASE 0xFFF00000 908c2ecf20Sopenharmony_ci#define BOOTROM_SIZE 0x100000 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci#define ARMADA_370_CRYPT0_ENG_TARGET 0x9 938c2ecf20Sopenharmony_ci#define ARMADA_370_CRYPT0_ENG_ATTR 0x1 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ciextern void ll_disable_coherency(void); 968c2ecf20Sopenharmony_ciextern void ll_enable_coherency(void); 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ciextern void armada_370_xp_cpu_resume(void); 998c2ecf20Sopenharmony_ciextern void armada_38x_cpu_resume(void); 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_cistatic phys_addr_t pmsu_mp_phys_base; 1028c2ecf20Sopenharmony_cistatic void __iomem *pmsu_mp_base; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_cistatic void *mvebu_cpu_resume; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_cistatic const struct of_device_id of_pmsu_table[] = { 1078c2ecf20Sopenharmony_ci { .compatible = "marvell,armada-370-pmsu", }, 1088c2ecf20Sopenharmony_ci { .compatible = "marvell,armada-370-xp-pmsu", }, 1098c2ecf20Sopenharmony_ci { .compatible = "marvell,armada-380-pmsu", }, 1108c2ecf20Sopenharmony_ci { /* end of list */ }, 1118c2ecf20Sopenharmony_ci}; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_civoid mvebu_pmsu_set_cpu_boot_addr(int hw_cpu, void *boot_addr) 1148c2ecf20Sopenharmony_ci{ 1158c2ecf20Sopenharmony_ci writel(__pa_symbol(boot_addr), pmsu_mp_base + 1168c2ecf20Sopenharmony_ci PMSU_BOOT_ADDR_REDIRECT_OFFSET(hw_cpu)); 1178c2ecf20Sopenharmony_ci} 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ciextern unsigned char mvebu_boot_wa_start[]; 1208c2ecf20Sopenharmony_ciextern unsigned char mvebu_boot_wa_end[]; 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci/* 1238c2ecf20Sopenharmony_ci * This function sets up the boot address workaround needed for SMP 1248c2ecf20Sopenharmony_ci * boot on Armada 375 Z1 and cpuidle on Armada 370. It unmaps the 1258c2ecf20Sopenharmony_ci * BootROM Mbus window, and instead remaps a crypto SRAM into which a 1268c2ecf20Sopenharmony_ci * custom piece of code is copied to replace the problematic BootROM. 1278c2ecf20Sopenharmony_ci */ 1288c2ecf20Sopenharmony_ciint mvebu_setup_boot_addr_wa(unsigned int crypto_eng_target, 1298c2ecf20Sopenharmony_ci unsigned int crypto_eng_attribute, 1308c2ecf20Sopenharmony_ci phys_addr_t resume_addr_reg) 1318c2ecf20Sopenharmony_ci{ 1328c2ecf20Sopenharmony_ci void __iomem *sram_virt_base; 1338c2ecf20Sopenharmony_ci u32 code_len = mvebu_boot_wa_end - mvebu_boot_wa_start; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci mvebu_mbus_del_window(BOOTROM_BASE, BOOTROM_SIZE); 1368c2ecf20Sopenharmony_ci mvebu_mbus_add_window_by_id(crypto_eng_target, crypto_eng_attribute, 1378c2ecf20Sopenharmony_ci SRAM_PHYS_BASE, SZ_64K); 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci sram_virt_base = ioremap(SRAM_PHYS_BASE, SZ_64K); 1408c2ecf20Sopenharmony_ci if (!sram_virt_base) { 1418c2ecf20Sopenharmony_ci pr_err("Unable to map SRAM to setup the boot address WA\n"); 1428c2ecf20Sopenharmony_ci return -ENOMEM; 1438c2ecf20Sopenharmony_ci } 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci memcpy(sram_virt_base, &mvebu_boot_wa_start, code_len); 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci /* 1488c2ecf20Sopenharmony_ci * The last word of the code copied in SRAM must contain the 1498c2ecf20Sopenharmony_ci * physical base address of the PMSU register. We 1508c2ecf20Sopenharmony_ci * intentionally store this address in the native endianness 1518c2ecf20Sopenharmony_ci * of the system. 1528c2ecf20Sopenharmony_ci */ 1538c2ecf20Sopenharmony_ci __raw_writel((unsigned long)resume_addr_reg, 1548c2ecf20Sopenharmony_ci sram_virt_base + code_len - 4); 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci iounmap(sram_virt_base); 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci return 0; 1598c2ecf20Sopenharmony_ci} 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_cistatic int __init mvebu_v7_pmsu_init(void) 1628c2ecf20Sopenharmony_ci{ 1638c2ecf20Sopenharmony_ci struct device_node *np; 1648c2ecf20Sopenharmony_ci struct resource res; 1658c2ecf20Sopenharmony_ci int ret = 0; 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci np = of_find_matching_node(NULL, of_pmsu_table); 1688c2ecf20Sopenharmony_ci if (!np) 1698c2ecf20Sopenharmony_ci return 0; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci pr_info("Initializing Power Management Service Unit\n"); 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci if (of_address_to_resource(np, 0, &res)) { 1748c2ecf20Sopenharmony_ci pr_err("unable to get resource\n"); 1758c2ecf20Sopenharmony_ci ret = -ENOENT; 1768c2ecf20Sopenharmony_ci goto out; 1778c2ecf20Sopenharmony_ci } 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci if (of_device_is_compatible(np, "marvell,armada-370-xp-pmsu")) { 1808c2ecf20Sopenharmony_ci pr_warn(FW_WARN "deprecated pmsu binding\n"); 1818c2ecf20Sopenharmony_ci res.start = res.start - PMSU_BASE_OFFSET; 1828c2ecf20Sopenharmony_ci res.end = res.start + PMSU_REG_SIZE - 1; 1838c2ecf20Sopenharmony_ci } 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci if (!request_mem_region(res.start, resource_size(&res), 1868c2ecf20Sopenharmony_ci np->full_name)) { 1878c2ecf20Sopenharmony_ci pr_err("unable to request region\n"); 1888c2ecf20Sopenharmony_ci ret = -EBUSY; 1898c2ecf20Sopenharmony_ci goto out; 1908c2ecf20Sopenharmony_ci } 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci pmsu_mp_phys_base = res.start; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci pmsu_mp_base = ioremap(res.start, resource_size(&res)); 1958c2ecf20Sopenharmony_ci if (!pmsu_mp_base) { 1968c2ecf20Sopenharmony_ci pr_err("unable to map registers\n"); 1978c2ecf20Sopenharmony_ci release_mem_region(res.start, resource_size(&res)); 1988c2ecf20Sopenharmony_ci ret = -ENOMEM; 1998c2ecf20Sopenharmony_ci goto out; 2008c2ecf20Sopenharmony_ci } 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci out: 2038c2ecf20Sopenharmony_ci of_node_put(np); 2048c2ecf20Sopenharmony_ci return ret; 2058c2ecf20Sopenharmony_ci} 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_cistatic void mvebu_v7_pmsu_enable_l2_powerdown_onidle(void) 2088c2ecf20Sopenharmony_ci{ 2098c2ecf20Sopenharmony_ci u32 reg; 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci if (pmsu_mp_base == NULL) 2128c2ecf20Sopenharmony_ci return; 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci /* Enable L2 & Fabric powerdown in Deep-Idle mode - Fabric */ 2158c2ecf20Sopenharmony_ci reg = readl(pmsu_mp_base + L2C_NFABRIC_PM_CTL); 2168c2ecf20Sopenharmony_ci reg |= L2C_NFABRIC_PM_CTL_PWR_DOWN; 2178c2ecf20Sopenharmony_ci writel(reg, pmsu_mp_base + L2C_NFABRIC_PM_CTL); 2188c2ecf20Sopenharmony_ci} 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_cienum pmsu_idle_prepare_flags { 2218c2ecf20Sopenharmony_ci PMSU_PREPARE_NORMAL = 0, 2228c2ecf20Sopenharmony_ci PMSU_PREPARE_DEEP_IDLE = BIT(0), 2238c2ecf20Sopenharmony_ci PMSU_PREPARE_SNOOP_DISABLE = BIT(1), 2248c2ecf20Sopenharmony_ci}; 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci/* No locking is needed because we only access per-CPU registers */ 2278c2ecf20Sopenharmony_cistatic int mvebu_v7_pmsu_idle_prepare(unsigned long flags) 2288c2ecf20Sopenharmony_ci{ 2298c2ecf20Sopenharmony_ci unsigned int hw_cpu = cpu_logical_map(smp_processor_id()); 2308c2ecf20Sopenharmony_ci u32 reg; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci if (pmsu_mp_base == NULL) 2338c2ecf20Sopenharmony_ci return -EINVAL; 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci /* 2368c2ecf20Sopenharmony_ci * Adjust the PMSU configuration to wait for WFI signal, enable 2378c2ecf20Sopenharmony_ci * IRQ and FIQ as wakeup events, set wait for snoop queue empty 2388c2ecf20Sopenharmony_ci * indication and mask IRQ and FIQ from CPU 2398c2ecf20Sopenharmony_ci */ 2408c2ecf20Sopenharmony_ci reg = readl(pmsu_mp_base + PMSU_STATUS_AND_MASK(hw_cpu)); 2418c2ecf20Sopenharmony_ci reg |= PMSU_STATUS_AND_MASK_CPU_IDLE_WAIT | 2428c2ecf20Sopenharmony_ci PMSU_STATUS_AND_MASK_IRQ_WAKEUP | 2438c2ecf20Sopenharmony_ci PMSU_STATUS_AND_MASK_FIQ_WAKEUP | 2448c2ecf20Sopenharmony_ci PMSU_STATUS_AND_MASK_SNP_Q_EMPTY_WAIT | 2458c2ecf20Sopenharmony_ci PMSU_STATUS_AND_MASK_IRQ_MASK | 2468c2ecf20Sopenharmony_ci PMSU_STATUS_AND_MASK_FIQ_MASK; 2478c2ecf20Sopenharmony_ci writel(reg, pmsu_mp_base + PMSU_STATUS_AND_MASK(hw_cpu)); 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci reg = readl(pmsu_mp_base + PMSU_CONTROL_AND_CONFIG(hw_cpu)); 2508c2ecf20Sopenharmony_ci /* ask HW to power down the L2 Cache if needed */ 2518c2ecf20Sopenharmony_ci if (flags & PMSU_PREPARE_DEEP_IDLE) 2528c2ecf20Sopenharmony_ci reg |= PMSU_CONTROL_AND_CONFIG_L2_PWDDN; 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci /* request power down */ 2558c2ecf20Sopenharmony_ci reg |= PMSU_CONTROL_AND_CONFIG_PWDDN_REQ; 2568c2ecf20Sopenharmony_ci writel(reg, pmsu_mp_base + PMSU_CONTROL_AND_CONFIG(hw_cpu)); 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci if (flags & PMSU_PREPARE_SNOOP_DISABLE) { 2598c2ecf20Sopenharmony_ci /* Disable snoop disable by HW - SW is taking care of it */ 2608c2ecf20Sopenharmony_ci reg = readl(pmsu_mp_base + PMSU_CPU_POWER_DOWN_CONTROL(hw_cpu)); 2618c2ecf20Sopenharmony_ci reg |= PMSU_CPU_POWER_DOWN_DIS_SNP_Q_SKIP; 2628c2ecf20Sopenharmony_ci writel(reg, pmsu_mp_base + PMSU_CPU_POWER_DOWN_CONTROL(hw_cpu)); 2638c2ecf20Sopenharmony_ci } 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_ci return 0; 2668c2ecf20Sopenharmony_ci} 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ciint armada_370_xp_pmsu_idle_enter(unsigned long deepidle) 2698c2ecf20Sopenharmony_ci{ 2708c2ecf20Sopenharmony_ci unsigned long flags = PMSU_PREPARE_SNOOP_DISABLE; 2718c2ecf20Sopenharmony_ci int ret; 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci if (deepidle) 2748c2ecf20Sopenharmony_ci flags |= PMSU_PREPARE_DEEP_IDLE; 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_ci ret = mvebu_v7_pmsu_idle_prepare(flags); 2778c2ecf20Sopenharmony_ci if (ret) 2788c2ecf20Sopenharmony_ci return ret; 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ci v7_exit_coherency_flush(all); 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_ci ll_disable_coherency(); 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_ci dsb(); 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci wfi(); 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci /* If we are here, wfi failed. As processors run out of 2898c2ecf20Sopenharmony_ci * coherency for some time, tlbs might be stale, so flush them 2908c2ecf20Sopenharmony_ci */ 2918c2ecf20Sopenharmony_ci local_flush_tlb_all(); 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ci ll_enable_coherency(); 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci /* Test the CR_C bit and set it if it was cleared */ 2968c2ecf20Sopenharmony_ci asm volatile( 2978c2ecf20Sopenharmony_ci "mrc p15, 0, r0, c1, c0, 0 \n\t" 2988c2ecf20Sopenharmony_ci "tst r0, %0 \n\t" 2998c2ecf20Sopenharmony_ci "orreq r0, r0, #(1 << 2) \n\t" 3008c2ecf20Sopenharmony_ci "mcreq p15, 0, r0, c1, c0, 0 \n\t" 3018c2ecf20Sopenharmony_ci "isb " 3028c2ecf20Sopenharmony_ci : : "Ir" (CR_C) : "r0"); 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ci pr_debug("Failed to suspend the system\n"); 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_ci return 0; 3078c2ecf20Sopenharmony_ci} 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_cistatic int armada_370_xp_cpu_suspend(unsigned long deepidle) 3108c2ecf20Sopenharmony_ci{ 3118c2ecf20Sopenharmony_ci return cpu_suspend(deepidle, armada_370_xp_pmsu_idle_enter); 3128c2ecf20Sopenharmony_ci} 3138c2ecf20Sopenharmony_ci 3148c2ecf20Sopenharmony_ciint armada_38x_do_cpu_suspend(unsigned long deepidle) 3158c2ecf20Sopenharmony_ci{ 3168c2ecf20Sopenharmony_ci unsigned long flags = 0; 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_ci if (deepidle) 3198c2ecf20Sopenharmony_ci flags |= PMSU_PREPARE_DEEP_IDLE; 3208c2ecf20Sopenharmony_ci 3218c2ecf20Sopenharmony_ci mvebu_v7_pmsu_idle_prepare(flags); 3228c2ecf20Sopenharmony_ci /* 3238c2ecf20Sopenharmony_ci * Already flushed cache, but do it again as the outer cache 3248c2ecf20Sopenharmony_ci * functions dirty the cache with spinlocks 3258c2ecf20Sopenharmony_ci */ 3268c2ecf20Sopenharmony_ci v7_exit_coherency_flush(louis); 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_ci scu_power_mode(mvebu_get_scu_base(), SCU_PM_POWEROFF); 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_ci cpu_do_idle(); 3318c2ecf20Sopenharmony_ci 3328c2ecf20Sopenharmony_ci return 1; 3338c2ecf20Sopenharmony_ci} 3348c2ecf20Sopenharmony_ci 3358c2ecf20Sopenharmony_cistatic int armada_38x_cpu_suspend(unsigned long deepidle) 3368c2ecf20Sopenharmony_ci{ 3378c2ecf20Sopenharmony_ci return cpu_suspend(false, armada_38x_do_cpu_suspend); 3388c2ecf20Sopenharmony_ci} 3398c2ecf20Sopenharmony_ci 3408c2ecf20Sopenharmony_ci/* No locking is needed because we only access per-CPU registers */ 3418c2ecf20Sopenharmony_civoid mvebu_v7_pmsu_idle_exit(void) 3428c2ecf20Sopenharmony_ci{ 3438c2ecf20Sopenharmony_ci unsigned int hw_cpu = cpu_logical_map(smp_processor_id()); 3448c2ecf20Sopenharmony_ci u32 reg; 3458c2ecf20Sopenharmony_ci 3468c2ecf20Sopenharmony_ci if (pmsu_mp_base == NULL) 3478c2ecf20Sopenharmony_ci return; 3488c2ecf20Sopenharmony_ci /* cancel ask HW to power down the L2 Cache if possible */ 3498c2ecf20Sopenharmony_ci reg = readl(pmsu_mp_base + PMSU_CONTROL_AND_CONFIG(hw_cpu)); 3508c2ecf20Sopenharmony_ci reg &= ~PMSU_CONTROL_AND_CONFIG_L2_PWDDN; 3518c2ecf20Sopenharmony_ci writel(reg, pmsu_mp_base + PMSU_CONTROL_AND_CONFIG(hw_cpu)); 3528c2ecf20Sopenharmony_ci 3538c2ecf20Sopenharmony_ci /* cancel Enable wakeup events and mask interrupts */ 3548c2ecf20Sopenharmony_ci reg = readl(pmsu_mp_base + PMSU_STATUS_AND_MASK(hw_cpu)); 3558c2ecf20Sopenharmony_ci reg &= ~(PMSU_STATUS_AND_MASK_IRQ_WAKEUP | PMSU_STATUS_AND_MASK_FIQ_WAKEUP); 3568c2ecf20Sopenharmony_ci reg &= ~PMSU_STATUS_AND_MASK_CPU_IDLE_WAIT; 3578c2ecf20Sopenharmony_ci reg &= ~PMSU_STATUS_AND_MASK_SNP_Q_EMPTY_WAIT; 3588c2ecf20Sopenharmony_ci reg &= ~(PMSU_STATUS_AND_MASK_IRQ_MASK | PMSU_STATUS_AND_MASK_FIQ_MASK); 3598c2ecf20Sopenharmony_ci writel(reg, pmsu_mp_base + PMSU_STATUS_AND_MASK(hw_cpu)); 3608c2ecf20Sopenharmony_ci} 3618c2ecf20Sopenharmony_ci 3628c2ecf20Sopenharmony_cistatic int mvebu_v7_cpu_pm_notify(struct notifier_block *self, 3638c2ecf20Sopenharmony_ci unsigned long action, void *hcpu) 3648c2ecf20Sopenharmony_ci{ 3658c2ecf20Sopenharmony_ci if (action == CPU_PM_ENTER) { 3668c2ecf20Sopenharmony_ci unsigned int hw_cpu = cpu_logical_map(smp_processor_id()); 3678c2ecf20Sopenharmony_ci mvebu_pmsu_set_cpu_boot_addr(hw_cpu, mvebu_cpu_resume); 3688c2ecf20Sopenharmony_ci } else if (action == CPU_PM_EXIT) { 3698c2ecf20Sopenharmony_ci mvebu_v7_pmsu_idle_exit(); 3708c2ecf20Sopenharmony_ci } 3718c2ecf20Sopenharmony_ci 3728c2ecf20Sopenharmony_ci return NOTIFY_OK; 3738c2ecf20Sopenharmony_ci} 3748c2ecf20Sopenharmony_ci 3758c2ecf20Sopenharmony_cistatic struct notifier_block mvebu_v7_cpu_pm_notifier = { 3768c2ecf20Sopenharmony_ci .notifier_call = mvebu_v7_cpu_pm_notify, 3778c2ecf20Sopenharmony_ci}; 3788c2ecf20Sopenharmony_ci 3798c2ecf20Sopenharmony_cistatic struct platform_device mvebu_v7_cpuidle_device; 3808c2ecf20Sopenharmony_ci 3818c2ecf20Sopenharmony_cistatic int broken_idle(struct device_node *np) 3828c2ecf20Sopenharmony_ci{ 3838c2ecf20Sopenharmony_ci if (of_property_read_bool(np, "broken-idle")) { 3848c2ecf20Sopenharmony_ci pr_warn("CPU idle is currently broken: disabling\n"); 3858c2ecf20Sopenharmony_ci return 1; 3868c2ecf20Sopenharmony_ci } 3878c2ecf20Sopenharmony_ci 3888c2ecf20Sopenharmony_ci return 0; 3898c2ecf20Sopenharmony_ci} 3908c2ecf20Sopenharmony_ci 3918c2ecf20Sopenharmony_cistatic __init int armada_370_cpuidle_init(void) 3928c2ecf20Sopenharmony_ci{ 3938c2ecf20Sopenharmony_ci struct device_node *np; 3948c2ecf20Sopenharmony_ci phys_addr_t redirect_reg; 3958c2ecf20Sopenharmony_ci 3968c2ecf20Sopenharmony_ci np = of_find_compatible_node(NULL, NULL, "marvell,coherency-fabric"); 3978c2ecf20Sopenharmony_ci if (!np) 3988c2ecf20Sopenharmony_ci return -ENODEV; 3998c2ecf20Sopenharmony_ci 4008c2ecf20Sopenharmony_ci if (broken_idle(np)) 4018c2ecf20Sopenharmony_ci goto end; 4028c2ecf20Sopenharmony_ci 4038c2ecf20Sopenharmony_ci /* 4048c2ecf20Sopenharmony_ci * On Armada 370, there is "a slow exit process from the deep 4058c2ecf20Sopenharmony_ci * idle state due to heavy L1/L2 cache cleanup operations 4068c2ecf20Sopenharmony_ci * performed by the BootROM software". To avoid this, we 4078c2ecf20Sopenharmony_ci * replace the restart code of the bootrom by a a simple jump 4088c2ecf20Sopenharmony_ci * to the boot address. Then the code located at this boot 4098c2ecf20Sopenharmony_ci * address will take care of the initialization. 4108c2ecf20Sopenharmony_ci */ 4118c2ecf20Sopenharmony_ci redirect_reg = pmsu_mp_phys_base + PMSU_BOOT_ADDR_REDIRECT_OFFSET(0); 4128c2ecf20Sopenharmony_ci mvebu_setup_boot_addr_wa(ARMADA_370_CRYPT0_ENG_TARGET, 4138c2ecf20Sopenharmony_ci ARMADA_370_CRYPT0_ENG_ATTR, 4148c2ecf20Sopenharmony_ci redirect_reg); 4158c2ecf20Sopenharmony_ci 4168c2ecf20Sopenharmony_ci mvebu_cpu_resume = armada_370_xp_cpu_resume; 4178c2ecf20Sopenharmony_ci mvebu_v7_cpuidle_device.dev.platform_data = armada_370_xp_cpu_suspend; 4188c2ecf20Sopenharmony_ci mvebu_v7_cpuidle_device.name = "cpuidle-armada-370"; 4198c2ecf20Sopenharmony_ci 4208c2ecf20Sopenharmony_ciend: 4218c2ecf20Sopenharmony_ci of_node_put(np); 4228c2ecf20Sopenharmony_ci return 0; 4238c2ecf20Sopenharmony_ci} 4248c2ecf20Sopenharmony_ci 4258c2ecf20Sopenharmony_cistatic __init int armada_38x_cpuidle_init(void) 4268c2ecf20Sopenharmony_ci{ 4278c2ecf20Sopenharmony_ci struct device_node *np; 4288c2ecf20Sopenharmony_ci void __iomem *mpsoc_base; 4298c2ecf20Sopenharmony_ci u32 reg; 4308c2ecf20Sopenharmony_ci 4318c2ecf20Sopenharmony_ci pr_warn("CPU idle is currently broken on Armada 38x: disabling\n"); 4328c2ecf20Sopenharmony_ci return 0; 4338c2ecf20Sopenharmony_ci 4348c2ecf20Sopenharmony_ci np = of_find_compatible_node(NULL, NULL, 4358c2ecf20Sopenharmony_ci "marvell,armada-380-coherency-fabric"); 4368c2ecf20Sopenharmony_ci if (!np) 4378c2ecf20Sopenharmony_ci return -ENODEV; 4388c2ecf20Sopenharmony_ci 4398c2ecf20Sopenharmony_ci if (broken_idle(np)) 4408c2ecf20Sopenharmony_ci goto end; 4418c2ecf20Sopenharmony_ci 4428c2ecf20Sopenharmony_ci of_node_put(np); 4438c2ecf20Sopenharmony_ci 4448c2ecf20Sopenharmony_ci np = of_find_compatible_node(NULL, NULL, 4458c2ecf20Sopenharmony_ci "marvell,armada-380-mpcore-soc-ctrl"); 4468c2ecf20Sopenharmony_ci if (!np) 4478c2ecf20Sopenharmony_ci return -ENODEV; 4488c2ecf20Sopenharmony_ci mpsoc_base = of_iomap(np, 0); 4498c2ecf20Sopenharmony_ci BUG_ON(!mpsoc_base); 4508c2ecf20Sopenharmony_ci 4518c2ecf20Sopenharmony_ci /* Set up reset mask when powering down the cpus */ 4528c2ecf20Sopenharmony_ci reg = readl(mpsoc_base + MPCORE_RESET_CTL); 4538c2ecf20Sopenharmony_ci reg |= MPCORE_RESET_CTL_L2; 4548c2ecf20Sopenharmony_ci reg |= MPCORE_RESET_CTL_DEBUG; 4558c2ecf20Sopenharmony_ci writel(reg, mpsoc_base + MPCORE_RESET_CTL); 4568c2ecf20Sopenharmony_ci iounmap(mpsoc_base); 4578c2ecf20Sopenharmony_ci 4588c2ecf20Sopenharmony_ci /* Set up delay */ 4598c2ecf20Sopenharmony_ci reg = readl(pmsu_mp_base + PMSU_POWERDOWN_DELAY); 4608c2ecf20Sopenharmony_ci reg &= ~PMSU_POWERDOWN_DELAY_MASK; 4618c2ecf20Sopenharmony_ci reg |= PMSU_DFLT_ARMADA38X_DELAY; 4628c2ecf20Sopenharmony_ci reg |= PMSU_POWERDOWN_DELAY_PMU; 4638c2ecf20Sopenharmony_ci writel(reg, pmsu_mp_base + PMSU_POWERDOWN_DELAY); 4648c2ecf20Sopenharmony_ci 4658c2ecf20Sopenharmony_ci mvebu_cpu_resume = armada_38x_cpu_resume; 4668c2ecf20Sopenharmony_ci mvebu_v7_cpuidle_device.dev.platform_data = armada_38x_cpu_suspend; 4678c2ecf20Sopenharmony_ci mvebu_v7_cpuidle_device.name = "cpuidle-armada-38x"; 4688c2ecf20Sopenharmony_ci 4698c2ecf20Sopenharmony_ciend: 4708c2ecf20Sopenharmony_ci of_node_put(np); 4718c2ecf20Sopenharmony_ci return 0; 4728c2ecf20Sopenharmony_ci} 4738c2ecf20Sopenharmony_ci 4748c2ecf20Sopenharmony_cistatic __init int armada_xp_cpuidle_init(void) 4758c2ecf20Sopenharmony_ci{ 4768c2ecf20Sopenharmony_ci struct device_node *np; 4778c2ecf20Sopenharmony_ci 4788c2ecf20Sopenharmony_ci np = of_find_compatible_node(NULL, NULL, "marvell,coherency-fabric"); 4798c2ecf20Sopenharmony_ci if (!np) 4808c2ecf20Sopenharmony_ci return -ENODEV; 4818c2ecf20Sopenharmony_ci 4828c2ecf20Sopenharmony_ci if (broken_idle(np)) 4838c2ecf20Sopenharmony_ci goto end; 4848c2ecf20Sopenharmony_ci 4858c2ecf20Sopenharmony_ci mvebu_cpu_resume = armada_370_xp_cpu_resume; 4868c2ecf20Sopenharmony_ci mvebu_v7_cpuidle_device.dev.platform_data = armada_370_xp_cpu_suspend; 4878c2ecf20Sopenharmony_ci mvebu_v7_cpuidle_device.name = "cpuidle-armada-xp"; 4888c2ecf20Sopenharmony_ci 4898c2ecf20Sopenharmony_ciend: 4908c2ecf20Sopenharmony_ci of_node_put(np); 4918c2ecf20Sopenharmony_ci return 0; 4928c2ecf20Sopenharmony_ci} 4938c2ecf20Sopenharmony_ci 4948c2ecf20Sopenharmony_cistatic int __init mvebu_v7_cpu_pm_init(void) 4958c2ecf20Sopenharmony_ci{ 4968c2ecf20Sopenharmony_ci struct device_node *np; 4978c2ecf20Sopenharmony_ci int ret; 4988c2ecf20Sopenharmony_ci 4998c2ecf20Sopenharmony_ci np = of_find_matching_node(NULL, of_pmsu_table); 5008c2ecf20Sopenharmony_ci if (!np) 5018c2ecf20Sopenharmony_ci return 0; 5028c2ecf20Sopenharmony_ci of_node_put(np); 5038c2ecf20Sopenharmony_ci 5048c2ecf20Sopenharmony_ci /* 5058c2ecf20Sopenharmony_ci * Currently the CPU idle support for Armada 38x is broken, as 5068c2ecf20Sopenharmony_ci * the CPU hotplug uses some of the CPU idle functions it is 5078c2ecf20Sopenharmony_ci * broken too, so let's disable it 5088c2ecf20Sopenharmony_ci */ 5098c2ecf20Sopenharmony_ci if (of_machine_is_compatible("marvell,armada380")) { 5108c2ecf20Sopenharmony_ci cpu_hotplug_disable(); 5118c2ecf20Sopenharmony_ci pr_warn("CPU hotplug support is currently broken on Armada 38x: disabling\n"); 5128c2ecf20Sopenharmony_ci } 5138c2ecf20Sopenharmony_ci 5148c2ecf20Sopenharmony_ci if (of_machine_is_compatible("marvell,armadaxp")) 5158c2ecf20Sopenharmony_ci ret = armada_xp_cpuidle_init(); 5168c2ecf20Sopenharmony_ci else if (of_machine_is_compatible("marvell,armada370")) 5178c2ecf20Sopenharmony_ci ret = armada_370_cpuidle_init(); 5188c2ecf20Sopenharmony_ci else if (of_machine_is_compatible("marvell,armada380")) 5198c2ecf20Sopenharmony_ci ret = armada_38x_cpuidle_init(); 5208c2ecf20Sopenharmony_ci else 5218c2ecf20Sopenharmony_ci return 0; 5228c2ecf20Sopenharmony_ci 5238c2ecf20Sopenharmony_ci if (ret) 5248c2ecf20Sopenharmony_ci return ret; 5258c2ecf20Sopenharmony_ci 5268c2ecf20Sopenharmony_ci mvebu_v7_pmsu_enable_l2_powerdown_onidle(); 5278c2ecf20Sopenharmony_ci if (mvebu_v7_cpuidle_device.name) 5288c2ecf20Sopenharmony_ci platform_device_register(&mvebu_v7_cpuidle_device); 5298c2ecf20Sopenharmony_ci cpu_pm_register_notifier(&mvebu_v7_cpu_pm_notifier); 5308c2ecf20Sopenharmony_ci 5318c2ecf20Sopenharmony_ci return 0; 5328c2ecf20Sopenharmony_ci} 5338c2ecf20Sopenharmony_ci 5348c2ecf20Sopenharmony_ciarch_initcall(mvebu_v7_cpu_pm_init); 5358c2ecf20Sopenharmony_ciearly_initcall(mvebu_v7_pmsu_init); 5368c2ecf20Sopenharmony_ci 5378c2ecf20Sopenharmony_cistatic void mvebu_pmsu_dfs_request_local(void *data) 5388c2ecf20Sopenharmony_ci{ 5398c2ecf20Sopenharmony_ci u32 reg; 5408c2ecf20Sopenharmony_ci u32 cpu = smp_processor_id(); 5418c2ecf20Sopenharmony_ci unsigned long flags; 5428c2ecf20Sopenharmony_ci 5438c2ecf20Sopenharmony_ci local_irq_save(flags); 5448c2ecf20Sopenharmony_ci 5458c2ecf20Sopenharmony_ci /* Prepare to enter idle */ 5468c2ecf20Sopenharmony_ci reg = readl(pmsu_mp_base + PMSU_STATUS_AND_MASK(cpu)); 5478c2ecf20Sopenharmony_ci reg |= PMSU_STATUS_AND_MASK_CPU_IDLE_WAIT | 5488c2ecf20Sopenharmony_ci PMSU_STATUS_AND_MASK_IRQ_MASK | 5498c2ecf20Sopenharmony_ci PMSU_STATUS_AND_MASK_FIQ_MASK; 5508c2ecf20Sopenharmony_ci writel(reg, pmsu_mp_base + PMSU_STATUS_AND_MASK(cpu)); 5518c2ecf20Sopenharmony_ci 5528c2ecf20Sopenharmony_ci /* Request the DFS transition */ 5538c2ecf20Sopenharmony_ci reg = readl(pmsu_mp_base + PMSU_CONTROL_AND_CONFIG(cpu)); 5548c2ecf20Sopenharmony_ci reg |= PMSU_CONTROL_AND_CONFIG_DFS_REQ; 5558c2ecf20Sopenharmony_ci writel(reg, pmsu_mp_base + PMSU_CONTROL_AND_CONFIG(cpu)); 5568c2ecf20Sopenharmony_ci 5578c2ecf20Sopenharmony_ci /* The fact of entering idle will trigger the DFS transition */ 5588c2ecf20Sopenharmony_ci wfi(); 5598c2ecf20Sopenharmony_ci 5608c2ecf20Sopenharmony_ci /* 5618c2ecf20Sopenharmony_ci * We're back from idle, the DFS transition has completed, 5628c2ecf20Sopenharmony_ci * clear the idle wait indication. 5638c2ecf20Sopenharmony_ci */ 5648c2ecf20Sopenharmony_ci reg = readl(pmsu_mp_base + PMSU_STATUS_AND_MASK(cpu)); 5658c2ecf20Sopenharmony_ci reg &= ~PMSU_STATUS_AND_MASK_CPU_IDLE_WAIT; 5668c2ecf20Sopenharmony_ci writel(reg, pmsu_mp_base + PMSU_STATUS_AND_MASK(cpu)); 5678c2ecf20Sopenharmony_ci 5688c2ecf20Sopenharmony_ci local_irq_restore(flags); 5698c2ecf20Sopenharmony_ci} 5708c2ecf20Sopenharmony_ci 5718c2ecf20Sopenharmony_ciint mvebu_pmsu_dfs_request(int cpu) 5728c2ecf20Sopenharmony_ci{ 5738c2ecf20Sopenharmony_ci unsigned long timeout; 5748c2ecf20Sopenharmony_ci int hwcpu = cpu_logical_map(cpu); 5758c2ecf20Sopenharmony_ci u32 reg; 5768c2ecf20Sopenharmony_ci 5778c2ecf20Sopenharmony_ci /* Clear any previous DFS DONE event */ 5788c2ecf20Sopenharmony_ci reg = readl(pmsu_mp_base + PMSU_EVENT_STATUS_AND_MASK(hwcpu)); 5798c2ecf20Sopenharmony_ci reg &= ~PMSU_EVENT_STATUS_AND_MASK_DFS_DONE; 5808c2ecf20Sopenharmony_ci writel(reg, pmsu_mp_base + PMSU_EVENT_STATUS_AND_MASK(hwcpu)); 5818c2ecf20Sopenharmony_ci 5828c2ecf20Sopenharmony_ci /* Mask the DFS done interrupt, since we are going to poll */ 5838c2ecf20Sopenharmony_ci reg = readl(pmsu_mp_base + PMSU_EVENT_STATUS_AND_MASK(hwcpu)); 5848c2ecf20Sopenharmony_ci reg |= PMSU_EVENT_STATUS_AND_MASK_DFS_DONE_MASK; 5858c2ecf20Sopenharmony_ci writel(reg, pmsu_mp_base + PMSU_EVENT_STATUS_AND_MASK(hwcpu)); 5868c2ecf20Sopenharmony_ci 5878c2ecf20Sopenharmony_ci /* Trigger the DFS on the appropriate CPU */ 5888c2ecf20Sopenharmony_ci smp_call_function_single(cpu, mvebu_pmsu_dfs_request_local, 5898c2ecf20Sopenharmony_ci NULL, false); 5908c2ecf20Sopenharmony_ci 5918c2ecf20Sopenharmony_ci /* Poll until the DFS done event is generated */ 5928c2ecf20Sopenharmony_ci timeout = jiffies + HZ; 5938c2ecf20Sopenharmony_ci while (time_before(jiffies, timeout)) { 5948c2ecf20Sopenharmony_ci reg = readl(pmsu_mp_base + PMSU_EVENT_STATUS_AND_MASK(hwcpu)); 5958c2ecf20Sopenharmony_ci if (reg & PMSU_EVENT_STATUS_AND_MASK_DFS_DONE) 5968c2ecf20Sopenharmony_ci break; 5978c2ecf20Sopenharmony_ci udelay(10); 5988c2ecf20Sopenharmony_ci } 5998c2ecf20Sopenharmony_ci 6008c2ecf20Sopenharmony_ci if (time_after(jiffies, timeout)) 6018c2ecf20Sopenharmony_ci return -ETIME; 6028c2ecf20Sopenharmony_ci 6038c2ecf20Sopenharmony_ci /* Restore the DFS mask to its original state */ 6048c2ecf20Sopenharmony_ci reg = readl(pmsu_mp_base + PMSU_EVENT_STATUS_AND_MASK(hwcpu)); 6058c2ecf20Sopenharmony_ci reg &= ~PMSU_EVENT_STATUS_AND_MASK_DFS_DONE_MASK; 6068c2ecf20Sopenharmony_ci writel(reg, pmsu_mp_base + PMSU_EVENT_STATUS_AND_MASK(hwcpu)); 6078c2ecf20Sopenharmony_ci 6088c2ecf20Sopenharmony_ci return 0; 6098c2ecf20Sopenharmony_ci} 610