From nobody Mon May 25 08:11:54 2026 Received: from mail-pj1-f44.google.com (mail-pj1-f44.google.com [209.85.216.44]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3EF40366820 for ; Fri, 15 May 2026 15:57:19 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.44 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778860642; cv=none; b=LPElaqN4YW/y4DKIjCNyBin++FSN9TBVl1w9QydPGVgF1TjkfpvdbEDxbk+b+r/0t0iBhSHes1WcwmtmSVTCKTVpQ+PN6O//hBWpc59pmpQEfGKWC5Bwtl+YXgc7azzJAmLLLwUyMafVAE7G8YtE/lIZbE0t6+EyEXXAMG2rTO8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778860642; c=relaxed/simple; bh=kLlsYcJSnsvCZnQXdP6Bwc4tgSSUeDZCYCCOR62WaJQ=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=r7TcykbK07sH5gqr4O0/GmI7RNQ5BoW9UKMNIsr6fRS3mhbHpbi1A7czKiil3SlsdEXha5eWjTEFNGQVgqRhaAZeOYT5sEFr5npPnvGRvS04iMa74FOpSPfFRizScmxaevhLCm7GWJcs47YwhCgogTjcqlyc/IRDbutfJGEzEQo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=raHEK7G7; arc=none smtp.client-ip=209.85.216.44 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="raHEK7G7" Received: by mail-pj1-f44.google.com with SMTP id 98e67ed59e1d1-3660daea6a5so5200751a91.1 for ; Fri, 15 May 2026 08:57:19 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1778860639; x=1779465439; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=yX0chCqXf0mQpC8vWLQku9y3n8pCGrDqgza1e2QxHwo=; b=raHEK7G7AjSRL3bM14e4Jvge8Y36brFnqLT/moVsQQ/RHvaJLANaVSLCPAV11WTml3 zEhz6v2DT0F8j+/F/LKW7PRGgj7v9visKt/Z+q+h/+3Q+Klhl3im4owMpucxzRVTVbVX 9UatyNNr92Q8x1apecYOynEaN19CFAHX3aUP2SL1yI9QNGafD/zGgBM45u1Yn0iE2G+y XRX2kPV7lxZZ6Aqt/1xig9BHPc7dbT4PUP5SK6SItFkHsJR2NO+Gqm0HGlqdEcUPnf8u gbYdWSOMCIOnY9kPsxCC2eJL3wX7bcE/ux2tuPeAEcm309vXg7DUQLijH2RVSDZlv+pP mIPg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1778860639; x=1779465439; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=yX0chCqXf0mQpC8vWLQku9y3n8pCGrDqgza1e2QxHwo=; b=Q6S+R8ThnSs6ZiVbXFlqzysPk6nB9jzUwR/Nq3YaC4EwpHqGbJJIXn0nLtpd5k0GJW bS3OBX+p0HgSJiD/joc6fFst6ptH7RxujHLn6/9TMB9qIfw73UZlVaeK5o2l9e0wkfcD PA0092b51mBwndXhLCsecIHU16Kt5m15q6xhqoDqepDlT/MPaesAvx1iVexyjDOcOLtH tnJiFCVx955EaB7MTtaQioFIPY5MDkx10UEsqjNOGVlHBJ27Z0T5looymh3se3L+P8/m uiO5V4WhWwdPGmoQ5D0ap383PL+mL44afFnoogXdFpf55FbWjPn9LS07YhF+nIec0WZt r/HA== X-Forwarded-Encrypted: i=1; AFNElJ9s/NPv7TOtaDl7jgLpAgAESHNuHlzI4LEDntQPYgqVuRCrJjmHweK3PsFBw/gCtKpZdriaanywRhys7n4=@vger.kernel.org X-Gm-Message-State: AOJu0Yzb0hK4XiMCjg68ugpHgXkc8/ROPfhqhYWHhiEhHkPbAEkzUB3u ajvN2gr/LSz8+dAACSt1MGslzS6GnIXunsGanMQVskWEWSqV3eNWieVC X-Gm-Gg: Acq92OFrg82YmMFxKSS4Iyva7X3vEzpU2tSOrMAX7brbPzCg89m3AGKadcTu1VxsURs rgCCyqy6GNr6sgunJ7F8ox9Dkpa4l489KsAgTzIBK15k3eFKvbdmk4czq+Yb5sq1sej9kfqsEc+ BELcoiNUKOC8FLmSkhtHwhMgeavSbeArZ/xf+mC4g+v+drVqRcIT1uVWjt94kIIRvi75q3DWEtn Ed/KPSh825ulPC/82IddnaTvW78+iREcn3pod021FNjorTUEp6mFC+HJBkTqsasbmMrErGpWko/ sSTcCdTsTRiqPGjfVImlDJcqS0Vec1oLIMUFuLDdJiiFztwoZCJgUvj5ETSBnYmHT4WRiWPYls0 Ia0tU7abrzBL9RsGs2QBChOySC6K8eBJ5I9MlVb3jhiqS5cbmGRt9+PAJyPNk0XBvzmjE+CpDNW NrfFWASTd3EPQg5IPqy28W5LPOkBgVc1KdEFZGVY1BNAozcPtZtlRpzA== X-Received: by 2002:a17:90b:5348:b0:368:83e6:ca95 with SMTP id 98e67ed59e1d1-36951733ddcmr4642211a91.0.1778860639055; Fri, 15 May 2026 08:57:19 -0700 (PDT) Received: from ZC-202510311500 ([117.22.251.226]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3696e550dfcsm75221a91.1.2026.05.15.08.57.12 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 15 May 2026 08:57:18 -0700 (PDT) From: Haoyu Lu To: Alexandre Belloni Cc: Frank Li , linux-i3c@lists.infradead.org, linux-kernel@vger.kernel.org, Haoyu Lu Subject: [RFC PATCH] i3c: dw: add DMA support for data transfers Date: Fri, 15 May 2026 23:56:35 +0800 Message-ID: <20260515155638.1840-1-hechushiguitu666@gmail.com> X-Mailer: git-send-email 2.53.0.windows.1 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add DMA support for the DesignWare I3C master controller to accelerate data transfers. The driver now requests tx and rx DMA channels during probe and uses them for data transfers of 4 bytes or more. Transfers smaller than 4 bytes, or transfers where DMA setup fails, fall back to PIO mode. DMA tail bytes (non-4-byte-aligned remainder) are handled with PIO as well. DMA transfers may sleep, so the xferqueue spinlock is temporarily dropped for DMA operations. This is safe because xferqueue.cur is only changed when a transfer completes, and the calling context serializes simultaneous transfers. The DMA enable bit (DEV_CTRL_DMA_EN, BIT(28)) is set when DMA is used and cleared otherwise, to ensure the hardware uses the correct data transfer path. Signed-off-by: Haoyu Lu --- This is an RFC patch to gather feedback on the overall approach before a formal submission. A few specific questions for reviewers: 1. DMA threshold: The current implementation uses DMA for transfers >=3D 4 bytes, with tail bytes handled by PIO. Is 4 bytes a reasonable threshold, or should we consider a higher value (e.g. 16 or 32) to avoid DMA setup overhead for very small transfers? 2. Spinlock safety: dw_i3c_master_start_xfer_locked() and dw_i3c_master_end_xfer_locked() temporarily drop xferqueue.lock around DMA operations. The analysis is that xferqueue.cur is only modified on transfer completion, and the caller serializes simultaneous transfers. Is this safe, or should we restructure the locking (e.g., convert to mutex)? 3. DMA channel handling: dw_i3c_dma_request() is called during probe and silently degrades to PIO if DMA channels are unavailable. Is this the right behavior, or should probe fail on DMA errors other than -ENODEV/-EPROBE_DEFER? 4. Error handling for DMA timeouts: when a DMA timeout occurs, dmaengine_terminate_all() is called but the transfer proceeds as if it were successful, potentially reading stale data. Should we instead signal an error to the I3C core? 5. IBI path: dw_i3c_master_read_ibi_fifo() still uses PIO. Should IBI reads also use DMA, or is the IBI payload typically small enough that PIO is preferred? Any other feedback on the design or implementation is welcome. drivers/i3c/master/dw-i3c-master.c | 260 ++++++++++++++++++++++++++++- drivers/i3c/master/dw-i3c-master.h | 17 ++ 2 files changed, 270 insertions(+), 7 deletions(-) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c= -master.c index d6bdb32397fb..7b6c8681ff0c 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,10 @@ #include #include =20 +#include +#include +#include + #include "../internals.h" #include "dw-i3c-master.h" =20 @@ -32,6 +37,7 @@ #define DEV_CTRL_RESUME BIT(30) #define DEV_CTRL_HOT_JOIN_NACK BIT(8) #define DEV_CTRL_I2C_SLAVE_PRESENT BIT(7) +#define DEV_CTRL_DMA_EN BIT(28) =20 #define DEVICE_ADDR 0x4 #define DEV_ADDR_DYNAMIC_ADDR_VALID BIT(31) @@ -359,16 +365,123 @@ static int dw_i3c_master_get_free_pos(struct dw_i3c_= master *master) return ffs(master->free_pos) - 1; } =20 +static void dw_i3c_dma_callback(void *arg) +{ + struct dw_i3c_dma *dma =3D arg; + + dma_unmap_single(dma->chan_using->device->dev, dma->dma_buf, + dma->dma_len, dma->dma_data_dir); + complete(&dma->complete); +} + +static int dw_i3c_master_dma_xfer(struct dw_i3c_dma *dma, const u8 *bytes) +{ + struct device *chan_dev =3D dma->chan_using->device->dev; + struct dma_async_tx_descriptor *txdesc; + + dma->dma_buf =3D dma_map_single(chan_dev, (void *)bytes, dma->dma_len, + dma->dma_data_dir); + if (dma_mapping_error(chan_dev, dma->dma_buf)) + return -EINVAL; + + txdesc =3D dmaengine_prep_slave_single(dma->chan_using, dma->dma_buf, + dma->dma_len, dma->dma_transfer_dir, + DMA_PREP_INTERRUPT); + if (!txdesc) { + dma_unmap_single(chan_dev, dma->dma_buf, dma->dma_len, + dma->dma_data_dir); + return -ENOMEM; + } + + reinit_completion(&dma->complete); + txdesc->callback =3D dw_i3c_dma_callback; + txdesc->callback_param =3D dma; + if (dma_submit_error(dmaengine_submit(txdesc))) { + dma_unmap_single(chan_dev, dma->dma_buf, dma->dma_len, + dma->dma_data_dir); + return -EIO; + } + + dma_async_issue_pending(dma->chan_using); + return 0; +} + static void dw_i3c_master_wr_tx_fifo(struct dw_i3c_master *master, const u8 *bytes, int nbytes) { - i3c_writel_fifo(master->regs + RX_TX_DATA_PORT, bytes, nbytes); + struct dw_i3c_dma *dma =3D master->dma; + unsigned long time_left; + int ret; + + if (!master->use_dma || nbytes < 4) { + i3c_writel_fifo(master->regs + RX_TX_DATA_PORT, bytes, nbytes); + return; + } + + dma->chan_using =3D dma->chan_tx; + dma->dma_transfer_dir =3D DMA_MEM_TO_DEV; + dma->dma_data_dir =3D DMA_TO_DEVICE; + dma->dma_len =3D ALIGN_DOWN(nbytes, 4); + + ret =3D dw_i3c_master_dma_xfer(dma, bytes); + if (ret) { + dev_err(&master->base.dev, "DMA TX setup failed (%d)\n", ret); + i3c_writel_fifo(master->regs + RX_TX_DATA_PORT, bytes, nbytes); + return; + } + + time_left =3D wait_for_completion_timeout(&dma->complete, + XFER_TIMEOUT); + if (!time_left) { + dmaengine_terminate_all(dma->chan_using); + dev_err(&master->base.dev, "DMA TX timeout\n"); + } + + if (nbytes & 3) { + u32 tmp =3D 0; + + memcpy(&tmp, bytes + (nbytes & ~3), nbytes & 3); + writesl(master->regs + RX_TX_DATA_PORT, &tmp, 1); + } } =20 static void dw_i3c_master_read_rx_fifo(struct dw_i3c_master *master, u8 *bytes, int nbytes) { - i3c_readl_fifo(master->regs + RX_TX_DATA_PORT, bytes, nbytes); + struct dw_i3c_dma *dma =3D master->dma; + unsigned long time_left; + int ret; + + if (!master->use_dma || nbytes < 4) { + i3c_readl_fifo(master->regs + RX_TX_DATA_PORT, bytes, nbytes); + return; + } + + dma->chan_using =3D dma->chan_rx; + dma->dma_transfer_dir =3D DMA_DEV_TO_MEM; + dma->dma_data_dir =3D DMA_FROM_DEVICE; + dma->dma_len =3D ALIGN_DOWN(nbytes, 4); + + ret =3D dw_i3c_master_dma_xfer(dma, bytes); + if (ret) { + dev_err(&master->base.dev, "DMA RX setup failed (%d)\n", ret); + i3c_readl_fifo(master->regs + RX_TX_DATA_PORT, bytes, nbytes); + return; + } + + time_left =3D wait_for_completion_timeout(&dma->complete, + XFER_TIMEOUT); + if (!time_left) { + dmaengine_terminate_all(dma->chan_using); + dev_err(&master->base.dev, "DMA RX timeout\n"); + } + + if (nbytes & 3) { + u32 tmp; + + readsl(master->regs + RX_TX_DATA_PORT, &tmp, 1); + memcpy(bytes + (nbytes & ~3), &tmp, nbytes & 3); + } } =20 static void dw_i3c_master_read_ibi_fifo(struct dw_i3c_master *master, @@ -403,14 +516,29 @@ static void dw_i3c_master_start_xfer_locked(struct dw= _i3c_master *master) struct dw_i3c_xfer *xfer =3D master->xferqueue.cur; unsigned int i; u32 thld_ctrl; + u32 dev_ctrl; =20 if (!xfer) return; =20 - for (i =3D 0; i < xfer->ncmds; i++) { - struct dw_i3c_cmd *cmd =3D &xfer->cmds[i]; - - dw_i3c_master_wr_tx_fifo(master, cmd->tx_buf, cmd->tx_len); + /* + * DMA transfers may sleep, so we must drop the spinlock while + * writing the TX FIFO. The xferqueue lock is held by our caller; + * xferqueue.cur is safe because: + * - New xfers are only added to the list (not cur) when cur is set. + * - Timeout-based dequeue is blocked until the caller calls + * wait_for_completion_timeout(), which happens after we return. + */ + if (master->use_dma) { + spin_unlock(&master->xferqueue.lock); + for (i =3D 0; i < xfer->ncmds; i++) + dw_i3c_master_wr_tx_fifo(master, xfer->cmds[i].tx_buf, + xfer->cmds[i].tx_len); + spin_lock(&master->xferqueue.lock); + } else { + for (i =3D 0; i < xfer->ncmds; i++) + dw_i3c_master_wr_tx_fifo(master, xfer->cmds[i].tx_buf, + xfer->cmds[i].tx_len); } =20 thld_ctrl =3D readl(master->regs + QUEUE_THLD_CTRL); @@ -418,6 +546,13 @@ static void dw_i3c_master_start_xfer_locked(struct dw_= i3c_master *master) thld_ctrl |=3D QUEUE_THLD_CTRL_RESP_BUF(xfer->ncmds); writel(thld_ctrl, master->regs + QUEUE_THLD_CTRL); =20 + dev_ctrl =3D readl(master->regs + DEVICE_CTRL); + if (master->use_dma) + dev_ctrl |=3D DEV_CTRL_DMA_EN; + else + dev_ctrl &=3D ~DEV_CTRL_DMA_EN; + writel(dev_ctrl, master->regs + DEVICE_CTRL); + for (i =3D 0; i < xfer->ncmds; i++) { struct dw_i3c_cmd *cmd =3D &xfer->cmds[i]; =20 @@ -486,9 +621,17 @@ static void dw_i3c_master_end_xfer_locked(struct dw_i3= c_master *master, u32 isr) cmd =3D &xfer->cmds[RESPONSE_PORT_TID(resp)]; cmd->rx_len =3D RESPONSE_PORT_DATA_LEN(resp); cmd->error =3D RESPONSE_PORT_ERR_STATUS(resp); - if (cmd->rx_len && !cmd->error) + if (cmd->rx_len && !cmd->error) { + /* + * DMA RX may sleep; drop the lock temporarily. + * Safe because xferqueue.cur is not changed + * by other paths while a transfer is in flight. + */ + spin_unlock(&master->xferqueue.lock); dw_i3c_master_read_rx_fifo(master, cmd->rx_buf, cmd->rx_len); + spin_lock(&master->xferqueue.lock); + } } =20 for (i =3D 0; i < nresp; i++) { @@ -1582,6 +1725,107 @@ static void dw_i3c_hj_work(struct work_struct *work) i3c_master_do_daa(&master->base); } =20 +static void dw_i3c_dma_free(struct dw_i3c_master *master) +{ + struct dw_i3c_dma *dma =3D master->dma; + + if (!dma) + return; + + if (dma->chan_tx) { + dma_release_channel(dma->chan_tx); + dma->chan_tx =3D NULL; + } + if (dma->chan_rx) { + dma_release_channel(dma->chan_rx); + dma->chan_rx =3D NULL; + } + dma->chan_using =3D NULL; + master->dma =3D NULL; + master->use_dma =3D false; +} + +static void dw_i3c_dma_request(struct platform_device *pdev) +{ + struct dw_i3c_dma *dma; + struct dma_slave_config dma_sconfig; + struct dw_i3c_master *master; + struct resource *res; + dma_addr_t phy_addr; + int ret; + + master =3D platform_get_drvdata(pdev); + + res =3D platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return; + phy_addr =3D res->start; + + dma =3D devm_kzalloc(&pdev->dev, sizeof(*dma), GFP_KERNEL); + if (!dma) + return; + + dma->chan_tx =3D dma_request_chan(&pdev->dev, "tx"); + if (IS_ERR(dma->chan_tx)) { + ret =3D PTR_ERR(dma->chan_tx); + if (ret !=3D -ENODEV && ret !=3D -EPROBE_DEFER) + dev_err(&pdev->dev, + "can't request DMA tx channel (%d)\n", ret); + goto fail; + } + + memset(&dma_sconfig, 0, sizeof(dma_sconfig)); + dma_sconfig.dst_addr =3D phy_addr + RX_TX_DATA_PORT; + dma_sconfig.dst_addr_width =3D DMA_SLAVE_BUSWIDTH_4_BYTES; + dma_sconfig.dst_maxburst =3D 1; + dma_sconfig.direction =3D DMA_MEM_TO_DEV; + ret =3D dmaengine_slave_config(dma->chan_tx, &dma_sconfig); + if (ret < 0) { + dev_err(&pdev->dev, + "can't configure DMA tx channel (%d)\n", ret); + goto fail_tx; + } + + dma->chan_rx =3D dma_request_chan(&pdev->dev, "rx"); + if (IS_ERR(dma->chan_rx)) { + ret =3D PTR_ERR(dma->chan_rx); + if (ret !=3D -ENODEV && ret !=3D -EPROBE_DEFER) + dev_err(&pdev->dev, + "can't request DMA rx channel (%d)\n", ret); + goto fail_tx; + } + + memset(&dma_sconfig, 0, sizeof(dma_sconfig)); + dma_sconfig.src_addr =3D phy_addr + RX_TX_DATA_PORT; + dma_sconfig.src_addr_width =3D DMA_SLAVE_BUSWIDTH_4_BYTES; + dma_sconfig.src_maxburst =3D 1; + dma_sconfig.direction =3D DMA_DEV_TO_MEM; + ret =3D dmaengine_slave_config(dma->chan_rx, &dma_sconfig); + if (ret < 0) { + dev_err(&pdev->dev, + "can't configure DMA rx channel (%d)\n", ret); + goto fail_rx; + } + + init_completion(&dma->complete); + master->dma =3D dma; + master->use_dma =3D true; + + dev_info(&pdev->dev, "using %s (tx) and %s (rx) for DMA transfers\n", + dma_chan_name(dma->chan_tx), dma_chan_name(dma->chan_rx)); + + return; + +fail_rx: + dma_release_channel(dma->chan_rx); + dma->chan_rx =3D NULL; +fail_tx: + dma_release_channel(dma->chan_tx); + dma->chan_tx =3D NULL; +fail: + devm_kfree(&pdev->dev, dma); +} + int dw_i3c_common_probe(struct dw_i3c_master *master, struct platform_device *pdev) { @@ -1627,6 +1871,7 @@ int dw_i3c_common_probe(struct dw_i3c_master *master, goto err_assert_rst; =20 platform_set_drvdata(pdev, master); + dw_i3c_dma_request(pdev); =20 pm_runtime_set_autosuspend_delay(&pdev->dev, RPM_AUTOSUSPEND_TIMEOUT); pm_runtime_use_autosuspend(&pdev->dev); @@ -1683,6 +1928,7 @@ EXPORT_SYMBOL_GPL(dw_i3c_common_probe); void dw_i3c_common_remove(struct dw_i3c_master *master) { cancel_work_sync(&master->hj_work); + dw_i3c_dma_free(master); i3c_master_unregister(&master->base); =20 /* Balance pm_runtime_get_noresume() from probe() */ diff --git a/drivers/i3c/master/dw-i3c-master.h b/drivers/i3c/master/dw-i3c= -master.h index c5cb695c16ab..633b7abdfaae 100644 --- a/drivers/i3c/master/dw-i3c-master.h +++ b/drivers/i3c/master/dw-i3c-master.h @@ -6,12 +6,27 @@ */ =20 #include +#include +#include +#include +#include #include #include #include =20 #define DW_I3C_MAX_DEVS 32 =20 +struct dw_i3c_dma { + struct dma_chan *chan_tx; + struct dma_chan *chan_rx; + struct dma_chan *chan_using; + dma_addr_t dma_buf; + unsigned int dma_len; + enum dma_transfer_direction dma_transfer_dir; + enum dma_data_direction dma_data_dir; + struct completion complete; +}; + struct dw_i3c_master_caps { u8 cmdfifodepth; u8 datafifodepth; @@ -70,6 +85,8 @@ struct dw_i3c_master { const struct dw_i3c_platform_ops *platform_ops; =20 struct work_struct hj_work; + bool use_dma; + struct dw_i3c_dma *dma; }; =20 struct dw_i3c_platform_ops { --=20 2.17.1