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