18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * OMAP4+ Power Management Routines 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2010-2013 Texas Instruments, Inc. 68c2ecf20Sopenharmony_ci * Rajendra Nayak <rnayak@ti.com> 78c2ecf20Sopenharmony_ci * Santosh Shilimkar <santosh.shilimkar@ti.com> 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/pm.h> 118c2ecf20Sopenharmony_ci#include <linux/suspend.h> 128c2ecf20Sopenharmony_ci#include <linux/module.h> 138c2ecf20Sopenharmony_ci#include <linux/list.h> 148c2ecf20Sopenharmony_ci#include <linux/err.h> 158c2ecf20Sopenharmony_ci#include <linux/slab.h> 168c2ecf20Sopenharmony_ci#include <asm/system_misc.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#include "soc.h" 198c2ecf20Sopenharmony_ci#include "common.h" 208c2ecf20Sopenharmony_ci#include "clockdomain.h" 218c2ecf20Sopenharmony_ci#include "powerdomain.h" 228c2ecf20Sopenharmony_ci#include "pm.h" 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ciu16 pm44xx_errata; 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_cistruct power_state { 278c2ecf20Sopenharmony_ci struct powerdomain *pwrdm; 288c2ecf20Sopenharmony_ci u32 next_state; 298c2ecf20Sopenharmony_ci u32 next_logic_state; 308c2ecf20Sopenharmony_ci#ifdef CONFIG_SUSPEND 318c2ecf20Sopenharmony_ci u32 saved_state; 328c2ecf20Sopenharmony_ci u32 saved_logic_state; 338c2ecf20Sopenharmony_ci#endif 348c2ecf20Sopenharmony_ci struct list_head node; 358c2ecf20Sopenharmony_ci}; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci/** 388c2ecf20Sopenharmony_ci * struct static_dep_map - Static dependency map 398c2ecf20Sopenharmony_ci * @from: from clockdomain 408c2ecf20Sopenharmony_ci * @to: to clockdomain 418c2ecf20Sopenharmony_ci */ 428c2ecf20Sopenharmony_cistruct static_dep_map { 438c2ecf20Sopenharmony_ci const char *from; 448c2ecf20Sopenharmony_ci const char *to; 458c2ecf20Sopenharmony_ci}; 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_cistatic u32 cpu_suspend_state = PWRDM_POWER_OFF; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_cistatic LIST_HEAD(pwrst_list); 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci#ifdef CONFIG_SUSPEND 528c2ecf20Sopenharmony_cistatic int omap4_pm_suspend(void) 538c2ecf20Sopenharmony_ci{ 548c2ecf20Sopenharmony_ci struct power_state *pwrst; 558c2ecf20Sopenharmony_ci int state, ret = 0; 568c2ecf20Sopenharmony_ci u32 cpu_id = smp_processor_id(); 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci /* Save current powerdomain state */ 598c2ecf20Sopenharmony_ci list_for_each_entry(pwrst, &pwrst_list, node) { 608c2ecf20Sopenharmony_ci pwrst->saved_state = pwrdm_read_next_pwrst(pwrst->pwrdm); 618c2ecf20Sopenharmony_ci pwrst->saved_logic_state = pwrdm_read_logic_retst(pwrst->pwrdm); 628c2ecf20Sopenharmony_ci } 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci /* Set targeted power domain states by suspend */ 658c2ecf20Sopenharmony_ci list_for_each_entry(pwrst, &pwrst_list, node) { 668c2ecf20Sopenharmony_ci omap_set_pwrdm_state(pwrst->pwrdm, pwrst->next_state); 678c2ecf20Sopenharmony_ci pwrdm_set_logic_retst(pwrst->pwrdm, pwrst->next_logic_state); 688c2ecf20Sopenharmony_ci } 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci /* 718c2ecf20Sopenharmony_ci * For MPUSS to hit power domain retention(CSWR or OSWR), 728c2ecf20Sopenharmony_ci * CPU0 and CPU1 power domains need to be in OFF or DORMANT state, 738c2ecf20Sopenharmony_ci * since CPU power domain CSWR is not supported by hardware 748c2ecf20Sopenharmony_ci * Only master CPU follows suspend path. All other CPUs follow 758c2ecf20Sopenharmony_ci * CPU hotplug path in system wide suspend. On OMAP4, CPU power 768c2ecf20Sopenharmony_ci * domain CSWR is not supported by hardware. 778c2ecf20Sopenharmony_ci * More details can be found in OMAP4430 TRM section 4.3.4.2. 788c2ecf20Sopenharmony_ci */ 798c2ecf20Sopenharmony_ci omap4_enter_lowpower(cpu_id, cpu_suspend_state); 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci /* Restore next powerdomain state */ 828c2ecf20Sopenharmony_ci list_for_each_entry(pwrst, &pwrst_list, node) { 838c2ecf20Sopenharmony_ci state = pwrdm_read_prev_pwrst(pwrst->pwrdm); 848c2ecf20Sopenharmony_ci if (state > pwrst->next_state) { 858c2ecf20Sopenharmony_ci pr_info("Powerdomain (%s) didn't enter target state %d\n", 868c2ecf20Sopenharmony_ci pwrst->pwrdm->name, pwrst->next_state); 878c2ecf20Sopenharmony_ci ret = -1; 888c2ecf20Sopenharmony_ci } 898c2ecf20Sopenharmony_ci omap_set_pwrdm_state(pwrst->pwrdm, pwrst->saved_state); 908c2ecf20Sopenharmony_ci pwrdm_set_logic_retst(pwrst->pwrdm, pwrst->saved_logic_state); 918c2ecf20Sopenharmony_ci } 928c2ecf20Sopenharmony_ci if (ret) { 938c2ecf20Sopenharmony_ci pr_crit("Could not enter target state in pm_suspend\n"); 948c2ecf20Sopenharmony_ci /* 958c2ecf20Sopenharmony_ci * OMAP4 chip PM currently works only with certain (newer) 968c2ecf20Sopenharmony_ci * versions of bootloaders. This is due to missing code in the 978c2ecf20Sopenharmony_ci * kernel to properly reset and initialize some devices. 988c2ecf20Sopenharmony_ci * Warn the user about the bootloader version being one of the 998c2ecf20Sopenharmony_ci * possible causes. 1008c2ecf20Sopenharmony_ci * http://www.spinics.net/lists/arm-kernel/msg218641.html 1018c2ecf20Sopenharmony_ci */ 1028c2ecf20Sopenharmony_ci pr_warn("A possible cause could be an old bootloader - try u-boot >= v2012.07\n"); 1038c2ecf20Sopenharmony_ci } else { 1048c2ecf20Sopenharmony_ci pr_info("Successfully put all powerdomains to target state\n"); 1058c2ecf20Sopenharmony_ci } 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci return 0; 1088c2ecf20Sopenharmony_ci} 1098c2ecf20Sopenharmony_ci#else 1108c2ecf20Sopenharmony_ci#define omap4_pm_suspend NULL 1118c2ecf20Sopenharmony_ci#endif /* CONFIG_SUSPEND */ 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_cistatic int __init pwrdms_setup(struct powerdomain *pwrdm, void *unused) 1148c2ecf20Sopenharmony_ci{ 1158c2ecf20Sopenharmony_ci struct power_state *pwrst; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci if (!pwrdm->pwrsts) 1188c2ecf20Sopenharmony_ci return 0; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci /* 1218c2ecf20Sopenharmony_ci * Skip CPU0 and CPU1 power domains. CPU1 is programmed 1228c2ecf20Sopenharmony_ci * through hotplug path and CPU0 explicitly programmed 1238c2ecf20Sopenharmony_ci * further down in the code path 1248c2ecf20Sopenharmony_ci */ 1258c2ecf20Sopenharmony_ci if (!strncmp(pwrdm->name, "cpu", 3)) { 1268c2ecf20Sopenharmony_ci if (IS_PM44XX_ERRATUM(PM_OMAP4_CPU_OSWR_DISABLE)) 1278c2ecf20Sopenharmony_ci cpu_suspend_state = PWRDM_POWER_RET; 1288c2ecf20Sopenharmony_ci return 0; 1298c2ecf20Sopenharmony_ci } 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci if (!strncmp(pwrdm->name, "core", 4) || 1328c2ecf20Sopenharmony_ci !strncmp(pwrdm->name, "l4per", 5)) 1338c2ecf20Sopenharmony_ci pwrdm_set_logic_retst(pwrdm, PWRDM_POWER_OFF); 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci pwrst = kmalloc(sizeof(struct power_state), GFP_ATOMIC); 1368c2ecf20Sopenharmony_ci if (!pwrst) 1378c2ecf20Sopenharmony_ci return -ENOMEM; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci pwrst->pwrdm = pwrdm; 1408c2ecf20Sopenharmony_ci pwrst->next_state = pwrdm_get_valid_lp_state(pwrdm, false, 1418c2ecf20Sopenharmony_ci PWRDM_POWER_RET); 1428c2ecf20Sopenharmony_ci pwrst->next_logic_state = pwrdm_get_valid_lp_state(pwrdm, true, 1438c2ecf20Sopenharmony_ci PWRDM_POWER_OFF); 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci list_add(&pwrst->node, &pwrst_list); 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci return omap_set_pwrdm_state(pwrst->pwrdm, pwrst->next_state); 1488c2ecf20Sopenharmony_ci} 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci/** 1518c2ecf20Sopenharmony_ci * omap_default_idle - OMAP4 default ilde routine.' 1528c2ecf20Sopenharmony_ci * 1538c2ecf20Sopenharmony_ci * Implements OMAP4 memory, IO ordering requirements which can't be addressed 1548c2ecf20Sopenharmony_ci * with default cpu_do_idle() hook. Used by all CPUs with !CONFIG_CPU_IDLE and 1558c2ecf20Sopenharmony_ci * by secondary CPU with CONFIG_CPU_IDLE. 1568c2ecf20Sopenharmony_ci */ 1578c2ecf20Sopenharmony_cistatic void omap_default_idle(void) 1588c2ecf20Sopenharmony_ci{ 1598c2ecf20Sopenharmony_ci omap_do_wfi(); 1608c2ecf20Sopenharmony_ci} 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci/* 1638c2ecf20Sopenharmony_ci * The dynamic dependency between MPUSS -> MEMIF and 1648c2ecf20Sopenharmony_ci * MPUSS -> L4_PER/L3_* and DUCATI -> L3_* doesn't work as 1658c2ecf20Sopenharmony_ci * expected. The hardware recommendation is to enable static 1668c2ecf20Sopenharmony_ci * dependencies for these to avoid system lock ups or random crashes. 1678c2ecf20Sopenharmony_ci * The L4 wakeup depedency is added to workaround the OCP sync hardware 1688c2ecf20Sopenharmony_ci * BUG with 32K synctimer which lead to incorrect timer value read 1698c2ecf20Sopenharmony_ci * from the 32K counter. The BUG applies for GPTIMER1 and WDT2 which 1708c2ecf20Sopenharmony_ci * are part of L4 wakeup clockdomain. 1718c2ecf20Sopenharmony_ci */ 1728c2ecf20Sopenharmony_cistatic const struct static_dep_map omap4_static_dep_map[] = { 1738c2ecf20Sopenharmony_ci {.from = "mpuss_clkdm", .to = "l3_emif_clkdm"}, 1748c2ecf20Sopenharmony_ci {.from = "mpuss_clkdm", .to = "l3_1_clkdm"}, 1758c2ecf20Sopenharmony_ci {.from = "mpuss_clkdm", .to = "l3_2_clkdm"}, 1768c2ecf20Sopenharmony_ci {.from = "ducati_clkdm", .to = "l3_1_clkdm"}, 1778c2ecf20Sopenharmony_ci {.from = "ducati_clkdm", .to = "l3_2_clkdm"}, 1788c2ecf20Sopenharmony_ci {.from = NULL} /* TERMINATION */ 1798c2ecf20Sopenharmony_ci}; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_cistatic const struct static_dep_map omap5_dra7_static_dep_map[] = { 1828c2ecf20Sopenharmony_ci {.from = "mpu_clkdm", .to = "emif_clkdm"}, 1838c2ecf20Sopenharmony_ci {.from = NULL} /* TERMINATION */ 1848c2ecf20Sopenharmony_ci}; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci/** 1878c2ecf20Sopenharmony_ci * omap4plus_init_static_deps() - Initialize a static dependency map 1888c2ecf20Sopenharmony_ci * @map: Mapping of clock domains 1898c2ecf20Sopenharmony_ci */ 1908c2ecf20Sopenharmony_cistatic inline int omap4plus_init_static_deps(const struct static_dep_map *map) 1918c2ecf20Sopenharmony_ci{ 1928c2ecf20Sopenharmony_ci int ret; 1938c2ecf20Sopenharmony_ci struct clockdomain *from, *to; 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci if (!map) 1968c2ecf20Sopenharmony_ci return 0; 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci while (map->from) { 1998c2ecf20Sopenharmony_ci from = clkdm_lookup(map->from); 2008c2ecf20Sopenharmony_ci to = clkdm_lookup(map->to); 2018c2ecf20Sopenharmony_ci if (!from || !to) { 2028c2ecf20Sopenharmony_ci pr_err("Failed lookup %s or %s for wakeup dependency\n", 2038c2ecf20Sopenharmony_ci map->from, map->to); 2048c2ecf20Sopenharmony_ci return -EINVAL; 2058c2ecf20Sopenharmony_ci } 2068c2ecf20Sopenharmony_ci ret = clkdm_add_wkdep(from, to); 2078c2ecf20Sopenharmony_ci if (ret) { 2088c2ecf20Sopenharmony_ci pr_err("Failed to add %s -> %s wakeup dependency(%d)\n", 2098c2ecf20Sopenharmony_ci map->from, map->to, ret); 2108c2ecf20Sopenharmony_ci return ret; 2118c2ecf20Sopenharmony_ci } 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci map++; 2148c2ecf20Sopenharmony_ci } 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci return 0; 2178c2ecf20Sopenharmony_ci} 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci/** 2208c2ecf20Sopenharmony_ci * omap4_pm_init_early - Does early initialization necessary for OMAP4+ devices 2218c2ecf20Sopenharmony_ci * 2228c2ecf20Sopenharmony_ci * Initializes basic stuff for power management functionality. 2238c2ecf20Sopenharmony_ci */ 2248c2ecf20Sopenharmony_ciint __init omap4_pm_init_early(void) 2258c2ecf20Sopenharmony_ci{ 2268c2ecf20Sopenharmony_ci if (cpu_is_omap446x()) 2278c2ecf20Sopenharmony_ci pm44xx_errata |= PM_OMAP4_ROM_SMP_BOOT_ERRATUM_GICD; 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci if (soc_is_omap54xx() || soc_is_dra7xx()) 2308c2ecf20Sopenharmony_ci pm44xx_errata |= PM_OMAP4_CPU_OSWR_DISABLE; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci return 0; 2338c2ecf20Sopenharmony_ci} 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci/** 2368c2ecf20Sopenharmony_ci * omap4_pm_init - Init routine for OMAP4+ devices 2378c2ecf20Sopenharmony_ci * 2388c2ecf20Sopenharmony_ci * Initializes all powerdomain and clockdomain target states 2398c2ecf20Sopenharmony_ci * and all PRCM settings. 2408c2ecf20Sopenharmony_ci * Return: Returns the error code returned by called functions. 2418c2ecf20Sopenharmony_ci */ 2428c2ecf20Sopenharmony_ciint __init omap4_pm_init(void) 2438c2ecf20Sopenharmony_ci{ 2448c2ecf20Sopenharmony_ci int ret = 0; 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci if (omap_rev() == OMAP4430_REV_ES1_0) { 2478c2ecf20Sopenharmony_ci WARN(1, "Power Management not supported on OMAP4430 ES1.0\n"); 2488c2ecf20Sopenharmony_ci return -ENODEV; 2498c2ecf20Sopenharmony_ci } 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci pr_info("Power Management for TI OMAP4+ devices.\n"); 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci /* 2548c2ecf20Sopenharmony_ci * OMAP4 chip PM currently works only with certain (newer) 2558c2ecf20Sopenharmony_ci * versions of bootloaders. This is due to missing code in the 2568c2ecf20Sopenharmony_ci * kernel to properly reset and initialize some devices. 2578c2ecf20Sopenharmony_ci * http://www.spinics.net/lists/arm-kernel/msg218641.html 2588c2ecf20Sopenharmony_ci */ 2598c2ecf20Sopenharmony_ci if (cpu_is_omap44xx()) 2608c2ecf20Sopenharmony_ci pr_warn("OMAP4 PM: u-boot >= v2012.07 is required for full PM support\n"); 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci ret = pwrdm_for_each(pwrdms_setup, NULL); 2638c2ecf20Sopenharmony_ci if (ret) { 2648c2ecf20Sopenharmony_ci pr_err("Failed to setup powerdomains.\n"); 2658c2ecf20Sopenharmony_ci goto err2; 2668c2ecf20Sopenharmony_ci } 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci if (cpu_is_omap44xx()) 2698c2ecf20Sopenharmony_ci ret = omap4plus_init_static_deps(omap4_static_dep_map); 2708c2ecf20Sopenharmony_ci else if (soc_is_omap54xx() || soc_is_dra7xx()) 2718c2ecf20Sopenharmony_ci ret = omap4plus_init_static_deps(omap5_dra7_static_dep_map); 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci if (ret) { 2748c2ecf20Sopenharmony_ci pr_err("Failed to initialise static dependencies.\n"); 2758c2ecf20Sopenharmony_ci goto err2; 2768c2ecf20Sopenharmony_ci } 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci ret = omap4_mpuss_init(); 2798c2ecf20Sopenharmony_ci if (ret) { 2808c2ecf20Sopenharmony_ci pr_err("Failed to initialise OMAP4 MPUSS\n"); 2818c2ecf20Sopenharmony_ci goto err2; 2828c2ecf20Sopenharmony_ci } 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_ci (void) clkdm_for_each(omap_pm_clkdms_setup, NULL); 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci omap_common_suspend_init(omap4_pm_suspend); 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci /* Overwrite the default cpu_do_idle() */ 2898c2ecf20Sopenharmony_ci arm_pm_idle = omap_default_idle; 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_ci if (cpu_is_omap44xx() || soc_is_omap54xx()) 2928c2ecf20Sopenharmony_ci omap4_idle_init(); 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_cierr2: 2958c2ecf20Sopenharmony_ci return ret; 2968c2ecf20Sopenharmony_ci} 297