summaryrefslogtreecommitdiff
path: root/arch/arm/mach-imx
diff options
context:
space:
mode:
authorLeonard Crestez <leonard.crestez@nxp.com>2017-07-04 20:52:26 +0300
committerJason Liu <jason.hui.liu@nxp.com>2019-02-12 10:31:21 +0800
commit75d08f3dc5f42450ddc574c56fa48d383fee4ae6 (patch)
treeddd69ebfc5b224fb2669338a4b4cb2f3d70e4448 /arch/arm/mach-imx
parent3d2fb8c5eb494f5be1f2192629625bdb1361c9e7 (diff)
MLK15034-2: ARM: cpuidle imx7d: Check IPIs manually before LPI
The GPC will wake us on peripheral interrupts but not IPIs. So check them manually by reading the GIC's GICD_SPENDSGIR* registers and aborting idle if something is pending. We do this only for the last cpu and after taking the required locks. We know that at this stage the other cpu is in WFI itself or waiting for the imx_pen_lock and can't trigger any additional IPIs. This means that the check is not racy. This fixes occasional lost IPIs causing tasks to get stuck in the TASK_WAKING 'W' state for long periods. This eventually manifested as rcu stalls. Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
Diffstat (limited to 'arch/arm/mach-imx')
-rw-r--r--arch/arm/mach-imx/cpuidle-imx7d.c26
1 files changed, 26 insertions, 0 deletions
diff --git a/arch/arm/mach-imx/cpuidle-imx7d.c b/arch/arm/mach-imx/cpuidle-imx7d.c
index 37d12b14134c..d19d76ef75d7 100644
--- a/arch/arm/mach-imx/cpuidle-imx7d.c
+++ b/arch/arm/mach-imx/cpuidle-imx7d.c
@@ -84,6 +84,9 @@ static atomic_t master_wait = ATOMIC_INIT(0);
static void (*imx7d_wfi_in_iram_fn)(void __iomem *iram_vbase);
static struct imx7_cpuidle_pm_info *cpuidle_pm_info;
+/* Mapped for the kernel, unlike cpuidle_pm_info->gic_dist_base.vbase */
+static void __iomem *imx7d_cpuidle_gic_base;
+
static void imx_pen_lock(int cpu)
{
if (cpu == 0) {
@@ -122,6 +125,16 @@ static int imx7d_idle_finish(unsigned long val)
return 0;
}
+static bool imx7d_gic_sgis_pending(void)
+{
+ void __iomem *sgip_base = imx7d_cpuidle_gic_base + 0x1f20;
+
+ return (readl_relaxed(sgip_base + 0x0) |
+ readl_relaxed(sgip_base + 0x4) |
+ readl_relaxed(sgip_base + 0x8) |
+ readl_relaxed(sgip_base + 0xc));
+}
+
static int imx7d_enter_low_power_idle(struct cpuidle_device *dev,
struct cpuidle_driver *drv, int index)
{
@@ -141,6 +154,16 @@ static int imx7d_enter_low_power_idle(struct cpuidle_device *dev,
cpu_pm_enter();
if (atomic_inc_return(&master_lpi) == num_online_cpus() &&
cpuidle_pm_info->last_cpu == -1) {
+ /*
+ * GPC will not wake on SGIs so check for them
+ * manually here. At this point we know the other cpu
+ * is in wfi or waiting for the lock and can't send
+ * any additional IPIs.
+ */
+ if (imx7d_gic_sgis_pending()) {
+ index = -1;
+ goto skip_lpi_flow;
+ }
imx_gpcv2_set_lpm_mode(WAIT_UNCLOCKED);
imx_gpcv2_set_cpu_power_gate_in_idle(true);
cpu_cluster_pm_enter();
@@ -162,6 +185,7 @@ static int imx7d_enter_low_power_idle(struct cpuidle_device *dev,
if (cpuidle_pm_info->last_cpu == dev->cpu)
cpuidle_pm_info->last_cpu = -1;
+skip_lpi_flow:
cpu_pm_exit();
imx_pen_unlock(dev->cpu);
}
@@ -308,6 +332,8 @@ int __init imx7d_cpuidle_init(void)
cpuidle_pm_info->gic_dist_base.vbase =
(void __iomem *)IMX_IO_P2V(MX7D_GIC_BASE_ADDR);
+ imx7d_cpuidle_gic_base = ioremap(MX7D_GIC_BASE_ADDR, MX7D_GIC_SIZE);
+
imx7d_enable_rcosc();
/* code size should include cpuidle_pm_info size */