From nobody Fri Apr 3 08:22:41 2026 Received: from mail-wm1-f43.google.com (mail-wm1-f43.google.com [209.85.128.43]) (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 F26CA3A785F for ; Fri, 20 Mar 2026 11:28:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774006135; cv=none; b=mGOswzakMjjkjrMcUGm9+C3PXr5H96N6Eb41k/GHEKj8AB2tq/MyI32d0PdUdmqBDG1QtgzrsPSY72qf4eMTN6R1rKkYfiTprnBnrm/2x86g6sShyWF1Q3gd0/UqIvUeq9EpIiIVC0tpraliQ5pJ+806OA7zQ9M0IU1RXnZzQpA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774006135; c=relaxed/simple; bh=8W8B7IOd8EkL9ZxUe9bYG4G40slTwd/W6qN26L0cJfs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=W8+h1CGZ+EcdPF3jD3wuATiLi43u4lKQO/zi25BuHKBnkwAfHRCUcNHrxBB4e7Kmidkfp8ICygMvBOsuvmc3cU4j/CKy34tBvbpjOOc70aTgI0MYlYtFI8NK6NbDUnXct3l9iR0Ak2vNBdIylM/G4OH7uqK+QOh2v3v6Mr9Ukcc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=tuxon.dev; spf=pass smtp.mailfrom=tuxon.dev; dkim=pass (2048-bit key) header.d=tuxon.dev header.i=@tuxon.dev header.b=XwKMm19E; arc=none smtp.client-ip=209.85.128.43 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=tuxon.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=tuxon.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=tuxon.dev header.i=@tuxon.dev header.b="XwKMm19E" Received: by mail-wm1-f43.google.com with SMTP id 5b1f17b1804b1-4852f8ac7e9so18039685e9.1 for ; Fri, 20 Mar 2026 04:28:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=tuxon.dev; s=google; t=1774006131; x=1774610931; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=/rHziFuJH0O/h8GS9IhPrrtY5hqbHqGAKO0ZR1pWFq0=; b=XwKMm19EF1f6+dUkf+axH13Br3z7haOiYC5QilJ+Tl/vgnIyFCLsJssD/VqS82z+Np spXclsqZe1LU/Mw4KSq7EZPLwQrsvrRXIdQfpMoFb8XWu7IKQ3EnfFWNFDsVgRooeR8R wYkfCxK89g9aXiCcGZNgzBSrkziWuHhv/zQmmfDMR2atwk+7fJF3qHQfw344Av/h+YJY tZ5JDtH/MMMJsnXEncL/iEbr0E8uUSkJ2SKWq6qDY+HR5XAkfkmViLJaDOJHZPAMlASf azhpzg1Q1batZdKRR4QpyR8QRpRNAC10hY41hV4AkSUuJ0xzZgXk+w0I+j7pQ5JzrZdL k8jg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774006131; x=1774610931; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=/rHziFuJH0O/h8GS9IhPrrtY5hqbHqGAKO0ZR1pWFq0=; b=mQ5DgPBKxNXZ4jBSiQtxTVZIeEJTM78yDiGJRnl7btzeEuL7P+OAEbbRODZiC0BQlD vLjSlgmGStrfsRgRSp5biNCPbEtipmj39Q2BEwBuvp/JAARRS+9t7FZ5pbH9BOS/ZdQ0 NPew8avT9zsJF/t59895XOI4sZ3SCAoBQInKdAQei9mD0F1cJ0eMU+H7IHDwsPmDtXIj NF4YTCxXW9EptISP+wPiaT0fnMpwOb3MNNdfZ/TvRrqhm/JJqBJNzZ4vymejtJ0QuRFJ TEnOhzyloy1xV/MmjBAIA5AWFM/Pkai1ZoUZZxYD6KaRgdEdinNLTDDyqv83pwVHuaW3 /wRg== X-Forwarded-Encrypted: i=1; AJvYcCXGLzBFsN4frKHf3BLXARlX/Esr/pPjotnggoQf4yZmku2Dh6kq4IT68F2Ipe38sGSAv7PtYspz8bjXBZc=@vger.kernel.org X-Gm-Message-State: AOJu0Yz/qmxmH5HLVyX0U89im/GTBw/NrpZa9heDzZihUa2fRq6ZEznH 9XR8D4Uxu3kJ1U02nu617M7+vZ4uIsKoInVY08OfTugmHo4iMNnsFU3eh0UMbsfGSvc= X-Gm-Gg: ATEYQzyNPxseO/+ZaQDyYCGOYMbD/STFLiBeaGsJHvO1sGwzTZSzk3JD7hJtwnmKbyq QHr7D3Nx0nU4+5VwxsXhEatiTAo3fzlzDZoqmW63MqEFOrj4FMzwP6yTjAuNs/4Y0iq3pe4Z3PM 0RBXnDuQb9WPBMZX1MNvMi07/GBGn08MwtVmMAYeflxg0HoHLhESSvKgckopgyq+yqIzcOkGLm9 zme4EoG9KOOu190JtRk7yHbvXd2D/KJnnuWjR8oaDsKtNS85klMNA//s3Sh/SFGhNVNru77tTt0 jv3gx6Vz+JwwBHeuGrVXXaVsws57dC3vUsEr5oXmezHjncWFOfdrkxkyr7Vc/Tfs6i9xy3mIK6c /dhDCkaoM93JS9GAzzl7nRt8vt7u6Shs9XH4D+cqSi98MJ08eSqdmt76gakQDLcC4n+t54CR2Ib fRhy/MJ0Icrmlk2V1YRoSVL8F3g21BBCjMbPSldjznAIP+rbdu+/re X-Received: by 2002:a05:600d:8401:b0:485:35a4:939f with SMTP id 5b1f17b1804b1-486fee297bcmr29826535e9.28.1774006131195; Fri, 20 Mar 2026 04:28:51 -0700 (PDT) Received: from claudiu-X670E-Pro-RS.. ([82.78.167.216]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-486fe836784sm49869935e9.13.2026.03.20.04.28.49 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 20 Mar 2026 04:28:50 -0700 (PDT) From: Claudiu X-Google-Original-From: Claudiu To: vkoul@kernel.org, Frank.Li@kernel.org, lgirdwood@gmail.com, broonie@kernel.org, perex@perex.cz, tiwai@suse.com, biju.das.jz@bp.renesas.com, prabhakar.mahadev-lad.rj@bp.renesas.com, p.zabel@pengutronix.de, geert+renesas@glider.be, fabrizio.castro.jz@renesas.com, john.madieu.xa@bp.renesas.com, kuninori.morimoto.gx@renesas.com, tommaso.merciai.xr@bp.renesas.com Cc: claudiu.beznea@tuxon.dev, dmaengine@vger.kernel.org, linux-kernel@vger.kernel.org, linux-sound@vger.kernel.org, linux-renesas-soc@vger.kernel.org, Claudiu Beznea Subject: [PATCH v2 5/7] dmaengine: sh: rz-dmac: Add suspend to RAM support Date: Fri, 20 Mar 2026 13:28:36 +0200 Message-ID: <20260320112838.2200198-6-claudiu.beznea.uj@bp.renesas.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260320112838.2200198-1-claudiu.beznea.uj@bp.renesas.com> References: <20260320112838.2200198-1-claudiu.beznea.uj@bp.renesas.com> 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" From: Claudiu Beznea The Renesas RZ/G3S SoC supports a power saving mode in which power to most of the SoC components is turned off, including the DMA IP. Add suspend to RAM support to save and restore the DMA IP registers. Cyclic DMA channels require special handling. Since they can be paused and resumed during system suspend/resume, the driver restores additional registers for these channels during the system resume phase. If a channel was not explicitly paused during suspend, the driver ensures that it is paused and resumed as part of the system suspend/resume flow. This might be the case of a serial device being used with no_console_suspend. For non-cyclic channels, the dev_pm_ops::prepare callback waits for all the ongoing transfers to complete before allowing suspend-to-RAM to proceed. Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai --- Changes in v2: - fixed typos in patch description - in rz_dmac_suspend_prepare(): return -EAGAIN based on the value returned by vchan_issue_pending() - in rz_dmac_suspend_recover(): clear RZ_DMAC_CHAN_STATUS_SYS_SUSPENDED for non cyclic channels - in rz_dmac_resume(): call rz_dmac_set_dma_req_no() only for cyclic channe= ls drivers/dma/sh/rz-dmac.c | 185 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 177 insertions(+), 8 deletions(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index ca8c0aa8ae59..6f83ccdf94c6 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -69,11 +69,15 @@ struct rz_dmac_desc { * enum rz_dmac_chan_status: RZ DMAC channel status * @RZ_DMAC_CHAN_STATUS_ENABLED: Channel is enabled * @RZ_DMAC_CHAN_STATUS_PAUSED: Channel is paused though DMA engine callba= cks + * @RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL: Channel is paused through driver = internal logic + * @RZ_DMAC_CHAN_STATUS_SYS_SUSPENDED: Channel was prepared for system sus= pend * @RZ_DMAC_CHAN_STATUS_CYCLIC: Channel is cyclic */ enum rz_dmac_chan_status { RZ_DMAC_CHAN_STATUS_ENABLED, RZ_DMAC_CHAN_STATUS_PAUSED, + RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL, + RZ_DMAC_CHAN_STATUS_SYS_SUSPENDED, RZ_DMAC_CHAN_STATUS_CYCLIC, }; =20 @@ -94,6 +98,10 @@ struct rz_dmac_chan { u32 chctrl; int mid_rid; =20 + struct { + u32 nxla; + } pm_state; + struct list_head ld_free; struct list_head ld_queue; struct list_head ld_active; @@ -994,10 +1002,17 @@ static int rz_dmac_device_pause(struct dma_chan *cha= n) return rz_dmac_device_pause_set(channel, RZ_DMAC_CHAN_STATUS_PAUSED); } =20 +static int rz_dmac_device_pause_internal(struct rz_dmac_chan *channel) +{ + lockdep_assert_held(&channel->vc.lock); + + return rz_dmac_device_pause_set(channel, RZ_DMAC_CHAN_STATUS_PAUSED_INTER= NAL); +} + static int rz_dmac_device_resume_set(struct rz_dmac_chan *channel, enum rz_dmac_chan_status status) { - u32 val; + u32 val, chctrl; int ret; =20 lockdep_assert_held(&channel->vc.lock); @@ -1005,14 +1020,33 @@ static int rz_dmac_device_resume_set(struct rz_dmac= _chan *channel, if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_PAUSED))) return 0; =20 - rz_dmac_ch_writel(channel, CHCTRL_CLRSUS, CHCTRL, 1); - ret =3D read_poll_timeout_atomic(rz_dmac_ch_readl, val, - !(val & CHSTAT_SUS), 1, 1024, false, - channel, CHSTAT, 1); - if (ret) - return ret; + if (channel->status & BIT(RZ_DMAC_CHAN_STATUS_SYS_SUSPENDED)) { + /* + * We can be after a sleep state with power loss. If power was + * lost, the CHSTAT_SUS bit is zero. In this case, we need to + * enable the channel directly. Otherwise, just set the CLRSUS + * bit. + */ + val =3D rz_dmac_ch_readl(channel, CHSTAT, 1); + if (val & CHSTAT_SUS) + chctrl =3D CHCTRL_CLRSUS; + else + chctrl =3D CHCTRL_SETEN; + } else { + chctrl =3D CHCTRL_CLRSUS; + } + + rz_dmac_ch_writel(channel, chctrl, CHCTRL, 1); =20 - channel->status &=3D ~BIT(status); + if (chctrl & CHCTRL_CLRSUS) { + ret =3D read_poll_timeout_atomic(rz_dmac_ch_readl, val, + !(val & CHSTAT_SUS), 1, 1024, false, + channel, CHSTAT, 1); + if (ret) + return ret; + } + + channel->status &=3D ~(BIT(status) | BIT(RZ_DMAC_CHAN_STATUS_SYS_SUSPENDE= D)); =20 return 0; } @@ -1026,6 +1060,13 @@ static int rz_dmac_device_resume(struct dma_chan *ch= an) return rz_dmac_device_resume_set(channel, RZ_DMAC_CHAN_STATUS_PAUSED); } =20 +static int rz_dmac_device_resume_internal(struct rz_dmac_chan *channel) +{ + lockdep_assert_held(&channel->vc.lock); + + return rz_dmac_device_resume_set(channel, RZ_DMAC_CHAN_STATUS_PAUSED_INTE= RNAL); +} + /* * -----------------------------------------------------------------------= ------ * IRQ handling @@ -1430,6 +1471,133 @@ static void rz_dmac_remove(struct platform_device *= pdev) pm_runtime_disable(&pdev->dev); } =20 +static int rz_dmac_suspend_prepare(struct device *dev) +{ + struct rz_dmac *dmac =3D dev_get_drvdata(dev); + + for (unsigned int i =3D 0; i < dmac->n_channels; i++) { + struct rz_dmac_chan *channel =3D &dmac->channels[i]; + + guard(spinlock_irqsave)(&channel->vc.lock); + + /* Wait for transfer completion, except in cyclic case. */ + if (vchan_issue_pending(&channel->vc) && + !(channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC))) + return -EAGAIN; + } + + return 0; +} + +static void rz_dmac_suspend_recover(struct rz_dmac *dmac) +{ + for (unsigned int i =3D 0; i < dmac->n_channels; i++) { + struct rz_dmac_chan *channel =3D &dmac->channels[i]; + + guard(spinlock_irqsave)(&channel->vc.lock); + + if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC))) + continue; + + if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL))) { + channel->status &=3D ~BIT(RZ_DMAC_CHAN_STATUS_SYS_SUSPENDED); + continue; + } + + rz_dmac_device_resume_internal(channel); + } +} + +static int rz_dmac_suspend(struct device *dev) +{ + struct rz_dmac *dmac =3D dev_get_drvdata(dev); + int ret; + + for (unsigned int i =3D 0; i < dmac->n_channels; i++) { + struct rz_dmac_chan *channel =3D &dmac->channels[i]; + + guard(spinlock_irqsave)(&channel->vc.lock); + + if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC))) + continue; + + if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_PAUSED))) { + ret =3D rz_dmac_device_pause_internal(channel); + if (ret) { + dev_err(dev, "Failed to suspend channel %s\n", + dma_chan_name(&channel->vc.chan)); + continue; + } + } + + channel->pm_state.nxla =3D rz_dmac_ch_readl(channel, NXLA, 1); + channel->status |=3D BIT(RZ_DMAC_CHAN_STATUS_SYS_SUSPENDED); + } + + pm_runtime_put_sync(dmac->dev); + + ret =3D reset_control_assert(dmac->rstc); + if (ret) { + pm_runtime_resume_and_get(dmac->dev); + rz_dmac_suspend_recover(dmac); + } + + return ret; +} + +static int rz_dmac_resume(struct device *dev) +{ + struct rz_dmac *dmac =3D dev_get_drvdata(dev); + int ret; + + ret =3D reset_control_deassert(dmac->rstc); + if (ret) + return ret; + + ret =3D pm_runtime_resume_and_get(dmac->dev); + if (ret) { + reset_control_assert(dmac->rstc); + return ret; + } + + rz_dmac_writel(dmac, DCTRL_DEFAULT, CHANNEL_0_7_COMMON_BASE + DCTRL); + rz_dmac_writel(dmac, DCTRL_DEFAULT, CHANNEL_8_15_COMMON_BASE + DCTRL); + + for (unsigned int i =3D 0; i < dmac->n_channels; i++) { + struct rz_dmac_chan *channel =3D &dmac->channels[i]; + + guard(spinlock_irqsave)(&channel->vc.lock); + + if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC))) { + rz_dmac_ch_writel(&dmac->channels[i], CHCTRL_DEFAULT, CHCTRL, 1); + continue; + } + + rz_dmac_set_dma_req_no(dmac, channel->index, channel->mid_rid); + + rz_dmac_ch_writel(channel, channel->pm_state.nxla, NXLA, 1); + rz_dmac_ch_writel(channel, channel->chcfg, CHCFG, 1); + rz_dmac_ch_writel(channel, CHCTRL_SWRST, CHCTRL, 1); + rz_dmac_ch_writel(channel, channel->chctrl, CHCTRL, 1); + + if (channel->status & BIT(RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL)) { + ret =3D rz_dmac_device_resume_internal(channel); + if (ret) { + dev_err(dev, "Failed to resume channel %s\n", + dma_chan_name(&channel->vc.chan)); + continue; + } + } + } + + return 0; +} + +static const struct dev_pm_ops rz_dmac_pm_ops =3D { + .prepare =3D rz_dmac_suspend_prepare, + SYSTEM_SLEEP_PM_OPS(rz_dmac_suspend, rz_dmac_resume) +}; + static const struct rz_dmac_info rz_dmac_v2h_info =3D { .icu_register_dma_req =3D rzv2h_icu_register_dma_req, .default_dma_req_no =3D RZV2H_ICU_DMAC_REQ_NO_DEFAULT, @@ -1456,6 +1624,7 @@ static struct platform_driver rz_dmac_driver =3D { .driver =3D { .name =3D "rz-dmac", .of_match_table =3D of_rz_dmac_match, + .pm =3D pm_sleep_ptr(&rz_dmac_pm_ops), }, .probe =3D rz_dmac_probe, .remove =3D rz_dmac_remove, --=20 2.43.0