* [PATCH v2 0/2] irqchip: add MSM8x60 MPM wakeup interrupt controller
[not found] <cover.1780148149.git.github.com@herrie.org>
@ 2026-05-31 4:09 ` Herman van Hazendonk
2026-05-31 4:09 ` [PATCH v2 1/2] dt-bindings: interrupt-controller: qcom: add msm8660-mpm Herman van Hazendonk
2026-05-31 4:09 ` [PATCH v2 2/2] irqchip: add MSM8x60 MPM wakeup interrupt controller driver Herman van Hazendonk
0 siblings, 2 replies; 6+ messages in thread
From: Herman van Hazendonk @ 2026-05-31 4:09 UTC (permalink / raw)
To: Bjorn Andersson, Clark Williams, Conor Dooley, devicetree,
Konrad Dybcio, Krzysztof Kozlowski, linux-arm-msm, linux-kernel,
linux-rt-devel, Rob Herring, Sebastian Andrzej Siewior,
Steven Rostedt, Thomas Gleixner, van Hazendonk
Hi all,
Self-review (with Sashiko AI assist) caught eight real issues in v1
before maintainer review reached them; re-rolling promptly. v1:
https://lore.kernel.org/linux-arm-msm/cover.1780148149.git.github.com@herrie.org/
v2 changes:
- PREEMPT_RT: drop the syscon regmap and ioremap the vMPM sub-region
of the RPM control block directly. The IRQ core invokes the
irqchip mask/unmask/set_type/set_wake callbacks with the irq_desc
raw_spinlock_t held, and syscon regmaps use a sleepable
spinlock_t that becomes an rt_mutex on PREEMPT_RT. Direct MMIO
(readl_relaxed/writel_relaxed) is what every other SoC irqchip
in tree does for the same reason (qcom-pdc, gic-v3, ...).
- RPM doorbell: after every vMPM register write, send a message
through the qcom-apcs-ipc mailbox. v1 acquired the mailbox
channel but never used it, so the RPM kept running with its
cached enable / detect / polarity state and our configuration
changes had no effect.
- Teardown order: v1 used devm_request_irq() and then called
irq_domain_remove() manually in ->remove(); the devres-managed
handler outlives the domain, so an IRQ arriving in the removal
window would dereference a freed domain. Switch to plain
request_irq() / free_irq() and remove in strict reverse-init
order (free_irq, free mailbox, irq_domain_remove).
- .irq_set_wake: implement it (toggle the MPM enable bit + delegate
to irq_chip_set_wake_parent for the GIC) and drop
IRQCHIP_SKIP_SET_WAKE. v1 declared SKIP and provided no
callback, so enable_irq_wake() was a silent no-op for every
consumer.
- Clear-before-dispatch: in the IPC IRQ handler, write CLEAR for
the pending bits before calling generic_handle_domain_irq() for
each pin. v1 cleared after the handler returned, racing against
a fresh edge latched during dispatch and silently dropping it.
- DT pin-map bounds check: validate each qcom,mpm-pin-map pin
entry against MSM8660_MPM_PIN_COUNT before storing it, so a
malformed DT cannot induce out-of-bounds register accesses in
later set_type/enable paths.
- Consumer API lifetime: msm8660_mpm_get() now takes a consumer
device argument and establishes a DL_FLAG_AUTOREMOVE_CONSUMER
device link to the MPM device. Without this, an unbind of the
MPM driver would free the devres-managed struct while consumers
still hold pointers, causing UAF on the next call.
- regmap_read return check: moot now that we use readl_relaxed,
but the original Sashiko finding (uninitialised val on regmap
error) is closed.
DT binding: drop qcom,rpm-syscon and qcom,mpm-offset; replace with a
single reg property pointing at the vMPM window inside the RPM
control block. dt_binding_check passes on the example.
Driver passes checkpatch with zero warnings or errors.
Thanks,
Herman
Herman van Hazendonk (2):
dt-bindings: interrupt-controller: qcom: add msm8660-mpm
irqchip: add MSM8x60 MPM wakeup interrupt controller driver
.../qcom,msm8660-mpm.yaml | 122 +++
drivers/irqchip/Kconfig | 23 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-msm8660-mpm.c | 740 ++++++++++++++++++
include/soc/qcom/msm8660-mpm.h | 83 ++
5 files changed, 969 insertions(+)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/qcom,msm8660-mpm.yaml
create mode 100644 drivers/irqchip/irq-msm8660-mpm.c
create mode 100644 include/soc/qcom/msm8660-mpm.h
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v2 1/2] dt-bindings: interrupt-controller: qcom: add msm8660-mpm
2026-05-31 4:09 ` [PATCH v2 0/2] irqchip: add MSM8x60 MPM wakeup interrupt controller Herman van Hazendonk
@ 2026-05-31 4:09 ` Herman van Hazendonk
2026-05-31 8:01 ` Krzysztof Kozlowski
2026-05-31 4:09 ` [PATCH v2 2/2] irqchip: add MSM8x60 MPM wakeup interrupt controller driver Herman van Hazendonk
1 sibling, 1 reply; 6+ messages in thread
From: Herman van Hazendonk @ 2026-05-31 4:09 UTC (permalink / raw)
To: Bjorn Andersson, Clark Williams, Conor Dooley, devicetree,
Konrad Dybcio, Krzysztof Kozlowski, linux-arm-msm, linux-kernel,
linux-rt-devel, Rob Herring, Sebastian Andrzej Siewior,
Steven Rostedt, Thomas Gleixner, van Hazendonk
Add the binding for the Modem Power Manager (MPM) interrupt
controller on the MSM8x60 family (MSM8260/MSM8660/APQ8060). The MPM
is a small wake-capable interrupt aggregator that lets the SoC stay
in low-power states while a small set of GIC SPIs continues to be
monitored and can wake the system on an edge.
The binding describes its register region (in the RPM syscon), the
GIC SPI it raises on a wake event, the GIC mailbox it shares with
the RPM, and the per-pin wake-source mapping table.
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
.../qcom,msm8660-mpm.yaml | 122 ++++++++++++++++++
1 file changed, 122 insertions(+)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/qcom,msm8660-mpm.yaml
diff --git a/Documentation/devicetree/bindings/interrupt-controller/qcom,msm8660-mpm.yaml b/Documentation/devicetree/bindings/interrupt-controller/qcom,msm8660-mpm.yaml
new file mode 100644
index 000000000000..1288e83d75d6
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/qcom,msm8660-mpm.yaml
@@ -0,0 +1,122 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/interrupt-controller/qcom,msm8660-mpm.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm MSM8x60 MPM wakeup interrupt controller
+
+maintainers:
+ - Herman van Hazendonk <github.com@herrie.org>
+
+description: |
+ The MSM Power Manager (MPM) on the MSM8x60 family (MSM8260/MSM8660/
+ APQ8060) is an always-on hardware block that keeps a set of wake
+ interrupts alive while the application processor is powered down.
+
+ The vMPM (virtual MPM) registers live INSIDE the RPM control block:
+ the request window (ENABLE, DETECT_CTL, POLARITY, CLEAR) starts at
+ RPM_BASE + 0x9d8 and the status window starts 0x420 above the
+ request window. The reg property points at this vMPM sub-region;
+ the qcom,rpm-msm8660 driver maps the surrounding RPM control area
+ for its own use, so the two mappings overlap but neither claims
+ exclusive ownership of the other's range.
+
+ Notification of new wake-source configuration is delivered via the
+ qcom-apcs-ipc mailbox (writing GCC + 0x008 bit 1).
+
+ This binding is MSM8x60-specific. Newer Qualcomm SoCs (sm6375, etc.)
+ use the qcom,mpm binding which assumes a dedicated MPM SRAM region
+ and an IPCC mailbox - neither holds on MSM8x60. The driver lives at
+ drivers/irqchip/irq-msm8660-mpm.c.
+
+allOf:
+ - $ref: /schemas/interrupt-controller.yaml#
+
+properties:
+ compatible:
+ const: qcom,msm8660-mpm
+
+ reg:
+ maxItems: 1
+ description: |
+ vMPM register window inside the RPM control block. Must cover
+ the request registers at offset 0 of the window and the status
+ registers at offset 0x420 of the window. A length of 0x440 is
+ sufficient for the four-word request banks and the status bank.
+
+ interrupts:
+ maxItems: 1
+ description:
+ IPC IRQ raised by MPM when one of the enabled wake sources fires.
+ On MSM8x60 this is wired to GIC SPI 2.
+
+ mboxes:
+ maxItems: 1
+ description:
+ Mailbox channel used to notify MPM that the vMPM request
+ registers have been updated. On MSM8x60 this is the
+ qcom-apcs-ipc mailbox channel 1 (writes GCC + 0x008 bit 1).
+
+ interrupt-controller: true
+
+ '#interrupt-cells':
+ const: 2
+ description:
+ First cell is the MPM pin number / parent GIC SPI; second cell
+ is the trigger type.
+
+ qcom,mpm-pin-count:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: Total number of MPM pins exposed.
+
+ qcom,mpm-pin-map:
+ $ref: /schemas/types.yaml#/definitions/uint32-matrix
+ items:
+ items:
+ - description: MPM pin number
+ - description: GIC SPI number this pin maps to
+ description: |
+ List of (MPM-pin, GIC-SPI) tuples for wake sources that have a
+ corresponding GIC IRQ. Consumers route their interrupts through
+ the MPM hierarchical irqdomain to take advantage of these
+ mappings.
+
+ Raw wake pins like SDC3/4 DATx (pins 21-24) are NOT listed
+ here - they have no GIC SPI mapping and are accessed through
+ the msm8660_mpm_set_pin_wake() / msm8660_mpm_enable_pin() C API
+ by consumer drivers.
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - mboxes
+ - interrupt-controller
+ - '#interrupt-cells'
+ - qcom,mpm-pin-count
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ soc {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ msm8660_mpm: interrupt-controller@1049d8 {
+ compatible = "qcom,msm8660-mpm";
+ reg = <0x001049d8 0x440>;
+ interrupts = <GIC_SPI 2 IRQ_TYPE_EDGE_RISING>;
+ mboxes = <&gcc_ipc 1>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ interrupt-parent = <&intc>;
+ qcom,mpm-pin-count = <64>;
+ qcom,mpm-pin-map = <25 100>,
+ <26 50>,
+ <27 79>;
+ };
+ };
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v2 2/2] irqchip: add MSM8x60 MPM wakeup interrupt controller driver
2026-05-31 4:09 ` [PATCH v2 0/2] irqchip: add MSM8x60 MPM wakeup interrupt controller Herman van Hazendonk
2026-05-31 4:09 ` [PATCH v2 1/2] dt-bindings: interrupt-controller: qcom: add msm8660-mpm Herman van Hazendonk
@ 2026-05-31 4:09 ` Herman van Hazendonk
2026-06-01 7:25 ` Sebastian Andrzej Siewior
2026-06-03 15:12 ` Thomas Gleixner
1 sibling, 2 replies; 6+ messages in thread
From: Herman van Hazendonk @ 2026-05-31 4:09 UTC (permalink / raw)
To: Bjorn Andersson, Clark Williams, Conor Dooley, devicetree,
Konrad Dybcio, Krzysztof Kozlowski, linux-arm-msm, linux-kernel,
linux-rt-devel, Rob Herring, Sebastian Andrzej Siewior,
Steven Rostedt, Thomas Gleixner, van Hazendonk
Add a driver for the MSM Power Manager (MPM) on the MSM8x60 family
(MSM8260/MSM8660/APQ8060). The MPM is a small wake-source controller
implemented in the always-on power domain that latches edge-triggered
interrupts during APPS power collapse and signals the RPM to wake the
APPS back up.
The driver:
- implements an irqchip that wraps a parent GIC SPI line and
overrides ->irq_set_wake() to enable the MPM mirror of the same
interrupt;
- exposes msm8660_mpm_set_pin_wake() etc. for consumers (e.g.
mmci's SDC4 SDIO wake) that do not flow through irq_set_wake()
directly;
- communicates with the RPM via SMSM/syscon to apply the wake
enable/clear bits and request a power collapse vote.
Used on the HP TouchPad (Tenderloin) for SDIO-attached Wi-Fi and
panel touch wake-from-suspend.
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
drivers/irqchip/Kconfig | 23 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-msm8660-mpm.c | 740 ++++++++++++++++++++++++++++++
include/soc/qcom/msm8660-mpm.h | 83 ++++
4 files changed, 847 insertions(+)
create mode 100644 drivers/irqchip/irq-msm8660-mpm.c
create mode 100644 include/soc/qcom/msm8660-mpm.h
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index e755a2a05209..35598a56ac79 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -525,6 +525,29 @@ config QCOM_MPM
MSM Power Manager driver to manage and configure wakeup
IRQs for Qualcomm Technologies Inc (QTI) mobile chips.
+config QCOM_MSM8660_MPM
+ bool "MSM8x60 MPM wakeup interrupt controller"
+ depends on ARCH_QCOM
+ depends on MFD_SYSCON
+ depends on MAILBOX
+ select IRQ_DOMAIN_HIERARCHY
+ default y
+ help
+ Platform driver for the MSM Power Manager (MPM) wakeup interrupt
+ controller on the MSM8x60 family (MSM8260/MSM8660/APQ8060). The vMPM registers live inside
+ the RPM control block, which makes the generic QCOM_MPM driver
+ unusable here (it assumes a dedicated MPM SRAM region, an IPCC
+ mailbox, and uses IRQCHIP_DECLARE early-init that races platform
+ device creation).
+
+ This driver replicates the legacy 2.6.35-palm arch/arm/mach-msm/
+ mpm.c mechanism as a regular platform driver, accessing the vMPM
+ registers via a syscon phandle to the RPM block and signaling the
+ MPM via the qcom-apcs-ipc mailbox (writing to GCC + 0x008 bit 1).
+
+ Required for cpuidle deep-sleep states (SPC/PC) and for
+ WoWLAN-style wake-from-suspend on MSM8x60 SoCs.
+
config CSKY_MPINTC
bool
depends on CSKY
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 26aa3b6ec99f..3e64591f0f5b 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -100,6 +100,7 @@ obj-$(CONFIG_MESON_IRQ_GPIO) += irq-meson-gpio.o
obj-$(CONFIG_GOLDFISH_PIC) += irq-goldfish-pic.o
obj-$(CONFIG_QCOM_PDC) += qcom-pdc.o
obj-$(CONFIG_QCOM_MPM) += irq-qcom-mpm.o
+obj-$(CONFIG_QCOM_MSM8660_MPM) += irq-msm8660-mpm.o
obj-$(CONFIG_CSKY_MPINTC) += irq-csky-mpintc.o
obj-$(CONFIG_CSKY_APB_INTC) += irq-csky-apb-intc.o
obj-$(CONFIG_RISCV_INTC) += irq-riscv-intc.o
diff --git a/drivers/irqchip/irq-msm8660-mpm.c b/drivers/irqchip/irq-msm8660-mpm.c
new file mode 100644
index 000000000000..b8394d9c076d
--- /dev/null
+++ b/drivers/irqchip/irq-msm8660-mpm.c
@@ -0,0 +1,740 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MSM8x60 family (MSM8260/MSM8660/APQ8060) MPM (MSM Power Manager) wakeup interrupt controller
+ *
+ * The MPM is an always-on hardware block that keeps a small set of wake
+ * sources alive while the application processor is powered down for
+ * cpuidle Power Collapse or suspend-to-RAM. On MSM8x60 the
+ * vMPM (virtual MPM) registers live INSIDE the RPM's 4 KB control block
+ * at:
+ *
+ * request (control) regs: RPM_BASE + 0x9d8 (ENABLE, DETECT_CTL,
+ * POLARITY, CLEAR)
+ * status (pending) regs: RPM_BASE + 0xdf8 (== 0x9d8 + 0x420)
+ *
+ * The mainline qcom-mpm driver (drivers/irqchip/irq-qcom-mpm.c) is
+ * fundamentally incompatible with this layout:
+ * - it assumes a dedicated MPM SRAM region separate from RPM;
+ * - it assumes a mailbox controller (IPCC) for wake notification;
+ * - it uses IRQCHIP_DECLARE which runs before platform devices exist,
+ * so of_find_device_by_node() returns NULL and the init silently
+ * hangs.
+ *
+ * This driver replicates the 2.6.35-palm `arch/arm/mach-msm/mpm.c`
+ * mechanism as a regular platform driver: probes after platform
+ * infrastructure is ready, ioremaps the vMPM sub-region of the RPM
+ * control block (the qcom,rpm driver maps the surrounding area for
+ * its own use; the two mappings overlap and neither claims exclusive
+ * ownership), and uses the qcom-apcs-ipc mailbox for wake notification
+ * (writing to GCC + 0x008 bit 1).
+ *
+ * Register access is done with readl_relaxed/writel_relaxed rather than
+ * via the RPM syscon regmap. The IRQ core invokes our mask/unmask/
+ * set_type/set_wake callbacks with the irq_desc's raw_spinlock_t held,
+ * and syscon regmaps use a sleepable spinlock_t which on PREEMPT_RT
+ * would deadlock under that raw lock. Direct MMIO is also what every
+ * other SoC irqchip (qcom-pdc, gic-v3, ...) does.
+ *
+ * Two consumer interfaces:
+ *
+ * 1. Hierarchical irqdomain: for MPM pins that map to GIC SPIs (USB,
+ * HDMI, ...). Consumers wire their interrupts through this
+ * controller via interrupts-extended and the kernel manages
+ * enable / mask / set_type / set_wake via the IRQ subsystem.
+ *
+ * 2. Raw-pin API: for MPM pins that do NOT correspond to a GIC IRQ
+ * (SDC3_DAT1=21, SDC3_DAT3=22, SDC4_DAT1=23, SDC4_DAT3=24).
+ * These are physical wake-signal lines monitored by MPM
+ * directly. Consumers (mmci for SDC4 wake) call
+ * msm8660_mpm_set_pin_wake() etc. The consumer API establishes
+ * a device_link from consumer to producer so the MPM device
+ * cannot disappear while a consumer holds a handle.
+ *
+ * Copyright (c) 2026 Herman van Hazendonk <github.com@herrie.org>
+ * Copyright (c) 2010-2012, The Linux Foundation (legacy mpm.c reference)
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/mailbox_client.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include <soc/qcom/msm8660-mpm.h>
+
+/*
+ * vMPM register offsets (relative to the start of the ioremap'd window
+ * = RPM base + 0x9d8). Each register is two 32-bit slots because MPM
+ * exposes 64 wake pins.
+ */
+#define MSM8660_MPM_REG_ENABLE 0x00
+#define MSM8660_MPM_REG_DETECT_CTL 0x08
+#define MSM8660_MPM_REG_POLARITY 0x10
+#define MSM8660_MPM_REG_CLEAR 0x18
+
+/* Status registers at +0x420 from vMPM base (== RPM + 0xdf8). */
+#define MSM8660_MPM_STATUS_OFFSET 0x420
+
+#define MSM8660_MPM_PIN_COUNT 64
+#define MSM8660_MPM_REG_WIDTH 2
+
+struct msm8660_mpm_pin {
+ int pin;
+ int hwirq;
+};
+
+struct msm8660_mpm {
+ struct device *dev;
+ void __iomem *base;
+ struct irq_domain *domain;
+ struct msm8660_mpm_pin *pin_map;
+ unsigned int pin_map_count;
+ int parent_irq;
+ struct mbox_client mbox_client;
+ struct mbox_chan *mbox_chan;
+};
+
+/*
+ * Singleton - there is only one MPM instance per SoC. msm8660_mpm_get()
+ * returns this. Updates are serialised through the binding lifecycle so
+ * a plain pointer is sufficient.
+ */
+static struct msm8660_mpm *msm8660_mpm_global;
+
+static u32 msm8660_mpm_read(struct msm8660_mpm *mpm, unsigned int reg)
+{
+ return readl_relaxed(mpm->base + reg);
+}
+
+static void msm8660_mpm_write(struct msm8660_mpm *mpm, unsigned int reg,
+ u32 val)
+{
+ writel_relaxed(val, mpm->base + reg);
+}
+
+/*
+ * Doorbell the RPM after touching the vMPM request registers. Without
+ * this the RPM keeps using its last cached copy of the enable/detect/
+ * polarity state and our configuration changes have no effect.
+ *
+ * Called from raw_spinlock_t-held contexts (irq_chip mask/unmask/
+ * set_type/set_wake), so the mailbox driver must accept that. The
+ * qcom-apcs-ipc mailbox just does a writel into the IPC trigger
+ * register; it is safe under a raw lock.
+ */
+static void msm8660_mpm_doorbell(struct msm8660_mpm *mpm)
+{
+ int ret;
+
+ if (!mpm->mbox_chan)
+ return;
+
+ ret = mbox_send_message(mpm->mbox_chan, NULL);
+ if (ret < 0)
+ dev_warn_ratelimited(mpm->dev,
+ "RPM doorbell failed: %d\n", ret);
+}
+
+static int msm8660_mpm_pin_to_hwirq(struct msm8660_mpm *mpm, int pin)
+{
+ int i;
+
+ for (i = 0; i < mpm->pin_map_count; i++) {
+ if (mpm->pin_map[i].pin == pin)
+ return mpm->pin_map[i].hwirq;
+ }
+ return -ENOENT;
+}
+
+/*
+ * IPC handler: MPM fires this IRQ when one or more enabled wake pins
+ * have pending activity. Read pending status, CLEAR the pending bits
+ * BEFORE dispatching the per-pin handlers so a fresh edge that arrives
+ * during dispatch cannot be wiped out by a later CLEAR write, then
+ * replay each pending pin through the irqdomain.
+ */
+static irqreturn_t msm8660_mpm_irq(int irq, void *data)
+{
+ struct msm8660_mpm *mpm = data;
+ unsigned long pending[MSM8660_MPM_REG_WIDTH];
+ unsigned long enable[MSM8660_MPM_REG_WIDTH];
+ int i, j;
+
+ for (i = 0; i < MSM8660_MPM_REG_WIDTH; i++) {
+ pending[i] = msm8660_mpm_read(mpm,
+ MSM8660_MPM_STATUS_OFFSET + i * 4);
+ enable[i] = msm8660_mpm_read(mpm,
+ MSM8660_MPM_REG_ENABLE + i * 4);
+ pending[i] &= enable[i];
+
+ /*
+ * Clear before dispatching: a new edge latched on this pin
+ * after this point will set the pending bit again and we
+ * will service it on the next IPC IRQ. Clearing AFTER the
+ * handler would race with that new latch and silently lose
+ * the new edge.
+ */
+ if (pending[i])
+ msm8660_mpm_write(mpm,
+ MSM8660_MPM_REG_CLEAR + i * 4, pending[i]);
+ }
+
+ for (i = 0; i < MSM8660_MPM_REG_WIDTH; i++) {
+ unsigned long bits = pending[i];
+
+ for_each_set_bit(j, &bits, 32) {
+ int pin = i * 32 + j;
+ int hwirq = msm8660_mpm_pin_to_hwirq(mpm, pin);
+
+ if (hwirq >= 0) {
+ dev_dbg(mpm->dev, "wake pin %d -> hwirq %d\n",
+ pin, hwirq);
+ generic_handle_domain_irq(mpm->domain, hwirq);
+ }
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Resolve an irq_data hwirq back to its MPM pin number, or -1 if the
+ * pin is not in the qcom,mpm-pin-map (which would mean a consumer
+ * bound to a hwirq that has no MPM mapping).
+ */
+static int msm8660_mpm_hwirq_to_pin(struct msm8660_mpm *mpm, unsigned int hwirq)
+{
+ int i;
+
+ for (i = 0; i < mpm->pin_map_count; i++) {
+ if (mpm->pin_map[i].hwirq == hwirq)
+ return mpm->pin_map[i].pin;
+ }
+ return -1;
+}
+
+static void msm8660_mpm_enable_hwirq(struct irq_data *d, bool enable)
+{
+ struct msm8660_mpm *mpm = irq_data_get_irq_chip_data(d);
+ int pin;
+ u32 val, mask;
+
+ pin = msm8660_mpm_hwirq_to_pin(mpm, d->hwirq);
+ if (pin < 0)
+ return;
+
+ mask = BIT(pin % 32);
+ val = msm8660_mpm_read(mpm,
+ MSM8660_MPM_REG_ENABLE + (pin / 32) * 4);
+ if (enable)
+ val |= mask;
+ else
+ val &= ~mask;
+ msm8660_mpm_write(mpm,
+ MSM8660_MPM_REG_ENABLE + (pin / 32) * 4, val);
+
+ msm8660_mpm_doorbell(mpm);
+}
+
+static void msm8660_mpm_mask_irq(struct irq_data *d)
+{
+ msm8660_mpm_enable_hwirq(d, false);
+ irq_chip_mask_parent(d);
+}
+
+static void msm8660_mpm_unmask_irq(struct irq_data *d)
+{
+ msm8660_mpm_enable_hwirq(d, true);
+ irq_chip_unmask_parent(d);
+}
+
+static int msm8660_mpm_set_type(struct irq_data *d, unsigned int type)
+{
+ struct msm8660_mpm *mpm = irq_data_get_irq_chip_data(d);
+ u32 detect, polarity, mask;
+ int pin;
+
+ pin = msm8660_mpm_hwirq_to_pin(mpm, d->hwirq);
+ if (pin < 0)
+ return -ENOENT;
+
+ mask = BIT(pin % 32);
+ detect = msm8660_mpm_read(mpm,
+ MSM8660_MPM_REG_DETECT_CTL + (pin / 32) * 4);
+ polarity = msm8660_mpm_read(mpm,
+ MSM8660_MPM_REG_POLARITY + (pin / 32) * 4);
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ detect |= mask;
+ polarity |= mask;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ detect |= mask;
+ polarity &= ~mask;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ detect &= ~mask;
+ polarity |= mask;
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ detect &= ~mask;
+ polarity &= ~mask;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ msm8660_mpm_write(mpm,
+ MSM8660_MPM_REG_DETECT_CTL + (pin / 32) * 4, detect);
+ msm8660_mpm_write(mpm,
+ MSM8660_MPM_REG_POLARITY + (pin / 32) * 4, polarity);
+
+ msm8660_mpm_doorbell(mpm);
+
+ return irq_chip_set_type_parent(d, type);
+}
+
+/*
+ * Toggle MPM monitoring of the pin and propagate the wake request to
+ * the parent GIC so it also stays alive during power-collapse.
+ *
+ * Without this callback the generic IRQ core would either silently
+ * succeed (with IRQCHIP_SKIP_SET_WAKE) or fail outright, and neither
+ * the MPM nor the GIC would actually be programmed to wake the system.
+ */
+static int msm8660_mpm_set_wake(struct irq_data *d, unsigned int on)
+{
+ struct msm8660_mpm *mpm = irq_data_get_irq_chip_data(d);
+ int pin;
+
+ pin = msm8660_mpm_hwirq_to_pin(mpm, d->hwirq);
+ if (pin < 0)
+ return -ENOENT;
+
+ msm8660_mpm_enable_hwirq(d, !!on);
+
+ return irq_chip_set_wake_parent(d, on);
+}
+
+static struct irq_chip msm8660_mpm_chip = {
+ .name = "msm8660-mpm",
+ .irq_mask = msm8660_mpm_mask_irq,
+ .irq_unmask = msm8660_mpm_unmask_irq,
+ .irq_set_type = msm8660_mpm_set_type,
+ .irq_set_wake = msm8660_mpm_set_wake,
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .flags = IRQCHIP_MASK_ON_SUSPEND,
+};
+
+static int msm8660_mpm_domain_alloc(struct irq_domain *domain,
+ unsigned int virq, unsigned int nr_irqs,
+ void *data)
+{
+ struct msm8660_mpm *mpm = domain->host_data;
+ struct irq_fwspec *fwspec = data;
+ struct irq_fwspec parent_fwspec;
+ irq_hw_number_t hwirq;
+ int i, ret;
+
+ if (fwspec->param_count != 2)
+ return -EINVAL;
+
+ hwirq = fwspec->param[0];
+
+ for (i = 0; i < nr_irqs; i++)
+ irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
+ &msm8660_mpm_chip, mpm);
+
+ parent_fwspec.fwnode = domain->parent->fwnode;
+ parent_fwspec.param_count = 3;
+ parent_fwspec.param[0] = 0;
+ parent_fwspec.param[1] = hwirq;
+ parent_fwspec.param[2] = fwspec->param[1];
+
+ ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs,
+ &parent_fwspec);
+ if (ret) {
+ dev_err(mpm->dev, "irq_domain_alloc_irqs_parent failed: %d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int msm8660_mpm_translate(struct irq_domain *d,
+ struct irq_fwspec *fwspec,
+ unsigned long *hwirq, unsigned int *type)
+{
+ if (fwspec->param_count != 2)
+ return -EINVAL;
+
+ *hwirq = fwspec->param[0];
+ *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;
+ return 0;
+}
+
+static const struct irq_domain_ops msm8660_mpm_domain_ops = {
+ .translate = msm8660_mpm_translate,
+ .alloc = msm8660_mpm_domain_alloc,
+ .free = irq_domain_free_irqs_common,
+};
+
+/* ===================================================================
+ * Raw-pin consumer API
+ * ===================================================================
+ */
+
+/**
+ * msm8660_mpm_get() - acquire a handle to the MPM for raw-pin use
+ * @consumer: device of the consumer driver
+ * @np: optional device-tree node containing a phandle reference
+ * @propname: optional property name for that phandle (e.g. "qcom,mpm")
+ *
+ * Returns the singleton MPM handle, ERR_PTR(-EPROBE_DEFER) if the MPM
+ * driver has not finished probing yet, or ERR_PTR(-ENOENT) if @np is
+ * given and the phandle does not resolve.
+ *
+ * On success this also establishes a consumer-supplier device_link so
+ * the MPM device cannot be unbound while the consumer holds the
+ * handle. The link is auto-removed when @consumer is unbound.
+ */
+struct msm8660_mpm *msm8660_mpm_get(struct device *consumer,
+ struct device_node *np,
+ const char *propname)
+{
+ struct device_node *mpm_np;
+ struct device_link *link;
+
+ if (!msm8660_mpm_global)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ if (np && propname) {
+ mpm_np = of_parse_phandle(np, propname, 0);
+ if (!mpm_np)
+ return ERR_PTR(-ENOENT);
+ of_node_put(mpm_np);
+ }
+
+ if (!consumer)
+ return msm8660_mpm_global;
+
+ link = device_link_add(consumer, msm8660_mpm_global->dev,
+ DL_FLAG_AUTOREMOVE_CONSUMER);
+ if (!link) {
+ dev_warn(consumer, "failed to link to MPM, deferring\n");
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ return msm8660_mpm_global;
+}
+EXPORT_SYMBOL_GPL(msm8660_mpm_get);
+
+/**
+ * msm8660_mpm_enable_pin() - enable/disable MPM monitoring of a pin
+ * @mpm: handle from msm8660_mpm_get()
+ * @pin: MPM pin index (0..MSM8660_MPM_PIN_COUNT-1)
+ * @enable: true to monitor, false to ignore
+ *
+ * Programs the ENABLE register directly. Intended for "raw" wake pins
+ * (SDC3_DAT1=21, SDC3_DAT3=22, SDC4_DAT1=23, SDC4_DAT3=24) that have no
+ * GIC IRQ mapping. For pins that DO have a GIC mapping (in
+ * qcom,mpm-pin-map), use the irqdomain path instead.
+ */
+int msm8660_mpm_enable_pin(struct msm8660_mpm *mpm, unsigned int pin,
+ bool enable)
+{
+ u32 val;
+
+ if (!mpm || pin >= MSM8660_MPM_PIN_COUNT)
+ return -EINVAL;
+
+ val = msm8660_mpm_read(mpm,
+ MSM8660_MPM_REG_ENABLE + (pin / 32) * 4);
+ if (enable)
+ val |= BIT(pin % 32);
+ else
+ val &= ~BIT(pin % 32);
+ msm8660_mpm_write(mpm,
+ MSM8660_MPM_REG_ENABLE + (pin / 32) * 4, val);
+
+ msm8660_mpm_doorbell(mpm);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(msm8660_mpm_enable_pin);
+
+/**
+ * msm8660_mpm_set_pin_wake() - mark a pin as wake-capable
+ * @mpm: handle
+ * @pin: MPM pin index
+ * @on: true to allow this pin to wake the system, false to clear.
+ *
+ * Equivalent to msm8660_mpm_enable_pin() on MSM8660 - the hardware has
+ * a single ENABLE register, not separate enable + wake masks. The name
+ * is kept for API parity with the legacy mpm.h interface so consumers
+ * can express the wake-source intent explicitly.
+ */
+int msm8660_mpm_set_pin_wake(struct msm8660_mpm *mpm, unsigned int pin,
+ bool on)
+{
+ return msm8660_mpm_enable_pin(mpm, pin, on);
+}
+EXPORT_SYMBOL_GPL(msm8660_mpm_set_pin_wake);
+
+/**
+ * msm8660_mpm_set_pin_type() - set trigger type for a raw MPM pin
+ * @mpm: handle
+ * @pin: MPM pin index
+ * @flow_type: standard IRQ_TYPE_* constants
+ *
+ * On MSM8660 the trigger config is split across DETECT_CTL (edge vs
+ * level) and POLARITY (rising/high vs falling/low).
+ */
+int msm8660_mpm_set_pin_type(struct msm8660_mpm *mpm, unsigned int pin,
+ unsigned int flow_type)
+{
+ u32 detect, polarity;
+ bool edge, polarity_high;
+
+ if (!mpm || pin >= MSM8660_MPM_PIN_COUNT)
+ return -EINVAL;
+
+ edge = !!(flow_type & IRQ_TYPE_EDGE_BOTH);
+ polarity_high = !!(flow_type & (IRQ_TYPE_EDGE_RISING |
+ IRQ_TYPE_LEVEL_HIGH));
+
+ detect = msm8660_mpm_read(mpm,
+ MSM8660_MPM_REG_DETECT_CTL + (pin / 32) * 4);
+ polarity = msm8660_mpm_read(mpm,
+ MSM8660_MPM_REG_POLARITY + (pin / 32) * 4);
+
+ if (edge)
+ detect |= BIT(pin % 32);
+ else
+ detect &= ~BIT(pin % 32);
+
+ if (polarity_high)
+ polarity |= BIT(pin % 32);
+ else
+ polarity &= ~BIT(pin % 32);
+
+ msm8660_mpm_write(mpm,
+ MSM8660_MPM_REG_DETECT_CTL + (pin / 32) * 4, detect);
+ msm8660_mpm_write(mpm,
+ MSM8660_MPM_REG_POLARITY + (pin / 32) * 4, polarity);
+
+ msm8660_mpm_doorbell(mpm);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(msm8660_mpm_set_pin_type);
+
+/* ===================================================================
+ * Platform driver
+ * ===================================================================
+ */
+
+static int msm8660_mpm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct msm8660_mpm *mpm;
+ struct irq_domain *parent_domain;
+ struct device_node *parent_np;
+ struct resource *res;
+ int ret, i;
+
+ if (msm8660_mpm_global)
+ return dev_err_probe(dev, -EBUSY,
+ "only one MPM instance is supported\n");
+
+ mpm = devm_kzalloc(dev, sizeof(*mpm), GFP_KERNEL);
+ if (!mpm)
+ return -ENOMEM;
+
+ mpm->dev = dev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return dev_err_probe(dev, -ENODEV, "missing reg property\n");
+
+ /*
+ * Use a non-exclusive mapping: the qcom,rpm driver maps the
+ * surrounding RPM control block via its own platform resource,
+ * and our vMPM sub-region overlaps that mapping. devm_ioremap()
+ * does not call request_mem_region() so there is no conflict.
+ */
+ mpm->base = devm_ioremap(dev, res->start, resource_size(res));
+ if (!mpm->base)
+ return dev_err_probe(dev, -ENOMEM,
+ "failed to ioremap vMPM at %pR\n", res);
+
+ /*
+ * Parse pin map (IRQ-mapped wake pins; raw pins like SDC4_DAT1
+ * are not listed here - they are accessed via the pin-API).
+ */
+ ret = of_property_count_u32_elems(np, "qcom,mpm-pin-map");
+ if (ret < 0 || ret % 2)
+ return dev_err_probe(dev, -EINVAL,
+ "invalid qcom,mpm-pin-map\n");
+
+ mpm->pin_map_count = ret / 2;
+ mpm->pin_map = devm_kcalloc(dev, mpm->pin_map_count,
+ sizeof(*mpm->pin_map), GFP_KERNEL);
+ if (!mpm->pin_map)
+ return -ENOMEM;
+
+ for (i = 0; i < mpm->pin_map_count; i++) {
+ u32 pin, hwirq;
+
+ of_property_read_u32_index(np, "qcom,mpm-pin-map",
+ i * 2, &pin);
+ of_property_read_u32_index(np, "qcom,mpm-pin-map",
+ i * 2 + 1, &hwirq);
+
+ if (pin >= MSM8660_MPM_PIN_COUNT)
+ return dev_err_probe(dev, -EINVAL,
+ "qcom,mpm-pin-map entry %d: pin %u >= %u\n",
+ i, pin, MSM8660_MPM_PIN_COUNT);
+
+ mpm->pin_map[i].pin = pin;
+ mpm->pin_map[i].hwirq = hwirq;
+
+ dev_dbg(dev, "pin map: pin %u -> hwirq %u\n", pin, hwirq);
+ }
+
+ parent_np = of_irq_find_parent(np);
+ if (!parent_np)
+ return dev_err_probe(dev, -ENODEV,
+ "failed to find parent interrupt controller\n");
+
+ parent_domain = irq_find_host(parent_np);
+ of_node_put(parent_np);
+ if (!parent_domain)
+ return dev_err_probe(dev, -ENODEV,
+ "failed to find parent IRQ domain\n");
+
+ mpm->domain = irq_domain_create_hierarchy(parent_domain, 0,
+ MSM8660_MPM_PIN_COUNT,
+ of_fwnode_handle(np),
+ &msm8660_mpm_domain_ops,
+ mpm);
+ if (!mpm->domain)
+ return dev_err_probe(dev, -ENOMEM,
+ "failed to create IRQ domain\n");
+
+ mpm->parent_irq = platform_get_irq(pdev, 0);
+ if (mpm->parent_irq < 0) {
+ ret = mpm->parent_irq;
+ goto err_remove_domain;
+ }
+
+ /*
+ * Mailbox channel for poking MPM to re-read its config. Get this
+ * BEFORE registering our IRQ handler so that doorbell-from-IRQ
+ * never sees a partially-initialised channel pointer.
+ */
+ mpm->mbox_client.dev = dev;
+ mpm->mbox_client.knows_txdone = true;
+ mpm->mbox_chan = mbox_request_channel(&mpm->mbox_client, 0);
+ if (IS_ERR(mpm->mbox_chan)) {
+ ret = PTR_ERR(mpm->mbox_chan);
+ mpm->mbox_chan = NULL;
+ if (ret == -EPROBE_DEFER)
+ goto err_remove_domain;
+ dev_warn(dev, "no mailbox channel: %d (continuing without RPM doorbell)\n",
+ ret);
+ }
+
+ /*
+ * Register the parent IRQ last and use plain request_irq() (not
+ * devm_*) so we can free it explicitly in ->remove() before
+ * irq_domain_remove(). With devm_request_irq() the handler
+ * outlives irq_domain_remove() and a wake event arriving in the
+ * removal window would dereference a freed domain pointer.
+ */
+ ret = request_irq(mpm->parent_irq, msm8660_mpm_irq,
+ IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND,
+ "msm8660-mpm", mpm);
+ if (ret) {
+ dev_err(dev, "failed to request IRQ %d: %d\n",
+ mpm->parent_irq, ret);
+ goto err_free_mbox;
+ }
+
+ platform_set_drvdata(pdev, mpm);
+ msm8660_mpm_global = mpm;
+
+ dev_info(dev, "ready: %d pin mappings, irq=%d\n",
+ mpm->pin_map_count, mpm->parent_irq);
+
+ return 0;
+
+err_free_mbox:
+ if (mpm->mbox_chan)
+ mbox_free_channel(mpm->mbox_chan);
+err_remove_domain:
+ irq_domain_remove(mpm->domain);
+ return ret;
+}
+
+static void msm8660_mpm_remove(struct platform_device *pdev)
+{
+ struct msm8660_mpm *mpm = platform_get_drvdata(pdev);
+
+ /*
+ * Tear down in strict reverse order: drop the singleton so new
+ * consumers cannot grab a handle, free the IRQ so the handler
+ * cannot fire again, free the mailbox channel, then remove the
+ * domain. Consumer device_links established in msm8660_mpm_get()
+ * prevent the parent device from being unbound while a consumer
+ * still holds a handle.
+ */
+ msm8660_mpm_global = NULL;
+ free_irq(mpm->parent_irq, mpm);
+ if (mpm->mbox_chan)
+ mbox_free_channel(mpm->mbox_chan);
+ irq_domain_remove(mpm->domain);
+}
+
+static const struct of_device_id msm8660_mpm_of_match[] = {
+ { .compatible = "qcom,msm8660-mpm" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, msm8660_mpm_of_match);
+
+static struct platform_driver msm8660_mpm_driver = {
+ .probe = msm8660_mpm_probe,
+ .remove = msm8660_mpm_remove,
+ .driver = {
+ .name = "msm8660-mpm",
+ .of_match_table = msm8660_mpm_of_match,
+ },
+};
+
+static int __init msm8660_mpm_init(void)
+{
+ return platform_driver_register(&msm8660_mpm_driver);
+}
+subsys_initcall(msm8660_mpm_init);
+
+static void __exit msm8660_mpm_exit(void)
+{
+ platform_driver_unregister(&msm8660_mpm_driver);
+}
+module_exit(msm8660_mpm_exit);
+
+MODULE_DESCRIPTION("Qualcomm MSM8x60 MPM wakeup interrupt controller");
+MODULE_LICENSE("GPL");
diff --git a/include/soc/qcom/msm8660-mpm.h b/include/soc/qcom/msm8660-mpm.h
new file mode 100644
index 000000000000..27f3f22d57b4
--- /dev/null
+++ b/include/soc/qcom/msm8660-mpm.h
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Qualcomm MSM8x60 family (MSM8260/MSM8660/APQ8060) MPM wake-source consumer interface.
+ *
+ * The MPM driver lives at drivers/irqchip/irq-msm8660-mpm.c. It exposes
+ * TWO interfaces:
+ *
+ * 1. Hierarchical irqdomain (preferred). For wake sources that map to
+ * GIC SPIs (USB1_HS, HDMI, ...). Consumers wire their IRQ through
+ * the MPM via `interrupts-extended = <&msm8660_mpm ...>` in DT and
+ * the IRQ subsystem manages enable / mask / set_type via the
+ * irqdomain alloc path. No explicit C API call needed.
+ *
+ * 2. Raw-pin API (this header). For wake sources that do NOT have a
+ * GIC IRQ mapping: SDC3_DAT1=21, SDC3_DAT3=22, SDC4_DAT1=23,
+ * SDC4_DAT3=24. These are physical wake-signal lines that MPM
+ * monitors directly. mmci (for SDC4 WiFi wake) obtains a handle
+ * via msm8660_mpm_get() and uses the helpers below.
+ */
+
+#ifndef __SOC_QCOM_MSM8660_MPM_H__
+#define __SOC_QCOM_MSM8660_MPM_H__
+
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/irq.h>
+
+#define MSM8660_MPM_NR_PINS 64
+
+/* Well-known wake-source pin indices (from the legacy 2.6.35-palm tree). */
+#define MSM8660_MPM_PIN_SDC3_DAT1 21
+#define MSM8660_MPM_PIN_SDC3_DAT3 22
+#define MSM8660_MPM_PIN_SDC4_DAT1 23
+#define MSM8660_MPM_PIN_SDC4_DAT3 24
+
+struct device;
+struct device_node;
+struct msm8660_mpm;
+
+#if IS_ENABLED(CONFIG_QCOM_MSM8660_MPM)
+
+struct msm8660_mpm *msm8660_mpm_get(struct device *consumer,
+ struct device_node *np,
+ const char *propname);
+
+int msm8660_mpm_set_pin_wake(struct msm8660_mpm *mpm, unsigned int pin,
+ bool on);
+int msm8660_mpm_enable_pin(struct msm8660_mpm *mpm, unsigned int pin,
+ bool enable);
+int msm8660_mpm_set_pin_type(struct msm8660_mpm *mpm, unsigned int pin,
+ unsigned int flow_type);
+
+#else /* !CONFIG_QCOM_MSM8660_MPM */
+
+static inline struct msm8660_mpm *
+msm8660_mpm_get(struct device *consumer, struct device_node *np,
+ const char *propname)
+{
+ return ERR_PTR(-ENODEV);
+}
+
+static inline int msm8660_mpm_set_pin_wake(struct msm8660_mpm *mpm,
+ unsigned int pin, bool on)
+{
+ return -ENODEV;
+}
+
+static inline int msm8660_mpm_enable_pin(struct msm8660_mpm *mpm,
+ unsigned int pin, bool enable)
+{
+ return -ENODEV;
+}
+
+static inline int msm8660_mpm_set_pin_type(struct msm8660_mpm *mpm,
+ unsigned int pin,
+ unsigned int flow_type)
+{
+ return -ENODEV;
+}
+
+#endif /* CONFIG_QCOM_MSM8660_MPM */
+
+#endif /* __SOC_QCOM_MSM8660_MPM_H__ */
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v2 1/2] dt-bindings: interrupt-controller: qcom: add msm8660-mpm
2026-05-31 4:09 ` [PATCH v2 1/2] dt-bindings: interrupt-controller: qcom: add msm8660-mpm Herman van Hazendonk
@ 2026-05-31 8:01 ` Krzysztof Kozlowski
0 siblings, 0 replies; 6+ messages in thread
From: Krzysztof Kozlowski @ 2026-05-31 8:01 UTC (permalink / raw)
To: Herman van Hazendonk, Bjorn Andersson, Clark Williams,
Conor Dooley, devicetree, Konrad Dybcio, Krzysztof Kozlowski,
linux-arm-msm, linux-kernel, linux-rt-devel, Rob Herring,
Sebastian Andrzej Siewior, Steven Rostedt, Thomas Gleixner
On 31/05/2026 06:09, Herman van Hazendonk wrote:
> Add the binding for the Modem Power Manager (MPM) interrupt
> controller on the MSM8x60 family (MSM8260/MSM8660/APQ8060). The MPM
> is a small wake-capable interrupt aggregator that lets the SoC stay
> in low-power states while a small set of GIC SPIs continues to be
> monitored and can wake the system on an edge.
>
> The binding describes its register region (in the RPM syscon), the
> GIC SPI it raises on a wake event, the GIC mailbox it shares with
> the RPM, and the per-pin wake-source mapping table.
>
> Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
Dropping for the same reason as in other patches - mixed up with
everything else.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v2 2/2] irqchip: add MSM8x60 MPM wakeup interrupt controller driver
2026-05-31 4:09 ` [PATCH v2 2/2] irqchip: add MSM8x60 MPM wakeup interrupt controller driver Herman van Hazendonk
@ 2026-06-01 7:25 ` Sebastian Andrzej Siewior
2026-06-03 15:12 ` Thomas Gleixner
1 sibling, 0 replies; 6+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-06-01 7:25 UTC (permalink / raw)
To: Herman van Hazendonk
Cc: Bjorn Andersson, Clark Williams, Conor Dooley, devicetree,
Konrad Dybcio, Krzysztof Kozlowski, linux-arm-msm, linux-kernel,
linux-rt-devel, Rob Herring, Steven Rostedt, Thomas Gleixner
On 2026-05-31 06:09:11 [+0200], Herman van Hazendonk wrote:
> --- a/drivers/irqchip/Kconfig
> +++ b/drivers/irqchip/Kconfig
> @@ -525,6 +525,29 @@ config QCOM_MPM
> MSM Power Manager driver to manage and configure wakeup
> IRQs for Qualcomm Technologies Inc (QTI) mobile chips.
>
> +config QCOM_MSM8660_MPM
> + bool "MSM8x60 MPM wakeup interrupt controller"
> + depends on ARCH_QCOM
> + depends on MFD_SYSCON
> + depends on MAILBOX
> + select IRQ_DOMAIN_HIERARCHY
> + default y
This shouldn't by y by default.
> + help
> + Platform driver for the MSM Power Manager (MPM) wakeup interrupt
> + controller on the MSM8x60 family (MSM8260/MSM8660/APQ8060). The vMPM registers live inside
> + the RPM control block, which makes the generic QCOM_MPM driver
> + unusable here (it assumes a dedicated MPM SRAM region, an IPCC
> + mailbox, and uses IRQCHIP_DECLARE early-init that races platform
> + device creation).
> +
> + This driver replicates the legacy 2.6.35-palm arch/arm/mach-msm/
> + mpm.c mechanism as a regular platform driver, accessing the vMPM
> + registers via a syscon phandle to the RPM block and signaling the
> + MPM via the qcom-apcs-ipc mailbox (writing to GCC + 0x008 bit 1).
This is a reference to something that does not exist.
> + Required for cpuidle deep-sleep states (SPC/PC) and for
> + WoWLAN-style wake-from-suspend on MSM8x60 SoCs.
> +
> config CSKY_MPINTC
> bool
> depends on CSKY
> diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> index 26aa3b6ec99f..3e64591f0f5b 100644
> --- a/drivers/irqchip/Makefile
> +++ b/drivers/irqchip/Makefile
> @@ -100,6 +100,7 @@ obj-$(CONFIG_MESON_IRQ_GPIO) += irq-meson-gpio.o
> obj-$(CONFIG_GOLDFISH_PIC) += irq-goldfish-pic.o
> obj-$(CONFIG_QCOM_PDC) += qcom-pdc.o
> obj-$(CONFIG_QCOM_MPM) += irq-qcom-mpm.o
> +obj-$(CONFIG_QCOM_MSM8660_MPM) += irq-msm8660-mpm.o
> obj-$(CONFIG_CSKY_MPINTC) += irq-csky-mpintc.o
> obj-$(CONFIG_CSKY_APB_INTC) += irq-csky-apb-intc.o
> obj-$(CONFIG_RISCV_INTC) += irq-riscv-intc.o
> diff --git a/drivers/irqchip/irq-msm8660-mpm.c b/drivers/irqchip/irq-msm8660-mpm.c
> new file mode 100644
> index 000000000000..b8394d9c076d
> --- /dev/null
> +++ b/drivers/irqchip/irq-msm8660-mpm.c
> @@ -0,0 +1,740 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * MSM8x60 family (MSM8260/MSM8660/APQ8060) MPM (MSM Power Manager) wakeup interrupt controller
> + *
> + * The MPM is an always-on hardware block that keeps a small set of wake
> + * sources alive while the application processor is powered down for
> + * cpuidle Power Collapse or suspend-to-RAM. On MSM8x60 the
> + * vMPM (virtual MPM) registers live INSIDE the RPM's 4 KB control block
> + * at:
> + *
> + * request (control) regs: RPM_BASE + 0x9d8 (ENABLE, DETECT_CTL,
> + * POLARITY, CLEAR)
> + * status (pending) regs: RPM_BASE + 0xdf8 (== 0x9d8 + 0x420)
> + *
> + * The mainline qcom-mpm driver (drivers/irqchip/irq-qcom-mpm.c) is
> + * fundamentally incompatible with this layout:
> + * - it assumes a dedicated MPM SRAM region separate from RPM;
> + * - it assumes a mailbox controller (IPCC) for wake notification;
> + * - it uses IRQCHIP_DECLARE which runs before platform devices exist,
> + * so of_find_device_by_node() returns NULL and the init silently
> + * hangs.
> + *
> + * This driver replicates the 2.6.35-palm `arch/arm/mach-msm/mpm.c`
> + * mechanism as a regular platform driver: probes after platform
> + * infrastructure is ready, ioremaps the vMPM sub-region of the RPM
> + * control block (the qcom,rpm driver maps the surrounding area for
> + * its own use; the two mappings overlap and neither claims exclusive
> + * ownership), and uses the qcom-apcs-ipc mailbox for wake notification
> + * (writing to GCC + 0x008 bit 1).
Again, a file reference to something that does not exist.
> + * Register access is done with readl_relaxed/writel_relaxed rather than
> + * via the RPM syscon regmap. The IRQ core invokes our mask/unmask/
> + * set_type/set_wake callbacks with the irq_desc's raw_spinlock_t held,
> + * and syscon regmaps use a sleepable spinlock_t which on PREEMPT_RT
> + * would deadlock under that raw lock. Direct MMIO is also what every
> + * other SoC irqchip (qcom-pdc, gic-v3, ...) does.
Why is this kind of description here and not in the commit message.
> + * Two consumer interfaces:
> + *
> + * 1. Hierarchical irqdomain: for MPM pins that map to GIC SPIs (USB,
> + * HDMI, ...). Consumers wire their interrupts through this
> + * controller via interrupts-extended and the kernel manages
> + * enable / mask / set_type / set_wake via the IRQ subsystem.
> + *
> + * 2. Raw-pin API: for MPM pins that do NOT correspond to a GIC IRQ
> + * (SDC3_DAT1=21, SDC3_DAT3=22, SDC4_DAT1=23, SDC4_DAT3=24).
> + * These are physical wake-signal lines monitored by MPM
> + * directly. Consumers (mmci for SDC4 wake) call
> + * msm8660_mpm_set_pin_wake() etc. The consumer API establishes
> + * a device_link from consumer to producer so the MPM device
> + * cannot disappear while a consumer holds a handle.
> + *
> + * Copyright (c) 2026 Herman van Hazendonk <github.com@herrie.org>
> + * Copyright (c) 2010-2012, The Linux Foundation (legacy mpm.c reference)
> + */
> +
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/irq.h>
> +#include <linux/irqdomain.h>
> +#include <linux/mailbox_client.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +
> +#include <soc/qcom/msm8660-mpm.h>
> +
> +/*
> + * vMPM register offsets (relative to the start of the ioremap'd window
> + * = RPM base + 0x9d8). Each register is two 32-bit slots because MPM
> + * exposes 64 wake pins.
> + */
> +#define MSM8660_MPM_REG_ENABLE 0x00
> +#define MSM8660_MPM_REG_DETECT_CTL 0x08
> +#define MSM8660_MPM_REG_POLARITY 0x10
> +#define MSM8660_MPM_REG_CLEAR 0x18
> +
> +/* Status registers at +0x420 from vMPM base (== RPM + 0xdf8). */
> +#define MSM8660_MPM_STATUS_OFFSET 0x420
> +
> +#define MSM8660_MPM_PIN_COUNT 64
> +#define MSM8660_MPM_REG_WIDTH 2
> +
> +struct msm8660_mpm_pin {
> + int pin;
> + int hwirq;
> +};
> +
> +struct msm8660_mpm {
> + struct device *dev;
> + void __iomem *base;
> + struct irq_domain *domain;
> + struct msm8660_mpm_pin *pin_map;
> + unsigned int pin_map_count;
> + int parent_irq;
> + struct mbox_client mbox_client;
> + struct mbox_chan *mbox_chan;
> +};
> +
> +/*
> + * Singleton - there is only one MPM instance per SoC. msm8660_mpm_get()
> + * returns this. Updates are serialised through the binding lifecycle so
> + * a plain pointer is sufficient.
> + */
> +static struct msm8660_mpm *msm8660_mpm_global;
> +
> +static u32 msm8660_mpm_read(struct msm8660_mpm *mpm, unsigned int reg)
> +{
> + return readl_relaxed(mpm->base + reg);
> +}
> +
> +static void msm8660_mpm_write(struct msm8660_mpm *mpm, unsigned int reg,
> + u32 val)
> +{
> + writel_relaxed(val, mpm->base + reg);
> +}
> +
> +/*
> + * Doorbell the RPM after touching the vMPM request registers. Without
> + * this the RPM keeps using its last cached copy of the enable/detect/
> + * polarity state and our configuration changes have no effect.
> + *
> + * Called from raw_spinlock_t-held contexts (irq_chip mask/unmask/
> + * set_type/set_wake), so the mailbox driver must accept that. The
> + * qcom-apcs-ipc mailbox just does a writel into the IPC trigger
> + * register; it is safe under a raw lock.
> + */
Where are these comments coming from?
> +static void msm8660_mpm_doorbell(struct msm8660_mpm *mpm)
…
Sebastian
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v2 2/2] irqchip: add MSM8x60 MPM wakeup interrupt controller driver
2026-05-31 4:09 ` [PATCH v2 2/2] irqchip: add MSM8x60 MPM wakeup interrupt controller driver Herman van Hazendonk
2026-06-01 7:25 ` Sebastian Andrzej Siewior
@ 2026-06-03 15:12 ` Thomas Gleixner
1 sibling, 0 replies; 6+ messages in thread
From: Thomas Gleixner @ 2026-06-03 15:12 UTC (permalink / raw)
To: Herman van Hazendonk, Bjorn Andersson, Clark Williams,
Conor Dooley, devicetree, Konrad Dybcio, Krzysztof Kozlowski,
linux-arm-msm, linux-kernel, linux-rt-devel, Rob Herring,
Sebastian Andrzej Siewior, Steven Rostedt, van Hazendonk
On Sun, May 31 2026 at 06:09, Herman van Hazendonk wrote:
> + *
> + * 1. Hierarchical irqdomain: for MPM pins that map to GIC SPIs (USB,
> + * HDMI, ...). Consumers wire their interrupts through this
> + * controller via interrupts-extended and the kernel manages
> + * enable / mask / set_type / set_wake via the IRQ subsystem.
> + *
> + * 2. Raw-pin API: for MPM pins that do NOT correspond to a GIC IRQ
> + * (SDC3_DAT1=21, SDC3_DAT3=22, SDC4_DAT1=23, SDC4_DAT3=24).
> + * These are physical wake-signal lines monitored by MPM
> + * directly. Consumers (mmci for SDC4 wake) call
> + * msm8660_mpm_set_pin_wake() etc. The consumer API establishes
> + * a device_link from consumer to producer so the MPM device
> + * cannot disappear while a consumer holds a handle.
Why can't this be described in the device tree?
> +
> +struct msm8660_mpm {
> + struct device *dev;
> + void __iomem *base;
> + struct irq_domain *domain;
> + struct msm8660_mpm_pin *pin_map;
> + unsigned int pin_map_count;
> + int parent_irq;
> + struct mbox_client mbox_client;
> + struct mbox_chan *mbox_chan;
> +};
https://docs.kernel.org/process/maintainer-tip.html#struct-declarations-and-initializers
Please read the rest of this document too.
> +
> +/*
> + * Singleton - there is only one MPM instance per SoC. msm8660_mpm_get()
> + * returns this. Updates are serialised through the binding lifecycle so
> + * a plain pointer is sufficient.
> + */
> +static struct msm8660_mpm *msm8660_mpm_global;
> +
> +static u32 msm8660_mpm_read(struct msm8660_mpm *mpm, unsigned int reg)
> +{
> + return readl_relaxed(mpm->base + reg);
> +}
> +
> +static void msm8660_mpm_write(struct msm8660_mpm *mpm, unsigned int reg,
> + u32 val)
No line break required. You have 100 characters. All over the place.
> +static int msm8660_mpm_pin_to_hwirq(struct msm8660_mpm *mpm, int pin)
> +{
> + int i;
> +
> + for (i = 0; i < mpm->pin_map_count; i++) {
for (int i = 0; ....
> + if (mpm->pin_map[i].pin == pin)
> + return mpm->pin_map[i].hwirq;
> + }
> + return -ENOENT;
> +}
> +
> +/*
> + * IPC handler: MPM fires this IRQ when one or more enabled wake pins
> + * have pending activity. Read pending status, CLEAR the pending bits
> + * BEFORE dispatching the per-pin handlers so a fresh edge that arrives
> + * during dispatch cannot be wiped out by a later CLEAR write, then
> + * replay each pending pin through the irqdomain.
> + */
> +static irqreturn_t msm8660_mpm_irq(int irq, void *data)
> +{
> + struct msm8660_mpm *mpm = data;
> + unsigned long pending[MSM8660_MPM_REG_WIDTH];
> + unsigned long enable[MSM8660_MPM_REG_WIDTH];
> + int i, j;
See documented variable ordering and put the iterator variables into context.
> +static void msm8660_mpm_enable_hwirq(struct irq_data *d, bool enable)
> +{
> + struct msm8660_mpm *mpm = irq_data_get_irq_chip_data(d);
> + int pin;
> + u32 val, mask;
See docs
> +static int msm8660_mpm_domain_alloc(struct irq_domain *domain,
> + unsigned int virq, unsigned int nr_irqs,
> + void *data)
> +{
> + struct msm8660_mpm *mpm = domain->host_data;
> + struct irq_fwspec *fwspec = data;
> + struct irq_fwspec parent_fwspec;
> + irq_hw_number_t hwirq;
> + int i, ret;
> +
> + if (fwspec->param_count != 2)
> + return -EINVAL;
> +
> + hwirq = fwspec->param[0];
> +
> + for (i = 0; i < nr_irqs; i++)
> + irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
> + &msm8660_mpm_chip, mpm);
See bracket rules.
> +static void msm8660_mpm_remove(struct platform_device *pdev)
> +{
> + struct msm8660_mpm *mpm = platform_get_drvdata(pdev);
> +
> + /*
> + * Tear down in strict reverse order: drop the singleton so new
> + * consumers cannot grab a handle, free the IRQ so the handler
How is that serialized against a concurrent consumer request?
Also please look at:
https://sashiko.dev/#/message/20260531043213.D18801F00893%40smtp.kernel.org
Thanks,
tglx
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-06-03 15:13 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <cover.1780148149.git.github.com@herrie.org>
2026-05-31 4:09 ` [PATCH v2 0/2] irqchip: add MSM8x60 MPM wakeup interrupt controller Herman van Hazendonk
2026-05-31 4:09 ` [PATCH v2 1/2] dt-bindings: interrupt-controller: qcom: add msm8660-mpm Herman van Hazendonk
2026-05-31 8:01 ` Krzysztof Kozlowski
2026-05-31 4:09 ` [PATCH v2 2/2] irqchip: add MSM8x60 MPM wakeup interrupt controller driver Herman van Hazendonk
2026-06-01 7:25 ` Sebastian Andrzej Siewior
2026-06-03 15:12 ` Thomas Gleixner
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox