From ea9bdec20aa511fa064e230a768880d7b56e1786 Mon Sep 17 00:00:00 2001 From: Kunihiko Hayashi Date: Wed, 29 Jul 2026 13:53:05 +0900 Subject: [PATCH 01/22] mailbox: arm_mhuv2: Convert channel translation to fw_xlate() The MHUv2 channel translation callback only uses the mailbox specifier arguments and does not depend on any OF-specific data from struct of_phandle_args. Switch to the generic fw_xlate() callback and use struct fwnode_reference_args instead. This aligns the driver with the mailbox framework's fwnode based channel lookup support while preserving existing DT based operation. Signed-off-by: Kunihiko Hayashi Signed-off-by: Jassi Brar --- drivers/mailbox/arm_mhuv2.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/mailbox/arm_mhuv2.c b/drivers/mailbox/arm_mhuv2.c index f035284944c0..14071398280b 100644 --- a/drivers/mailbox/arm_mhuv2.c +++ b/drivers/mailbox/arm_mhuv2.c @@ -789,14 +789,14 @@ static const struct mbox_chan_ops mhuv2_receiver_ops = { .last_tx_done = mhuv2_receiver_last_tx_done, }; -static struct mbox_chan *mhuv2_mbox_of_xlate(struct mbox_controller *mbox, - const struct of_phandle_args *pa) +static struct mbox_chan *mhuv2_mbox_fw_xlate(struct mbox_controller *mbox, + const struct fwnode_reference_args *pa) { struct mhuv2 *mhu = mhu_from_mbox(mbox); struct mbox_chan *chans = mbox->chans; int channel = 0, i, offset, doorbell, protocol, windows; - if (pa->args_count != 2) + if (pa->nargs != 2) return ERR_PTR(-EINVAL); offset = pa->args[0]; @@ -828,7 +828,7 @@ static struct mbox_chan *mhuv2_mbox_of_xlate(struct mbox_controller *mbox, } out: - dev_err(mbox->dev, "Couldn't xlate to a valid channel (%d: %d)\n", + dev_err(mbox->dev, "Couldn't xlate to a valid channel (%llu: %d)\n", pa->args[0], doorbell); return ERR_PTR(-ENODEV); } @@ -1071,7 +1071,7 @@ static int mhuv2_probe(struct amba_device *adev, const struct amba_id *id) return -ENOMEM; mhu->mbox.dev = dev; - mhu->mbox.of_xlate = mhuv2_mbox_of_xlate; + mhu->mbox.fw_xlate = mhuv2_mbox_fw_xlate; if (of_device_is_compatible(np, "arm,mhuv2-tx")) ret = mhuv2_tx_init(adev, mhu, reg); From 9e9afcb436cec4f5331f2f4582595dffee72bed0 Mon Sep 17 00:00:00 2001 From: Kunihiko Hayashi Date: Wed, 29 Jul 2026 13:53:06 +0900 Subject: [PATCH 02/22] mailbox: arm_mhuv2: Use generic firmware property APIs The protocol parsing code only requires reading firmware properties and does not depend on OF-specific interfaces. Replace OF property helpers with the generic device property API. Signed-off-by: Kunihiko Hayashi Signed-off-by: Jassi Brar --- drivers/mailbox/arm_mhuv2.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/mailbox/arm_mhuv2.c b/drivers/mailbox/arm_mhuv2.c index 14071398280b..ff3b1c7f72c3 100644 --- a/drivers/mailbox/arm_mhuv2.c +++ b/drivers/mailbox/arm_mhuv2.c @@ -931,11 +931,10 @@ static int mhuv2_allocate_channels(struct mhuv2 *mhu) static int mhuv2_parse_channels(struct mhuv2 *mhu) { struct device *dev = mhu->mbox.dev; - const struct device_node *np = dev->of_node; int ret, count; u32 *protocols; - count = of_property_count_u32_elems(np, MHUV2_PROTOCOL_PROP); + count = device_property_count_u32(dev, MHUV2_PROTOCOL_PROP); if (count <= 0 || count % 2) { dev_err(dev, "Invalid %s property (%d)\n", MHUV2_PROTOCOL_PROP, count); @@ -946,7 +945,7 @@ static int mhuv2_parse_channels(struct mhuv2 *mhu) if (!protocols) return -ENOMEM; - ret = of_property_read_u32_array(np, MHUV2_PROTOCOL_PROP, protocols, count); + ret = device_property_read_u32_array(dev, MHUV2_PROTOCOL_PROP, protocols, count); if (ret) { dev_err(dev, "Failed to read %s property: %d\n", MHUV2_PROTOCOL_PROP, ret); From 3690aaa6d18f6775c3e7932fb8af8c5bf6a6b69c Mon Sep 17 00:00:00 2001 From: Jia Yang Date: Thu, 6 Aug 2026 15:03:56 +0800 Subject: [PATCH 03/22] mailbox: qcom-cpucp: fix PREEMPT_RT self-deadlock in IRQ handler qcom_cpucp_mbox_irq_fn() calls mbox_chan_received_data() while holding chan->lock. Under PREEMPT_RT, spin_lock_irqsave() is converted to an rt_spinlock (rtmutex-based), which tracks ownership and can sleep. The callback chain triggered by mbox_chan_received_data() eventually reaches mailbox_clear_channel() -> mbox_send_message() -> add_to_rbuf(), which attempts to re-acquire the same chan->lock. Since rtmutex detects the re-entrant lock attempt by the same owner, the thread blocks waiting for a lock it already holds, causing a permanent deadlock. This deadlock manifests as 'irq/N-apss_cpucp_mbox' stuck in D state with the following call trace: rt_spin_lock -> mbox_send_message -> mailbox_clear_channel -> scmi_rx_callback -> mbox_chan_received_data [<- held chan->lock here] Fix by saving chan->cl locally and clearing the HW interrupt register inside the lock, then invoking mbox_chan_received_data() after releasing the lock. This preserves the mutual exclusion for chan->cl access while avoiding the lock re-entrancy that causes the PREEMPT_RT deadlock. Fixes: 0e2a9a03106c ("mailbox: Add support for QTI CPUCP mailbox controller") Signed-off-by: Jia Yang Signed-off-by: Jassi Brar --- drivers/mailbox/qcom-cpucp-mbox.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/drivers/mailbox/qcom-cpucp-mbox.c b/drivers/mailbox/qcom-cpucp-mbox.c index 862e45e8fbd5..0f7fe189e8b1 100644 --- a/drivers/mailbox/qcom-cpucp-mbox.c +++ b/drivers/mailbox/qcom-cpucp-mbox.c @@ -63,14 +63,25 @@ static irqreturn_t qcom_cpucp_mbox_irq_fn(int irq, void *data) for_each_set_bit(i, (unsigned long *)&status, cpucp->mbox.num_chans) { u32 val = readl(cpucp->rx_base + APSS_CPUCP_RX_MBOX_CMD(i) + APSS_CPUCP_MBOX_CMD_OFF); struct mbox_chan *chan = &cpucp->chans[i]; + struct mbox_client *cl; unsigned long flags; - /* Provide mutual exclusion with changes to chan->cl */ + /* + * Provide mutual exclusion with changes to chan->cl. + * Save cl locally and clear the HW interrupt inside the lock, + * then invoke mbox_chan_received_data() outside the lock to + * avoid a PREEMPT_RT self-deadlock: mbox_chan_received_data() + * can call back into mbox_send_message() via scmi_rx_callback() + * -> mailbox_clear_channel(), which re-acquires chan->lock + * (converted to an rt_spinlock under PREEMPT_RT). + */ spin_lock_irqsave(&chan->lock, flags); - if (chan->cl) - mbox_chan_received_data(chan, &val); + cl = chan->cl; writeq(BIT(i), cpucp->rx_base + APSS_CPUCP_RX_MBOX_CLEAR); spin_unlock_irqrestore(&chan->lock, flags); + + if (cl) + mbox_chan_received_data(chan, &val); } return IRQ_HANDLED; From fc4f2f99530298a1739226947aa76525142d421d Mon Sep 17 00:00:00 2001 From: Jia Yang Date: Thu, 6 Aug 2026 15:03:57 +0800 Subject: [PATCH 04/22] mailbox: qcom-cpucp: handle NULL data in send_data callback mailbox_clear_channel() calls mbox_send_message() with NULL data to notify the remote side that the RX channel has been cleared. qcom_cpucp_mbox_send_data() blindly dereferenced the data pointer, causing a NULL pointer dereference kernel panic when invoked from this path under PREEMPT_RT. Add an explicit NULL check and return early without writing to the TX register, which is the correct behaviour for a channel-clear notification. Fixes: 0e2a9a03106c ("mailbox: Add support for QTI CPUCP mailbox controller") Signed-off-by: Jia Yang Reviewed-by: Dmitry Baryshkov Signed-off-by: Jassi Brar --- drivers/mailbox/qcom-cpucp-mbox.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/mailbox/qcom-cpucp-mbox.c b/drivers/mailbox/qcom-cpucp-mbox.c index 0f7fe189e8b1..298b357c0f9a 100644 --- a/drivers/mailbox/qcom-cpucp-mbox.c +++ b/drivers/mailbox/qcom-cpucp-mbox.c @@ -117,6 +117,14 @@ static int qcom_cpucp_mbox_send_data(struct mbox_chan *chan, void *data) unsigned long chan_id = channel_number(chan); u32 *val = data; + /* + * mailbox_clear_channel() calls mbox_send_message() with NULL data to + * signal the remote side that the channel has been cleared. Nothing + * needs to be written to the TX register in that case, so just return. + */ + if (!val) + return 0; + writel(*val, cpucp->tx_base + APSS_CPUCP_TX_MBOX_CMD(chan_id) + APSS_CPUCP_MBOX_CMD_OFF); return 0; From ded48fcbdc1e3ed1dc8e1974b7fe9639fcea6dd6 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Tue, 28 Jul 2026 17:16:22 +0800 Subject: [PATCH 05/22] mailbox: rockchip: disable pclk on probe failure and unbind rockchip_mbox_probe() enables the peripheral clock and then keeps going. None of the later failure paths - platform_get_irq(), devm_request_threaded_irq() and devm_mbox_controller_register() - disables it again. The driver has no remove callback either, so the clock also stays prepared and enabled once the device is unbound, and its enable count keeps growing over bind/unbind cycles. Use devm_clk_get_enabled() to tie disabling and unpreparing the clock to the device lifetime. It is registered before the interrupts and the mailbox controller, so devres releases it after both are gone. While rewriting the error path, switch it to dev_err_probe() so that a deferred probe is not reported as an error. Fixes: f70ed3b5dc8b ("mailbox: rockchip: Add Rockchip mailbox driver") Signed-off-by: Linmao Li Signed-off-by: Jassi Brar --- drivers/mailbox/rockchip-mailbox.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/drivers/mailbox/rockchip-mailbox.c b/drivers/mailbox/rockchip-mailbox.c index a1a7dee64356..d55cbf7f2cb6 100644 --- a/drivers/mailbox/rockchip-mailbox.c +++ b/drivers/mailbox/rockchip-mailbox.c @@ -196,19 +196,10 @@ static int rockchip_mbox_probe(struct platform_device *pdev) /* Each channel has two buffers for A2B and B2A */ mb->buf_size = (size_t)resource_size(res) / (drv_data->num_chans * 2); - mb->pclk = devm_clk_get(&pdev->dev, "pclk_mailbox"); - if (IS_ERR(mb->pclk)) { - ret = PTR_ERR(mb->pclk); - dev_err(&pdev->dev, "failed to get pclk_mailbox clock: %d\n", - ret); - return ret; - } - - ret = clk_prepare_enable(mb->pclk); - if (ret) { - dev_err(&pdev->dev, "failed to enable pclk: %d\n", ret); - return ret; - } + mb->pclk = devm_clk_get_enabled(&pdev->dev, "pclk_mailbox"); + if (IS_ERR(mb->pclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(mb->pclk), + "failed to get and enable pclk_mailbox clock\n"); for (i = 0; i < mb->mbox.num_chans; i++) { irq = platform_get_irq(pdev, i); From ef24ff4f5cda39f8a2e7da3d57773da8fbe600ce Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Tue, 28 Jul 2026 17:16:23 +0800 Subject: [PATCH 06/22] mailbox: rockchip: drop unneeded runtime pointer (pclk) The pclk pointer is only used in rockchip_mbox_probe() and is not needed after probe completes. Make it a local variable and drop it from struct rockchip_mbox, which saves a little bit of runtime memory. Signed-off-by: Linmao Li Signed-off-by: Jassi Brar --- drivers/mailbox/rockchip-mailbox.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/mailbox/rockchip-mailbox.c b/drivers/mailbox/rockchip-mailbox.c index d55cbf7f2cb6..bc2f9dd798dd 100644 --- a/drivers/mailbox/rockchip-mailbox.c +++ b/drivers/mailbox/rockchip-mailbox.c @@ -40,7 +40,6 @@ struct rockchip_mbox_chan { struct rockchip_mbox { struct mbox_controller mbox; - struct clk *pclk; void __iomem *mbox_base; /* The maximum size of buf for each channel */ @@ -166,6 +165,7 @@ static int rockchip_mbox_probe(struct platform_device *pdev) struct rockchip_mbox *mb; const struct rockchip_mbox_data *drv_data; struct resource *res; + struct clk *pclk; int ret, irq, i; if (!pdev->dev.of_node) @@ -196,9 +196,9 @@ static int rockchip_mbox_probe(struct platform_device *pdev) /* Each channel has two buffers for A2B and B2A */ mb->buf_size = (size_t)resource_size(res) / (drv_data->num_chans * 2); - mb->pclk = devm_clk_get_enabled(&pdev->dev, "pclk_mailbox"); - if (IS_ERR(mb->pclk)) - return dev_err_probe(&pdev->dev, PTR_ERR(mb->pclk), + pclk = devm_clk_get_enabled(&pdev->dev, "pclk_mailbox"); + if (IS_ERR(pclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(pclk), "failed to get and enable pclk_mailbox clock\n"); for (i = 0; i < mb->mbox.num_chans; i++) { From 1b7fd7229558be93c042c0b3c04292de36a4dae9 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Thu, 23 Jul 2026 15:39:26 +0100 Subject: [PATCH 07/22] mailbox: pcc: Notify clients on polled completion PCC channels without a platform interrupt rely on the mailbox polling path to detect command completion. That path currently only reports transmit completion to the mailbox core, so clients that wait for their receive callback do not get notified when the command completes. Call mbox_chan_received_data() when polling observes completion on a channel without a platform IRQ, matching the interrupt-driven completion path. Reported-by: Cristian Marussi Acked-by: Huisong Li Signed-off-by: Sudeep Holla Reviewed-by: Adam Young Signed-off-by: Jassi Brar --- drivers/mailbox/pcc.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/mailbox/pcc.c b/drivers/mailbox/pcc.c index 636879ae1db7..d96b8b54e77e 100644 --- a/drivers/mailbox/pcc.c +++ b/drivers/mailbox/pcc.c @@ -449,7 +449,15 @@ static bool pcc_last_tx_done(struct mbox_chan *chan) { struct pcc_chan_info *pchan = chan->con_priv; - return pcc_mbox_cmd_complete_check(pchan); + if (!(chan->txdone_method & MBOX_TXDONE_BY_POLL)) + return false; + + if (!pcc_mbox_cmd_complete_check(pchan)) + return false; + + mbox_chan_received_data(chan, NULL); + + return true; } /** From 0ffc8ef661224fe3338b1dc1e5341f03624dc3e5 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Thu, 23 Jul 2026 15:39:27 +0100 Subject: [PATCH 08/22] mailbox: pcc: Check shared memory signature on request ACPI 6.6 Tables 14.9 and 14.12 define the PCC shared memory signature as the bitwise OR of 0x50434300 and the PCC subspace ID. They also clarify that the signature is populated by the platform and verified by OSPM. The signature is at byte offset 0 in the generic, extended and reduced PCC shared memory layouts. Check the signature when a client requests a PCC mailbox channel, after mapping shared memory and before binding the mailbox client. This keeps the check in the PCC mailbox controller instead of duplicating it in individual clients. Treat a signature mismatch as a warning rather than rejecting the channel request. Making this newly added check fatal could break existing systems whose firmware did not populate the signature correctly even though PCC communication works. Continue to reject shared memory that is too small to contain a signature because it cannot be inspected safely. Cc: Huisong Li Signed-off-by: Sudeep Holla Tested_by: Adam Young Signed-off-by: Jassi Brar --- drivers/mailbox/pcc.c | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/drivers/mailbox/pcc.c b/drivers/mailbox/pcc.c index d96b8b54e77e..8dfa80b0a90f 100644 --- a/drivers/mailbox/pcc.c +++ b/drivers/mailbox/pcc.c @@ -345,6 +345,26 @@ static irqreturn_t pcc_mbox_irq(int irq, void *p) return IRQ_HANDLED; } +static int pcc_mbox_validate_signature(struct pcc_mbox_chan *pcc_mchan, + int subspace_id) +{ + u32 expected_signature = PCC_SIGNATURE | subspace_id; + u32 signature; + + if (pcc_mchan->shmem_size < sizeof(signature)) { + pr_err("PCC subspace %d shared memory is too small\n", + subspace_id); + return -EINVAL; + } + + signature = ioread32(pcc_mchan->shmem); + if (signature != expected_signature) + pr_warn("PCC subspace %d invalid signature %#x expected %#x\n", + subspace_id, signature, expected_signature); + + return 0; +} + /** * pcc_mbox_request_channel - PCC clients call this function to * request a pointer to their PCC subspace, from which they @@ -381,14 +401,20 @@ pcc_mbox_request_channel(struct mbox_client *cl, int subspace_id) if (!pcc_mchan->shmem) return ERR_PTR(-ENXIO); + rc = pcc_mbox_validate_signature(pcc_mchan, subspace_id); + if (rc) + goto err_unmap_shmem; + rc = mbox_bind_client(chan, cl); - if (rc) { - iounmap(pcc_mchan->shmem); - pcc_mchan->shmem = NULL; - return ERR_PTR(rc); - } + if (rc) + goto err_unmap_shmem; return pcc_mchan; + +err_unmap_shmem: + iounmap(pcc_mchan->shmem); + pcc_mchan->shmem = NULL; + return ERR_PTR(rc); } EXPORT_SYMBOL_GPL(pcc_mbox_request_channel); From 3360b088175dc5d5c9166685836158470d00571e Mon Sep 17 00:00:00 2001 From: Huisong Li Date: Thu, 23 Jul 2026 15:39:28 +0100 Subject: [PATCH 09/22] mailbox: pcc: Fix command timeout due to missed interrupt PCC command execution can time out when a fast platform completes a transaction and signals the platform interrupt before pcc_send_data() marks the channel as in use. For shared platform interrupts, the type 3 handler uses chan_in_use to decide whether the interrupt belongs to the channel. If it observes false, it ignores the completion and the caller waits until timeout. Publish chan_in_use before ringing the doorbell. Use WRITE_ONCE() for the lockless flag updates and READ_ONCE() in the interrupt handler. The following ordered I/O accessor orders the flag store before the platform is notified. Clear chan_in_use if ringing the doorbell fails. Otherwise, leave it set until the interrupt handler completes the transaction, clearing it before the mailbox core can submit another transfer. Fixes: 3db174e478cb ("mailbox: pcc: Support shared interrupt for multiple subspaces") Signed-off-by: Huisong Li Signed-off-by: Sudeep Holla Tested-by: Adam Young Signed-off-by: Jassi Brar --- drivers/mailbox/pcc.c | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/drivers/mailbox/pcc.c b/drivers/mailbox/pcc.c index 8dfa80b0a90f..9888dab64639 100644 --- a/drivers/mailbox/pcc.c +++ b/drivers/mailbox/pcc.c @@ -91,12 +91,11 @@ struct pcc_chan_reg { * @plat_irq: platform interrupt * @type: PCC subspace type * @plat_irq_flags: platform interrupt flags - * @chan_in_use: this flag is used just to check if the interrupt needs - * handling when it is shared. Since only one transfer can occur - * at a time and mailbox takes care of locking, this flag can be - * accessed without a lock. Note: the type only support the - * communication from OSPM to Platform, like type3, use it, and - * other types completely ignore it. + * @chan_in_use: lockless flag used by type 3 initiator subspaces to filter + * platform interrupts. Only one transfer can occur at a time, but + * the interrupt handler may sample the flag on another CPU, so all + * accesses must use READ_ONCE() or WRITE_ONCE(). Other subspace + * types do not test it. */ struct pcc_chan_info { struct pcc_mbox_chan chan; @@ -320,8 +319,13 @@ static irqreturn_t pcc_mbox_irq(int irq, void *p) if (pcc_chan_reg_read_modify_write(&pchan->plat_irq_ack)) return IRQ_NONE; + /* + * Initiator subspaces use this flag to filter shared interrupts. Use + * READ_ONCE() to sample the lockless flag written by pcc_send_data() + * on another CPU. + */ if (pchan->type == ACPI_PCCT_TYPE_EXT_PCC_MASTER_SUBSPACE && - !pchan->chan_in_use) + !READ_ONCE(pchan->chan_in_use)) return IRQ_NONE; if (!pcc_mbox_cmd_complete_check(pchan)) @@ -331,12 +335,12 @@ static irqreturn_t pcc_mbox_irq(int irq, void *p) return IRQ_NONE; /* - * Clear this flag after updating interrupt ack register and just - * before mbox_chan_received_data() which might call pcc_send_data() - * where the flag is set again to start new transfer. This is - * required to avoid any possible race in updatation of this flag. + * Clear this flag after updating the interrupt ack register and before + * notifying the client and mailbox core. mbox_chan_txdone() may submit + * the next queued transfer and set the flag again. Use WRITE_ONCE() for + * the lockless update observed by the send and interrupt paths. */ - pchan->chan_in_use = false; + WRITE_ONCE(pchan->chan_in_use, false); mbox_chan_received_data(chan, NULL); mbox_chan_txdone(chan, 0); @@ -464,9 +468,18 @@ static int pcc_send_data(struct mbox_chan *chan, void *data) if (ret) return ret; + /* + * Set chan_in_use before ringing the doorbell so a fast completion + * interrupt is not mistaken for a shared interrupt from another + * subspace. Use WRITE_ONCE() for the lockless flag update. The + * ordered I/O accessor used to ring the doorbell orders this store + * before the platform is notified. + */ + if (pchan->plat_irq > 0) + WRITE_ONCE(pchan->chan_in_use, true); ret = pcc_chan_reg_read_modify_write(&pchan->db); - if (!ret && pchan->plat_irq > 0) - pchan->chan_in_use = true; + if (ret && pchan->plat_irq > 0) + WRITE_ONCE(pchan->chan_in_use, false); return ret; } From fd8dc36a12b2a6d1c1d8d9e77636516afc30064b Mon Sep 17 00:00:00 2001 From: Alexey Klimov Date: Thu, 9 Jul 2026 15:45:36 +0100 Subject: [PATCH 10/22] dt-bindings: mailbox: google,gs101-mbox: Add samsung,exynos850-mbox Document support for a mailbox present on Exynos850-based platforms. The registers offsets are different from gs101 mailbox, but the workflow is similar, hence new compatible. Reviewed-by: Tudor Ambarus Reviewed-by: Krzysztof Kozlowski Reviewed-by: Peter Griffin Signed-off-by: Alexey Klimov Signed-off-by: Jassi Brar --- .../devicetree/bindings/mailbox/google,gs101-mbox.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/mailbox/google,gs101-mbox.yaml b/Documentation/devicetree/bindings/mailbox/google,gs101-mbox.yaml index e249db4c1fbc..c109c1f7af24 100644 --- a/Documentation/devicetree/bindings/mailbox/google,gs101-mbox.yaml +++ b/Documentation/devicetree/bindings/mailbox/google,gs101-mbox.yaml @@ -20,7 +20,9 @@ description: properties: compatible: - const: google,gs101-mbox + enum: + - google,gs101-mbox + - samsung,exynos850-mbox reg: maxItems: 1 From 4ff8c9df5e3d9c6521b9751ff64aeea246c25121 Mon Sep 17 00:00:00 2001 From: Alexey Klimov Date: Thu, 9 Jul 2026 15:45:37 +0100 Subject: [PATCH 11/22] mailbox: exynos: Add support for Exynos850 mailbox Exynos850-based platforms support ACPM and has similar workflow of communicating with ACPM via mailbox, however mailbox controller registers are located at different offsets and writes/reads could be different. To distinguish between such different behaviours, the registers offsets for Exynos850 and the platform-specific data structs are introduced and configuration is described in such structs for gs101 and exynos850 based SoCs. Probe routine now selects the corresponding platform-specific data via device_get_match_data(). Reviewed-by: Krzysztof Kozlowski Signed-off-by: Alexey Klimov Reviewed-by: Tudor Ambarus Reviewed-by: Peter Griffin Signed-off-by: Jassi Brar --- drivers/mailbox/exynos-mailbox.c | 72 +++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 6 deletions(-) diff --git a/drivers/mailbox/exynos-mailbox.c b/drivers/mailbox/exynos-mailbox.c index fa02f18948cf..ff75c1c7633c 100644 --- a/drivers/mailbox/exynos-mailbox.c +++ b/drivers/mailbox/exynos-mailbox.c @@ -24,14 +24,60 @@ #define EXYNOS_MBOX_CHAN_COUNT HWEIGHT32(EXYNOS_MBOX_INTGR1_MASK) +#define EXYNOS850_MBOX_INTGR0 0x8 /* Interrupt Generation Register 0 */ +#define EXYNOS850_MBOX_INTMR1 0x24 /* Interrupt Mask Register 1 */ + +#define EXYNOS850_MBOX_INTMR1_MASK GENMASK(15, 0) +#define EXYNOS850_MBOX_INTGR0_MASK GENMASK(31, 16) + +#define EXYNOS850_MBOX_CHAN_COUNT HWEIGHT32(EXYNOS850_MBOX_INTGR0_MASK) + +/** + * struct exynos_mbox_driver_data - platform-specific mailbox configuration. + * @intgr: offset to the IRQ generation register, doorbell + * to APM co-processor. + * @intgr_shift: shift to apply to the value written to IRQ generation + * register. + * @intmr: offset to the IRQ mask register. + * @intmr_mask: value to write to the mask register to mask out all + * interrupts. + * @num_chans: number of channels the mailbox can support (hardware + * capability). + */ +struct exynos_mbox_driver_data { + u32 intgr; + u32 intgr_shift; + u32 intmr; + u32 intmr_mask; + int num_chans; +}; + /** * struct exynos_mbox - driver's private data. * @regs: mailbox registers base address. * @mbox: pointer to the mailbox controller. + * @data: pointer to driver platform-specific data. */ struct exynos_mbox { void __iomem *regs; struct mbox_controller *mbox; + const struct exynos_mbox_driver_data *data; +}; + +static const struct exynos_mbox_driver_data exynos850_mbox_data = { + .intgr = EXYNOS850_MBOX_INTGR0, + .intgr_shift = 16, + .intmr = EXYNOS850_MBOX_INTMR1, + .intmr_mask = EXYNOS850_MBOX_INTMR1_MASK, + .num_chans = EXYNOS850_MBOX_CHAN_COUNT, +}; + +static const struct exynos_mbox_driver_data exynos_gs101_mbox_data = { + .intgr = EXYNOS_MBOX_INTGR1, + .intgr_shift = 0, + .intmr = EXYNOS_MBOX_INTMR0, + .intmr_mask = EXYNOS_MBOX_INTMR0_MASK, + .num_chans = EXYNOS_MBOX_CHAN_COUNT, }; static int exynos_mbox_send_data(struct mbox_chan *chan, void *data) @@ -50,7 +96,9 @@ static int exynos_mbox_send_data(struct mbox_chan *chan, void *data) return -EINVAL; } - writel(BIT(msg->chan_id), exynos_mbox->regs + EXYNOS_MBOX_INTGR1); + /* Ring the doorbell */ + writel(BIT(msg->chan_id) << exynos_mbox->data->intgr_shift, + exynos_mbox->regs + exynos_mbox->data->intgr); return 0; } @@ -80,19 +128,31 @@ static struct mbox_chan *exynos_mbox_of_xlate(struct mbox_controller *mbox, } static const struct of_device_id exynos_mbox_match[] = { - { .compatible = "google,gs101-mbox" }, + { + .compatible = "google,gs101-mbox", + .data = &exynos_gs101_mbox_data + }, + { + .compatible = "samsung,exynos850-mbox", + .data = &exynos850_mbox_data + }, {}, }; MODULE_DEVICE_TABLE(of, exynos_mbox_match); static int exynos_mbox_probe(struct platform_device *pdev) { + const struct exynos_mbox_driver_data *data; struct device *dev = &pdev->dev; struct exynos_mbox *exynos_mbox; struct mbox_controller *mbox; struct mbox_chan *chans; struct clk *pclk; + data = device_get_match_data(&pdev->dev); + if (!data) + return -ENODEV; + exynos_mbox = devm_kzalloc(dev, sizeof(*exynos_mbox), GFP_KERNEL); if (!exynos_mbox) return -ENOMEM; @@ -101,8 +161,7 @@ static int exynos_mbox_probe(struct platform_device *pdev) if (!mbox) return -ENOMEM; - chans = devm_kcalloc(dev, EXYNOS_MBOX_CHAN_COUNT, sizeof(*chans), - GFP_KERNEL); + chans = devm_kcalloc(dev, data->num_chans, sizeof(*chans), GFP_KERNEL); if (!chans) return -ENOMEM; @@ -115,7 +174,8 @@ static int exynos_mbox_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(pclk), "Failed to enable clock.\n"); - mbox->num_chans = EXYNOS_MBOX_CHAN_COUNT; + exynos_mbox->data = data; + mbox->num_chans = data->num_chans; mbox->chans = chans; mbox->dev = dev; mbox->ops = &exynos_mbox_chan_ops; @@ -126,7 +186,7 @@ static int exynos_mbox_probe(struct platform_device *pdev) platform_set_drvdata(pdev, exynos_mbox); /* Mask out all interrupts. We support just polling channels for now. */ - writel(EXYNOS_MBOX_INTMR0_MASK, exynos_mbox->regs + EXYNOS_MBOX_INTMR0); + writel(data->intmr_mask, exynos_mbox->regs + data->intmr); return devm_mbox_controller_register(dev, mbox); } From b8032527db12b460ea2206ee75e54d663cb31c47 Mon Sep 17 00:00:00 2001 From: Deepti Jaggi Date: Thu, 9 Jul 2026 16:08:48 +0800 Subject: [PATCH 12/22] dt-bindings: mailbox: qcom-ipcc: Document Nord IPCC Document Inter-Processor Communication Controller on Qualcomm Nord SoC. Signed-off-by: Deepti Jaggi Signed-off-by: Shawn Guo Reviewed-by: Bartosz Golaszewski Acked-by: Krzysztof Kozlowski Signed-off-by: Jassi Brar --- Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml b/Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml index 3839e1f5f904..a378fe8c7148 100644 --- a/Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml +++ b/Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml @@ -30,6 +30,7 @@ properties: - qcom,kaanapali-ipcc - qcom,maili-ipcc - qcom,milos-ipcc + - qcom,nord-ipcc - qcom,qcs8300-ipcc - qcom,qdu1000-ipcc - qcom,sa8255p-ipcc From 9411c0832b26e4009c9a63a2d1ed3cc41b476ca8 Mon Sep 17 00:00:00 2001 From: Varadarajan Narayanan Date: Wed, 1 Jul 2026 13:35:14 +0530 Subject: [PATCH 13/22] dt-bindings: mailbox: qcom: Add IPQ5210 APCS compatible Add the APCS mailbox compatible for the IPQ5210 SoC. It uses the IPQ6018 APCS mailbox compatible as a fallback, so document the valid compatible string combination in the binding. Signed-off-by: Varadarajan Narayanan Reviewed-by: Krzysztof Kozlowski Signed-off-by: Jassi Brar --- .../devicetree/bindings/mailbox/qcom,apcs-kpss-global.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/mailbox/qcom,apcs-kpss-global.yaml b/Documentation/devicetree/bindings/mailbox/qcom,apcs-kpss-global.yaml index 1b4ef0688ca7..39bc374300c3 100644 --- a/Documentation/devicetree/bindings/mailbox/qcom,apcs-kpss-global.yaml +++ b/Documentation/devicetree/bindings/mailbox/qcom,apcs-kpss-global.yaml @@ -19,6 +19,7 @@ properties: - items: - enum: - qcom,ipq5018-apcs-apps-global + - qcom,ipq5210-apcs-apps-global - qcom,ipq5332-apcs-apps-global - qcom,ipq5424-apcs-apps-global - qcom,ipq8074-apcs-apps-global From c8235bbe5dd69f6b7ef159bd0c8ebf29c963c092 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 16 Jul 2026 10:52:13 +0800 Subject: [PATCH 14/22] mailbox: Remove redundant dev_err()/dev_err_probe() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() and devm_request_threaded_irq() automatically log detailed error messages on failure. Remove the now-redundant driver-specific dev_err() and dev_err_probe() calls. Signed-off-by: Pan Chuang Acked-by: Chen-Yu Tsai Reviewed-by: Mikko Perttunen Signed-off-by: Jassi Brar --- drivers/mailbox/arm_mhu_db.c | 1 - drivers/mailbox/arm_mhuv2.c | 9 ++------- drivers/mailbox/arm_mhuv3.c | 5 ++--- drivers/mailbox/armada-37xx-rwtm-mailbox.c | 4 +--- drivers/mailbox/bcm-pdc-mailbox.c | 5 +---- drivers/mailbox/bcm2835-mailbox.c | 5 +---- drivers/mailbox/bcm74110-mailbox.c | 2 +- drivers/mailbox/cv1800-mailbox.c | 2 +- drivers/mailbox/hi6220-mailbox.c | 5 +---- drivers/mailbox/mailbox-mpfs.c | 2 -- drivers/mailbox/mailbox-sti.c | 4 +--- drivers/mailbox/qcom-cpucp-mbox.c | 2 +- drivers/mailbox/qcom-ipcc.c | 4 +--- drivers/mailbox/sprd-mailbox.c | 12 +++--------- drivers/mailbox/stm32-ipcc.c | 4 +--- drivers/mailbox/sun6i-msgbox.c | 4 +--- drivers/mailbox/tegra-hsp.c | 11 ++--------- 17 files changed, 20 insertions(+), 61 deletions(-) diff --git a/drivers/mailbox/arm_mhu_db.c b/drivers/mailbox/arm_mhu_db.c index 9e937b09c5fb..a39239e38a47 100644 --- a/drivers/mailbox/arm_mhu_db.c +++ b/drivers/mailbox/arm_mhu_db.c @@ -318,7 +318,6 @@ static int mhu_db_probe(struct amba_device *adev, const struct amba_id *id) mhu_db_mbox_rx_handler, IRQF_ONESHOT, "mhu_db_link", mhu); if (err) { - dev_err(dev, "Can't claim IRQ %d\n", irq); mbox_controller_unregister(&mhu->mbox); return err; } diff --git a/drivers/mailbox/arm_mhuv2.c b/drivers/mailbox/arm_mhuv2.c index ff3b1c7f72c3..d4077cd49360 100644 --- a/drivers/mailbox/arm_mhuv2.c +++ b/drivers/mailbox/arm_mhuv2.c @@ -985,10 +985,7 @@ static int mhuv2_tx_init(struct amba_device *adev, struct mhuv2 *mhu, ret = devm_request_threaded_irq(dev, adev->irq[0], NULL, mhuv2_sender_interrupt, IRQF_ONESHOT, "mhuv2-tx", mhu); - if (ret) { - dev_err(dev, "Failed to request tx IRQ, fallback to polling mode: %d\n", - ret); - } else { + if (!ret) { mhu->mbox.txdone_irq = true; mhu->mbox.txdone_poll = false; mhu->irq = adev->irq[0]; @@ -1038,10 +1035,8 @@ static int mhuv2_rx_init(struct amba_device *adev, struct mhuv2 *mhu, ret = devm_request_threaded_irq(dev, mhu->irq, NULL, mhuv2_receiver_interrupt, IRQF_ONESHOT, "mhuv2-rx", mhu); - if (ret) { - dev_err(dev, "Failed to request rx IRQ\n"); + if (ret) return ret; - } /* Mask all the channel windows */ for (i = 0; i < mhu->windows; i++) diff --git a/drivers/mailbox/arm_mhuv3.c b/drivers/mailbox/arm_mhuv3.c index a1c528be47f3..6569c3dda377 100644 --- a/drivers/mailbox/arm_mhuv3.c +++ b/drivers/mailbox/arm_mhuv3.c @@ -982,8 +982,7 @@ static int mhuv3_setup_pbx(struct mhuv3 *mhu) mhuv3_pbx_comb_interrupt, IRQF_ONESHOT, "mhuv3-pbx", mhu); if (ret) - return dev_err_probe(dev, ret, - "Failed to request PBX IRQ\n"); + return ret; mhu->mbox.txdone_irq = true; mhu->mbox.txdone_poll = false; @@ -1020,7 +1019,7 @@ static int mhuv3_setup_mbx(struct mhuv3 *mhu) mhuv3_mbx_comb_interrupt, IRQF_ONESHOT, "mhuv3-mbx", mhu); if (ret) - return dev_err_probe(dev, ret, "Failed to request MBX IRQ\n"); + return ret; for (i = 0; i < NUM_EXT; i++) if (mhu->ext[i]) diff --git a/drivers/mailbox/armada-37xx-rwtm-mailbox.c b/drivers/mailbox/armada-37xx-rwtm-mailbox.c index 456a117a65fd..047926711e96 100644 --- a/drivers/mailbox/armada-37xx-rwtm-mailbox.c +++ b/drivers/mailbox/armada-37xx-rwtm-mailbox.c @@ -108,10 +108,8 @@ static int a37xx_mbox_startup(struct mbox_chan *chan) ret = devm_request_irq(mbox->dev, mbox->irq, a37xx_mbox_irq_handler, 0, DRIVER_NAME, chan); - if (ret < 0) { - dev_err(mbox->dev, "Cannot request irq\n"); + if (ret < 0) return ret; - } /* enable IRQ generation */ reg = readl(mbox->base + RWTM_HOST_INT_MASK); diff --git a/drivers/mailbox/bcm-pdc-mailbox.c b/drivers/mailbox/bcm-pdc-mailbox.c index 406bc41cba60..6fcc4002ad4f 100644 --- a/drivers/mailbox/bcm-pdc-mailbox.c +++ b/drivers/mailbox/bcm-pdc-mailbox.c @@ -1403,11 +1403,8 @@ static int pdc_interrupts_init(struct pdc_state *pdcs) err = devm_request_irq(dev, pdcs->pdc_irq, pdc_irq_handler, 0, dev_name(dev), dev); - if (err) { - dev_err(dev, "IRQ %u request failed with err %d\n", - pdcs->pdc_irq, err); + if (err) return err; - } return PDC_SUCCESS; } diff --git a/drivers/mailbox/bcm2835-mailbox.c b/drivers/mailbox/bcm2835-mailbox.c index ea12fb8d2401..5af1e434c71c 100644 --- a/drivers/mailbox/bcm2835-mailbox.c +++ b/drivers/mailbox/bcm2835-mailbox.c @@ -147,11 +147,8 @@ static int bcm2835_mbox_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq_of_parse_and_map(dev->of_node, 0), bcm2835_mbox_irq, IRQF_NO_SUSPEND, dev_name(dev), mbox); - if (ret) { - dev_err(dev, "Failed to register a mailbox IRQ handler: %d\n", - ret); + if (ret) return -ENODEV; - } mbox->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(mbox->regs)) { diff --git a/drivers/mailbox/bcm74110-mailbox.c b/drivers/mailbox/bcm74110-mailbox.c index 344cfc35984b..c8709d509912 100644 --- a/drivers/mailbox/bcm74110-mailbox.c +++ b/drivers/mailbox/bcm74110-mailbox.c @@ -601,7 +601,7 @@ static int bcm74110_mbox_probe(struct platform_device *pdev) ret = devm_request_irq(dev, mbox->rx_irq, bcm74110_mbox_isr, IRQF_NO_SUSPEND, pdev->name, mbox); if (ret) - return dev_err_probe(dev, ret, "Failed to request irq\n"); + return ret; mbox->controller.ops = &bcm74110_mbox_chan_ops; mbox->controller.dev = dev; diff --git a/drivers/mailbox/cv1800-mailbox.c b/drivers/mailbox/cv1800-mailbox.c index 4bca9d8be4ba..b062c2c4fc61 100644 --- a/drivers/mailbox/cv1800-mailbox.c +++ b/drivers/mailbox/cv1800-mailbox.c @@ -189,7 +189,7 @@ static int cv1800_mbox_probe(struct platform_device *pdev) cv1800_mbox_isr, IRQF_ONESHOT, dev_name(&pdev->dev), mb); if (err < 0) - return dev_err_probe(dev, err, "Failed to register irq\n"); + return err; for (idx = 0; idx < MAILBOX_MAX_CHAN; idx++) { mb->priv[idx].idx = idx; diff --git a/drivers/mailbox/hi6220-mailbox.c b/drivers/mailbox/hi6220-mailbox.c index 69d15b6283e9..8eb8242209be 100644 --- a/drivers/mailbox/hi6220-mailbox.c +++ b/drivers/mailbox/hi6220-mailbox.c @@ -297,11 +297,8 @@ static int hi6220_mbox_probe(struct platform_device *pdev) err = devm_request_irq(dev, mbox->irq, hi6220_mbox_interrupt, 0, dev_name(dev), mbox); - if (err) { - dev_err(dev, "Failed to register a mailbox IRQ handler: %d\n", - err); + if (err) return -ENODEV; - } mbox->controller.dev = dev; mbox->controller.chans = &mbox->chan[0]; diff --git a/drivers/mailbox/mailbox-mpfs.c b/drivers/mailbox/mailbox-mpfs.c index 6c40d865b3f1..704a259bbca8 100644 --- a/drivers/mailbox/mailbox-mpfs.c +++ b/drivers/mailbox/mailbox-mpfs.c @@ -219,8 +219,6 @@ static int mpfs_mbox_startup(struct mbox_chan *chan) return -EINVAL; ret = devm_request_irq(mbox->dev, mbox->irq, mpfs_mbox_inbox_isr, 0, "mpfs-mailbox", chan); - if (ret) - dev_err(mbox->dev, "failed to register mailbox interrupt:%d\n", ret); return ret; } diff --git a/drivers/mailbox/mailbox-sti.c b/drivers/mailbox/mailbox-sti.c index b6c9ecbbc8ec..4fb5d1b5504a 100644 --- a/drivers/mailbox/mailbox-sti.c +++ b/drivers/mailbox/mailbox-sti.c @@ -468,10 +468,8 @@ static int sti_mbox_probe(struct platform_device *pdev) sti_mbox_irq_handler, sti_mbox_thread_handler, IRQF_ONESHOT, mdev->name, mdev); - if (ret) { - dev_err(&pdev->dev, "Can't claim IRQ %d\n", irq); + if (ret) return -EINVAL; - } dev_info(&pdev->dev, "%s: Registered Tx/Rx Mailbox\n", mdev->name); diff --git a/drivers/mailbox/qcom-cpucp-mbox.c b/drivers/mailbox/qcom-cpucp-mbox.c index 298b357c0f9a..515d2d12c01b 100644 --- a/drivers/mailbox/qcom-cpucp-mbox.c +++ b/drivers/mailbox/qcom-cpucp-mbox.c @@ -175,7 +175,7 @@ static int qcom_cpucp_mbox_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq, qcom_cpucp_mbox_irq_fn, IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND, "apss_cpucp_mbox", cpucp); if (ret < 0) - return dev_err_probe(dev, ret, "Failed to register irq: %d\n", irq); + return ret; writeq(APSS_CPUCP_RX_MBOX_CMD_MASK, cpucp->rx_base + APSS_CPUCP_RX_MBOX_MAP); diff --git a/drivers/mailbox/qcom-ipcc.c b/drivers/mailbox/qcom-ipcc.c index d957d989c0ce..e5ebb4a237ff 100644 --- a/drivers/mailbox/qcom-ipcc.c +++ b/drivers/mailbox/qcom-ipcc.c @@ -323,10 +323,8 @@ static int qcom_ipcc_probe(struct platform_device *pdev) ret = devm_request_irq(&pdev->dev, ipcc->irq, qcom_ipcc_irq_fn, IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND | IRQF_NO_THREAD, name, ipcc); - if (ret < 0) { - dev_err(&pdev->dev, "Failed to register the irq: %d\n", ret); + if (ret < 0) goto err_req_irq; - } platform_set_drvdata(pdev, ipcc); diff --git a/drivers/mailbox/sprd-mailbox.c b/drivers/mailbox/sprd-mailbox.c index 565502904e1f..9013d2ece55f 100644 --- a/drivers/mailbox/sprd-mailbox.c +++ b/drivers/mailbox/sprd-mailbox.c @@ -377,10 +377,8 @@ static int sprd_mbox_probe(struct platform_device *pdev) ret = devm_request_irq(dev, inbox_irq, sprd_mbox_inbox_isr, IRQF_NO_SUSPEND, dev_name(dev), priv); - if (ret) { - dev_err(dev, "failed to request inbox IRQ: %d\n", ret); + if (ret) return ret; - } outbox_irq = platform_get_irq_byname(pdev, "outbox"); if (outbox_irq < 0) @@ -388,20 +386,16 @@ static int sprd_mbox_probe(struct platform_device *pdev) ret = devm_request_irq(dev, outbox_irq, sprd_mbox_outbox_isr, IRQF_NO_SUSPEND, dev_name(dev), priv); - if (ret) { - dev_err(dev, "failed to request outbox IRQ: %d\n", ret); + if (ret) return ret; - } /* Supplementary outbox IRQ is optional */ supp_irq = platform_get_irq_byname(pdev, "supp-outbox"); if (supp_irq > 0) { ret = devm_request_irq(dev, supp_irq, sprd_mbox_supp_isr, IRQF_NO_SUSPEND, dev_name(dev), priv); - if (ret) { - dev_err(dev, "failed to request outbox IRQ: %d\n", ret); + if (ret) return ret; - } if (!priv->info->supp_id) { dev_err(dev, "no supplementary outbox specified\n"); diff --git a/drivers/mailbox/stm32-ipcc.c b/drivers/mailbox/stm32-ipcc.c index 4f63f1a14ca6..c6134de1a93b 100644 --- a/drivers/mailbox/stm32-ipcc.c +++ b/drivers/mailbox/stm32-ipcc.c @@ -263,10 +263,8 @@ static int stm32_ipcc_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, ipcc->irqs[i], NULL, irq_thread[i], IRQF_ONESHOT, dev_name(dev), ipcc); - if (ret) { - dev_err(dev, "failed to request irq %lu (%d)\n", i, ret); + if (ret) goto err_clk; - } } /* mask and enable rx/tx irq */ diff --git a/drivers/mailbox/sun6i-msgbox.c b/drivers/mailbox/sun6i-msgbox.c index 6ba6920f4645..1cbc4d478beb 100644 --- a/drivers/mailbox/sun6i-msgbox.c +++ b/drivers/mailbox/sun6i-msgbox.c @@ -257,10 +257,8 @@ static int sun6i_msgbox_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq_of_parse_and_map(dev->of_node, 0), sun6i_msgbox_irq, 0, dev_name(dev), mbox); - if (ret) { - dev_err(dev, "Failed to register IRQ handler: %d\n", ret); + if (ret) goto err_disable_unprepare; - } mbox->controller.dev = dev; mbox->controller.ops = &sun6i_msgbox_chan_ops; diff --git a/drivers/mailbox/tegra-hsp.c b/drivers/mailbox/tegra-hsp.c index 500fa77c7d53..03ee73aad488 100644 --- a/drivers/mailbox/tegra-hsp.c +++ b/drivers/mailbox/tegra-hsp.c @@ -708,11 +708,8 @@ static int tegra_hsp_request_shared_irq(struct tegra_hsp *hsp) err = devm_request_irq(hsp->dev, irq, tegra_hsp_shared_irq, 0, dev_name(hsp->dev), hsp); - if (err < 0) { - dev_err(hsp->dev, "failed to request interrupt: %d\n", - err); + if (err < 0) continue; - } hsp->shared_irq = i; @@ -856,12 +853,8 @@ static int tegra_hsp_probe(struct platform_device *pdev) err = devm_request_irq(&pdev->dev, hsp->doorbell_irq, tegra_hsp_doorbell_irq, IRQF_NO_SUSPEND, dev_name(&pdev->dev), hsp); - if (err < 0) { - dev_err(&pdev->dev, - "failed to request doorbell IRQ#%u: %d\n", - hsp->doorbell_irq, err); + if (err < 0) return err; - } } if (hsp->shared_irqs) { From b37c4d0a2fd90c0c31223acd37f763eb8953ed1a Mon Sep 17 00:00:00 2001 From: Griffin Kroah-Hartman Date: Thu, 9 Jul 2026 15:16:40 +0200 Subject: [PATCH 15/22] mailbox: mchp-ipc-sbi: Add null check for devm_kasprintf() Add a check to see if devm_kasprintf() is not NULL in mchp_ipc_get_cluster_aggr_irq(), returning -ENOMEM if the function failed. Assisted-by: gkh_clanker_t1000 CC: Jassi Brar Signed-off-by: Griffin Kroah-Hartman Signed-off-by: Jassi Brar --- drivers/mailbox/mailbox-mchp-ipc-sbi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mailbox/mailbox-mchp-ipc-sbi.c b/drivers/mailbox/mailbox-mchp-ipc-sbi.c index b87bf2fb4b9b..f081f8a9bcf8 100644 --- a/drivers/mailbox/mailbox-mchp-ipc-sbi.c +++ b/drivers/mailbox/mailbox-mchp-ipc-sbi.c @@ -378,6 +378,8 @@ static int mchp_ipc_get_cluster_aggr_irq(struct mchp_ipc_sbi_mbox *ipc) for_each_online_cpu(cpuid) { hartid = cpuid_to_hartid_map(cpuid); irq_name = devm_kasprintf(ipc->dev, GFP_KERNEL, "hart-%lu", hartid); + if (!irq_name) + return -ENOMEM; ret = platform_get_irq_byname_optional(pdev, irq_name); if (ret <= 0) continue; From 66c7bcad72430a02c860521031350b84b31ad9a8 Mon Sep 17 00:00:00 2001 From: Anup Vishwakarma Date: Wed, 5 Aug 2026 14:34:07 +0530 Subject: [PATCH 16/22] mailbox: qcom-ipcc: fix duplicate channel allocation across holes The IPCC of_xlate() both scans for a free mailbox channel and checks for duplicate references to the same underlying IPCC channel. When a channel has been shutdown it might have left a hole in the channel list, which would terminate the search without considering duplicates later in the list. Continue the traversal of the channel list to detect and reject duplicates, while keeping track of the first free channel. Fixes: d6fbfdbc1274 ("mailbox: qcom-ipcc: Fix IPCC mbox channel exhaustion") Cc: stable@vger.kernel.org Signed-off-by: Anup Vishwakarma Signed-off-by: Jassi Brar --- drivers/mailbox/qcom-ipcc.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/mailbox/qcom-ipcc.c b/drivers/mailbox/qcom-ipcc.c index e5ebb4a237ff..185b63f724d4 100644 --- a/drivers/mailbox/qcom-ipcc.c +++ b/drivers/mailbox/qcom-ipcc.c @@ -167,7 +167,7 @@ static struct mbox_chan *qcom_ipcc_mbox_xlate(struct mbox_controller *mbox, { struct qcom_ipcc *ipcc = to_qcom_ipcc(mbox); struct qcom_ipcc_chan_info *mchan; - struct mbox_chan *chan; + struct mbox_chan *chan, *free_chan = NULL; struct device *dev; int chan_id; @@ -180,16 +180,21 @@ static struct mbox_chan *qcom_ipcc_mbox_xlate(struct mbox_controller *mbox, chan = &ipcc->chans[chan_id]; mchan = chan->con_priv; - if (!mchan) - break; - else if (mchan->client_id == ph->args[0] && - mchan->signal_id == ph->args[1]) + if (!mchan) { + /* Keep scanning past holes to reject duplicate channel requests. */ + if (!free_chan) + free_chan = chan; + } else if (mchan->client_id == ph->args[0] && + mchan->signal_id == ph->args[1]) { return ERR_PTR(-EBUSY); + } } - if (chan_id >= mbox->num_chans) + if (!free_chan) return ERR_PTR(-EBUSY); + chan = free_chan; + mchan = devm_kzalloc(dev, sizeof(*mchan), GFP_KERNEL); if (!mchan) return ERR_PTR(-ENOMEM); From 3ccffcc3672af5fc2f5809d349f26d2be8d318a3 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 27 Jul 2026 12:46:04 -0700 Subject: [PATCH 17/22] mailbox: bcm2835: use platform_get_irq and simplify probe Replace irq_of_parse_and_map() with platform_get_irq() for the mailbox interrupt lookup, and move IRQ and MMIO resource acquisition to the top of the probe function before any memory allocation. Simplify error handling throughout: use direct return of platform_get_irq and PTR_ERR values, remove the redundant platform_set_drvdata and dev_info log, and inline the final return. Assisted-by: Opencode:Big-Pickle Signed-off-by: Rosen Penev Signed-off-by: Jassi Brar --- drivers/mailbox/bcm2835-mailbox.c | 32 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/drivers/mailbox/bcm2835-mailbox.c b/drivers/mailbox/bcm2835-mailbox.c index 5af1e434c71c..0ca75c378a60 100644 --- a/drivers/mailbox/bcm2835-mailbox.c +++ b/drivers/mailbox/bcm2835-mailbox.c @@ -136,25 +136,30 @@ static struct mbox_chan *bcm2835_mbox_index_xlate(struct mbox_controller *mbox, static int bcm2835_mbox_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; + void __iomem *regs; int ret = 0; + int irq; struct bcm2835_mbox *mbox; + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL); if (mbox == NULL) return -ENOMEM; + spin_lock_init(&mbox->lock); + mbox->regs = regs; - ret = devm_request_irq(dev, irq_of_parse_and_map(dev->of_node, 0), - bcm2835_mbox_irq, IRQF_NO_SUSPEND, dev_name(dev), - mbox); + ret = devm_request_irq(dev, irq, bcm2835_mbox_irq, + IRQF_NO_SUSPEND, dev_name(dev), mbox); if (ret) - return -ENODEV; - - mbox->regs = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(mbox->regs)) { - ret = PTR_ERR(mbox->regs); return ret; - } mbox->controller.txdone_poll = true; mbox->controller.txpoll_period = 5; @@ -167,14 +172,7 @@ static int bcm2835_mbox_probe(struct platform_device *pdev) if (!mbox->controller.chans) return -ENOMEM; - ret = devm_mbox_controller_register(dev, &mbox->controller); - if (ret) - return ret; - - platform_set_drvdata(pdev, mbox); - dev_info(dev, "mailbox enabled\n"); - - return ret; + return devm_mbox_controller_register(dev, &mbox->controller); } static const struct of_device_id bcm2835_mbox_of_match[] = { From 11d5af151bcbe78f5a579e0faecd3be9cea0399a Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Fri, 14 Aug 2026 16:02:15 +0800 Subject: [PATCH 18/22] mailbox: riscv-sbi-mpxy: validate RPMI notification lengths The SBI return value controls how many bytes are copied from shared memory into the RPMI notification buffer. It is not validated against the negotiated shared-memory size before that copy. The event walker also uses a reversed loop condition and can inspect a short event record. Validate the complete notification length before copying it, iterate only while a full event header remains, and stop when a declared event payload extends beyond the copied notification data. Fixes: bf3022a4eb11 ("mailbox: Add RISC-V SBI message proxy (MPXY) based mailbox driver") Assisted-by: Codex:gpt-5 Signed-off-by: Pengpeng Hou Signed-off-by: Jassi Brar --- drivers/mailbox/riscv-sbi-mpxy-mbox.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/mailbox/riscv-sbi-mpxy-mbox.c b/drivers/mailbox/riscv-sbi-mpxy-mbox.c index 7c9c006b7244..714f7fb97a2f 100644 --- a/drivers/mailbox/riscv-sbi-mpxy-mbox.c +++ b/drivers/mailbox/riscv-sbi-mpxy-mbox.c @@ -314,8 +314,13 @@ static int mpxy_get_notifications(u32 channel_id, channel_id, 0, 0, 0, 0, 0); if (sret.error) goto err_put_cpu; + if (sret.value < 0 || mpxy_shmem_size < sizeof(*notif_data) || + sret.value > mpxy_shmem_size - sizeof(*notif_data)) { + put_cpu(); + return -EOVERFLOW; + } - memcpy(notif_data, mpxy->shmem, sret.value + 16); + memcpy(notif_data, mpxy->shmem, sret.value + sizeof(*notif_data)); *events_data_len = sret.value; err_put_cpu: @@ -480,11 +485,14 @@ static void mpxy_mbox_peek_rpmi_data(struct mbox_chan *chan, struct rpmi_mbox_message msg; unsigned long pos = 0; - while (pos < events_data_len && (events_data_len - pos) <= sizeof(*event)) { + while (events_data_len - pos >= sizeof(*event)) { event = (struct rpmi_notification_event *)(notif->events_data + pos); msg.type = RPMI_MBOX_MSG_TYPE_NOTIFICATION_EVENT; msg.notif.event_datalen = le16_to_cpu(event->event_datalen); + if (msg.notif.event_datalen > + events_data_len - pos - sizeof(*event)) + break; msg.notif.event_id = event->event_id; msg.notif.event_data = event->event_data; msg.error = 0; From 7dffb1a4a336075182b8665ce97bcbca12658e3c Mon Sep 17 00:00:00 2001 From: Surendra Singh Chouhan Date: Wed, 12 Aug 2026 15:36:23 +0530 Subject: [PATCH 19/22] mailbox: cix: fix DT property name string typo and use dev_err_probe() cix_mbox_probe() logged property error messages referencing "cix,mbox_dir" (with an underscore) instead of the actual DT property string "cix,mbox-dir". Fix the DT property string in error log messages and convert probe error paths to dev_err_probe(). Signed-off-by: Surendra Singh Chouhan Reviewed-by: Guomin Chen Signed-off-by: Jassi Brar --- drivers/mailbox/cix-mailbox.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/drivers/mailbox/cix-mailbox.c b/drivers/mailbox/cix-mailbox.c index 43c76cdab24a..7fa979c1b8f0 100644 --- a/drivers/mailbox/cix-mailbox.c +++ b/drivers/mailbox/cix-mailbox.c @@ -587,19 +587,15 @@ static int cix_mbox_probe(struct platform_device *pdev) if (priv->irq < 0) return priv->irq; - if (device_property_read_string(dev, "cix,mbox-dir", &dir_str)) { - dev_err(priv->dev, "cix,mbox_dir property not found\n"); - return -EINVAL; - } + if (device_property_read_string(dev, "cix,mbox-dir", &dir_str)) + return dev_err_probe(dev, -EINVAL, "cix,mbox-dir property not found\n"); if (!strcmp(dir_str, "tx")) priv->dir = 0; else if (!strcmp(dir_str, "rx")) priv->dir = 1; - else { - dev_err(priv->dev, "cix,mbox_dir=%s is not expected\n", dir_str); - return -EINVAL; - } + else + return dev_err_probe(dev, -EINVAL, "cix,mbox-dir=%s is not expected\n", dir_str); cix_mbox_init(priv); @@ -613,9 +609,9 @@ static int cix_mbox_probe(struct platform_device *pdev) platform_set_drvdata(pdev, priv); ret = devm_mbox_controller_register(dev, &priv->mbox); if (ret) - dev_err(dev, "Failed to register mailbox %d\n", ret); + return dev_err_probe(dev, ret, "Failed to register mailbox\n"); - return ret; + return 0; } static const struct of_device_id cix_mbox_dt_ids[] = { From b568b68320cb757f010e430be75939d92af9d727 Mon Sep 17 00:00:00 2001 From: Eduard Bostina Date: Wed, 12 Aug 2026 09:51:53 +0000 Subject: [PATCH 20/22] dt-bindings: mailbox: Convert TI Message Manager to DT schema Convert the Texas Instruments Message Manager binding to DT schema. Signed-off-by: Eduard Bostina Signed-off-by: Jassi Brar --- .../mailbox/ti,k2g-message-manager.yaml | 67 +++++++++++++++++++ .../bindings/mailbox/ti,message-manager.txt | 50 -------------- 2 files changed, 67 insertions(+), 50 deletions(-) create mode 100644 Documentation/devicetree/bindings/mailbox/ti,k2g-message-manager.yaml delete mode 100644 Documentation/devicetree/bindings/mailbox/ti,message-manager.txt diff --git a/Documentation/devicetree/bindings/mailbox/ti,k2g-message-manager.yaml b/Documentation/devicetree/bindings/mailbox/ti,k2g-message-manager.yaml new file mode 100644 index 000000000000..481b7c8c0e96 --- /dev/null +++ b/Documentation/devicetree/bindings/mailbox/ti,k2g-message-manager.yaml @@ -0,0 +1,67 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mailbox/ti,k2g-message-manager.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments Message Manager + +maintainers: + - Eduard Bostina + +description: + The Texas Instruments Message Manager is a mailbox controller that has + configurable queues selectable at SoC (System on Chip) integration. The + Message manager is broken up into queues in different address regions + that are called "proxies" - each instance is unidirectional and is + instantiated at SoC integration level to indicate receive or transmit path. + +properties: + compatible: + const: ti,k2g-message-manager + + reg: + maxItems: 2 + + reg-names: + items: + - const: queue_proxy_region + - const: queue_state_debug_region + + "#mbox-cells": + const: 2 + description: + Contains the queue ID and proxy ID in that order referring to the + transfer path. + + interrupts: + maxItems: 2 + + interrupt-names: + items: + - const: rx_005 + - const: rx_057 + +required: + - compatible + - reg + - reg-names + - "#mbox-cells" + - interrupts + - interrupt-names + +additionalProperties: false + +examples: + - | + #include + + msgmgr@2a00000 { + compatible = "ti,k2g-message-manager"; + #mbox-cells = <2>; + reg-names = "queue_proxy_region", "queue_state_debug_region"; + reg = <0x02a00000 0x400000>, <0x028c3400 0x400>; + interrupt-names = "rx_005", "rx_057"; + interrupts = , + ; + }; diff --git a/Documentation/devicetree/bindings/mailbox/ti,message-manager.txt b/Documentation/devicetree/bindings/mailbox/ti,message-manager.txt deleted file mode 100644 index ebf0e3710cee..000000000000 --- a/Documentation/devicetree/bindings/mailbox/ti,message-manager.txt +++ /dev/null @@ -1,50 +0,0 @@ -Texas Instruments' Message Manager Driver -======================================== - -The Texas Instruments' Message Manager is a mailbox controller that has -configurable queues selectable at SoC(System on Chip) integration. The Message -manager is broken up into queues in different address regions that are called -"proxies" - each instance is unidirectional and is instantiated at SoC -integration level to indicate receive or transmit path. - -Message Manager Device Node: -=========================== -Required properties: --------------------- -- compatible: Shall be: "ti,k2g-message-manager" -- reg-names queue_proxy_region - Map the queue proxy region. - queue_state_debug_region - Map the queue state debug - region. -- reg: Contains the register map per reg-names. -- #mbox-cells Shall be 2. Contains the queue ID and proxy ID in that - order referring to the transfer path. -- interrupt-names: Contains interrupt names matching the rx transfer path - for a given SoC. Receive interrupts shall be of the - format: "rx_". - For ti,k2g-message-manager, this shall contain: - "rx_005", "rx_057" -- interrupts: Contains the interrupt information corresponding to - interrupt-names property. - -Example(K2G): ------------- - - msgmgr: msgmgr@2a00000 { - compatible = "ti,k2g-message-manager"; - #mbox-cells = <2>; - reg-names = "queue_proxy_region", "queue_state_debug_region"; - reg = <0x02a00000 0x400000>, <0x028c3400 0x400>; - interrupt-names = "rx_005", "rx_057"; - interrupts = , - ; - }; - - pmmc: pmmc { - [...] - mbox-names = "rx", "tx"; - # RX queue ID is 5, proxy ID is 2 - # TX queue ID is 0, proxy ID is 0 - mboxes= <&msgmgr 5 2>, - <&msgmgr 0 0>; - [...] - }; From 289413b8077af26bc8c4fed8311bc9092b9b40fc Mon Sep 17 00:00:00 2001 From: Swark Yang Date: Mon, 17 Aug 2026 20:34:07 -0700 Subject: [PATCH 21/22] dt-bindings: mailbox: add Axiado AX3005 mailbox Add a devicetree binding for the Axiado AX3005 mailbox controller. The controller provides inter-processor communication channels between the host CPU and the coprocessor, with separate TX and RX register regions. Add the corresponding MAINTAINERS entry covering the binding. Signed-off-by: Swark Yang Reviewed-by: Krzysztof Kozlowski Signed-off-by: Jassi Brar --- .../mailbox/axiado,ax3005-mailbox.yaml | 96 +++++++++++++++++++ MAINTAINERS | 7 ++ 2 files changed, 103 insertions(+) create mode 100644 Documentation/devicetree/bindings/mailbox/axiado,ax3005-mailbox.yaml diff --git a/Documentation/devicetree/bindings/mailbox/axiado,ax3005-mailbox.yaml b/Documentation/devicetree/bindings/mailbox/axiado,ax3005-mailbox.yaml new file mode 100644 index 000000000000..82bf7d1848c9 --- /dev/null +++ b/Documentation/devicetree/bindings/mailbox/axiado,ax3005-mailbox.yaml @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mailbox/axiado,ax3005-mailbox.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Axiado AX3005 Mailbox Controller + +maintainers: + - Krishnakumar Kesavan + - Prasad Bolisetty + - Swark Yang + +description: + The Axiado mailbox controller provides inter-processor communication + channels between subsystems within the Axiado SoC. The hardware has + separate TX and RX register regions. Only the RX channels (channels 8-15) + generate interrupts; TX channels are purely poll/write-based. + +properties: + compatible: + const: axiado,ax3005-mailbox + + reg: + items: + - description: TX mailbox register region + - description: RX mailbox register region + + reg-names: + items: + - const: tx + - const: rx + + interrupts: + description: + One interrupt per RX channel. The hardware exposes eight RX interrupt + lines corresponding to channels 8 through 15. + maxItems: 8 + + interrupt-names: + description: + Named interrupt lines for each RX channel, from ch8 to ch15. + items: + - const: ch8 + - const: ch9 + - const: ch10 + - const: ch11 + - const: ch12 + - const: ch13 + - const: ch14 + - const: ch15 + + "#mbox-cells": + description: + Number of cells in a mailbox specifier. A single cell encodes the + channel index as seen by the client. + const: 1 + +required: + - compatible + - reg + - reg-names + - interrupts + - interrupt-names + - "#mbox-cells" + +additionalProperties: false + +examples: + - | + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + mailbox@330f2000 { + compatible = "axiado,ax3005-mailbox"; + reg = <0x0 0x330f2000 0x0 0x2000>, + <0x0 0x331b0000 0x0 0x1000>; + reg-names = "tx", "rx"; + /* Only RX channels (8-15) have IRQs */ + interrupts = , + , + , + , + , + , + , + ; + interrupt-names = "ch8", "ch9", "ch10", "ch11", + "ch12", "ch13", "ch14", "ch15"; + interrupt-parent = <&gic500>; + #mbox-cells = <1>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 8014b9f8253e..948f1992ab44 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4428,6 +4428,13 @@ F: Documentation/devicetree/bindings/phy/axiado,ax3000-emmc-phy.yaml F: drivers/phy/axiado/Kconfig F: drivers/phy/axiado/phy-axiado-emmc.c +AXIADO MAILBOX DRIVER +M: Krishnakumar Kesavan +M: Prasad Bolisetty +M: Swark Yang +S: Supported +F: Documentation/devicetree/bindings/mailbox/axiado,ax3005-mailbox.yaml + AXI SPI ENGINE M: Michael Hennerich M: Nuno Sá From 14af7a96afa39f4f3c1972705489b9ba15c01857 Mon Sep 17 00:00:00 2001 From: Swark Yang Date: Mon, 17 Aug 2026 20:34:08 -0700 Subject: [PATCH 22/22] mailbox: add Axiado AX3005 mailbox driver Add a mailbox controller driver for the Axiado AX3005 SoC. The controller provides communication channels between the host CPU and the coprocessor. The hardware provides 8 TX channels and 8 RX channels through separate register regions. RX channels use per-channel interrupts, while TX completion is detected by polling the FIFO status. Add the driver path to the existing Axiado mailbox entry in MAINTAINERS. Signed-off-by: Swark Yang Signed-off-by: Jassi Brar --- MAINTAINERS | 1 + drivers/mailbox/Kconfig | 10 + drivers/mailbox/Makefile | 2 + drivers/mailbox/axiado-mailbox.c | 395 +++++++++++++++++++++++++++++++ 4 files changed, 408 insertions(+) create mode 100644 drivers/mailbox/axiado-mailbox.c diff --git a/MAINTAINERS b/MAINTAINERS index 948f1992ab44..40909560a92f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4434,6 +4434,7 @@ M: Prasad Bolisetty M: Swark Yang S: Supported F: Documentation/devicetree/bindings/mailbox/axiado,ax3005-mailbox.yaml +F: drivers/mailbox/axiado-mailbox.c AXI SPI ENGINE M: Michael Hennerich diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig index 3062ee352f78..ab869769e278 100644 --- a/drivers/mailbox/Kconfig +++ b/drivers/mailbox/Kconfig @@ -399,4 +399,14 @@ config RISCV_SBI_MPXY_MBOX or HS-mode hypervisor). Say Y here, unless you are sure you do not need this. +config AXIADO_MAILBOX + tristate "Axiado mailbox driver" + depends on ARCH_AXIADO || COMPILE_TEST + depends on OF + help + Enable support for the Axiado mailbox controller. The driver provides + communication channels between the host CPU and the coprocessor on + Axiado SoCs. + If unsure, say N. + endif diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile index 944d8ea39f34..45dc59bb3e7f 100644 --- a/drivers/mailbox/Makefile +++ b/drivers/mailbox/Makefile @@ -84,3 +84,5 @@ obj-$(CONFIG_CIX_MBOX) += cix-mailbox.o obj-$(CONFIG_BCM74110_MAILBOX) += bcm74110-mailbox.o obj-$(CONFIG_RISCV_SBI_MPXY_MBOX) += riscv-sbi-mpxy-mbox.o + +obj-$(CONFIG_AXIADO_MAILBOX) += axiado-mailbox.o diff --git a/drivers/mailbox/axiado-mailbox.c b/drivers/mailbox/axiado-mailbox.c new file mode 100644 index 000000000000..3a3cf7421863 --- /dev/null +++ b/drivers/mailbox/axiado-mailbox.c @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2021-2026 Axiado Corporation (or its affiliates). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AXIADO_MBOX_TX_CHANS 8 /* 0-7 */ +#define AXIADO_MBOX_RX_CHANS 8 /* 8-15 */ +#define AXIADO_MBOX_CHAN_STRIDE 0x4 +#define AXIADO_MBOX_TX_REG_STRIDE 0x40 +#define AXIADO_MBOX_RX_REG_STRIDE 0x30 +#define AXIADO_MBOX_RX_POLL_US 10 +#define AXIADO_MBOX_RX_TIMEOUT_US 1000 + +/* Mailbox CSR bit definitions */ +#define AXIADO_MBOX_CSR_EMPTY BIT(0) /* 1 = FIFO empty; 0 = data available */ +#define AXIADO_MBOX_CSR_OVERFLOW BIT(2) /* write 1 to clear (W1C) */ +#define AXIADO_MBOX_CSR_UNDERFLOW BIT(3) /* write 1 to clear (W1C) */ +#define AXIADO_MBOX_CSR_FLUSH BIT(4) /* W1TRG flush; startup/shutdown */ +#define AXIADO_MBOX_CSR_LEVEL GENMASK(11, 5) + +#define AXIADO_MBOX_CSR_ERRORS (AXIADO_MBOX_CSR_OVERFLOW | \ + AXIADO_MBOX_CSR_UNDERFLOW) + +struct axiado_mbox_data { + u8 num_chans; + u16 msg_size; +}; + +struct axiado_channel_data { + void __iomem *mbox_reg; + void __iomem *csr_reg; + void *rx_buffer; + u8 channel_num; + int irq; + struct mbox_chan *chan; + bool active; + atomic_t fifo_errors; +}; + +/* mailbox side */ +struct axiado_mbox { + struct mbox_controller mbox; + const struct axiado_mbox_data *drv_data; + void __iomem *tx_base; + void __iomem *rx_base; + struct dentry *debugfs_dir; +}; + +/* + * Checks OVERFLOW/UNDERFLOW, logs and W1C-clears them if set. Shared by the + * TX and RX paths; RX additionally flushes the FIFO afterwards to resync + * framing, which TX must not do since it could discard data the peer has + * not read yet. + */ +static bool axiado_mbox_clear_fifo_errors(struct axiado_channel_data *priv) +{ + struct device *dev = priv->chan->mbox->dev; + u32 errors; + + errors = readl(priv->csr_reg) & AXIADO_MBOX_CSR_ERRORS; + if (!errors) + return false; + + writel(errors, priv->csr_reg); + atomic_inc(&priv->fifo_errors); + + dev_warn_ratelimited(dev, "Channel %u FIFO error: %#x\n", + priv->channel_num, errors); + + return true; +} + +static int axiado_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct axiado_mbox *mb = dev_get_drvdata(chan->mbox->dev); + struct axiado_channel_data *priv = chan->con_priv; + unsigned int idx = priv->channel_num; + u8 tail[sizeof(u32)] = { 0 }; + const u8 *buf = data; + unsigned int tail_len; + unsigned int offset; + u32 msg_len; + + if (!data) + return -EINVAL; + + /* + * The Axiado mailbox message ABI stores the total message length, + * including this length word, as a little-endian byte count in the + * first word of every message. + */ + msg_len = get_unaligned_le32(data); + if (msg_len < sizeof(u32) || msg_len > mb->drv_data->msg_size) + return -EINVAL; + + /* Only touch the hardware after validating the message. */ + axiado_mbox_clear_fifo_errors(priv); + + if (!(readl(priv->csr_reg) & AXIADO_MBOX_CSR_EMPTY)) { + dev_warn_ratelimited(mb->mbox.dev, "Channel %u is busy\n", idx); + return -EBUSY; + } + + for (offset = 0; offset + sizeof(u32) <= msg_len; + offset += sizeof(u32)) + writel(get_unaligned_le32(buf + offset), priv->mbox_reg); + + tail_len = msg_len - offset; + if (tail_len) { + memcpy(tail, buf + offset, tail_len); + writel(get_unaligned_le32(tail), priv->mbox_reg); + } + + dev_dbg(mb->mbox.dev, "%s: Ch-%u sent\n", __func__, idx); + + return 0; +} + +static irqreturn_t axiado_rx_thread(int irq, void *dev_id) +{ + struct axiado_channel_data *priv = dev_id; + struct mbox_chan *chan = priv->chan; + struct axiado_mbox *mb = dev_get_drvdata(chan->mbox->dev); + u8 *buf = priv->rx_buffer; + unsigned int num_words; + unsigned int remaining; + unsigned int i; + u32 msg_len; + u32 word; + u32 csr; + int ret; + + /* + * Clear and log/count any pending errors, but don't discard data + * on their account alone: a rejected overflow write doesn't + * corrupt what was already safely queued ahead of it, so let the + * length-based read below decide whether what's here is usable. + */ + axiado_mbox_clear_fifo_errors(priv); + + csr = readl(priv->csr_reg); + if (csr & AXIADO_MBOX_CSR_EMPTY) + return IRQ_NONE; + + /* + * The first word contains the total message length in bytes, + * including the length word itself. + */ + word = readl(priv->mbox_reg); + msg_len = word; + put_unaligned_le32(word, buf); + + if (msg_len < sizeof(u32) || msg_len > mb->drv_data->msg_size) + goto invalid_message; + + num_words = DIV_ROUND_UP(msg_len, sizeof(u32)); + remaining = num_words - 1; + + /* + * The not-empty interrupt may occur as soon as the first DW enters + * the FIFO. Wait until all remaining DWs of this message arrive. + */ + if (remaining) { + ret = readl_poll_timeout(priv->csr_reg, csr, + (csr & AXIADO_MBOX_CSR_ERRORS) || + FIELD_GET(AXIADO_MBOX_CSR_LEVEL, csr) >= + remaining, + AXIADO_MBOX_RX_POLL_US, + AXIADO_MBOX_RX_TIMEOUT_US); + if (ret) + goto incomplete_message; + + axiado_mbox_clear_fifo_errors(priv); + } + + for (i = 1; i < num_words; i++) { + word = readl(priv->mbox_reg); + put_unaligned_le32(word, buf + i * sizeof(u32)); + } + + axiado_mbox_clear_fifo_errors(priv); + + if (READ_ONCE(priv->active)) + mbox_chan_received_data(chan, priv->rx_buffer); + + return IRQ_HANDLED; + +incomplete_message: + dev_warn_ratelimited(chan->mbox->dev, + "Channel %u received an incomplete message\n", + priv->channel_num); + +invalid_message: + writel(AXIADO_MBOX_CSR_FLUSH, priv->csr_reg); + + return IRQ_HANDLED; +} + +static int axiado_mbox_startup(struct mbox_chan *chan) +{ + struct axiado_channel_data *priv = chan->con_priv; + + /* + * Only flush on a genuine error. A blind flush here would discard + * a message the peer legitimately sent before this side started + * up (e.g. during normal boot sequencing), which is not corrupt + * and does not need resyncing. + */ + if (axiado_mbox_clear_fifo_errors(priv)) + writel(AXIADO_MBOX_CSR_FLUSH, priv->csr_reg); + + WRITE_ONCE(priv->active, true); + + if (priv->channel_num >= AXIADO_MBOX_TX_CHANS) + enable_irq(priv->irq); + + return 0; +} + +static void axiado_mbox_shutdown(struct mbox_chan *chan) +{ + struct axiado_channel_data *priv = chan->con_priv; + + WRITE_ONCE(priv->active, false); + + if (priv->channel_num >= AXIADO_MBOX_TX_CHANS) + disable_irq(priv->irq); + + if (axiado_mbox_clear_fifo_errors(priv)) + writel(AXIADO_MBOX_CSR_FLUSH, priv->csr_reg); +} + +static bool axiado_mbox_last_tx_done(struct mbox_chan *chan) +{ + struct axiado_channel_data *priv = chan->con_priv; + + return !!(readl(priv->csr_reg) & AXIADO_MBOX_CSR_EMPTY); +} + +static const struct mbox_chan_ops axiado_mbox_chan_ops = { + .send_data = axiado_mbox_send_data, + .startup = axiado_mbox_startup, + .shutdown = axiado_mbox_shutdown, + .last_tx_done = axiado_mbox_last_tx_done, +}; + +static void axiado_mbox_debugfs_remove(void *dentry) +{ + debugfs_remove_recursive(dentry); +} + +static int axiado_mbox_probe(struct platform_device *pdev) +{ + const struct axiado_mbox_data *drv_data; + struct axiado_channel_data *ch_data; + struct device *dev = &pdev->dev; + struct axiado_mbox *mb; + unsigned int irq_idx = 0; + unsigned int rx_chan; + unsigned int i; + int ret; + + drv_data = device_get_match_data(dev); + if (!drv_data) + return -ENODEV; + + mb = devm_kzalloc(dev, sizeof(*mb), GFP_KERNEL); + if (!mb) + return -ENOMEM; + + mb->mbox.dev = dev; + mb->mbox.num_chans = drv_data->num_chans; + + mb->mbox.chans = devm_kcalloc(&pdev->dev, + drv_data->num_chans, + sizeof(*mb->mbox.chans), + GFP_KERNEL); + if (!mb->mbox.chans) + return -ENOMEM; + + mb->tx_base = devm_platform_ioremap_resource_byname(pdev, "tx"); + if (IS_ERR(mb->tx_base)) + return PTR_ERR(mb->tx_base); + + mb->rx_base = devm_platform_ioremap_resource_byname(pdev, "rx"); + if (IS_ERR(mb->rx_base)) + return PTR_ERR(mb->rx_base); + + ch_data = devm_kcalloc(&pdev->dev, + drv_data->num_chans, + sizeof(*ch_data), + GFP_KERNEL); + if (!ch_data) + return -ENOMEM; + + mb->debugfs_dir = debugfs_create_dir(dev_name(dev), NULL); + ret = devm_add_action_or_reset(dev, axiado_mbox_debugfs_remove, + mb->debugfs_dir); + if (ret) + return ret; + + for (i = 0; i < drv_data->num_chans; i++) { + char name[16]; + + ch_data[i].channel_num = i; + ch_data[i].chan = &mb->mbox.chans[i]; + + mb->mbox.chans[i].con_priv = &ch_data[i]; + + snprintf(name, sizeof(name), "chan%u-errors", i); + debugfs_create_atomic_t(name, 0444, mb->debugfs_dir, + &ch_data[i].fifo_errors); + + if (i < AXIADO_MBOX_TX_CHANS) { + ch_data[i].mbox_reg = mb->tx_base + (i * AXIADO_MBOX_CHAN_STRIDE); + ch_data[i].csr_reg = mb->tx_base + AXIADO_MBOX_TX_REG_STRIDE + + (i * AXIADO_MBOX_CHAN_STRIDE); + ch_data[i].irq = -1; + continue; + } + + ch_data[i].rx_buffer = devm_kzalloc(dev, drv_data->msg_size, + GFP_KERNEL); + if (!ch_data[i].rx_buffer) + return -ENOMEM; + + rx_chan = i - AXIADO_MBOX_TX_CHANS; + ch_data[i].mbox_reg = mb->rx_base + + (rx_chan * AXIADO_MBOX_CHAN_STRIDE); + ch_data[i].csr_reg = mb->rx_base + AXIADO_MBOX_RX_REG_STRIDE + + (rx_chan * AXIADO_MBOX_CHAN_STRIDE); + ch_data[i].irq = platform_get_irq(pdev, irq_idx++); + if (ch_data[i].irq < 0) + return dev_err_probe(dev, ch_data[i].irq, + "Failed to get IRQ for channel %u\n", i); + + ret = devm_request_threaded_irq(dev, ch_data[i].irq, NULL, + axiado_rx_thread, + IRQF_ONESHOT | IRQF_NO_AUTOEN, + dev_name(dev), &ch_data[i]); + if (ret) + return dev_err_probe(dev, ret, + "Failed to request IRQ for channel %u\n", i); + + dev_dbg(dev, "RX chan %u -> irq %d\n", i, ch_data[i].irq); + } + + platform_set_drvdata(pdev, mb); + mb->drv_data = drv_data; + mb->mbox.ops = &axiado_mbox_chan_ops; + mb->mbox.txdone_irq = false; + mb->mbox.txdone_poll = true; + mb->mbox.txpoll_period = 5; + + return devm_mbox_controller_register(dev, &mb->mbox); +} + +static const struct axiado_mbox_data axiado_drv_data = { + .num_chans = AXIADO_MBOX_TX_CHANS + AXIADO_MBOX_RX_CHANS, + .msg_size = 256, +}; + +static const struct of_device_id axiado_mbox_of_match[] = { + { .compatible = "axiado,ax3005-mailbox", .data = &axiado_drv_data }, + { } +}; +MODULE_DEVICE_TABLE(of, axiado_mbox_of_match); + +static struct platform_driver axiado_mbox_driver = { + .driver = { + .name = "axiado-mailbox", + .of_match_table = axiado_mbox_of_match, + }, + .probe = axiado_mbox_probe, +}; +module_platform_driver(axiado_mbox_driver); + +MODULE_AUTHOR("Axiado Corporation"); +MODULE_DESCRIPTION("Axiado Mailbox driver"); +MODULE_LICENSE("GPL");