// SPDX-License-Identifier: GPL-2.0-only /* * OMAP MPUSS low power code * * Copyright (C) 2011 Texas Instruments, Inc. * Santosh Shilimkar <santosh.shilimkar@ti.com> * * OMAP4430 MPUSS mainly consists of dual Cortex-A9 with per-CPU * Local timer and Watchdog, GIC, SCU, PL310 L2 cache controller, * CPU0 and CPU1 LPRM modules. * CPU0, CPU1 and MPUSS each have there own power domain and * hence multiple low power combinations of MPUSS are possible. * * The CPU0 and CPU1 can't support Closed switch Retention (CSWR) * because the mode is not supported by hw constraints of dormant * mode. While waking up from the dormant mode, a reset signal * to the Cortex-A9 processor must be asserted by the external * power controller. * * With architectural inputs and hardware recommendations, only * below modes are supported from power gain vs latency point of view. * * CPU0 CPU1 MPUSS * ---------------------------------------------- * ON ON ON * ON(Inactive) OFF ON(Inactive) * OFF OFF CSWR * OFF OFF OSWR * OFF OFF OFF(Device OFF *TBD) * ---------------------------------------------- * * Note: CPU0 is the master core and it is the last CPU to go down * and first to wake-up when MPUSS low power states are excercised
*/
/** * struct cpu_pm_ops - CPU pm operations * @finish_suspend: CPU suspend finisher function pointer * @resume: CPU resume function pointer * @scu_prepare: CPU Snoop Control program function pointer * @hotplug_restart: CPU restart function pointer * * Structure holds functions pointer for CPU low power operations like * suspend, resume and scu programming.
*/ struct cpu_pm_ops { int (*finish_suspend)(unsignedlong cpu_state); void (*resume)(void); void (*scu_prepare)(unsignedint cpu_id, unsignedint cpu_state); void (*hotplug_restart)(void);
};
/* * Program the wakeup routine address for the CPU0 and CPU1 * used for OFF or DORMANT wakeup.
*/ staticinlinevoid set_cpu_wakeup_addr(unsignedint cpu_id, u32 addr)
{ struct omap4_cpu_pm_info *pm_info = &per_cpu(omap4_pm_info, cpu_id);
if (pm_info->wkup_sar_addr)
writel_relaxed(addr, pm_info->wkup_sar_addr);
}
/* * Store the SCU power status value to scratchpad memory
*/ staticvoid scu_pwrst_prepare(unsignedint cpu_id, unsignedint cpu_state)
{ struct omap4_cpu_pm_info *pm_info = &per_cpu(omap4_pm_info, cpu_id);
u32 scu_pwr_st;
switch (cpu_state) { case PWRDM_POWER_RET:
scu_pwr_st = SCU_PM_DORMANT; break; case PWRDM_POWER_OFF:
scu_pwr_st = SCU_PM_POWEROFF; break; case PWRDM_POWER_ON: case PWRDM_POWER_INACTIVE: default:
scu_pwr_st = SCU_PM_NORMAL; break;
}
if (pm_info->scu_sar_addr)
writel_relaxed(scu_pwr_st, pm_info->scu_sar_addr);
}
/* * Store the CPU cluster state for L2X0 low power operations.
*/ staticvoid l2x0_pwrst_prepare(unsignedint cpu_id, unsignedint save_state)
{ struct omap4_cpu_pm_info *pm_info = &per_cpu(omap4_pm_info, cpu_id);
if (pm_info->l2x0_sar_addr)
writel_relaxed(save_state, pm_info->l2x0_sar_addr);
}
/* * Save the L2X0 AUXCTRL and POR value to SAR memory. Its used to * in every restore MPUSS OFF path.
*/ #ifdef CONFIG_CACHE_L2X0 staticvoid __init save_l2x0_context(void)
{ void __iomem *l2x0_base = omap4_get_l2cache_base();
/** * omap4_enter_lowpower: OMAP4 MPUSS Low Power Entry Function * The purpose of this function is to manage low power programming * of OMAP4 MPUSS subsystem * @cpu : CPU ID * @power_state: Low power state. * @rcuidle: RCU needs to be idled * * MPUSS states for the context save: * save_state = * 0 - Nothing lost and no need to save: MPUSS INACTIVE * 1 - CPUx L1 and logic lost: MPUSS CSWR * 2 - CPUx L1 and logic lost + GIC lost: MPUSS OSWR * 3 - CPUx L1 and logic lost + GIC + L2 lost: DEVICE OFF
*/
__cpuidle int omap4_enter_lowpower(unsignedint cpu, unsignedint power_state, bool rcuidle)
{ struct omap4_cpu_pm_info *pm_info = &per_cpu(omap4_pm_info, cpu); unsignedint save_state = 0, cpu_logic_state = PWRDM_POWER_RET;
if (omap_rev() == OMAP4430_REV_ES1_0) return -ENXIO;
switch (power_state) { case PWRDM_POWER_ON: case PWRDM_POWER_INACTIVE:
save_state = 0; break; case PWRDM_POWER_OFF:
cpu_logic_state = PWRDM_POWER_OFF;
save_state = 1; break; case PWRDM_POWER_RET: if (IS_PM44XX_ERRATUM(PM_OMAP4_CPU_OSWR_DISABLE))
save_state = 0; break; default: /* * CPUx CSWR is invalid hardware state. Also CPUx OSWR * doesn't make much scense, since logic is lost and $L1 * needs to be cleaned because of coherency. This makes * CPUx OSWR equivalent to CPUX OFF and hence not supported
*/
WARN_ON(1); return -ENXIO;
}
pwrdm_pre_transition(NULL);
/* * Check MPUSS next state and save interrupt controller if needed. * In MPUSS OSWR or device OFF, interrupt controller contest is lost.
*/
mpuss_clear_prev_logic_pwrst(); if ((pwrdm_read_next_pwrst(mpuss_pd) == PWRDM_POWER_RET) &&
(pwrdm_read_logic_retst(mpuss_pd) == PWRDM_POWER_OFF))
save_state = 2;
/* * Call low level function with targeted low power state.
*/ if (save_state)
cpu_suspend(save_state, omap_pm_ops.finish_suspend); else
omap_pm_ops.finish_suspend(save_state);
if (IS_PM44XX_ERRATUM(PM_OMAP4_ROM_SMP_BOOT_ERRATUM_GICD) && cpu)
gic_dist_enable();
if (rcuidle)
ct_cpuidle_exit();
/* * Restore the CPUx power state to ON otherwise CPUx * power domain can transitions to programmed low power * state while doing WFI outside the low powe code. On * secure devices, CPUx does WFI which can result in * domain transition
*/
pwrdm_set_next_pwrst(pm_info->pwrdm, PWRDM_POWER_ON);
pwrdm_post_transition(NULL);
return 0;
}
/** * omap4_hotplug_cpu: OMAP4 CPU hotplug entry * @cpu : CPU ID * @power_state: CPU low power state.
*/ int omap4_hotplug_cpu(unsignedint cpu, unsignedint power_state)
{ struct omap4_cpu_pm_info *pm_info = &per_cpu(omap4_pm_info, cpu); unsignedint cpu_state = 0;
if (omap_rev() == OMAP4430_REV_ES1_0) return -ENXIO;
/* Use the achievable power state for the domain */
power_state = pwrdm_get_valid_lp_state(pm_info->pwrdm, false, power_state);
if (power_state == PWRDM_POWER_OFF)
cpu_state = 1;
/* * CPU never returns back if targeted power state is OFF mode. * CPU ONLINE follows normal CPU ONLINE ptah via * omap4_secondary_startup().
*/
omap_pm_ops.finish_suspend(cpu_state);
pm_info->pwrdm = pwrdm_lookup("cpu1_pwrdm"); if (!pm_info->pwrdm) {
pr_err("Lookup failed for CPU1 pwrdm\n"); return -ENODEV;
}
/* Clear CPU previous power domain state */
pwrdm_clear_all_prev_pwrst(pm_info->pwrdm);
cpu_clear_prev_logic_pwrst(1);
/* Initialise CPU1 power domain state to ON */
pwrdm_set_next_pwrst(pm_info->pwrdm, PWRDM_POWER_ON);
mpuss_pd = pwrdm_lookup("mpu_pwrdm"); if (!mpuss_pd) {
pr_err("Failed to lookup MPUSS power domain\n"); return -ENODEV;
}
pwrdm_clear_all_prev_pwrst(mpuss_pd);
mpuss_clear_prev_logic_pwrst();
if (sar_base) { /* Save device type on scratchpad for low level code to use */
writel_relaxed((omap_type() != OMAP2_DEVICE_TYPE_GP) ? 1 : 0,
sar_base + OMAP_TYPE_OFFSET);
save_l2x0_context();
}
/* * For kexec, we must set CPU1_WAKEUP_NS_PA_ADDR to point to * current kernel's secondary_startup() early before * clockdomains_init(). Otherwise clockdomain_init() can * wake CPU1 and cause a hang.
*/ void __init omap4_mpuss_early_init(void)
{ unsignedlong startup_pa; void __iomem *ns_pa_addr;
if (!(soc_is_omap44xx() || soc_is_omap54xx())) return;
sar_base = omap4_get_sar_ram_base();
/* Save old NS_PA_ADDR for validity checks later on */ if (soc_is_omap44xx())
ns_pa_addr = sar_base + CPU1_WAKEUP_NS_PA_ADDR_OFFSET; else
ns_pa_addr = sar_base + OMAP5_CPU1_WAKEUP_NS_PA_ADDR_OFFSET;
old_cpu1_ns_pa_addr = readl_relaxed(ns_pa_addr);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.