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
