18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * Suspend/resume support. Currently supporting Armada XP only. 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Copyright (C) 2014 Marvell 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * Thomas Petazzoni <thomas.petazzoni@free-electrons.com> 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * This file is licensed under the terms of the GNU General Public 98c2ecf20Sopenharmony_ci * License version 2. This program is licensed "as is" without any 108c2ecf20Sopenharmony_ci * warranty of any kind, whether express or implied. 118c2ecf20Sopenharmony_ci */ 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <linux/cpu_pm.h> 148c2ecf20Sopenharmony_ci#include <linux/delay.h> 158c2ecf20Sopenharmony_ci#include <linux/gpio.h> 168c2ecf20Sopenharmony_ci#include <linux/io.h> 178c2ecf20Sopenharmony_ci#include <linux/kernel.h> 188c2ecf20Sopenharmony_ci#include <linux/mbus.h> 198c2ecf20Sopenharmony_ci#include <linux/of_address.h> 208c2ecf20Sopenharmony_ci#include <linux/suspend.h> 218c2ecf20Sopenharmony_ci#include <asm/cacheflush.h> 228c2ecf20Sopenharmony_ci#include <asm/outercache.h> 238c2ecf20Sopenharmony_ci#include <asm/suspend.h> 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci#include "coherency.h" 268c2ecf20Sopenharmony_ci#include "common.h" 278c2ecf20Sopenharmony_ci#include "pmsu.h" 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci#define SDRAM_CONFIG_OFFS 0x0 308c2ecf20Sopenharmony_ci#define SDRAM_CONFIG_SR_MODE_BIT BIT(24) 318c2ecf20Sopenharmony_ci#define SDRAM_OPERATION_OFFS 0x18 328c2ecf20Sopenharmony_ci#define SDRAM_OPERATION_SELF_REFRESH 0x7 338c2ecf20Sopenharmony_ci#define SDRAM_DLB_EVICTION_OFFS 0x30c 348c2ecf20Sopenharmony_ci#define SDRAM_DLB_EVICTION_THRESHOLD_MASK 0xff 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_cistatic void (*mvebu_board_pm_enter)(void __iomem *sdram_reg, u32 srcmd); 378c2ecf20Sopenharmony_cistatic void __iomem *sdram_ctrl; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_cistatic int mvebu_pm_powerdown(unsigned long data) 408c2ecf20Sopenharmony_ci{ 418c2ecf20Sopenharmony_ci u32 reg, srcmd; 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci flush_cache_all(); 448c2ecf20Sopenharmony_ci outer_flush_all(); 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci /* 478c2ecf20Sopenharmony_ci * Issue a Data Synchronization Barrier instruction to ensure 488c2ecf20Sopenharmony_ci * that all state saving has been completed. 498c2ecf20Sopenharmony_ci */ 508c2ecf20Sopenharmony_ci dsb(); 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci /* Flush the DLB and wait ~7 usec */ 538c2ecf20Sopenharmony_ci reg = readl(sdram_ctrl + SDRAM_DLB_EVICTION_OFFS); 548c2ecf20Sopenharmony_ci reg &= ~SDRAM_DLB_EVICTION_THRESHOLD_MASK; 558c2ecf20Sopenharmony_ci writel(reg, sdram_ctrl + SDRAM_DLB_EVICTION_OFFS); 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci udelay(7); 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci /* Set DRAM in battery backup mode */ 608c2ecf20Sopenharmony_ci reg = readl(sdram_ctrl + SDRAM_CONFIG_OFFS); 618c2ecf20Sopenharmony_ci reg &= ~SDRAM_CONFIG_SR_MODE_BIT; 628c2ecf20Sopenharmony_ci writel(reg, sdram_ctrl + SDRAM_CONFIG_OFFS); 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci /* Prepare to go to self-refresh */ 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci srcmd = readl(sdram_ctrl + SDRAM_OPERATION_OFFS); 678c2ecf20Sopenharmony_ci srcmd &= ~0x1F; 688c2ecf20Sopenharmony_ci srcmd |= SDRAM_OPERATION_SELF_REFRESH; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci mvebu_board_pm_enter(sdram_ctrl + SDRAM_OPERATION_OFFS, srcmd); 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci return 0; 738c2ecf20Sopenharmony_ci} 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci#define BOOT_INFO_ADDR 0x3000 768c2ecf20Sopenharmony_ci#define BOOT_MAGIC_WORD 0xdeadb002 778c2ecf20Sopenharmony_ci#define BOOT_MAGIC_LIST_END 0xffffffff 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci/* 808c2ecf20Sopenharmony_ci * Those registers are accessed before switching the internal register 818c2ecf20Sopenharmony_ci * base, which is why we hardcode the 0xd0000000 base address, the one 828c2ecf20Sopenharmony_ci * used by the SoC out of reset. 838c2ecf20Sopenharmony_ci */ 848c2ecf20Sopenharmony_ci#define MBUS_WINDOW_12_CTRL 0xd00200b0 858c2ecf20Sopenharmony_ci#define MBUS_INTERNAL_REG_ADDRESS 0xd0020080 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci#define SDRAM_WIN_BASE_REG(x) (0x20180 + (0x8*x)) 888c2ecf20Sopenharmony_ci#define SDRAM_WIN_CTRL_REG(x) (0x20184 + (0x8*x)) 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_cistatic phys_addr_t mvebu_internal_reg_base(void) 918c2ecf20Sopenharmony_ci{ 928c2ecf20Sopenharmony_ci struct device_node *np; 938c2ecf20Sopenharmony_ci __be32 in_addr[2]; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci np = of_find_node_by_name(NULL, "internal-regs"); 968c2ecf20Sopenharmony_ci BUG_ON(!np); 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci /* 998c2ecf20Sopenharmony_ci * Ask the DT what is the internal register address on this 1008c2ecf20Sopenharmony_ci * platform. In the mvebu-mbus DT binding, 0xf0010000 1018c2ecf20Sopenharmony_ci * corresponds to the internal register window. 1028c2ecf20Sopenharmony_ci */ 1038c2ecf20Sopenharmony_ci in_addr[0] = cpu_to_be32(0xf0010000); 1048c2ecf20Sopenharmony_ci in_addr[1] = 0x0; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci return of_translate_address(np, in_addr); 1078c2ecf20Sopenharmony_ci} 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_cistatic void mvebu_pm_store_armadaxp_bootinfo(u32 *store_addr) 1108c2ecf20Sopenharmony_ci{ 1118c2ecf20Sopenharmony_ci phys_addr_t resume_pc; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci resume_pc = __pa_symbol(armada_370_xp_cpu_resume); 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci /* 1168c2ecf20Sopenharmony_ci * The bootloader expects the first two words to be a magic 1178c2ecf20Sopenharmony_ci * value (BOOT_MAGIC_WORD), followed by the address of the 1188c2ecf20Sopenharmony_ci * resume code to jump to. Then, it expects a sequence of 1198c2ecf20Sopenharmony_ci * (address, value) pairs, which can be used to restore the 1208c2ecf20Sopenharmony_ci * value of certain registers. This sequence must end with the 1218c2ecf20Sopenharmony_ci * BOOT_MAGIC_LIST_END magic value. 1228c2ecf20Sopenharmony_ci */ 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci writel(BOOT_MAGIC_WORD, store_addr++); 1258c2ecf20Sopenharmony_ci writel(resume_pc, store_addr++); 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci /* 1288c2ecf20Sopenharmony_ci * Some platforms remap their internal register base address 1298c2ecf20Sopenharmony_ci * to 0xf1000000. However, out of reset, window 12 starts at 1308c2ecf20Sopenharmony_ci * 0xf0000000 and ends at 0xf7ffffff, which would overlap with 1318c2ecf20Sopenharmony_ci * the internal registers. Therefore, disable window 12. 1328c2ecf20Sopenharmony_ci */ 1338c2ecf20Sopenharmony_ci writel(MBUS_WINDOW_12_CTRL, store_addr++); 1348c2ecf20Sopenharmony_ci writel(0x0, store_addr++); 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci /* 1378c2ecf20Sopenharmony_ci * Set the internal register base address to the value 1388c2ecf20Sopenharmony_ci * expected by Linux, as read from the Device Tree. 1398c2ecf20Sopenharmony_ci */ 1408c2ecf20Sopenharmony_ci writel(MBUS_INTERNAL_REG_ADDRESS, store_addr++); 1418c2ecf20Sopenharmony_ci writel(mvebu_internal_reg_base(), store_addr++); 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci /* 1448c2ecf20Sopenharmony_ci * Ask the mvebu-mbus driver to store the SDRAM window 1458c2ecf20Sopenharmony_ci * configuration, which has to be restored by the bootloader 1468c2ecf20Sopenharmony_ci * before re-entering the kernel on resume. 1478c2ecf20Sopenharmony_ci */ 1488c2ecf20Sopenharmony_ci store_addr += mvebu_mbus_save_cpu_target(store_addr); 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci writel(BOOT_MAGIC_LIST_END, store_addr); 1518c2ecf20Sopenharmony_ci} 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_cistatic int mvebu_pm_store_bootinfo(void) 1548c2ecf20Sopenharmony_ci{ 1558c2ecf20Sopenharmony_ci u32 *store_addr; 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci store_addr = phys_to_virt(BOOT_INFO_ADDR); 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci if (of_machine_is_compatible("marvell,armadaxp")) 1608c2ecf20Sopenharmony_ci mvebu_pm_store_armadaxp_bootinfo(store_addr); 1618c2ecf20Sopenharmony_ci else 1628c2ecf20Sopenharmony_ci return -ENODEV; 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci return 0; 1658c2ecf20Sopenharmony_ci} 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_cistatic int mvebu_enter_suspend(void) 1688c2ecf20Sopenharmony_ci{ 1698c2ecf20Sopenharmony_ci int ret; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci ret = mvebu_pm_store_bootinfo(); 1728c2ecf20Sopenharmony_ci if (ret) 1738c2ecf20Sopenharmony_ci return ret; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci cpu_pm_enter(); 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci cpu_suspend(0, mvebu_pm_powerdown); 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci outer_resume(); 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci mvebu_v7_pmsu_idle_exit(); 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci set_cpu_coherent(); 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci cpu_pm_exit(); 1868c2ecf20Sopenharmony_ci return 0; 1878c2ecf20Sopenharmony_ci} 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_cistatic int mvebu_pm_enter(suspend_state_t state) 1908c2ecf20Sopenharmony_ci{ 1918c2ecf20Sopenharmony_ci switch (state) { 1928c2ecf20Sopenharmony_ci case PM_SUSPEND_STANDBY: 1938c2ecf20Sopenharmony_ci cpu_do_idle(); 1948c2ecf20Sopenharmony_ci break; 1958c2ecf20Sopenharmony_ci case PM_SUSPEND_MEM: 1968c2ecf20Sopenharmony_ci pr_warn("Entering suspend to RAM. Only special wake-up sources will resume the system\n"); 1978c2ecf20Sopenharmony_ci return mvebu_enter_suspend(); 1988c2ecf20Sopenharmony_ci default: 1998c2ecf20Sopenharmony_ci return -EINVAL; 2008c2ecf20Sopenharmony_ci } 2018c2ecf20Sopenharmony_ci return 0; 2028c2ecf20Sopenharmony_ci} 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_cistatic int mvebu_pm_valid(suspend_state_t state) 2058c2ecf20Sopenharmony_ci{ 2068c2ecf20Sopenharmony_ci if (state == PM_SUSPEND_STANDBY) 2078c2ecf20Sopenharmony_ci return 1; 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci if (state == PM_SUSPEND_MEM && mvebu_board_pm_enter != NULL) 2108c2ecf20Sopenharmony_ci return 1; 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci return 0; 2138c2ecf20Sopenharmony_ci} 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_cistatic const struct platform_suspend_ops mvebu_pm_ops = { 2168c2ecf20Sopenharmony_ci .enter = mvebu_pm_enter, 2178c2ecf20Sopenharmony_ci .valid = mvebu_pm_valid, 2188c2ecf20Sopenharmony_ci}; 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_cistatic int __init mvebu_pm_init(void) 2218c2ecf20Sopenharmony_ci{ 2228c2ecf20Sopenharmony_ci if (!of_machine_is_compatible("marvell,armadaxp") && 2238c2ecf20Sopenharmony_ci !of_machine_is_compatible("marvell,armada370") && 2248c2ecf20Sopenharmony_ci !of_machine_is_compatible("marvell,armada380") && 2258c2ecf20Sopenharmony_ci !of_machine_is_compatible("marvell,armada390")) 2268c2ecf20Sopenharmony_ci return -ENODEV; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci suspend_set_ops(&mvebu_pm_ops); 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci return 0; 2318c2ecf20Sopenharmony_ci} 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_cilate_initcall(mvebu_pm_init); 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ciint __init mvebu_pm_suspend_init(void (*board_pm_enter)(void __iomem *sdram_reg, 2378c2ecf20Sopenharmony_ci u32 srcmd)) 2388c2ecf20Sopenharmony_ci{ 2398c2ecf20Sopenharmony_ci struct device_node *np; 2408c2ecf20Sopenharmony_ci struct resource res; 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci np = of_find_compatible_node(NULL, NULL, 2438c2ecf20Sopenharmony_ci "marvell,armada-xp-sdram-controller"); 2448c2ecf20Sopenharmony_ci if (!np) 2458c2ecf20Sopenharmony_ci return -ENODEV; 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci if (of_address_to_resource(np, 0, &res)) { 2488c2ecf20Sopenharmony_ci of_node_put(np); 2498c2ecf20Sopenharmony_ci return -ENODEV; 2508c2ecf20Sopenharmony_ci } 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci if (!request_mem_region(res.start, resource_size(&res), 2538c2ecf20Sopenharmony_ci np->full_name)) { 2548c2ecf20Sopenharmony_ci of_node_put(np); 2558c2ecf20Sopenharmony_ci return -EBUSY; 2568c2ecf20Sopenharmony_ci } 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci sdram_ctrl = ioremap(res.start, resource_size(&res)); 2598c2ecf20Sopenharmony_ci if (!sdram_ctrl) { 2608c2ecf20Sopenharmony_ci release_mem_region(res.start, resource_size(&res)); 2618c2ecf20Sopenharmony_ci of_node_put(np); 2628c2ecf20Sopenharmony_ci return -ENOMEM; 2638c2ecf20Sopenharmony_ci } 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_ci of_node_put(np); 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci mvebu_board_pm_enter = board_pm_enter; 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_ci return 0; 2708c2ecf20Sopenharmony_ci} 271