[PATCH v2] i2c: qup: Release DMA channels if interconnect lookup fails

From: Yige Jiang

Date: Fri Nov 06 2026 - 02:03:17 EST


qup_i2c_probe() acquires both BAM DMA channels before getting its
interconnect path. If devm_of_icc_get() fails, the direct return skips
the DMA cleanup and leaks both channels, including when probe is
deferred. A later probe can no longer acquire those channels.

Use devm_dma_request_chan() so probe failure automatically releases the
channels. Group the two requests so a failed RX request also releases
TX immediately before probe falls back to PIO. Keep reusing the acquired
pair when the transfer path calls qup_i2c_req_dma() again.

Close the successful group so it can be released explicitly at the
existing probe unwind and remove sites without releasing later device
resources. Release the group directly in remove, preserving the channel
pointer state there. The direct interconnect error return is covered by
the automatic devres cleanup.

Fixes: d4f35233a634 ("i2c: qup: Vote for interconnect bandwidth to DRAM")
Signed-off-by: Yige Jiang <yigejiang86@xxxxxxxxx>
---
Changes in v2:
- Use devm_dma_request_chan(), as suggested by Konrad.
- Group TX/RX requests to preserve immediate rollback before PIO fallback.
- Reuse an existing pair in the transfer path and release the closed group
at the existing probe unwind and remove sites.

v1: https://lore.kernel.org/r/rc-review-20261105-vc-48-dbf6a486c74f@xxxxxxxxx

Based on linux-next next-20261102 (2637bdec0535).
Validated this package on arm64: the W=1 i2c-qup module build and
a DMA_ENGINE-disabled driver build passed without warnings.
QEMU KUnit/KASAN passed 11 expected observations using real
DMAengine/devres code and a synthetic DMA provider. These checks
do not execute QUP hardware transfers or prove concurrent unbind
safety. No QUP/BAM hardware test was available.
AI assistance was used for analysis and patch preparation.

drivers/i2c/busses/i2c-qup.c | 54 +++++++++++++++++++++-----------------------
1 file changed, 26 insertions(+), 28 deletions(-)

diff --git a/drivers/i2c/busses/i2c-qup.c b/drivers/i2c/busses/i2c-qup.c
index ce30634e1f402ddbc092d9b220532d76455fc047..865c03d9aa891b9fc38538ae184541f74c0a2819 100644
--- a/drivers/i2c/busses/i2c-qup.c
+++ b/drivers/i2c/busses/i2c-qup.c
@@ -637,10 +637,7 @@ static int qup_sg_set_buf(struct scatterlist *sg, void *buf,

static void qup_i2c_rel_dma(struct qup_i2c_dev *qup)
{
- if (qup->btx.dma)
- dma_release_channel(qup->btx.dma);
- if (qup->brx.dma)
- dma_release_channel(qup->brx.dma);
+ devres_release_group(qup->dev, qup);
qup->btx.dma = NULL;
qup->brx.dma = NULL;
}
@@ -649,27 +646,32 @@ static int qup_i2c_req_dma(struct qup_i2c_dev *qup)
{
int err;

- if (!qup->btx.dma) {
- qup->btx.dma = dma_request_chan(qup->dev, "tx");
- if (IS_ERR(qup->btx.dma)) {
- err = PTR_ERR(qup->btx.dma);
- qup->btx.dma = NULL;
- dev_err(qup->dev, "\n tx channel not available");
- return err;
- }
+ if (qup->btx.dma && qup->brx.dma)
+ return 0;
+
+ if (!devres_open_group(qup->dev, qup, GFP_KERNEL))
+ return -ENOMEM;
+
+ qup->btx.dma = devm_dma_request_chan(qup->dev, "tx");
+ if (IS_ERR(qup->btx.dma)) {
+ err = PTR_ERR(qup->btx.dma);
+ dev_err(qup->dev, "\n tx channel not available");
+ goto err_release_group;
}

- if (!qup->brx.dma) {
- qup->brx.dma = dma_request_chan(qup->dev, "rx");
- if (IS_ERR(qup->brx.dma)) {
- dev_err(qup->dev, "\n rx channel not available");
- err = PTR_ERR(qup->brx.dma);
- qup->brx.dma = NULL;
- qup_i2c_rel_dma(qup);
- return err;
- }
+ qup->brx.dma = devm_dma_request_chan(qup->dev, "rx");
+ if (IS_ERR(qup->brx.dma)) {
+ err = PTR_ERR(qup->brx.dma);
+ dev_err(qup->dev, "\n rx channel not available");
+ goto err_release_group;
}
+
+ devres_close_group(qup->dev, qup);
return 0;
+
+err_release_group:
+ qup_i2c_rel_dma(qup);
+ return err;
}

static int qup_i2c_bam_make_desc(struct qup_i2c_dev *qup, struct i2c_msg *msg)
@@ -1952,9 +1954,7 @@ fail:
qup_i2c_disable_clocks(qup);
fail_dma:
if (qup->btx.dma)
- dma_release_channel(qup->btx.dma);
- if (qup->brx.dma)
- dma_release_channel(qup->brx.dma);
+ qup_i2c_rel_dma(qup);
return ret;
}

@@ -1962,10 +1962,8 @@ static void qup_i2c_remove(struct platform_device *pdev)
{
struct qup_i2c_dev *qup = platform_get_drvdata(pdev);

- if (qup->is_dma) {
- dma_release_channel(qup->btx.dma);
- dma_release_channel(qup->brx.dma);
- }
+ if (qup->is_dma)
+ devres_release_group(qup->dev, qup);

disable_irq(qup->irq);
qup_i2c_disable_clocks(qup);

base-commit: 2637bdec0535a2044cd830eba9a79363651d9043