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 <[email protected]>
---
 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


Reply via email to