[PATCH v2 08/23] drm/msm/dcp: Add IPCC power notifications
From: Jayant Shekhar
Date: Mon Oct 26 2026 - 02:28:17 EST
Wake and acknowledge DCP firmware through the platform IPCC
channel. DCP may power-collapse while VirtIO-MMIO remains accessible,
so a VirtIO queue kick alone cannot reliably wake firmware during
system power transitions.
Implement serialized IPCC notifications, bounded acknowledgment waits,
IRQ wake handling, and teardown-safe callback lifetime management
while leaving HFI data transport on VirtIO.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <jayant.shekhar@xxxxxxxxxxxxxxxx>
---
drivers/gpu/drm/msm/Makefile | 1 +
drivers/gpu/drm/msm/dcp/dcp_drv.h | 1 +
drivers/gpu/drm/msm/dcp/dcp_ipcc.c | 173 +++++++++++++++++++++++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_ipcc.h | 42 +++++++++
4 files changed, 217 insertions(+)
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 56c48fe2e2c5..9c698c4f4949 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -73,6 +73,7 @@ msm-display-$(CONFIG_DRM_MSM_MDP5) += \
msm-display-$(CONFIG_DRM_MSM_DCP) += \
dcp/dcp_hfi_core.o \
+ dcp/dcp_ipcc.o \
dcp/hfi/hfi_packer.o \
dcp/hfi/hfi_unpacker.o \
dcp/hfi/hfi_utils.o
diff --git a/drivers/gpu/drm/msm/dcp/dcp_drv.h b/drivers/gpu/drm/msm/dcp/dcp_drv.h
index 44d9b2cd5171..16f87035cb9a 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_drv.h
+++ b/drivers/gpu/drm/msm/dcp/dcp_drv.h
@@ -11,6 +11,7 @@
#include <linux/platform_device.h>
#include "dcp_hfi.h"
+#include "dcp_ipcc.h"
struct dcp_dpu_kms;
diff --git a/drivers/gpu/drm/msm/dcp/dcp_ipcc.c b/drivers/gpu/drm/msm/dcp/dcp_ipcc.c
new file mode 100644
index 000000000000..e161939dddc1
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_ipcc.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP IPCC power-notify mailbox client.
+ *
+ * The APSS-to-DCP channel must be signaled before a VirtIO queue kick.
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/jiffies.h>
+#include <linux/interrupt.h>
+#include <linux/mailbox_client.h>
+#include <linux/mutex.h>
+#include <linux/of_irq.h>
+#include <linux/string.h>
+#include <linux/virtio.h>
+
+#include "dcp_ipcc.h"
+
+#define DCP_IPCC_POWER_NOTIFY_TIMEOUT_MS 10000
+
+static void dcp_ipcc_handle_rx(struct dcp_ipcc *ipcc)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ipcc->state_lock, flags);
+ if (ipcc->shutting_down) {
+ spin_unlock_irqrestore(&ipcc->state_lock, flags);
+ return;
+ }
+
+ complete(&ipcc->power_notify_done);
+ if (ipcc->resp_vq && virtqueue_kick_prepare(ipcc->resp_vq))
+ virtqueue_notify(ipcc->resp_vq);
+
+ spin_unlock_irqrestore(&ipcc->state_lock, flags);
+}
+
+static irqreturn_t dcp_ipcc_power_notify_irq(int irq, void *data)
+{
+ struct dcp_ipcc *ipcc = data;
+
+ dcp_ipcc_handle_rx(ipcc);
+
+ return IRQ_HANDLED;
+}
+
+static void dcp_ipcc_rx_callback(struct mbox_client *cl, void *data)
+{
+ struct dcp_ipcc *ipcc = container_of(cl, struct dcp_ipcc, client);
+
+ dcp_ipcc_handle_rx(ipcc);
+}
+
+static void dcp_ipcc_free_channel(void *data)
+{
+ mbox_free_channel(data);
+}
+
+int dcp_ipcc_init(struct dcp_ipcc *ipcc, struct device *dev, unsigned int irq_index)
+{
+ int irq, ret;
+
+ memset(ipcc, 0, sizeof(*ipcc));
+ init_completion(&ipcc->power_notify_done);
+ mutex_init(&ipcc->power_notify_lock);
+ spin_lock_init(&ipcc->state_lock);
+
+ ipcc->client.dev = dev;
+ ipcc->client.tx_block = false;
+ ipcc->client.tx_tout = 0;
+ ipcc->client.knows_txdone = true;
+ ipcc->client.rx_callback = dcp_ipcc_rx_callback;
+
+ ipcc->power_chan = mbox_request_channel(&ipcc->client, 0);
+ if (IS_ERR(ipcc->power_chan)) {
+ ret = PTR_ERR(ipcc->power_chan);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "IPCC: failed to get DCP power channel: %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_add_action_or_reset(dev, dcp_ipcc_free_channel,
+ ipcc->power_chan);
+ if (ret)
+ return ret;
+
+ irq = of_irq_get(dev->of_node, irq_index);
+ if (irq <= 0)
+ return dev_err_probe(dev, irq ? irq : -EINVAL,
+ "failed to get required power notify IRQ\n");
+
+ ret = devm_request_irq(dev, irq, dcp_ipcc_power_notify_irq, 0,
+ "dcp-power-notify", ipcc);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to request power notify IRQ %d\n", irq);
+
+ ipcc->power_notify_irq = irq;
+
+ return 0;
+}
+
+void dcp_ipcc_set_resp_vq(struct dcp_ipcc *ipcc, struct virtqueue *resp_vq)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ipcc->state_lock, flags);
+ if (!ipcc->shutting_down)
+ ipcc->resp_vq = resp_vq;
+ spin_unlock_irqrestore(&ipcc->state_lock, flags);
+}
+
+void dcp_ipcc_deinit(struct dcp_ipcc *ipcc)
+{
+ struct device *dev = ipcc->client.dev;
+ unsigned long flags;
+
+ mutex_lock(&ipcc->power_notify_lock);
+
+ spin_lock_irqsave(&ipcc->state_lock, flags);
+ ipcc->shutting_down = true;
+ ipcc->resp_vq = NULL;
+ spin_unlock_irqrestore(&ipcc->state_lock, flags);
+
+ devm_free_irq(dev, ipcc->power_notify_irq, ipcc);
+ devm_release_action(dev, dcp_ipcc_free_channel, ipcc->power_chan);
+ mutex_unlock(&ipcc->power_notify_lock);
+}
+
+int dcp_ipcc_enable_irq_wake(struct dcp_ipcc *ipcc)
+{
+ return enable_irq_wake(ipcc->power_notify_irq);
+}
+
+void dcp_ipcc_disable_irq_wake(struct dcp_ipcc *ipcc)
+{
+ disable_irq_wake(ipcc->power_notify_irq);
+}
+
+int dcp_ipcc_send_power_notify(struct dcp_ipcc *ipcc)
+{
+ unsigned long timeout;
+ int ret;
+
+ mutex_lock(&ipcc->power_notify_lock);
+
+ reinit_completion(&ipcc->power_notify_done);
+
+ ret = mbox_send_message(ipcc->power_chan, NULL);
+ mbox_client_txdone(ipcc->power_chan, ret);
+ if (ret < 0) {
+ dev_err(ipcc->client.dev,
+ "IPCC TX: power notify failed: %d\n", ret);
+ goto out_unlock;
+ }
+
+ timeout = wait_for_completion_timeout(&ipcc->power_notify_done,
+ msecs_to_jiffies(DCP_IPCC_POWER_NOTIFY_TIMEOUT_MS));
+ if (!timeout) {
+ dev_err(ipcc->client.dev,
+ "IPCC power notify acknowledgment timed out\n");
+ ret = -ETIMEDOUT;
+ } else {
+ ret = 0;
+ }
+
+out_unlock:
+ mutex_unlock(&ipcc->power_notify_lock);
+ return ret;
+}
diff --git a/drivers/gpu/drm/msm/dcp/dcp_ipcc.h b/drivers/gpu/drm/msm/dcp/dcp_ipcc.h
new file mode 100644
index 000000000000..2d1ddd5adebb
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_ipcc.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * IPCC power-notify mailbox interface for DCP firmware.
+ *
+ * Channel 5 carries APSS-to-DCP wake notifications; responses arrive through
+ * the mailbox client's RX callback.
+ */
+
+#ifndef __DCP_IPCC_H__
+#define __DCP_IPCC_H__
+
+#include <linux/completion.h>
+#include <linux/mailbox_client.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+
+struct device;
+struct virtqueue;
+
+struct dcp_ipcc {
+ struct mbox_client client;
+ struct mbox_chan *power_chan;
+ struct virtqueue *resp_vq;
+ struct completion power_notify_done;
+ /* Serializes concurrent power-notify transactions. */
+ struct mutex power_notify_lock;
+ /* Protects callback state and response queue access. */
+ spinlock_t state_lock;
+ bool shutting_down;
+ int power_notify_irq;
+};
+
+int dcp_ipcc_init(struct dcp_ipcc *ipcc, struct device *dev, unsigned int irq_index);
+void dcp_ipcc_set_resp_vq(struct dcp_ipcc *ipcc, struct virtqueue *resp_vq);
+void dcp_ipcc_deinit(struct dcp_ipcc *ipcc);
+int dcp_ipcc_send_power_notify(struct dcp_ipcc *ipcc);
+int dcp_ipcc_enable_irq_wake(struct dcp_ipcc *ipcc);
+void dcp_ipcc_disable_irq_wake(struct dcp_ipcc *ipcc);
+
+#endif /* __DCP_IPCC_H__ */
--
2.43.0