[PATCH 4/7] irqchip/irq-qcom-mpm: Program wakeup timer when CPU cluster goes to LPM

From: Sneh Mankad

Date: Mon Jul 13 2026 - 06:28:12 EST


The next wakeup timer value needs to be set in MPM timer as the arch timer
interrupt can not wakeup the SoC if after the deepest CPUidle states the
SoC also enters deepest low power state.

To wakeup the SoC in such scenarios the earliest wakeup time is set in MPM
timer and the Resource Power Manager (RPM processor) takes care of setting
the timer in HW.

Add MPM timer programming when CPU cluster enters power collapse.

Signed-off-by: Sneh Mankad <sneh.mankad@xxxxxxxxxxxxxxxx>
---
drivers/irqchip/irq-qcom-mpm.c | 44 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 44 insertions(+)

diff --git a/drivers/irqchip/irq-qcom-mpm.c b/drivers/irqchip/irq-qcom-mpm.c
index 763eddee99dc4cdd5edab22ce54808528f9ef165..f43c4a1c35f78b6cdae194dc7ae88c5c307ada94 100644
--- a/drivers/irqchip/irq-qcom-mpm.c
+++ b/drivers/irqchip/irq-qcom-mpm.c
@@ -13,6 +13,7 @@
#include <linux/io.h>
#include <linux/irqchip.h>
#include <linux/irqdomain.h>
+#include <linux/ktime.h>
#include <linux/mailbox_client.h>
#include <linux/module.h>
#include <linux/of.h>
@@ -25,6 +26,8 @@
#include <linux/soc/qcom/irq.h>
#include <linux/spinlock.h>

+#include <clocksource/arm_arch_timer.h>
+
/*
* This is the driver for Qualcomm MPM (MSM Power Manager) interrupt controller,
* which is commonly found on Qualcomm SoCs built on the RPM architecture.
@@ -77,6 +80,13 @@ enum qcom_mpm_reg {
MPM_REG_STATUS,
};

+#define USECS_TO_CYCLES(time_usecs) xloops_to_cycles((time_usecs) * 0x10C7UL)
+
+static inline unsigned long xloops_to_cycles(u64 xloops)
+{
+ return (xloops * loops_per_jiffy * HZ) >> 32;
+}
+
/* MPM pin map to GIC hwirq */
struct mpm_gic_map {
int pin;
@@ -84,6 +94,7 @@ struct mpm_gic_map {
};

struct qcom_mpm_priv {
+ struct device *dev;
void __iomem *base;
raw_spinlock_t lock;
struct mbox_client mbox_client;
@@ -320,6 +331,36 @@ static irqreturn_t qcom_mpm_handler(int irq, void *dev_id)
return ret;
}

+static void mpm_write_next_wakeup(struct qcom_mpm_priv *priv)
+{
+ ktime_t now, wakeup = KTIME_MAX;
+ u64 wakeup_us, wakeup_cycles = ~0;
+ u32 lo, hi;
+
+ /* Set highest time when system (timekeeping) is suspended */
+ if (system_state == SYSTEM_SUSPEND)
+ goto exit;
+
+ /* Find the relative wakeup in kernel time scale */
+ wakeup = dev_pm_genpd_get_next_hrtimer(priv->dev);
+
+ /* Find the relative wakeup in kernel time scale */
+ now = ktime_get();
+ wakeup = ktime_sub(wakeup, now);
+ wakeup_us = ktime_to_us(wakeup);
+
+ /* Convert the wakeup to arch timer scale */
+ wakeup_cycles = USECS_TO_CYCLES(wakeup_us);
+ wakeup_cycles += arch_timer_read_counter();
+
+exit:
+ lo = wakeup_cycles;
+ hi = wakeup_cycles >> 32;
+
+ qcom_mpm_write(priv, MPM_REG_TIMER, 0, lo);
+ qcom_mpm_write(priv, MPM_REG_TIMER, 1, hi);
+}
+
static int handle_rpm_notification(struct qcom_mpm_priv *priv)
{
int i, ret;
@@ -332,6 +373,7 @@ static int handle_rpm_notification(struct qcom_mpm_priv *priv)
if (ret < 0)
return ret;

+ mpm_write_next_wakeup(priv);
mbox_client_txdone(priv->mbox_chan, 0);
return 0;
}
@@ -412,6 +454,8 @@ static int qcom_mpm_probe(struct platform_device *pdev, struct device_node *pare
if (!priv)
return -ENOMEM;

+ priv->dev = &pdev->dev;
+
ret = of_property_read_u32(np, "qcom,mpm-pin-count", &pin_cnt);
if (ret) {
dev_err(dev, "failed to read qcom,mpm-pin-count: %d\n", ret);

--
2.34.1