* [PATCH v6 0/4] Enable multi-owner I2C support for QCOM GENI controllers
From: Mukesh Kumar Savaliya @ 2026-03-31 11:47 UTC (permalink / raw)
To: viken.dadhaniya, andi.shyti, robh, krzk+dt, conor+dt, vkoul,
Frank.Li, andersson, konradybcio, dmitry.baryshkov, linmq006,
quic_jseerapu, agross, linux-arm-msm, linux-i2c, devicetree,
linux-kernel, dmaengine
Cc: krzysztof.kozlowski, bartosz.golaszewski, bjorn.andersson,
konrad.dybcio, Mukesh Kumar Savaliya
The QUP-based GENI I2C controller driver currently assumes exclusive
ownership of the controller by a single system processor. This prevents
safe use of a single I2C controller by multiple system processors
(e.g. APPS and a DSP) running the same or different operating systems.
One practical example is an EEPROM connected to an I2C controller that
needs to be accessed independently by firmware running on a DSP and by
Linux running on the application processor, without causing bus-level
interference during transfers.
This series adds support for operating a QUP GENI I2C Serial Engine in a
multi-owner configuration. Each system processor uses its own dedicated
GPI instance (GPII) as the data path between the Serial Engine and the
GSI DMA engine. As a result, controller sharing is supported only when
the I2C controller operates in GPI mode; FIFO/CPU DMA modes are not
supported for this configuration.
To serialize access at the hardware level, the GPI DMA engine is used to
emit lock and unlock Transfer Ring Elements (TREs) around I2C transfers.
The lock is acquired before the first transfer and released after the
last transfer, ensuring uninterrupted access to the controller while a
processor owns it.
In addition, when a controller is shared, the GENI common layer avoids
placing the associated GPIOs into the pinctrl "sleep" state during
runtime suspend. This prevents disruption of transfers that may still
be in progress on another system processor using the same controller
pins.
The multi-owner behavior is enabled via a DeviceTree property,
`qcom,qup-multi-owner`, on the I2C controller node. This property must be
used only when the hardware configuration requires controller sharing
and when GPI mode is enabled.
Patch overview:
1. Document the `qcom,qup-multi-owner` DeviceTree property for GENI I2C.
2. Extend the QCOM GPI DMA driver to support lock and unlock TREs with a
simplified single-field API.
3. Update the GENI common layer to keep pinctrl active for shared
controllers during runtime suspend.
4. Enable multi-owner operation in the GENI I2C driver using the new
DeviceTree property and GPI lock/unlock support.
Signed-off-by: Mukesh Kumar Savaliya <mukesh.savaliya@oss.qualcomm.com>
---
Link to V5 : https://lore.kernel.org/lkml/20241129144357.2008465-2-mukesh.savaliya@oss.qualcomm.com/
Changes in V6:
- Addressed review feedback from Krzysztof Kozlowski and other reviewers, primarily
around clarifying the feature semantics and improving the DeviceTree flag naming.
- Renamed the DeviceTree property from qcom,shared-se to qcom,qup-multi-owner to
better describe the multi-owner controller use case.
- Updated the cover letter to clearly describe the multi-owner I2C design, the
GPI-only limitation, and the role of the new qcom,qup-multi-owner flag.
- Updated the DeviceTree binding documentation to reflect the new qcom,qup-multi-owner
property and refined its description for clarity and correctness.
- [Patch 2/4] Simplify the GPI I2C interface by replacing multiple shared SE related
state flags with a single internal lock/unlock control managed entirely in the GPI
driver - Suggested by Vinod Koul.
- [Patch 3/4] Updated the GENI common layer to avoid selecting the pinctrl “sleep”
state for multi-owner controllers, preventing disruption of transfers initiated by
another system processor during runtime suspend.
- [Patch 4/4] Updated the GENI I2C driver to:
- Detect the qcom,qup-multi-owner DeviceTree property.
- Mark the underlying serial engine as shared.
- Request GPI lock and unlock TRE sequencing around I2C transfers using the
simplified single field API.
- Clarified commit messages across all patches to avoid ambiguous terminology
(such as “subsystem”), expand abbreviations, and better explain functional
requirements rather than optimizations.
- Updated copyright headers across all files wherever applicable.
- Renamed variable shared_geni_se to multi_owner to match the DT property naming.
- Changed dev_err(print_log) during probe() to dev_err_probe().
---
Mukesh Kumar Savaliya (4):
dt-bindings: i2c: qcom,i2c-geni: Document multi-owner controller
support
dmaengine: qcom: gpi: Add lock/unlock TREs for multi-owner I2C
transfers
soc: qcom: geni-se: Keep pinctrl active for multi-owner controllers
i2c: qcom-geni: Support multi-owner controllers in GPI mode
.../bindings/i2c/qcom,i2c-geni-qcom.yaml | 7 +++
drivers/dma/qcom/gpi.c | 44 ++++++++++++++++++-
drivers/i2c/busses/i2c-qcom-geni.c | 27 +++++++++++-
drivers/soc/qcom/qcom-geni-se.c | 15 +++++--
include/linux/dma/qcom-gpi-dma.h | 18 ++++++++
include/linux/soc/qcom/geni-se.h | 2 +
6 files changed, 107 insertions(+), 6 deletions(-)
--
2.25.1
^ permalink raw reply
* Re: [PATCH v5 4/4] i2c: i2c-qcom-geni: Enable i2c controller sharing between two subsystems
From: Mukesh Kumar Savaliya @ 2026-03-31 11:34 UTC (permalink / raw)
To: Konrad Dybcio, konrad.dybcio, andersson, andi.shyti,
linux-arm-msm, dmaengine, linux-kernel, linux-i2c, conor+dt,
agross, devicetree, vkoul, linux, dan.carpenter, Frank.Li,
konradybcio, bryan.odonoghue, krzk+dt, robh
Cc: quic_vdadhani
In-Reply-To: <5e83f946-e157-4ec0-8ebf-14dbbdb93e34@quicinc.com>
Hi Konrad, Thanks for the review and sorry for long delay replying on
this change. I was completely away from this work.
Let me upload V6 to share latest changes and cover letter details to
help review ahead.
On 12/16/2024 6:17 PM, Mukesh Kumar Savaliya wrote:
>
>
> On 12/16/2024 5:40 PM, Konrad Dybcio wrote:
>> On 15.12.2024 9:59 AM, Mukesh Kumar Savaliya wrote:
>>> Hi Konrad,
>>>
>>> On 12/13/2024 6:35 PM, Konrad Dybcio wrote:
>>>> On 29.11.2024 3:43 PM, Mukesh Kumar Savaliya wrote:
>>>>> Add support to share I2C controller in multiprocessor system in a
>>>>> mutually
>>>>> exclusive way. Use "qcom,shared-se" flag in a particular i2c
>>>>> instance node
>>>>> if the usecase requires i2c controller to be shared.
>>>>>
>>>>> Sharing of I2C SE(Serial engine) is possible only for GSI mode as
>>>>> client
>>>>> from each processor can queue transfers over its own GPII Channel. For
>>>>> non GSI mode, we should force disable this feature even if set by user
>>>>> from DT by mistake.
>>>>>
>>>>> I2C driver just need to mark first_msg and last_msg flag to help
>>>>> indicate
>>>>> GPI driver to take lock and unlock TRE there by protecting from
>>>>> concurrent
>>>>> access from other EE or Subsystem.
>>>>>
>>>>> gpi_create_i2c_tre() function at gpi.c will take care of adding
>>>>> Lock and
>>>>> Unlock TRE for the respective transfer operations.
>>>>>
>>>>> Since the GPIOs are also shared between two SS, do not unconfigure
>>>>> them
>>>>> during runtime suspend. This will allow other SS to continue to
>>>>> transfer
>>>>> the data without any disturbance over the IO lines.
>>>>>
>>>>> For example, Assume an I2C EEPROM device connected with an I2C
>>>>> controller.
>>>>> Each client from ADSP and APPS processor can perform i2c transactions
>>>>> without any disturbance from each other.
>>>>>
>>>>> Signed-off-by: Mukesh Kumar Savaliya <quic_msavaliy@quicinc.com>
>>>>> ---
>>>>> drivers/i2c/busses/i2c-qcom-geni.c | 22 +++++++++++++++++++---
>>>>> 1 file changed, 19 insertions(+), 3 deletions(-)
>>>>>
>>>>> diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/
>>>>> busses/i2c-qcom-geni.c
>>>>> index 7a22e1f46e60..ccf9933e2dad 100644
>>>>> --- a/drivers/i2c/busses/i2c-qcom-geni.c
>>>>> +++ b/drivers/i2c/busses/i2c-qcom-geni.c
>>>>> @@ -1,5 +1,6 @@
>>>>> // SPDX-License-Identifier: GPL-2.0
>>>>> // Copyright (c) 2017-2018, The Linux Foundation. All rights
>>>>> reserved.
>>>>> +// Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights
>>>>> reserved.
>>>>> #include <linux/acpi.h>
>>>>> #include <linux/clk.h>
>>>>> @@ -617,6 +618,7 @@ static int geni_i2c_gpi_xfer(struct
>>>>> geni_i2c_dev *gi2c, struct i2c_msg msgs[], i
>>>>> peripheral.clk_div = itr->clk_div;
>>>>> peripheral.set_config = 1;
>>>>> peripheral.multi_msg = false;
>>>>> + peripheral.shared_se = gi2c->se.shared_geni_se;
>>>>> for (i = 0; i < num; i++) {
>>>>> gi2c->cur = &msgs[i];
>>>>> @@ -627,6 +629,8 @@ static int geni_i2c_gpi_xfer(struct
>>>>> geni_i2c_dev *gi2c, struct i2c_msg msgs[], i
>>>>> if (i < num - 1)
>>>>> peripheral.stretch = 1;
>>>>> + peripheral.first_msg = (i == 0);
>>>>> + peripheral.last_msg = (i == num - 1);
>>>>> peripheral.addr = msgs[i].addr;
>>>>> ret = geni_i2c_gpi(gi2c, &msgs[i], &config,
>>>>> @@ -815,6 +819,11 @@ static int geni_i2c_probe(struct
>>>>> platform_device *pdev)
>>>>> gi2c->clk_freq_out = KHZ(100);
>>>>> }
>>>>> + if (of_property_read_bool(pdev->dev.of_node, "qcom,shared-
>>>>> se")) {
>>>>> + gi2c->se.shared_geni_se = true;
>>>>> + dev_dbg(&pdev->dev, "I2C is shared between subsystems\n");
>>>>> + }
>>>>> +
>>>>> if (has_acpi_companion(dev))
>>>>> ACPI_COMPANION_SET(&gi2c->adap.dev, ACPI_COMPANION(dev));
>>>>> @@ -887,8 +896,10 @@ static int geni_i2c_probe(struct
>>>>> platform_device *pdev)
>>>>> else
>>>>> fifo_disable = readl_relaxed(gi2c->se.base +
>>>>> GENI_IF_DISABLE_RO) & FIFO_IF_DISABLE;
>>>>> - if (fifo_disable) {
>>>>> - /* FIFO is disabled, so we can only use GPI DMA */
>>>>> + if (fifo_disable || gi2c->se.shared_geni_se) {
>>>>> + /* FIFO is disabled, so we can only use GPI DMA.
>>>>> + * SE can be shared in GSI mode between subsystems, each
>>>>> SS owns a GPII.
>>>>> + **/
>>>>
>>>> I don't think this change makes things clearer, I would drop it
>>> Shall i revert back to previous change ? What's your suggestion ?
>>
>> Yes, drop changing this comment.
> Sure, Thanks for confirming !
Done this change with new flag name addressing the previous comments
from dt-binding file.
>>
>> Konrad
>
^ permalink raw reply
* Re: [PATCH v5 2/4] dmaengine: gpi: Add Lock and Unlock TRE support to access I2C exclusively
From: Mukesh Kumar Savaliya @ 2026-03-31 11:33 UTC (permalink / raw)
To: Vinod Koul, Md Sadre Alam
Cc: konrad.dybcio, andersson, andi.shyti, linux-arm-msm, dmaengine,
linux-kernel, linux-i2c, conor+dt, agross, devicetree, linux,
dan.carpenter, Frank.Li, konradybcio, bryan.odonoghue, krzk+dt,
robh, quic_vdadhani
In-Reply-To: <1566eafb-7286-4f27-922d-0bbaaab8120b@quicinc.com>
Hi Vinod, sorry for responding here much lately. I was completely away
from this work for long, restarting now. Will close this actively
following up here and I will upload V6 to share latest changes and cover
letter details to help review ahead with context.
Thanks for your time and help review this ahead.
On 1/14/2025 2:48 PM, Mukesh Kumar Savaliya wrote:
> Hi Vinod,
>
> On 12/26/2024 5:52 PM, Mukesh Kumar Savaliya wrote:
>>
>>
>> On 12/24/2024 3:28 PM, Vinod Koul wrote:
>>> On 18-12-24, 18:04, Mukesh Kumar Savaliya wrote:
>>>> Hi Vinod, Thanks ! I just saw your comments now as somehow it was
>>>> going in
>>>> some other folder and didn't realize.
>>>>
>>>> On 12/4/2024 5:51 PM, Vinod Koul wrote:
>>>>> On 02-12-24, 16:13, Mukesh Kumar Savaliya wrote:
>>>>>> Thanks for the review comments Vinod !
>>>>>>
>>>>>> On 12/2/2024 12:17 PM, Vinod Koul wrote:
>>>>>>> On 29-11-24, 20:13, Mukesh Kumar Savaliya wrote:
>>>>>>>> GSI DMA provides specific TREs(Transfer ring element) namely
>>>>>>>> Lock and
>>>>>>>> Unlock TRE. It provides mutually exclusive access to I2C
>>>>>>>> controller from
>>>>>>>> any of the processor(Apps,ADSP). Lock prevents other subsystems
>>>>>>>> from
>>>>>>>> concurrently performing DMA transfers and avoids disturbance to
>>>>>>>> data path.
>>>>>>>> Basically for shared I2C usecase, lock the SE(Serial Engine) for
>>>>>>>> one of
>>>>>>>> the processor, complete the transfer, unlock the SE.
>>>>>>>>
>>>>>>>> Apply Lock TRE for the first transfer of shared SE and Apply Unlock
>>>>>>>> TRE for the last transfer.
>>>>>>>>
>>>>>>>> Also change MAX_TRE macro to 5 from 3 because of the two
>>>>>>>> additional TREs.
>>>>>>>>
>>>>>>>
>>>>>>> ...
>>>>>>>
>>>>>>>> @@ -65,6 +65,9 @@ enum i2c_op {
>>>>>>>> * @rx_len: receive length for buffer
>>>>>>>> * @op: i2c cmd
>>>>>>>> * @muli-msg: is part of multi i2c r-w msgs
>>>>>>>> + * @shared_se: bus is shared between subsystems
>>>>>>>> + * @bool first_msg: use it for tracking multimessage xfer
>>>>>>>> + * @bool last_msg: use it for tracking multimessage xfer
>>>>>>>> */
>>>>>>>> struct gpi_i2c_config {
>>>>>>>> u8 set_config;
>>>>>>>> @@ -78,6 +81,9 @@ struct gpi_i2c_config {
>>>>>>>> u32 rx_len;
>>>>>>>> enum i2c_op op;
>>>>>>>> bool multi_msg;
>>>>>>>> + bool shared_se;
>>>>>>>
>>>>>>> Looking at this why do you need this field? It can be internal to
>>>>>>> your
>>>>>>> i2c driver... Why not just set an enum for lock and use the
>>>>>>> values as
>>>>>>> lock/unlock/dont care and make the interface simpler. I see no
>>>>>>> reason to
>>>>>>> use three variables to communicate the info which can be handled in
>>>>>>> simpler way..?
>>>>>>>
>>>>>> Below was earlier reply to [PATCH V3, 2/4], please let me know if
>>>>>> you have
>>>>>> any additional comment and need further clarifications.
>>>>>
>>>>> Looks like you misunderstood, the question is why do you need three
>>>>> variables to convey this info..? Use a single variable please
>>>> Yes, I think so. Please let me clarify.
>>>> First variable is a feature flag and it's required to be explicitly
>>>> mentioned by client (i2c/spi/etc) to GSI driver.
>>>>
>>>> Second and third, can be optimized to boolean so either first or
>>>> last can be
>>>> passed.
>>>>
>>>> Please correct me or add simple change where you would like to make,
>>>> i can
>>>> add that.
>>>
>>> I though we could do with a single and derive
>>>
>> Sure, so as mentioned in the other crypto BAM patch probably
>> dmaengine.h can hold flag and that can add support for lock/unlock
>> similar to that patch.
>> I just realized it from your shared patch. let me work internally with
>> Md sadre and review. Thanks for the comment.
>>> Also, please see 20241212041639.4109039-3-quic_mdalam@quicinc.com, folks
>>> from same company should talk together on same solutions, please
>>> converge and come up with a single proposal which works for both drivers
>>>
> I have discussed with Md Sadre and tried to understand and utilize the
> enum of lock and unlock in my changes. Below is the summary.
>
> I can't use those lock and unlock enums here because it's required for
> first and last message respectively. intermediate transfers will not use
> anything. So we need to define one more enum like dma_ctrl_none.
>
> if i create another internal parent structure having required 3 members,
> then also it will need 3 child members. So i think current one looks
> good to me.
>
> Please help review and suggest if anything can be better here.
>
I have added enum from gpi driver and set it from i2c driver. so GPI
driver handles the action accordingly for lock/unlock. Let me know if
this approach makes sense in the V6.
>> Sure
>>
>>
>
>
^ permalink raw reply
* Re: [PATCH v5 1/4] dt-bindindgs: i2c: qcom,i2c-geni: Document shared flag
From: Mukesh Kumar Savaliya @ 2026-03-31 11:32 UTC (permalink / raw)
To: Bjorn Andersson, Konrad Dybcio
Cc: Krzysztof Kozlowski, Konrad Dybcio, konrad.dybcio, andersson,
andi.shyti, linux-arm-msm, dmaengine, linux-kernel, linux-i2c,
conor+dt, agross, devicetree, vkoul, linux, dan.carpenter,
Frank.Li, konradybcio, bryan.odonoghue, krzk+dt, robh,
quic_vdadhani
In-Reply-To: <Z1h/x+QJD5Uob8GZ@hu-bjorande-lv.qualcomm.com>
Hi Bjorn, Konrad, Krzysztof, Thanks for all the time and review,
discussion on this. Really sorry for such long wait, as i was completely
away from this activity. Now restarting back and will continue on this.
Let me upload v6 as to get context of older series and vet all changes
and description including DT property flag as the main point of discussion.
On 12/10/2024 11:22 PM, Bjorn Andersson wrote:
> On Tue, Dec 10, 2024 at 01:38:28PM +0100, Konrad Dybcio wrote:
>>
>>
>> On 12/10/24 13:05, Krzysztof Kozlowski wrote:
>>> On 10/12/2024 12:53, Krzysztof Kozlowski wrote:
>>>>>>> I'm not sure a single property name+description can fit all possible
>>>>>>> cases here. The hardware being "shared" can mean a number of different
>>>>>>
>>>>>> Existing property does not explain anything more, either. To recap -
>>>>>> this block is SE and property is named "se-shared", so basically it is
>>>>>> equal to just "shared". "shared" is indeed quite vague, so I was
>>>>>> expecting some wider work here.
>>>>>>
>>>>>>
>>>>>>> things, with some blocks having hardware provisions for that, while
>>>>>>> others may have totally none and rely on external mechanisms (e.g.
>>>>>>> a shared memory buffer) to indicate whether an external entity
>>>>>>> manages power to them.
>>>>>>
>>>>>> We have properties for that too. Qualcomm SoCs need once per year for
>>>>>> such shared properties. BAM has two or three. IPA has two. There are
>>>>>> probably even more blocks which I don't remember now.
>>>>>
>>>>> So, the problem is "driver must not toggle GPIO states", because
>>>>> "the bus controller must not be muxed away from the endpoint".
>>>>> You can come up with a number of similar problems by swapping out
>>>>> the quoted text.
>>>>>
>>>>> We can either describe what the driver must do (A), or what the
>>>>> reason for it is (B).
>>>>>
>>>>>
>>>>> If we go with A, we could have a property like:
>>>>>
>>>>> &i2c1 {
>>>>> externally-handled-resources = <(EHR_PINCTRL_STATE | EHR_CLOCK_RATE)>
>>>>> };
>>>>>
>>>>> which would be a generic list of things that the OS would have to
>>>>> tiptoe around, fitting Linux's framework split quite well
>>>>>
>>>>>
>>>>>
>>>>> or if we go with B, we could add a property like:
>>>>>
>>>>> &i2c1 {
>>>>> qcom,shared-controller;
>>>>> };
>>>>>
>>>>> which would hide the implementation details into the driver
>>>>>
>>>>> I could see both approaches having their place, but in this specific
>>>>> instance I think A would be more fitting, as the problem is quite
>>>>> simple.
>>>>
>>>>
>>>> The second is fine with me, maybe missing information about "whom" do
>>>> you share it with. Or maybe we get to the point that all this is
>>>> specific to SoC, thus implied by compatible and we do not need
>>>> downstream approach (another discussion in USB pushed by Qcom: I want
>>>> one compatible and 1000 properties).
>>>>
>>>> I really wished Qualcomm start reworking their bindings before they are
>>>> being sent upstream to match standard DT guidelines, not downstream
>>>> approach. Somehow these hundreds reviews we give could result in new
>>>> patches doing things better, not just repeating the same issues.
>>>
>>> This is BTW v5, with all the same concerns from v1 and still no answers
>>> in commit msg about these concerns. Nothing explained in commit msg
>>> which hardware needs it or why the same SoC have it once shared, once
>>> not (exclusive). Basically there is nothing here corresponding to any
>>> real product, so since five versions all this for me is just copy-paste
>>> from downstream approach.
>>
>> So since this is a software contract and not a hardware
>> feature, this is not bound to any specific SoC or "firmware",
>> but rather to what runs on other cores (e.g. DSPs, MCUs spread
>> across the SoC or in a different software world, like TZ).
>>
>
> I don't think this is a reasonable distinction, the DeviceTree must
> describe the interfaces/environment that the OS is to operate in.
> Claiming that certain properties of that world directly or indirectly
> comes from (static) "software choices" would make the whole concept of
> DeviceTree useless.
>
> The fact that a serial engine is shared, or not, is a static property of
> the firmware for a given board, no different from "i2c1 being accessible
> by this OS or not" or the fact that i2c1 is actually implement I2C and
> not SPI (i.e. should this node be enabled in the DeviceTree passed to
> the OS or not).
>
>
> That said, the commit message still doesn't clearly describe the system
> design or when this property should be set or not, which is what
> Krzysztof has been asking for multiple times.
>
> Let's circle back and help Mukesh rewrite the commit message such that
> it clearly documents the problem being solved.
>
In the next patch to be uploaded, Tried to gave clarity on previous
concerns.
>> Specifying the specific intended use would be helpful though,
>> indeed.
>>
>> Let's see if we can somehow make this saner.
>>
>>
>> Mukesh, do we have any spare registers that we could use to
>> indicate that a given SE is shared? Preferably within the
>> SE's register space itself. The bootloader or another entity
>> (DSP or what have you) would then set that bit before Linux
>> runs and we could skip the bindings story altogether.
>>
>> It would need to be reserved on all SoCs though (future and
>> past), to make sure the contract is always held up, but I
>> think finding a persistent bit that has never been used
>> shouldn't be impossible.
>>
>
> Let's not invent a custom one-off "hardware description" passing
> interface.
>
As you know, as such there is no HW flag as of now indicating this HW
capability or feature. Please let me know if v6 with description about
static feature flag helps.
If no, then i need to devise some flag (create/add bit) in existing
HW/FW register and then read back the register to derive feature flag.
I am clear that each DTSI entry describes the hardware, but provided
such SW based feature, wanted to review.
> Regards,
> Bjorn
>
>> Konrad
^ permalink raw reply
* [PATCH v6 10/10] arm64: tegra: Enable GPCDMA in Tegra264 and add iommu-map
From: Akhil R @ 2026-03-31 10:23 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Thierry Reding, Jonathan Hunter, Laxman Dewangan,
Philipp Zabel, dmaengine, devicetree, linux-tegra, linux-kernel
Cc: Akhil R
In-Reply-To: <20260331102303.33181-1-akhilrajeev@nvidia.com>
Enable GPCDMA in Tegra264 and add the iommu-map property so that each
channel uses a separate stream ID and gets its own IOMMU domain for
memory.
Signed-off-by: Akhil R <akhilrajeev@nvidia.com>
---
arch/arm64/boot/dts/nvidia/tegra264-p3834.dtsi | 4 ++++
arch/arm64/boot/dts/nvidia/tegra264.dtsi | 1 +
2 files changed, 5 insertions(+)
diff --git a/arch/arm64/boot/dts/nvidia/tegra264-p3834.dtsi b/arch/arm64/boot/dts/nvidia/tegra264-p3834.dtsi
index 7e2c3e66c2ab..58cd81bc33d7 100644
--- a/arch/arm64/boot/dts/nvidia/tegra264-p3834.dtsi
+++ b/arch/arm64/boot/dts/nvidia/tegra264-p3834.dtsi
@@ -9,6 +9,10 @@ aliases {
};
bus@0 {
+ dma-controller@8400000 {
+ status = "okay";
+ };
+
serial@c4e0000 {
status = "okay";
};
diff --git a/arch/arm64/boot/dts/nvidia/tegra264.dtsi b/arch/arm64/boot/dts/nvidia/tegra264.dtsi
index af077420d7d9..b2f20d4b567a 100644
--- a/arch/arm64/boot/dts/nvidia/tegra264.dtsi
+++ b/arch/arm64/boot/dts/nvidia/tegra264.dtsi
@@ -3244,6 +3244,7 @@ gpcdma: dma-controller@8400000 {
<GIC_SPI 615 IRQ_TYPE_LEVEL_HIGH>;
#dma-cells = <1>;
iommus = <&smmu1 0x00000800>;
+ iommu-map = <1 &smmu1 0x801 31>;
dma-coherent;
dma-channel-mask = <0xfffffffe>;
status = "disabled";
--
2.50.1
^ permalink raw reply related
* [PATCH v6 09/10] dmaengine: tegra: Add Tegra264 support
From: Akhil R @ 2026-03-31 10:23 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Thierry Reding, Jonathan Hunter, Laxman Dewangan,
Philipp Zabel, dmaengine, devicetree, linux-tegra, linux-kernel
Cc: Akhil R, Frank Li
In-Reply-To: <20260331102303.33181-1-akhilrajeev@nvidia.com>
Add compatible and chip data to support GPCDMA in Tegra264, which has
differences in register layout and address bits compared to previous
versions.
Signed-off-by: Akhil R <akhilrajeev@nvidia.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
---
drivers/dma/tegra186-gpc-dma.c | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/drivers/dma/tegra186-gpc-dma.c b/drivers/dma/tegra186-gpc-dma.c
index cd480d047204..2383a2fe73f1 100644
--- a/drivers/dma/tegra186-gpc-dma.c
+++ b/drivers/dma/tegra186-gpc-dma.c
@@ -1319,6 +1319,23 @@ static const struct tegra_dma_channel_regs tegra186_reg_offsets = {
.fixed_pattern = 0x34,
};
+static const struct tegra_dma_channel_regs tegra264_reg_offsets = {
+ .csr = 0x0,
+ .status = 0x4,
+ .csre = 0x8,
+ .src = 0xc,
+ .dst = 0x10,
+ .src_high = 0x14,
+ .dst_high = 0x18,
+ .mc_seq = 0x1c,
+ .mmio_seq = 0x20,
+ .wcount = 0x24,
+ .wxfer = 0x28,
+ .wstatus = 0x2c,
+ .err_status = 0x34,
+ .fixed_pattern = 0x38,
+};
+
static const struct tegra_dma_chip_data tegra186_dma_chip_data = {
.nr_channels = 32,
.addr_bits = 39,
@@ -1349,6 +1366,16 @@ static const struct tegra_dma_chip_data tegra234_dma_chip_data = {
.terminate = tegra_dma_pause_noerr,
};
+static const struct tegra_dma_chip_data tegra264_dma_chip_data = {
+ .nr_channels = 32,
+ .addr_bits = 41,
+ .channel_reg_size = SZ_64K,
+ .max_dma_count = SZ_1G,
+ .hw_support_pause = true,
+ .channel_regs = &tegra264_reg_offsets,
+ .terminate = tegra_dma_pause_noerr,
+};
+
static const struct of_device_id tegra_dma_of_match[] = {
{
.compatible = "nvidia,tegra186-gpcdma",
@@ -1359,6 +1386,9 @@ static const struct of_device_id tegra_dma_of_match[] = {
}, {
.compatible = "nvidia,tegra234-gpcdma",
.data = &tegra234_dma_chip_data,
+ }, {
+ .compatible = "nvidia,tegra264-gpcdma",
+ .data = &tegra264_dma_chip_data,
}, {
},
};
--
2.50.1
^ permalink raw reply related
* [PATCH v6 08/10] dmaengine: tegra: Use iommu-map for stream ID
From: Akhil R @ 2026-03-31 10:23 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Thierry Reding, Jonathan Hunter, Laxman Dewangan,
Philipp Zabel, dmaengine, devicetree, linux-tegra, linux-kernel
Cc: Akhil R
In-Reply-To: <20260331102303.33181-1-akhilrajeev@nvidia.com>
Use 'iommu-map', when provided, to get the stream ID to be programmed
for each channel. Iterate over the channels registered and configure
each channel device separately using of_dma_configure_id() to allow
it to use a separate IOMMU domain for the transfer. However, do this
in a second loop since the first loop populates the DMA device channels
list and async_device_register() registers the channels. Both are
prerequisites for using the channel device in the next loop.
Channels will continue to use the same global stream ID if the
'iommu-map' property is not present in the device tree.
Signed-off-by: Akhil R <akhilrajeev@nvidia.com>
---
drivers/dma/tegra186-gpc-dma.c | 53 ++++++++++++++++++++++++++++------
1 file changed, 44 insertions(+), 9 deletions(-)
diff --git a/drivers/dma/tegra186-gpc-dma.c b/drivers/dma/tegra186-gpc-dma.c
index 9bea2ffb3b9e..cd480d047204 100644
--- a/drivers/dma/tegra186-gpc-dma.c
+++ b/drivers/dma/tegra186-gpc-dma.c
@@ -15,6 +15,7 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_dma.h>
+#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <linux/slab.h>
@@ -1380,9 +1381,13 @@ static int tegra_dma_program_sid(struct tegra_dma_channel *tdc, int stream_id)
static int tegra_dma_probe(struct platform_device *pdev)
{
const struct tegra_dma_chip_data *cdata = NULL;
+ struct tegra_dma_channel *tdc;
+ struct tegra_dma *tdma;
+ struct dma_chan *chan;
+ struct device *chdev;
+ bool use_iommu_map = false;
unsigned int i;
u32 stream_id;
- struct tegra_dma *tdma;
int ret;
cdata = of_device_get_match_data(&pdev->dev);
@@ -1410,9 +1415,10 @@ static int tegra_dma_probe(struct platform_device *pdev)
tdma->dma_dev.dev = &pdev->dev;
- if (!tegra_dev_iommu_get_stream_id(&pdev->dev, &stream_id)) {
- dev_err(&pdev->dev, "Missing iommu stream-id\n");
- return -EINVAL;
+ use_iommu_map = of_property_present(pdev->dev.of_node, "iommu-map");
+ if (!use_iommu_map) {
+ if (!tegra_dev_iommu_get_stream_id(&pdev->dev, &stream_id))
+ return dev_err_probe(&pdev->dev, -EINVAL, "Missing iommu stream-id\n");
}
ret = device_property_read_u32(&pdev->dev, "dma-channel-mask",
@@ -1424,9 +1430,10 @@ static int tegra_dma_probe(struct platform_device *pdev)
tdma->chan_mask = TEGRA_GPCDMA_DEFAULT_CHANNEL_MASK;
}
+ /* Initialize vchan for each channel and populate the channels list */
INIT_LIST_HEAD(&tdma->dma_dev.channels);
for (i = 0; i < cdata->nr_channels; i++) {
- struct tegra_dma_channel *tdc = &tdma->channels[i];
+ tdc = &tdma->channels[i];
/* Check for channel mask */
if (!(tdma->chan_mask & BIT(i)))
@@ -1446,10 +1453,6 @@ static int tegra_dma_probe(struct platform_device *pdev)
vchan_init(&tdc->vc, &tdma->dma_dev);
tdc->vc.desc_free = tegra_dma_desc_free;
-
- /* program stream-id for this channel */
- tegra_dma_program_sid(tdc, stream_id);
- tdc->stream_id = stream_id;
}
dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(cdata->addr_bits));
@@ -1483,6 +1486,7 @@ static int tegra_dma_probe(struct platform_device *pdev)
tdma->dma_dev.device_synchronize = tegra_dma_chan_synchronize;
tdma->dma_dev.residue_granularity = DMA_RESIDUE_GRANULARITY_BURST;
+ /* Register the DMA device and the channels */
ret = dmaenginem_async_device_register(&tdma->dma_dev);
if (ret < 0) {
dev_err_probe(&pdev->dev, ret,
@@ -1490,6 +1494,37 @@ static int tegra_dma_probe(struct platform_device *pdev)
return ret;
}
+ /*
+ * Configure stream ID for each channel from the channels registered
+ * above. This is done in a separate iteration to ensure that only
+ * the channels available and registered for the DMA device are used.
+ */
+ list_for_each_entry(chan, &tdma->dma_dev.channels, device_node) {
+ chdev = &chan->dev->device;
+ tdc = to_tegra_dma_chan(chan);
+
+ if (use_iommu_map) {
+ chdev->bus = pdev->dev.bus;
+ dma_coerce_mask_and_coherent(chdev, DMA_BIT_MASK(cdata->addr_bits));
+
+ ret = of_dma_configure_id(chdev, pdev->dev.of_node,
+ true, &tdc->id);
+ if (ret)
+ return dev_err_probe(chdev, ret,
+ "Failed to configure IOMMU for channel %d\n", tdc->id);
+
+ if (!tegra_dev_iommu_get_stream_id(chdev, &stream_id))
+ return dev_err_probe(chdev, -EINVAL,
+ "Failed to get stream ID for channel %d\n", tdc->id);
+
+ chan->dev->chan_dma_dev = true;
+ }
+
+ /* program stream-id for this channel */
+ tegra_dma_program_sid(tdc, stream_id);
+ tdc->stream_id = stream_id;
+ }
+
ret = devm_of_dma_controller_register(&pdev->dev, pdev->dev.of_node,
tegra_dma_of_xlate, tdma);
if (ret < 0) {
--
2.50.1
^ permalink raw reply related
* [PATCH v6 07/10] dmaengine: tegra: Use managed DMA controller registration
From: Akhil R @ 2026-03-31 10:23 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Thierry Reding, Jonathan Hunter, Laxman Dewangan,
Philipp Zabel, dmaengine, devicetree, linux-tegra, linux-kernel
Cc: Akhil R, Frank Li
In-Reply-To: <20260331102303.33181-1-akhilrajeev@nvidia.com>
Switch to managed registration in probe. This simplifies the error
paths in the probe and also removes the requirement of the driver
remove function.
Signed-off-by: Akhil R <akhilrajeev@nvidia.com>
Suggested-by: Frank Li <frank.li@nxp.com>
---
drivers/dma/tegra186-gpc-dma.c | 19 ++++---------------
1 file changed, 4 insertions(+), 15 deletions(-)
diff --git a/drivers/dma/tegra186-gpc-dma.c b/drivers/dma/tegra186-gpc-dma.c
index 3ac43ad19ed6..9bea2ffb3b9e 100644
--- a/drivers/dma/tegra186-gpc-dma.c
+++ b/drivers/dma/tegra186-gpc-dma.c
@@ -1483,37 +1483,27 @@ static int tegra_dma_probe(struct platform_device *pdev)
tdma->dma_dev.device_synchronize = tegra_dma_chan_synchronize;
tdma->dma_dev.residue_granularity = DMA_RESIDUE_GRANULARITY_BURST;
- ret = dma_async_device_register(&tdma->dma_dev);
+ ret = dmaenginem_async_device_register(&tdma->dma_dev);
if (ret < 0) {
dev_err_probe(&pdev->dev, ret,
"GPC DMA driver registration failed\n");
return ret;
}
- ret = of_dma_controller_register(pdev->dev.of_node,
- tegra_dma_of_xlate, tdma);
+ ret = devm_of_dma_controller_register(&pdev->dev, pdev->dev.of_node,
+ tegra_dma_of_xlate, tdma);
if (ret < 0) {
dev_err_probe(&pdev->dev, ret,
"GPC DMA OF registration failed\n");
-
- dma_async_device_unregister(&tdma->dma_dev);
return ret;
}
- dev_info(&pdev->dev, "GPC DMA driver register %lu channels\n",
+ dev_info(&pdev->dev, "GPC DMA driver registered %lu channels\n",
hweight_long(tdma->chan_mask));
return 0;
}
-static void tegra_dma_remove(struct platform_device *pdev)
-{
- struct tegra_dma *tdma = platform_get_drvdata(pdev);
-
- of_dma_controller_free(pdev->dev.of_node);
- dma_async_device_unregister(&tdma->dma_dev);
-}
-
static int __maybe_unused tegra_dma_pm_suspend(struct device *dev)
{
struct tegra_dma *tdma = dev_get_drvdata(dev);
@@ -1564,7 +1554,6 @@ static struct platform_driver tegra_dma_driver = {
.of_match_table = tegra_dma_of_match,
},
.probe = tegra_dma_probe,
- .remove = tegra_dma_remove,
};
module_platform_driver(tegra_dma_driver);
--
2.50.1
^ permalink raw reply related
* [PATCH v6 06/10] dmaengine: tegra: Support address width > 39 bits
From: Akhil R @ 2026-03-31 10:22 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Thierry Reding, Jonathan Hunter, Laxman Dewangan,
Philipp Zabel, dmaengine, devicetree, linux-tegra, linux-kernel
Cc: Akhil R, Frank Li
In-Reply-To: <20260331102303.33181-1-akhilrajeev@nvidia.com>
Tegra264 supports address width of 41 bits. Unlike older SoCs which use
a common high_addr register for upper address bits, Tegra264 has separate
src_high and dst_high registers to accommodate this wider address space.
Add an addr_bits property to the device data structure to specify the
number of address bits supported on each device and use that to program
the appropriate registers.
Update the sg_req struct to remove the high_addr field and use
dma_addr_t for src and dst to store the complete addresses. Extract
the high address bits only when programming the registers.
Signed-off-by: Akhil R <akhilrajeev@nvidia.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
---
drivers/dma/tegra186-gpc-dma.c | 83 +++++++++++++++++++++-------------
1 file changed, 52 insertions(+), 31 deletions(-)
diff --git a/drivers/dma/tegra186-gpc-dma.c b/drivers/dma/tegra186-gpc-dma.c
index b213c4ae07d2..3ac43ad19ed6 100644
--- a/drivers/dma/tegra186-gpc-dma.c
+++ b/drivers/dma/tegra186-gpc-dma.c
@@ -146,6 +146,7 @@ struct tegra_dma_channel;
*/
struct tegra_dma_chip_data {
bool hw_support_pause;
+ unsigned int addr_bits;
unsigned int nr_channels;
unsigned int channel_reg_size;
unsigned int max_dma_count;
@@ -161,6 +162,8 @@ struct tegra_dma_channel_regs {
u32 src;
u32 dst;
u32 high_addr;
+ u32 src_high;
+ u32 dst_high;
u32 mc_seq;
u32 mmio_seq;
u32 wcount;
@@ -179,10 +182,9 @@ struct tegra_dma_channel_regs {
*/
struct tegra_dma_sg_req {
unsigned int len;
+ dma_addr_t src;
+ dma_addr_t dst;
u32 csr;
- u32 src;
- u32 dst;
- u32 high_addr;
u32 mc_seq;
u32 mmio_seq;
u32 wcount;
@@ -266,6 +268,25 @@ static inline struct device *tdc2dev(struct tegra_dma_channel *tdc)
return tdc->vc.chan.device->dev;
}
+static void tegra_dma_program_addr(struct tegra_dma_channel *tdc,
+ struct tegra_dma_sg_req *sg_req)
+{
+ tdc_write(tdc, tdc->regs->src, lower_32_bits(sg_req->src));
+ tdc_write(tdc, tdc->regs->dst, lower_32_bits(sg_req->dst));
+
+ if (tdc->tdma->chip_data->addr_bits > 39) {
+ tdc_write(tdc, tdc->regs->src_high, upper_32_bits(sg_req->src));
+ tdc_write(tdc, tdc->regs->dst_high, upper_32_bits(sg_req->dst));
+ } else {
+ u32 src_high = FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_SRC_PTR,
+ upper_32_bits(sg_req->src));
+ u32 dst_high = FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_DST_PTR,
+ upper_32_bits(sg_req->dst));
+
+ tdc_write(tdc, tdc->regs->high_addr, src_high | dst_high);
+ }
+}
+
static void tegra_dma_dump_chan_regs(struct tegra_dma_channel *tdc)
{
dev_dbg(tdc2dev(tdc), "DMA Channel %d name %s register dump:\n",
@@ -274,10 +295,20 @@ static void tegra_dma_dump_chan_regs(struct tegra_dma_channel *tdc)
tdc_read(tdc, tdc->regs->csr),
tdc_read(tdc, tdc->regs->status),
tdc_read(tdc, tdc->regs->csre));
- dev_dbg(tdc2dev(tdc), "SRC %x DST %x HI ADDR %x\n",
- tdc_read(tdc, tdc->regs->src),
- tdc_read(tdc, tdc->regs->dst),
- tdc_read(tdc, tdc->regs->high_addr));
+
+ if (tdc->tdma->chip_data->addr_bits > 39) {
+ dev_dbg(tdc2dev(tdc), "SRC %x SRC HI %x DST %x DST HI %x\n",
+ tdc_read(tdc, tdc->regs->src),
+ tdc_read(tdc, tdc->regs->src_high),
+ tdc_read(tdc, tdc->regs->dst),
+ tdc_read(tdc, tdc->regs->dst_high));
+ } else {
+ dev_dbg(tdc2dev(tdc), "SRC %x DST %x HI ADDR %x\n",
+ tdc_read(tdc, tdc->regs->src),
+ tdc_read(tdc, tdc->regs->dst),
+ tdc_read(tdc, tdc->regs->high_addr));
+ }
+
dev_dbg(tdc2dev(tdc), "MCSEQ %x IOSEQ %x WCNT %x XFER %x WSTA %x\n",
tdc_read(tdc, tdc->regs->mc_seq),
tdc_read(tdc, tdc->regs->mmio_seq),
@@ -480,9 +511,7 @@ static void tegra_dma_configure_next_sg(struct tegra_dma_channel *tdc)
sg_req = &dma_desc->sg_req[dma_desc->sg_idx];
tdc_write(tdc, tdc->regs->wcount, sg_req->wcount);
- tdc_write(tdc, tdc->regs->src, sg_req->src);
- tdc_write(tdc, tdc->regs->dst, sg_req->dst);
- tdc_write(tdc, tdc->regs->high_addr, sg_req->high_addr);
+ tegra_dma_program_addr(tdc, sg_req);
/* Start DMA */
tdc_write(tdc, tdc->regs->csr,
@@ -510,11 +539,9 @@ static void tegra_dma_start(struct tegra_dma_channel *tdc)
sg_req = &dma_desc->sg_req[dma_desc->sg_idx];
+ tegra_dma_program_addr(tdc, sg_req);
tdc_write(tdc, tdc->regs->wcount, sg_req->wcount);
tdc_write(tdc, tdc->regs->csr, 0);
- tdc_write(tdc, tdc->regs->src, sg_req->src);
- tdc_write(tdc, tdc->regs->dst, sg_req->dst);
- tdc_write(tdc, tdc->regs->high_addr, sg_req->high_addr);
tdc_write(tdc, tdc->regs->fixed_pattern, sg_req->fixed_pattern);
tdc_write(tdc, tdc->regs->mmio_seq, sg_req->mmio_seq);
tdc_write(tdc, tdc->regs->mc_seq, sg_req->mc_seq);
@@ -819,7 +846,7 @@ static unsigned int get_burst_size(struct tegra_dma_channel *tdc,
static int get_transfer_param(struct tegra_dma_channel *tdc,
enum dma_transfer_direction direction,
- u32 *apb_addr,
+ dma_addr_t *apb_addr,
u32 *mmio_seq,
u32 *csr,
unsigned int *burst_size,
@@ -897,11 +924,9 @@ tegra_dma_prep_dma_memset(struct dma_chan *dc, dma_addr_t dest, int value,
dma_desc->bytes_req = len;
dma_desc->sg_count = 1;
sg_req = dma_desc->sg_req;
-
sg_req[0].src = 0;
sg_req[0].dst = dest;
- sg_req[0].high_addr =
- FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_DST_PTR, (dest >> 32));
+
sg_req[0].fixed_pattern = value;
/* Word count reg takes value as (N +1) words */
sg_req[0].wcount = ((len - 4) >> 2);
@@ -969,10 +994,7 @@ tegra_dma_prep_dma_memcpy(struct dma_chan *dc, dma_addr_t dest,
sg_req[0].src = src;
sg_req[0].dst = dest;
- sg_req[0].high_addr =
- FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_SRC_PTR, (src >> 32));
- sg_req[0].high_addr |=
- FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_DST_PTR, (dest >> 32));
+
/* Word count reg takes value as (N +1) words */
sg_req[0].wcount = ((len - 4) >> 2);
sg_req[0].csr = csr;
@@ -992,7 +1014,8 @@ tegra_dma_prep_slave_sg(struct dma_chan *dc, struct scatterlist *sgl,
struct tegra_dma_channel *tdc = to_tegra_dma_chan(dc);
unsigned int max_dma_count = tdc->tdma->chip_data->max_dma_count;
enum dma_slave_buswidth slave_bw = DMA_SLAVE_BUSWIDTH_UNDEFINED;
- u32 csr, mc_seq, apb_ptr = 0, mmio_seq = 0;
+ u32 csr, mc_seq, mmio_seq = 0;
+ dma_addr_t apb_ptr = 0;
struct tegra_dma_sg_req *sg_req;
struct tegra_dma_desc *dma_desc;
struct scatterlist *sg;
@@ -1080,13 +1103,9 @@ tegra_dma_prep_slave_sg(struct dma_chan *dc, struct scatterlist *sgl,
if (direction == DMA_MEM_TO_DEV) {
sg_req[i].src = mem;
sg_req[i].dst = apb_ptr;
- sg_req[i].high_addr =
- FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_SRC_PTR, (mem >> 32));
} else if (direction == DMA_DEV_TO_MEM) {
sg_req[i].src = apb_ptr;
sg_req[i].dst = mem;
- sg_req[i].high_addr =
- FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_DST_PTR, (mem >> 32));
}
/*
@@ -1110,7 +1129,8 @@ tegra_dma_prep_dma_cyclic(struct dma_chan *dc, dma_addr_t buf_addr, size_t buf_l
unsigned long flags)
{
enum dma_slave_buswidth slave_bw = DMA_SLAVE_BUSWIDTH_UNDEFINED;
- u32 csr, mc_seq, apb_ptr = 0, mmio_seq = 0, burst_size;
+ u32 csr, mc_seq, mmio_seq = 0, burst_size;
+ dma_addr_t apb_ptr = 0;
unsigned int max_dma_count, len, period_count, i;
struct tegra_dma_channel *tdc = to_tegra_dma_chan(dc);
struct tegra_dma_desc *dma_desc;
@@ -1201,13 +1221,9 @@ tegra_dma_prep_dma_cyclic(struct dma_chan *dc, dma_addr_t buf_addr, size_t buf_l
if (direction == DMA_MEM_TO_DEV) {
sg_req[i].src = mem;
sg_req[i].dst = apb_ptr;
- sg_req[i].high_addr =
- FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_SRC_PTR, (mem >> 32));
} else if (direction == DMA_DEV_TO_MEM) {
sg_req[i].src = apb_ptr;
sg_req[i].dst = mem;
- sg_req[i].high_addr =
- FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_DST_PTR, (mem >> 32));
}
/*
* Word count register takes input in words. Writing a value
@@ -1304,6 +1320,7 @@ static const struct tegra_dma_channel_regs tegra186_reg_offsets = {
static const struct tegra_dma_chip_data tegra186_dma_chip_data = {
.nr_channels = 32,
+ .addr_bits = 39,
.channel_reg_size = SZ_64K,
.max_dma_count = SZ_1G,
.hw_support_pause = false,
@@ -1313,6 +1330,7 @@ static const struct tegra_dma_chip_data tegra186_dma_chip_data = {
static const struct tegra_dma_chip_data tegra194_dma_chip_data = {
.nr_channels = 32,
+ .addr_bits = 39,
.channel_reg_size = SZ_64K,
.max_dma_count = SZ_1G,
.hw_support_pause = true,
@@ -1322,6 +1340,7 @@ static const struct tegra_dma_chip_data tegra194_dma_chip_data = {
static const struct tegra_dma_chip_data tegra234_dma_chip_data = {
.nr_channels = 32,
+ .addr_bits = 39,
.channel_reg_size = SZ_64K,
.max_dma_count = SZ_1G,
.hw_support_pause = true,
@@ -1433,6 +1452,8 @@ static int tegra_dma_probe(struct platform_device *pdev)
tdc->stream_id = stream_id;
}
+ dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(cdata->addr_bits));
+
dma_cap_set(DMA_SLAVE, tdma->dma_dev.cap_mask);
dma_cap_set(DMA_PRIVATE, tdma->dma_dev.cap_mask);
dma_cap_set(DMA_MEMCPY, tdma->dma_dev.cap_mask);
--
2.50.1
^ permalink raw reply related
* [PATCH v6 05/10] dmaengine: tegra: Use struct for register offsets
From: Akhil R @ 2026-03-31 10:22 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Thierry Reding, Jonathan Hunter, Laxman Dewangan,
Philipp Zabel, dmaengine, devicetree, linux-tegra, linux-kernel
Cc: Akhil R, Frank Li
In-Reply-To: <20260331102303.33181-1-akhilrajeev@nvidia.com>
Repurpose the struct tegra_dma_channel_regs to define offsets for all the
channel registers. Previously, the struct only held the register values
for each transfer and was wrapped within tegra_dma_sg_req. Move the
values directly into tegra_dma_sg_req and use channel_regs for
storing the register offsets. Update all register reads/writes to use
the struct channel_regs. This prepares for the register offset change
in Tegra264.
Signed-off-by: Akhil R <akhilrajeev@nvidia.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
---
drivers/dma/tegra186-gpc-dma.c | 282 +++++++++++++++++----------------
1 file changed, 142 insertions(+), 140 deletions(-)
diff --git a/drivers/dma/tegra186-gpc-dma.c b/drivers/dma/tegra186-gpc-dma.c
index a0522a992ebc..b213c4ae07d2 100644
--- a/drivers/dma/tegra186-gpc-dma.c
+++ b/drivers/dma/tegra186-gpc-dma.c
@@ -22,7 +22,6 @@
#include "virt-dma.h"
/* CSR register */
-#define TEGRA_GPCDMA_CHAN_CSR 0x00
#define TEGRA_GPCDMA_CSR_ENB BIT(31)
#define TEGRA_GPCDMA_CSR_IE_EOC BIT(30)
#define TEGRA_GPCDMA_CSR_ONCE BIT(27)
@@ -58,7 +57,6 @@
#define TEGRA_GPCDMA_CSR_WEIGHT GENMASK(13, 10)
/* STATUS register */
-#define TEGRA_GPCDMA_CHAN_STATUS 0x004
#define TEGRA_GPCDMA_STATUS_BUSY BIT(31)
#define TEGRA_GPCDMA_STATUS_ISE_EOC BIT(30)
#define TEGRA_GPCDMA_STATUS_PING_PONG BIT(28)
@@ -70,22 +68,13 @@
#define TEGRA_GPCDMA_STATUS_IRQ_STA BIT(21)
#define TEGRA_GPCDMA_STATUS_IRQ_TRIG_STA BIT(20)
-#define TEGRA_GPCDMA_CHAN_CSRE 0x008
#define TEGRA_GPCDMA_CHAN_CSRE_PAUSE BIT(31)
-/* Source address */
-#define TEGRA_GPCDMA_CHAN_SRC_PTR 0x00C
-
-/* Destination address */
-#define TEGRA_GPCDMA_CHAN_DST_PTR 0x010
-
/* High address pointer */
-#define TEGRA_GPCDMA_CHAN_HIGH_ADDR_PTR 0x014
#define TEGRA_GPCDMA_HIGH_ADDR_SRC_PTR GENMASK(7, 0)
#define TEGRA_GPCDMA_HIGH_ADDR_DST_PTR GENMASK(23, 16)
/* MC sequence register */
-#define TEGRA_GPCDMA_CHAN_MCSEQ 0x18
#define TEGRA_GPCDMA_MCSEQ_DATA_SWAP BIT(31)
#define TEGRA_GPCDMA_MCSEQ_REQ_COUNT GENMASK(30, 25)
#define TEGRA_GPCDMA_MCSEQ_BURST GENMASK(24, 23)
@@ -101,7 +90,6 @@
#define TEGRA_GPCDMA_MCSEQ_STREAM_ID0_MASK GENMASK(6, 0)
/* MMIO sequence register */
-#define TEGRA_GPCDMA_CHAN_MMIOSEQ 0x01c
#define TEGRA_GPCDMA_MMIOSEQ_DBL_BUF BIT(31)
#define TEGRA_GPCDMA_MMIOSEQ_BUS_WIDTH GENMASK(30, 28)
#define TEGRA_GPCDMA_MMIOSEQ_BUS_WIDTH_8 \
@@ -120,17 +108,7 @@
#define TEGRA_GPCDMA_MMIOSEQ_WRAP_WORD GENMASK(18, 16)
#define TEGRA_GPCDMA_MMIOSEQ_MMIO_PROT GENMASK(8, 7)
-/* Channel WCOUNT */
-#define TEGRA_GPCDMA_CHAN_WCOUNT 0x20
-
-/* Transfer count */
-#define TEGRA_GPCDMA_CHAN_XFER_COUNT 0x24
-
-/* DMA byte count status */
-#define TEGRA_GPCDMA_CHAN_DMA_BYTE_STATUS 0x28
-
/* Error Status Register */
-#define TEGRA_GPCDMA_CHAN_ERR_STATUS 0x30
#define TEGRA_GPCDMA_CHAN_ERR_TYPE_SHIFT 8
#define TEGRA_GPCDMA_CHAN_ERR_TYPE_MASK 0xF
#define TEGRA_GPCDMA_CHAN_ERR_TYPE(err) ( \
@@ -143,16 +121,6 @@
#define TEGRA_DMA_MC_SLAVE_ERR 0xB
#define TEGRA_DMA_MMIO_SLAVE_ERR 0xA
-/* Fixed Pattern */
-#define TEGRA_GPCDMA_CHAN_FIXED_PATTERN 0x34
-
-#define TEGRA_GPCDMA_CHAN_TZ 0x38
-#define TEGRA_GPCDMA_CHAN_TZ_MMIO_PROT_1 BIT(0)
-#define TEGRA_GPCDMA_CHAN_TZ_MC_PROT_1 BIT(1)
-
-#define TEGRA_GPCDMA_CHAN_SPARE 0x3c
-#define TEGRA_GPCDMA_CHAN_SPARE_EN_LEGACY_FC BIT(16)
-
/*
* If any burst is in flight and DMA paused then this is the time to complete
* on-flight burst and update DMA status register.
@@ -181,18 +149,24 @@ struct tegra_dma_chip_data {
unsigned int nr_channels;
unsigned int channel_reg_size;
unsigned int max_dma_count;
+ const struct tegra_dma_channel_regs *channel_regs;
int (*terminate)(struct tegra_dma_channel *tdc);
};
/* DMA channel registers */
struct tegra_dma_channel_regs {
u32 csr;
- u32 src_ptr;
- u32 dst_ptr;
- u32 high_addr_ptr;
+ u32 status;
+ u32 csre;
+ u32 src;
+ u32 dst;
+ u32 high_addr;
u32 mc_seq;
u32 mmio_seq;
u32 wcount;
+ u32 wxfer;
+ u32 wstatus;
+ u32 err_status;
u32 fixed_pattern;
};
@@ -205,7 +179,14 @@ struct tegra_dma_channel_regs {
*/
struct tegra_dma_sg_req {
unsigned int len;
- struct tegra_dma_channel_regs ch_regs;
+ u32 csr;
+ u32 src;
+ u32 dst;
+ u32 high_addr;
+ u32 mc_seq;
+ u32 mmio_seq;
+ u32 wcount;
+ u32 fixed_pattern;
};
/*
@@ -228,19 +209,20 @@ struct tegra_dma_desc {
* tegra_dma_channel: Channel specific information
*/
struct tegra_dma_channel {
- bool config_init;
- char name[30];
- enum dma_transfer_direction sid_dir;
- enum dma_status status;
- int id;
- int irq;
- int slave_id;
+ const struct tegra_dma_channel_regs *regs;
struct tegra_dma *tdma;
struct virt_dma_chan vc;
struct tegra_dma_desc *dma_desc;
struct dma_slave_config dma_sconfig;
+ enum dma_transfer_direction sid_dir;
+ enum dma_status status;
unsigned int stream_id;
unsigned long chan_base_offset;
+ bool config_init;
+ char name[30];
+ int id;
+ int irq;
+ int slave_id;
};
/*
@@ -288,22 +270,22 @@ static void tegra_dma_dump_chan_regs(struct tegra_dma_channel *tdc)
{
dev_dbg(tdc2dev(tdc), "DMA Channel %d name %s register dump:\n",
tdc->id, tdc->name);
- dev_dbg(tdc2dev(tdc), "CSR %x STA %x CSRE %x SRC %x DST %x\n",
- tdc_read(tdc, TEGRA_GPCDMA_CHAN_CSR),
- tdc_read(tdc, TEGRA_GPCDMA_CHAN_STATUS),
- tdc_read(tdc, TEGRA_GPCDMA_CHAN_CSRE),
- tdc_read(tdc, TEGRA_GPCDMA_CHAN_SRC_PTR),
- tdc_read(tdc, TEGRA_GPCDMA_CHAN_DST_PTR)
- );
- dev_dbg(tdc2dev(tdc), "MCSEQ %x IOSEQ %x WCNT %x XFER %x BSTA %x\n",
- tdc_read(tdc, TEGRA_GPCDMA_CHAN_MCSEQ),
- tdc_read(tdc, TEGRA_GPCDMA_CHAN_MMIOSEQ),
- tdc_read(tdc, TEGRA_GPCDMA_CHAN_WCOUNT),
- tdc_read(tdc, TEGRA_GPCDMA_CHAN_XFER_COUNT),
- tdc_read(tdc, TEGRA_GPCDMA_CHAN_DMA_BYTE_STATUS)
- );
+ dev_dbg(tdc2dev(tdc), "CSR %x STA %x CSRE %x\n",
+ tdc_read(tdc, tdc->regs->csr),
+ tdc_read(tdc, tdc->regs->status),
+ tdc_read(tdc, tdc->regs->csre));
+ dev_dbg(tdc2dev(tdc), "SRC %x DST %x HI ADDR %x\n",
+ tdc_read(tdc, tdc->regs->src),
+ tdc_read(tdc, tdc->regs->dst),
+ tdc_read(tdc, tdc->regs->high_addr));
+ dev_dbg(tdc2dev(tdc), "MCSEQ %x IOSEQ %x WCNT %x XFER %x WSTA %x\n",
+ tdc_read(tdc, tdc->regs->mc_seq),
+ tdc_read(tdc, tdc->regs->mmio_seq),
+ tdc_read(tdc, tdc->regs->wcount),
+ tdc_read(tdc, tdc->regs->wxfer),
+ tdc_read(tdc, tdc->regs->wstatus));
dev_dbg(tdc2dev(tdc), "DMA ERR_STA %x\n",
- tdc_read(tdc, TEGRA_GPCDMA_CHAN_ERR_STATUS));
+ tdc_read(tdc, tdc->regs->err_status));
}
static int tegra_dma_sid_reserve(struct tegra_dma_channel *tdc,
@@ -377,13 +359,13 @@ static int tegra_dma_pause(struct tegra_dma_channel *tdc)
int ret;
u32 val;
- val = tdc_read(tdc, TEGRA_GPCDMA_CHAN_CSRE);
+ val = tdc_read(tdc, tdc->regs->csre);
val |= TEGRA_GPCDMA_CHAN_CSRE_PAUSE;
- tdc_write(tdc, TEGRA_GPCDMA_CHAN_CSRE, val);
+ tdc_write(tdc, tdc->regs->csre, val);
/* Wait until busy bit is de-asserted */
ret = readl_relaxed_poll_timeout_atomic(tdc->tdma->base_addr +
- tdc->chan_base_offset + TEGRA_GPCDMA_CHAN_STATUS,
+ tdc->chan_base_offset + tdc->regs->status,
val,
!(val & TEGRA_GPCDMA_STATUS_BUSY),
TEGRA_GPCDMA_BURST_COMPLETE_TIME,
@@ -419,9 +401,9 @@ static void tegra_dma_resume(struct tegra_dma_channel *tdc)
{
u32 val;
- val = tdc_read(tdc, TEGRA_GPCDMA_CHAN_CSRE);
+ val = tdc_read(tdc, tdc->regs->csre);
val &= ~TEGRA_GPCDMA_CHAN_CSRE_PAUSE;
- tdc_write(tdc, TEGRA_GPCDMA_CHAN_CSRE, val);
+ tdc_write(tdc, tdc->regs->csre, val);
tdc->status = DMA_IN_PROGRESS;
}
@@ -456,27 +438,27 @@ static void tegra_dma_disable(struct tegra_dma_channel *tdc)
{
u32 csr, status;
- csr = tdc_read(tdc, TEGRA_GPCDMA_CHAN_CSR);
+ csr = tdc_read(tdc, tdc->regs->csr);
/* Disable interrupts */
csr &= ~TEGRA_GPCDMA_CSR_IE_EOC;
/* Disable DMA */
csr &= ~TEGRA_GPCDMA_CSR_ENB;
- tdc_write(tdc, TEGRA_GPCDMA_CHAN_CSR, csr);
+ tdc_write(tdc, tdc->regs->csr, csr);
/* Clear interrupt status if it is there */
- status = tdc_read(tdc, TEGRA_GPCDMA_CHAN_STATUS);
+ status = tdc_read(tdc, tdc->regs->status);
if (status & TEGRA_GPCDMA_STATUS_ISE_EOC) {
dev_dbg(tdc2dev(tdc), "%s():clearing interrupt\n", __func__);
- tdc_write(tdc, TEGRA_GPCDMA_CHAN_STATUS, status);
+ tdc_write(tdc, tdc->regs->status, status);
}
}
static void tegra_dma_configure_next_sg(struct tegra_dma_channel *tdc)
{
struct tegra_dma_desc *dma_desc = tdc->dma_desc;
- struct tegra_dma_channel_regs *ch_regs;
+ struct tegra_dma_sg_req *sg_req;
int ret;
u32 val;
@@ -488,29 +470,29 @@ static void tegra_dma_configure_next_sg(struct tegra_dma_channel *tdc)
/* Configure next transfer immediately after DMA is busy */
ret = readl_relaxed_poll_timeout_atomic(tdc->tdma->base_addr +
- tdc->chan_base_offset + TEGRA_GPCDMA_CHAN_STATUS,
+ tdc->chan_base_offset + tdc->regs->status,
val,
(val & TEGRA_GPCDMA_STATUS_BUSY), 0,
TEGRA_GPCDMA_BURST_COMPLETION_TIMEOUT);
if (ret)
return;
- ch_regs = &dma_desc->sg_req[dma_desc->sg_idx].ch_regs;
+ sg_req = &dma_desc->sg_req[dma_desc->sg_idx];
- tdc_write(tdc, TEGRA_GPCDMA_CHAN_WCOUNT, ch_regs->wcount);
- tdc_write(tdc, TEGRA_GPCDMA_CHAN_SRC_PTR, ch_regs->src_ptr);
- tdc_write(tdc, TEGRA_GPCDMA_CHAN_DST_PTR, ch_regs->dst_ptr);
- tdc_write(tdc, TEGRA_GPCDMA_CHAN_HIGH_ADDR_PTR, ch_regs->high_addr_ptr);
+ tdc_write(tdc, tdc->regs->wcount, sg_req->wcount);
+ tdc_write(tdc, tdc->regs->src, sg_req->src);
+ tdc_write(tdc, tdc->regs->dst, sg_req->dst);
+ tdc_write(tdc, tdc->regs->high_addr, sg_req->high_addr);
/* Start DMA */
- tdc_write(tdc, TEGRA_GPCDMA_CHAN_CSR,
- ch_regs->csr | TEGRA_GPCDMA_CSR_ENB);
+ tdc_write(tdc, tdc->regs->csr,
+ sg_req->csr | TEGRA_GPCDMA_CSR_ENB);
}
static void tegra_dma_start(struct tegra_dma_channel *tdc)
{
struct tegra_dma_desc *dma_desc = tdc->dma_desc;
- struct tegra_dma_channel_regs *ch_regs;
+ struct tegra_dma_sg_req *sg_req;
struct virt_dma_desc *vdesc;
if (!dma_desc) {
@@ -526,21 +508,21 @@ static void tegra_dma_start(struct tegra_dma_channel *tdc)
tegra_dma_resume(tdc);
}
- ch_regs = &dma_desc->sg_req[dma_desc->sg_idx].ch_regs;
+ sg_req = &dma_desc->sg_req[dma_desc->sg_idx];
- tdc_write(tdc, TEGRA_GPCDMA_CHAN_WCOUNT, ch_regs->wcount);
- tdc_write(tdc, TEGRA_GPCDMA_CHAN_CSR, 0);
- tdc_write(tdc, TEGRA_GPCDMA_CHAN_SRC_PTR, ch_regs->src_ptr);
- tdc_write(tdc, TEGRA_GPCDMA_CHAN_DST_PTR, ch_regs->dst_ptr);
- tdc_write(tdc, TEGRA_GPCDMA_CHAN_HIGH_ADDR_PTR, ch_regs->high_addr_ptr);
- tdc_write(tdc, TEGRA_GPCDMA_CHAN_FIXED_PATTERN, ch_regs->fixed_pattern);
- tdc_write(tdc, TEGRA_GPCDMA_CHAN_MMIOSEQ, ch_regs->mmio_seq);
- tdc_write(tdc, TEGRA_GPCDMA_CHAN_MCSEQ, ch_regs->mc_seq);
- tdc_write(tdc, TEGRA_GPCDMA_CHAN_CSR, ch_regs->csr);
+ tdc_write(tdc, tdc->regs->wcount, sg_req->wcount);
+ tdc_write(tdc, tdc->regs->csr, 0);
+ tdc_write(tdc, tdc->regs->src, sg_req->src);
+ tdc_write(tdc, tdc->regs->dst, sg_req->dst);
+ tdc_write(tdc, tdc->regs->high_addr, sg_req->high_addr);
+ tdc_write(tdc, tdc->regs->fixed_pattern, sg_req->fixed_pattern);
+ tdc_write(tdc, tdc->regs->mmio_seq, sg_req->mmio_seq);
+ tdc_write(tdc, tdc->regs->mc_seq, sg_req->mc_seq);
+ tdc_write(tdc, tdc->regs->csr, sg_req->csr);
/* Start DMA */
- tdc_write(tdc, TEGRA_GPCDMA_CHAN_CSR,
- ch_regs->csr | TEGRA_GPCDMA_CSR_ENB);
+ tdc_write(tdc, tdc->regs->csr,
+ sg_req->csr | TEGRA_GPCDMA_CSR_ENB);
}
static void tegra_dma_xfer_complete(struct tegra_dma_channel *tdc)
@@ -601,19 +583,19 @@ static irqreturn_t tegra_dma_isr(int irq, void *dev_id)
u32 status;
/* Check channel error status register */
- status = tdc_read(tdc, TEGRA_GPCDMA_CHAN_ERR_STATUS);
+ status = tdc_read(tdc, tdc->regs->err_status);
if (status) {
tegra_dma_chan_decode_error(tdc, status);
tegra_dma_dump_chan_regs(tdc);
- tdc_write(tdc, TEGRA_GPCDMA_CHAN_ERR_STATUS, 0xFFFFFFFF);
+ tdc_write(tdc, tdc->regs->err_status, 0xFFFFFFFF);
}
spin_lock(&tdc->vc.lock);
- status = tdc_read(tdc, TEGRA_GPCDMA_CHAN_STATUS);
+ status = tdc_read(tdc, tdc->regs->status);
if (!(status & TEGRA_GPCDMA_STATUS_ISE_EOC))
goto irq_done;
- tdc_write(tdc, TEGRA_GPCDMA_CHAN_STATUS,
+ tdc_write(tdc, tdc->regs->status,
TEGRA_GPCDMA_STATUS_ISE_EOC);
if (!dma_desc)
@@ -673,10 +655,10 @@ static int tegra_dma_stop_client(struct tegra_dma_channel *tdc)
* to stop DMA engine from starting any more bursts for
* the given client and wait for in flight bursts to complete
*/
- csr = tdc_read(tdc, TEGRA_GPCDMA_CHAN_CSR);
+ csr = tdc_read(tdc, tdc->regs->csr);
csr &= ~(TEGRA_GPCDMA_CSR_REQ_SEL_MASK);
csr |= TEGRA_GPCDMA_CSR_REQ_SEL_UNUSED;
- tdc_write(tdc, TEGRA_GPCDMA_CHAN_CSR, csr);
+ tdc_write(tdc, tdc->regs->csr, csr);
/* Wait for in flight data transfer to finish */
udelay(TEGRA_GPCDMA_BURST_COMPLETE_TIME);
@@ -687,7 +669,7 @@ static int tegra_dma_stop_client(struct tegra_dma_channel *tdc)
ret = readl_relaxed_poll_timeout_atomic(tdc->tdma->base_addr +
tdc->chan_base_offset +
- TEGRA_GPCDMA_CHAN_STATUS,
+ tdc->regs->status,
status,
!(status & (TEGRA_GPCDMA_STATUS_CHANNEL_TX |
TEGRA_GPCDMA_STATUS_CHANNEL_RX)),
@@ -739,14 +721,14 @@ static int tegra_dma_get_residual(struct tegra_dma_channel *tdc)
unsigned int bytes_xfer, residual;
u32 wcount = 0, status;
- wcount = tdc_read(tdc, TEGRA_GPCDMA_CHAN_XFER_COUNT);
+ wcount = tdc_read(tdc, tdc->regs->wxfer);
/*
* Set wcount = 0 if EOC bit is set. The transfer would have
* already completed and the CHAN_XFER_COUNT could have updated
* for the next transfer, specifically in case of cyclic transfers.
*/
- status = tdc_read(tdc, TEGRA_GPCDMA_CHAN_STATUS);
+ status = tdc_read(tdc, tdc->regs->status);
if (status & TEGRA_GPCDMA_STATUS_ISE_EOC)
wcount = 0;
@@ -893,7 +875,7 @@ tegra_dma_prep_dma_memset(struct dma_chan *dc, dma_addr_t dest, int value,
/* Configure default priority weight for the channel */
csr |= FIELD_PREP(TEGRA_GPCDMA_CSR_WEIGHT, 1);
- mc_seq = tdc_read(tdc, TEGRA_GPCDMA_CHAN_MCSEQ);
+ mc_seq = tdc_read(tdc, tdc->regs->mc_seq);
/* retain stream-id and clean rest */
mc_seq &= TEGRA_GPCDMA_MCSEQ_STREAM_ID0_MASK;
@@ -916,16 +898,16 @@ tegra_dma_prep_dma_memset(struct dma_chan *dc, dma_addr_t dest, int value,
dma_desc->sg_count = 1;
sg_req = dma_desc->sg_req;
- sg_req[0].ch_regs.src_ptr = 0;
- sg_req[0].ch_regs.dst_ptr = dest;
- sg_req[0].ch_regs.high_addr_ptr =
+ sg_req[0].src = 0;
+ sg_req[0].dst = dest;
+ sg_req[0].high_addr =
FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_DST_PTR, (dest >> 32));
- sg_req[0].ch_regs.fixed_pattern = value;
+ sg_req[0].fixed_pattern = value;
/* Word count reg takes value as (N +1) words */
- sg_req[0].ch_regs.wcount = ((len - 4) >> 2);
- sg_req[0].ch_regs.csr = csr;
- sg_req[0].ch_regs.mmio_seq = 0;
- sg_req[0].ch_regs.mc_seq = mc_seq;
+ sg_req[0].wcount = ((len - 4) >> 2);
+ sg_req[0].csr = csr;
+ sg_req[0].mmio_seq = 0;
+ sg_req[0].mc_seq = mc_seq;
sg_req[0].len = len;
dma_desc->cyclic = false;
@@ -961,7 +943,7 @@ tegra_dma_prep_dma_memcpy(struct dma_chan *dc, dma_addr_t dest,
/* Configure default priority weight for the channel */
csr |= FIELD_PREP(TEGRA_GPCDMA_CSR_WEIGHT, 1);
- mc_seq = tdc_read(tdc, TEGRA_GPCDMA_CHAN_MCSEQ);
+ mc_seq = tdc_read(tdc, tdc->regs->mc_seq);
/* retain stream-id and clean rest */
mc_seq &= (TEGRA_GPCDMA_MCSEQ_STREAM_ID0_MASK) |
(TEGRA_GPCDMA_MCSEQ_STREAM_ID1_MASK);
@@ -985,17 +967,17 @@ tegra_dma_prep_dma_memcpy(struct dma_chan *dc, dma_addr_t dest,
dma_desc->sg_count = 1;
sg_req = dma_desc->sg_req;
- sg_req[0].ch_regs.src_ptr = src;
- sg_req[0].ch_regs.dst_ptr = dest;
- sg_req[0].ch_regs.high_addr_ptr =
+ sg_req[0].src = src;
+ sg_req[0].dst = dest;
+ sg_req[0].high_addr =
FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_SRC_PTR, (src >> 32));
- sg_req[0].ch_regs.high_addr_ptr |=
+ sg_req[0].high_addr |=
FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_DST_PTR, (dest >> 32));
/* Word count reg takes value as (N +1) words */
- sg_req[0].ch_regs.wcount = ((len - 4) >> 2);
- sg_req[0].ch_regs.csr = csr;
- sg_req[0].ch_regs.mmio_seq = 0;
- sg_req[0].ch_regs.mc_seq = mc_seq;
+ sg_req[0].wcount = ((len - 4) >> 2);
+ sg_req[0].csr = csr;
+ sg_req[0].mmio_seq = 0;
+ sg_req[0].mc_seq = mc_seq;
sg_req[0].len = len;
dma_desc->cyclic = false;
@@ -1049,7 +1031,7 @@ tegra_dma_prep_slave_sg(struct dma_chan *dc, struct scatterlist *sgl,
if (flags & DMA_PREP_INTERRUPT)
csr |= TEGRA_GPCDMA_CSR_IE_EOC;
- mc_seq = tdc_read(tdc, TEGRA_GPCDMA_CHAN_MCSEQ);
+ mc_seq = tdc_read(tdc, tdc->regs->mc_seq);
/* retain stream-id and clean rest */
mc_seq &= TEGRA_GPCDMA_MCSEQ_STREAM_ID0_MASK;
@@ -1096,14 +1078,14 @@ tegra_dma_prep_slave_sg(struct dma_chan *dc, struct scatterlist *sgl,
dma_desc->bytes_req += len;
if (direction == DMA_MEM_TO_DEV) {
- sg_req[i].ch_regs.src_ptr = mem;
- sg_req[i].ch_regs.dst_ptr = apb_ptr;
- sg_req[i].ch_regs.high_addr_ptr =
+ sg_req[i].src = mem;
+ sg_req[i].dst = apb_ptr;
+ sg_req[i].high_addr =
FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_SRC_PTR, (mem >> 32));
} else if (direction == DMA_DEV_TO_MEM) {
- sg_req[i].ch_regs.src_ptr = apb_ptr;
- sg_req[i].ch_regs.dst_ptr = mem;
- sg_req[i].ch_regs.high_addr_ptr =
+ sg_req[i].src = apb_ptr;
+ sg_req[i].dst = mem;
+ sg_req[i].high_addr =
FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_DST_PTR, (mem >> 32));
}
@@ -1111,10 +1093,10 @@ tegra_dma_prep_slave_sg(struct dma_chan *dc, struct scatterlist *sgl,
* Word count register takes input in words. Writing a value
* of N into word count register means a req of (N+1) words.
*/
- sg_req[i].ch_regs.wcount = ((len - 4) >> 2);
- sg_req[i].ch_regs.csr = csr;
- sg_req[i].ch_regs.mmio_seq = mmio_seq;
- sg_req[i].ch_regs.mc_seq = mc_seq;
+ sg_req[i].wcount = ((len - 4) >> 2);
+ sg_req[i].csr = csr;
+ sg_req[i].mmio_seq = mmio_seq;
+ sg_req[i].mc_seq = mc_seq;
sg_req[i].len = len;
}
@@ -1186,7 +1168,7 @@ tegra_dma_prep_dma_cyclic(struct dma_chan *dc, dma_addr_t buf_addr, size_t buf_l
mmio_seq |= FIELD_PREP(TEGRA_GPCDMA_MMIOSEQ_WRAP_WORD, 1);
- mc_seq = tdc_read(tdc, TEGRA_GPCDMA_CHAN_MCSEQ);
+ mc_seq = tdc_read(tdc, tdc->regs->mc_seq);
/* retain stream-id and clean rest */
mc_seq &= TEGRA_GPCDMA_MCSEQ_STREAM_ID0_MASK;
@@ -1217,24 +1199,24 @@ tegra_dma_prep_dma_cyclic(struct dma_chan *dc, dma_addr_t buf_addr, size_t buf_l
for (i = 0; i < period_count; i++) {
mmio_seq |= get_burst_size(tdc, burst_size, slave_bw, len);
if (direction == DMA_MEM_TO_DEV) {
- sg_req[i].ch_regs.src_ptr = mem;
- sg_req[i].ch_regs.dst_ptr = apb_ptr;
- sg_req[i].ch_regs.high_addr_ptr =
+ sg_req[i].src = mem;
+ sg_req[i].dst = apb_ptr;
+ sg_req[i].high_addr =
FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_SRC_PTR, (mem >> 32));
} else if (direction == DMA_DEV_TO_MEM) {
- sg_req[i].ch_regs.src_ptr = apb_ptr;
- sg_req[i].ch_regs.dst_ptr = mem;
- sg_req[i].ch_regs.high_addr_ptr =
+ sg_req[i].src = apb_ptr;
+ sg_req[i].dst = mem;
+ sg_req[i].high_addr =
FIELD_PREP(TEGRA_GPCDMA_HIGH_ADDR_DST_PTR, (mem >> 32));
}
/*
* Word count register takes input in words. Writing a value
* of N into word count register means a req of (N+1) words.
*/
- sg_req[i].ch_regs.wcount = ((len - 4) >> 2);
- sg_req[i].ch_regs.csr = csr;
- sg_req[i].ch_regs.mmio_seq = mmio_seq;
- sg_req[i].ch_regs.mc_seq = mc_seq;
+ sg_req[i].wcount = ((len - 4) >> 2);
+ sg_req[i].csr = csr;
+ sg_req[i].mmio_seq = mmio_seq;
+ sg_req[i].mc_seq = mc_seq;
sg_req[i].len = len;
mem += len;
@@ -1304,11 +1286,28 @@ static struct dma_chan *tegra_dma_of_xlate(struct of_phandle_args *dma_spec,
return chan;
}
+static const struct tegra_dma_channel_regs tegra186_reg_offsets = {
+ .csr = 0x0,
+ .status = 0x4,
+ .csre = 0x8,
+ .src = 0xc,
+ .dst = 0x10,
+ .high_addr = 0x14,
+ .mc_seq = 0x18,
+ .mmio_seq = 0x1c,
+ .wcount = 0x20,
+ .wxfer = 0x24,
+ .wstatus = 0x28,
+ .err_status = 0x30,
+ .fixed_pattern = 0x34,
+};
+
static const struct tegra_dma_chip_data tegra186_dma_chip_data = {
.nr_channels = 32,
.channel_reg_size = SZ_64K,
.max_dma_count = SZ_1G,
.hw_support_pause = false,
+ .channel_regs = &tegra186_reg_offsets,
.terminate = tegra_dma_stop_client,
};
@@ -1317,6 +1316,7 @@ static const struct tegra_dma_chip_data tegra194_dma_chip_data = {
.channel_reg_size = SZ_64K,
.max_dma_count = SZ_1G,
.hw_support_pause = true,
+ .channel_regs = &tegra186_reg_offsets,
.terminate = tegra_dma_pause,
};
@@ -1325,6 +1325,7 @@ static const struct tegra_dma_chip_data tegra234_dma_chip_data = {
.channel_reg_size = SZ_64K,
.max_dma_count = SZ_1G,
.hw_support_pause = true,
+ .channel_regs = &tegra186_reg_offsets,
.terminate = tegra_dma_pause_noerr,
};
@@ -1345,7 +1346,7 @@ MODULE_DEVICE_TABLE(of, tegra_dma_of_match);
static int tegra_dma_program_sid(struct tegra_dma_channel *tdc, int stream_id)
{
- unsigned int reg_val = tdc_read(tdc, TEGRA_GPCDMA_CHAN_MCSEQ);
+ unsigned int reg_val = tdc_read(tdc, tdc->regs->mc_seq);
reg_val &= ~(TEGRA_GPCDMA_MCSEQ_STREAM_ID0_MASK);
reg_val &= ~(TEGRA_GPCDMA_MCSEQ_STREAM_ID1_MASK);
@@ -1353,7 +1354,7 @@ static int tegra_dma_program_sid(struct tegra_dma_channel *tdc, int stream_id)
reg_val |= FIELD_PREP(TEGRA_GPCDMA_MCSEQ_STREAM_ID0_MASK, stream_id);
reg_val |= FIELD_PREP(TEGRA_GPCDMA_MCSEQ_STREAM_ID1_MASK, stream_id);
- tdc_write(tdc, TEGRA_GPCDMA_CHAN_MCSEQ, reg_val);
+ tdc_write(tdc, tdc->regs->mc_seq, reg_val);
return 0;
}
@@ -1419,6 +1420,7 @@ static int tegra_dma_probe(struct platform_device *pdev)
tdc->chan_base_offset = TEGRA_GPCDMA_CHANNEL_BASE_ADDR_OFFSET +
i * cdata->channel_reg_size;
snprintf(tdc->name, sizeof(tdc->name), "gpcdma.%d", i);
+ tdc->regs = cdata->channel_regs;
tdc->tdma = tdma;
tdc->id = i;
tdc->slave_id = -1;
--
2.50.1
^ permalink raw reply related
* [PATCH v6 03/10] dt-bindings: dma: nvidia,tegra186-gpc-dma: Add iommu-map property
From: Akhil R @ 2026-03-31 10:22 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Thierry Reding, Jonathan Hunter, Laxman Dewangan,
Philipp Zabel, dmaengine, devicetree, linux-tegra, linux-kernel
Cc: Akhil R
In-Reply-To: <20260331102303.33181-1-akhilrajeev@nvidia.com>
Add iommu-map property to specify separate stream IDs for each DMA
channel. This enables each channel to be in its own IOMMU domain,
keeping memory isolated from other devices sharing the same DMA
controller.
Define the constraints such that if the channel and stream IDs are
contiguous, a single entry can map all the channels, but if the
channels or stream IDs are non-contiguous support multiple entries.
Signed-off-by: Akhil R <akhilrajeev@nvidia.com>
Acked-by: Rob Herring (Arm) <robh@kernel.org>
---
.../devicetree/bindings/dma/nvidia,tegra186-gpc-dma.yaml | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/Documentation/devicetree/bindings/dma/nvidia,tegra186-gpc-dma.yaml b/Documentation/devicetree/bindings/dma/nvidia,tegra186-gpc-dma.yaml
index 64f1e9d9896d..bc093c783d98 100644
--- a/Documentation/devicetree/bindings/dma/nvidia,tegra186-gpc-dma.yaml
+++ b/Documentation/devicetree/bindings/dma/nvidia,tegra186-gpc-dma.yaml
@@ -14,6 +14,7 @@ description: |
maintainers:
- Jon Hunter <jonathanh@nvidia.com>
- Rajesh Gumasta <rgumasta@nvidia.com>
+ - Akhil R <akhilrajeev@nvidia.com>
properties:
compatible:
@@ -49,6 +50,14 @@ properties:
iommus:
maxItems: 1
+ iommu-map:
+ description:
+ Maps DMA channel numbers to IOMMU stream IDs. A single entry can map all
+ channels when stream IDs are contiguous. In systems where the channels or
+ stream IDs are not contiguous, multiple entries may be needed.
+ minItems: 1
+ maxItems: 32
+
dma-coherent: true
dma-channel-mask:
--
2.50.1
^ permalink raw reply related
* [PATCH v6 04/10] dmaengine: tegra: Make reset control optional
From: Akhil R @ 2026-03-31 10:22 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Thierry Reding, Jonathan Hunter, Laxman Dewangan,
Philipp Zabel, dmaengine, devicetree, linux-tegra, linux-kernel
Cc: Akhil R, Frank Li
In-Reply-To: <20260331102303.33181-1-akhilrajeev@nvidia.com>
On Tegra264, reset is not available for the driver to control as
this is handled by the boot firmware. Hence make the reset control
optional and update the error message to reflect the correct error.
Signed-off-by: Akhil R <akhilrajeev@nvidia.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
---
drivers/dma/tegra186-gpc-dma.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/dma/tegra186-gpc-dma.c b/drivers/dma/tegra186-gpc-dma.c
index 5948fbf32c21..a0522a992ebc 100644
--- a/drivers/dma/tegra186-gpc-dma.c
+++ b/drivers/dma/tegra186-gpc-dma.c
@@ -1381,10 +1381,10 @@ static int tegra_dma_probe(struct platform_device *pdev)
if (IS_ERR(tdma->base_addr))
return PTR_ERR(tdma->base_addr);
- tdma->rst = devm_reset_control_get_exclusive(&pdev->dev, "gpcdma");
+ tdma->rst = devm_reset_control_get_optional_exclusive(&pdev->dev, "gpcdma");
if (IS_ERR(tdma->rst)) {
return dev_err_probe(&pdev->dev, PTR_ERR(tdma->rst),
- "Missing controller reset\n");
+ "Failed to get controller reset\n");
}
reset_control_reset(tdma->rst);
--
2.50.1
^ permalink raw reply related
* [PATCH v6 02/10] arm64: tegra: Remove fallback compatible for GPCDMA
From: Akhil R @ 2026-03-31 10:22 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Thierry Reding, Jonathan Hunter, Laxman Dewangan,
Philipp Zabel, dmaengine, devicetree, linux-tegra, linux-kernel
Cc: Akhil R
In-Reply-To: <20260331102303.33181-1-akhilrajeev@nvidia.com>
Remove the fallback compatible string "nvidia,tegra186-gpcdma" for GPCDMA
in Tegra264. Tegra186 compatible cannot work on Tegra264 because of the
register offset changes and absence of the reset property.
Fixes: 65ef237e4810 ("arm64: tegra: Add Tegra264 support")
Signed-off-by: Akhil R <akhilrajeev@nvidia.com>
---
arch/arm64/boot/dts/nvidia/tegra264.dtsi | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/nvidia/tegra264.dtsi b/arch/arm64/boot/dts/nvidia/tegra264.dtsi
index 24cc2c51a272..af077420d7d9 100644
--- a/arch/arm64/boot/dts/nvidia/tegra264.dtsi
+++ b/arch/arm64/boot/dts/nvidia/tegra264.dtsi
@@ -3208,7 +3208,7 @@ agic_page5: interrupt-controller@99b0000 {
};
gpcdma: dma-controller@8400000 {
- compatible = "nvidia,tegra264-gpcdma", "nvidia,tegra186-gpcdma";
+ compatible = "nvidia,tegra264-gpcdma";
reg = <0x0 0x08400000 0x0 0x210000>;
interrupts = <GIC_SPI 584 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 585 IRQ_TYPE_LEVEL_HIGH>,
--
2.50.1
^ permalink raw reply related
* [PATCH v6 01/10] dt-bindings: dma: nvidia,tegra186-gpc-dma: Make reset optional
From: Akhil R @ 2026-03-31 10:22 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Thierry Reding, Jonathan Hunter, Laxman Dewangan,
Philipp Zabel, dmaengine, devicetree, linux-tegra, linux-kernel
Cc: Akhil R
In-Reply-To: <20260331102303.33181-1-akhilrajeev@nvidia.com>
On Tegra264, GPCDMA reset control is not exposed to Linux and is handled
by the boot firmware.
Although reset was not exposed in Tegra234 as well, the firmware supported
a dummy reset which just returns success on reset without doing an actual
reset. This is also not supported in Tegra264 BPMP. Therefore mark 'reset'
and 'reset-names' properties as required only for devices prior to
Tegra264.
This also necessitates that the Tegra264 compatible be standalone and
cannot have the fallback compatible of Tegra186. Since there is no
functional impact, we keep reset as required for Tegra234 to avoid
breaking the ABI.
Fixes: bb8c97571db5 ("dt-bindings: dma: Add Tegra264 compatible string")
Signed-off-by: Akhil R <akhilrajeev@nvidia.com>
Acked-by: Rob Herring (Arm) <robh@kernel.org>
---
.../bindings/dma/nvidia,tegra186-gpc-dma.yaml | 23 +++++++++++++------
1 file changed, 16 insertions(+), 7 deletions(-)
diff --git a/Documentation/devicetree/bindings/dma/nvidia,tegra186-gpc-dma.yaml b/Documentation/devicetree/bindings/dma/nvidia,tegra186-gpc-dma.yaml
index 0dabe9bbb219..64f1e9d9896d 100644
--- a/Documentation/devicetree/bindings/dma/nvidia,tegra186-gpc-dma.yaml
+++ b/Documentation/devicetree/bindings/dma/nvidia,tegra186-gpc-dma.yaml
@@ -15,16 +15,14 @@ maintainers:
- Jon Hunter <jonathanh@nvidia.com>
- Rajesh Gumasta <rgumasta@nvidia.com>
-allOf:
- - $ref: dma-controller.yaml#
-
properties:
compatible:
oneOf:
- - const: nvidia,tegra186-gpcdma
+ - enum:
+ - nvidia,tegra264-gpcdma
+ - nvidia,tegra186-gpcdma
- items:
- enum:
- - nvidia,tegra264-gpcdma
- nvidia,tegra234-gpcdma
- nvidia,tegra194-gpcdma
- const: nvidia,tegra186-gpcdma
@@ -60,12 +58,23 @@ required:
- compatible
- reg
- interrupts
- - resets
- - reset-names
- "#dma-cells"
- iommus
- dma-channel-mask
+allOf:
+ - $ref: dma-controller.yaml#
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - nvidia,tegra186-gpcdma
+ then:
+ required:
+ - resets
+ - reset-names
+
additionalProperties: false
examples:
--
2.50.1
^ permalink raw reply related
* [PATCH v6 00/10] Add GPCDMA support in Tegra264
From: Akhil R @ 2026-03-31 10:22 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Thierry Reding, Jonathan Hunter, Laxman Dewangan,
Philipp Zabel, dmaengine, devicetree, linux-tegra, linux-kernel
Cc: Akhil R
This series adds support for GPCDMA in Tegra264 with additional
support for separate stream ID for each channel. Tegra264 GPCDMA
controller has changes in the register offsets and uses 41-bit
addressing for memory. Add changes in the tegra186-gpc-dma driver
to support these.
v5->v6:
- Replace dev_err() with dev_err_probe() in the probe function for fixed
return values also.
v4->v5:
- Use dev_err_probe() when returning error from the probe function.
- Remove tegra194 and tegra234 compatible from the reset 'if' condition
in the bindings as suggested in v2 (which I missed).
v3->v4:
- Split device tree changes to two patches.
- Reordered patches to have fixes first.
- Added fixes tag to dt-bindings and device tree changes.
v2->v3:
- Add description for iommu-map property and update commit descriptions.
- Use enum for compatible string instead of const.
- Remove unused registers from struct tegra_dma_channel_regs.
- Use devm_of_dma_controller_register() to register the DMA controller.
- Remove return value check for mask setting in the driver as the bitmask
value is always greater than 32.
v1->v2:
- Fix dt_bindings_check warnings
- Drop fallback compatible "nvidia,tegra186-gpcdma" from Tegra264 DT
- Use dma_addr_t for sg_req src/dst fields and drop separate high_add
variable and check for the addr_bits only when programming the
registers.
- Update address width to 39 bits for Tegra234 and before since the SMMU
supports only up to 39 bits till Tegra234.
- Add a patch to do managed DMA controller registration.
- Describe the second iteration in the probe.
- Update commit descriptions.
Akhil R (10):
dt-bindings: dma: nvidia,tegra186-gpc-dma: Make reset optional
arm64: tegra: Remove fallback compatible for GPCDMA
dt-bindings: dma: nvidia,tegra186-gpc-dma: Add iommu-map property
dmaengine: tegra: Make reset control optional
dmaengine: tegra: Use struct for register offsets
dmaengine: tegra: Support address width > 39 bits
dmaengine: tegra: Use managed DMA controller registration
dmaengine: tegra: Use iommu-map for stream ID
dmaengine: tegra: Add Tegra264 support
arm64: tegra: Enable GPCDMA in Tegra264 and add iommu-map
.../bindings/dma/nvidia,tegra186-gpc-dma.yaml | 32 +-
.../arm64/boot/dts/nvidia/tegra264-p3834.dtsi | 4 +
arch/arm64/boot/dts/nvidia/tegra264.dtsi | 3 +-
drivers/dma/tegra186-gpc-dma.c | 429 +++++++++++-------
4 files changed, 284 insertions(+), 184 deletions(-)
--
2.50.1
^ permalink raw reply
* Re: [PATCH v2 4/4] dmaengine: dma-axi-dmac: Defer freeing DMA descriptors
From: Nuno Sá @ 2026-03-31 8:53 UTC (permalink / raw)
To: Frank Li
Cc: Nuno Sá, dmaengine, linux-kernel, Lars-Peter Clausen,
Vinod Koul, Frank Li, Eliza Balas
In-Reply-To: <acqVsvQo87NvlqU7@lizhi-Precision-Tower-5810>
On Mon, Mar 30, 2026 at 11:24:34AM -0400, Frank Li wrote:
> On Fri, Mar 27, 2026 at 04:58:41PM +0000, Nuno Sá wrote:
> > From: Eliza Balas <eliza.balas@analog.com>
> >
> > This IP core can be used in architectures (like Microblaze) where DMA
> > descriptors are allocated with vmalloc().
>
> strage, why use vmalloc()?
It's just one of the paths in dma_alloc_coherent(). It should be
architecture dependant.
- Nuno Sá
>
> Frank
>
> > Hence, given that freeing the
> > descriptors happen in softirq context, vunmpap() will BUG().
> >
> > To solve the above, we setup a work item during allocation of the
> > descriptors and schedule in softirq context. Hence, the actual freeing
> > happens in threaded context.
> >
> > Also note that to account for the possible race where the struct axi_dmac
> > object is gone between scheduling the work and actually running it, we
> > now save and get a reference of struct device when allocating the
> > descriptor (given that's all we need in axi_dmac_free_desc()) and
> > release it in axi_dmac_free_desc().
> >
> > Signed-off-by: Eliza Balas <eliza.balas@analog.com>
> > Co-developed-by: Nuno Sá <nuno.sa@analog.com>
> > Signed-off-by: Nuno Sá <nuno.sa@analog.com>
> > ---
> > drivers/dma/dma-axi-dmac.c | 50 ++++++++++++++++++++++++++++++++++------------
> > 1 file changed, 37 insertions(+), 13 deletions(-)
> >
> > diff --git a/drivers/dma/dma-axi-dmac.c b/drivers/dma/dma-axi-dmac.c
> > index 70d3ad7e7d37..46f1ead0c7d7 100644
> > --- a/drivers/dma/dma-axi-dmac.c
> > +++ b/drivers/dma/dma-axi-dmac.c
> > @@ -25,6 +25,7 @@
> > #include <linux/regmap.h>
> > #include <linux/slab.h>
> > #include <linux/spinlock.h>
> > +#include <linux/workqueue.h>
> >
> > #include <dt-bindings/dma/axi-dmac.h>
> >
> > @@ -133,6 +134,9 @@ struct axi_dmac_sg {
> > struct axi_dmac_desc {
> > struct virt_dma_desc vdesc;
> > struct axi_dmac_chan *chan;
> > + struct device *dev;
> > +
> > + struct work_struct sched_work;
> >
> > bool cyclic;
> > bool cyclic_eot;
> > @@ -666,6 +670,25 @@ static void axi_dmac_issue_pending(struct dma_chan *c)
> > spin_unlock_irqrestore(&chan->vchan.lock, flags);
> > }
> >
> > +static void axi_dmac_free_desc(struct axi_dmac_desc *desc)
> > +{
> > + struct axi_dmac_hw_desc *hw = desc->sg[0].hw;
> > + dma_addr_t hw_phys = desc->sg[0].hw_phys;
> > +
> > + dma_free_coherent(desc->dev, PAGE_ALIGN(desc->num_sgs * sizeof(*hw)),
> > + hw, hw_phys);
> > + put_device(desc->dev);
> > + kfree(desc);
> > +}
> > +
> > +static void axi_dmac_free_desc_schedule_work(struct work_struct *work)
> > +{
> > + struct axi_dmac_desc *desc = container_of(work, struct axi_dmac_desc,
> > + sched_work);
> > +
> > + axi_dmac_free_desc(desc);
> > +}
> > +
> > static struct axi_dmac_desc *
> > axi_dmac_alloc_desc(struct axi_dmac_chan *chan, unsigned int num_sgs)
> > {
> > @@ -681,6 +704,7 @@ axi_dmac_alloc_desc(struct axi_dmac_chan *chan, unsigned int num_sgs)
> > return NULL;
> > desc->num_sgs = num_sgs;
> > desc->chan = chan;
> > + desc->dev = get_device(dmac->dma_dev.dev);
> >
> > hws = dma_alloc_coherent(dev, PAGE_ALIGN(num_sgs * sizeof(*hws)),
> > &hw_phys, GFP_ATOMIC);
> > @@ -703,21 +727,18 @@ axi_dmac_alloc_desc(struct axi_dmac_chan *chan, unsigned int num_sgs)
> > /* The last hardware descriptor will trigger an interrupt */
> > desc->sg[num_sgs - 1].hw->flags = AXI_DMAC_HW_FLAG_LAST | AXI_DMAC_HW_FLAG_IRQ;
> >
> > + /*
> > + * We need to setup a work item because this IP can be used on archs
> > + * that rely on vmalloced memory for descriptors. And given that freeing
> > + * the descriptors happens in softirq context, vunmpap() will BUG().
> > + * Hence, setup the worker so that we can queue it and free the
> > + * descriptor in threaded context.
> > + */
> > + INIT_WORK(&desc->sched_work, axi_dmac_free_desc_schedule_work);
> > +
> > return desc;
> > }
> >
> > -static void axi_dmac_free_desc(struct axi_dmac_desc *desc)
> > -{
> > - struct axi_dmac *dmac = chan_to_axi_dmac(desc->chan);
> > - struct device *dev = dmac->dma_dev.dev;
> > - struct axi_dmac_hw_desc *hw = desc->sg[0].hw;
> > - dma_addr_t hw_phys = desc->sg[0].hw_phys;
> > -
> > - dma_free_coherent(dev, PAGE_ALIGN(desc->num_sgs * sizeof(*hw)),
> > - hw, hw_phys);
> > - kfree(desc);
> > -}
> > -
> > static struct axi_dmac_sg *axi_dmac_fill_linear_sg(struct axi_dmac_chan *chan,
> > enum dma_transfer_direction direction, dma_addr_t addr,
> > unsigned int num_periods, unsigned int period_len,
> > @@ -958,7 +979,10 @@ static void axi_dmac_free_chan_resources(struct dma_chan *c)
> >
> > static void axi_dmac_desc_free(struct virt_dma_desc *vdesc)
> > {
> > - axi_dmac_free_desc(to_axi_dmac_desc(vdesc));
> > + struct axi_dmac_desc *desc = to_axi_dmac_desc(vdesc);
> > +
> > + /* See the comment in axi_dmac_alloc_desc() for the why! */
> > + schedule_work(&desc->sched_work);
> > }
> >
> > static bool axi_dmac_regmap_rdwr(struct device *dev, unsigned int reg)
> >
> > --
> > 2.53.0
> >
^ permalink raw reply
* Re: [PATCH v2 3/4] dmaengine: dma-axi-dmac: fix use-after-free on unbind
From: Nuno Sá @ 2026-03-31 8:46 UTC (permalink / raw)
To: Frank Li
Cc: Nuno Sá, dmaengine, linux-kernel, Lars-Peter Clausen,
Vinod Koul, Frank Li
In-Reply-To: <acqVIrUwtIM5AaG3@lizhi-Precision-Tower-5810>
On Mon, Mar 30, 2026 at 11:22:10AM -0400, Frank Li wrote:
> On Fri, Mar 27, 2026 at 04:58:40PM +0000, Nuno Sá wrote:
> > The DMA device lifetime can extend beyond the platform driver unbind if
> > DMA channels are still referenced by client drivers. This leads to
> > use-after-free when the devm-managed memory is freed on unbind but the
> > DMA device callbacks still access it.
> >
> > Fix this by:
> > - Allocating axi_dmac with kzalloc_obj() instead of devm_kzalloc() so
> > its lifetime is not tied to the platform device.
> > - Implementing the device_release callback that so that we can free
> > the object when reference count gets to 0 (no users).
> > - Adding an 'unbound' flag protected by the vchan lock that is set
> > during driver removal, preventing MMIO accesses after the device has been
> > unbound.
> >
> > While at it, explicitly include spinlock.h given it was missing.
> >
> > Signed-off-by: Nuno Sá <nuno.sa@analog.com>
> > ---
>
> Not sure if it similar with
> https://lore.kernel.org/dmaengine/20250903-v6-16-topic-sdma-v1-9-ac7bab629e8b@pengutronix.de/
>
> It looks like miss device link between comsumer and provider.
Well, it surely it's related. I mean, if we ensure the consumers are
gone through devlinks and nothing is left behind, then this patch is basically unneeded.
But, FWIW, my 2cents would also go into questioning if AUTOREMOVE is
really want we want in every situation? Might be to harsh to assume that
a DMA channel consumer is useless even if DMA is gone. Anyways, is there
a v2 already? I would be interested in following this one...
- Nuno Sá
>
> Frank
>
> > drivers/dma/dma-axi-dmac.c | 70 +++++++++++++++++++++++++++++++++++++++-------
> > 1 file changed, 60 insertions(+), 10 deletions(-)
> >
> > diff --git a/drivers/dma/dma-axi-dmac.c b/drivers/dma/dma-axi-dmac.c
> > index 127c3cf80a0e..70d3ad7e7d37 100644
> > --- a/drivers/dma/dma-axi-dmac.c
> > +++ b/drivers/dma/dma-axi-dmac.c
> > @@ -24,6 +24,7 @@
> > #include <linux/platform_device.h>
> > #include <linux/regmap.h>
> > #include <linux/slab.h>
> > +#include <linux/spinlock.h>
> >
> > #include <dt-bindings/dma/axi-dmac.h>
> >
> > @@ -174,6 +175,8 @@ struct axi_dmac {
> >
> > struct dma_device dma_dev;
> > struct axi_dmac_chan chan;
> > +
> > + bool unbound;
> > };
> >
> > static struct axi_dmac *chan_to_axi_dmac(struct axi_dmac_chan *chan)
> > @@ -182,6 +185,11 @@ static struct axi_dmac *chan_to_axi_dmac(struct axi_dmac_chan *chan)
> > dma_dev);
> > }
> >
> > +static struct axi_dmac *dev_to_axi_dmac(struct dma_device *dev)
> > +{
> > + return container_of(dev, struct axi_dmac, dma_dev);
> > +}
> > +
> > static struct axi_dmac_chan *to_axi_dmac_chan(struct dma_chan *c)
> > {
> > return container_of(c, struct axi_dmac_chan, vchan.chan);
> > @@ -614,7 +622,12 @@ static int axi_dmac_terminate_all(struct dma_chan *c)
> > LIST_HEAD(head);
> >
> > spin_lock_irqsave(&chan->vchan.lock, flags);
> > - axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, 0);
> > + /*
> > + * Only allow the MMIO access if the device is live. Otherwise still
> > + * go for freeing the descriptors.
> > + */
> > + if (!dmac->unbound)
> > + axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, 0);
> > chan->next_desc = NULL;
> > vchan_get_all_descriptors(&chan->vchan, &head);
> > list_splice_tail_init(&chan->active_descs, &head);
> > @@ -642,9 +655,12 @@ static void axi_dmac_issue_pending(struct dma_chan *c)
> > if (chan->hw_sg)
> > ctrl |= AXI_DMAC_CTRL_ENABLE_SG;
> >
> > - axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, ctrl);
> > -
> > spin_lock_irqsave(&chan->vchan.lock, flags);
> > + if (dmac->unbound) {
> > + spin_unlock_irqrestore(&chan->vchan.lock, flags);
> > + return;
> > + }
> > + axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, ctrl);
> > if (vchan_issue_pending(&chan->vchan))
> > axi_dmac_start_transfer(chan);
> > spin_unlock_irqrestore(&chan->vchan.lock, flags);
> > @@ -1184,6 +1200,14 @@ static int axi_dmac_detect_caps(struct axi_dmac *dmac, unsigned int version)
> > return 0;
> > }
> >
> > +static void axi_dmac_release(struct dma_device *dma_dev)
> > +{
> > + struct axi_dmac *dmac = dev_to_axi_dmac(dma_dev);
> > +
> > + put_device(dma_dev->dev);
> > + kfree(dmac);
> > +}
> > +
> > static void axi_dmac_tasklet_kill(void *task)
> > {
> > tasklet_kill(task);
> > @@ -1194,16 +1218,27 @@ static void axi_dmac_free_dma_controller(void *of_node)
> > of_dma_controller_free(of_node);
> > }
> >
> > +static void axi_dmac_disable(void *__dmac)
> > +{
> > + struct axi_dmac *dmac = __dmac;
> > + unsigned long flags;
> > +
> > + spin_lock_irqsave(&dmac->chan.vchan.lock, flags);
> > + dmac->unbound = true;
> > + spin_unlock_irqrestore(&dmac->chan.vchan.lock, flags);
> > + axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, 0);
> > +}
> > +
> > static int axi_dmac_probe(struct platform_device *pdev)
> > {
> > struct dma_device *dma_dev;
> > - struct axi_dmac *dmac;
> > + struct axi_dmac *__dmac;
> > struct regmap *regmap;
> > unsigned int version;
> > u32 irq_mask = 0;
> > int ret;
> >
> > - dmac = devm_kzalloc(&pdev->dev, sizeof(*dmac), GFP_KERNEL);
> > + struct axi_dmac *dmac __free(kfree) = kzalloc_obj(struct axi_dmac);
> > if (!dmac)
> > return -ENOMEM;
> >
> > @@ -1251,6 +1286,7 @@ static int axi_dmac_probe(struct platform_device *pdev)
> > dma_dev->dev = &pdev->dev;
> > dma_dev->src_addr_widths = BIT(dmac->chan.src_width);
> > dma_dev->dst_addr_widths = BIT(dmac->chan.dest_width);
> > + dma_dev->device_release = axi_dmac_release;
> > dma_dev->directions = BIT(dmac->chan.direction);
> > dma_dev->residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR;
> > dma_dev->max_sg_burst = 31; /* 31 SGs maximum in one burst */
> > @@ -1285,12 +1321,21 @@ static int axi_dmac_probe(struct platform_device *pdev)
> > if (ret)
> > return ret;
> >
> > + /*
> > + * From this point on, our dmac object has it's lifetime bounded with
> > + * dma_dev. Will be freed when dma_dev refcount goes to 0. That means,
> > + * no more automatic kfree(). Also note that dmac is now NULL so we
> > + * need __dmac.
> > + */
> > + __dmac = no_free_ptr(dmac);
> > + get_device(&pdev->dev);
> > +
> > /*
> > * Put the action in here so it get's done before unregistering the DMA
> > * device.
> > */
> > ret = devm_add_action_or_reset(&pdev->dev, axi_dmac_tasklet_kill,
> > - &dmac->chan.vchan.task);
> > + &__dmac->chan.vchan.task);
> > if (ret)
> > return ret;
> >
> > @@ -1304,13 +1349,18 @@ static int axi_dmac_probe(struct platform_device *pdev)
> > if (ret)
> > return ret;
> >
> > - ret = devm_request_irq(&pdev->dev, dmac->irq, axi_dmac_interrupt_handler,
> > - IRQF_SHARED, dev_name(&pdev->dev), dmac);
> > + /* So that we can mark the device as unbound and disable it */
> > + ret = devm_add_action_or_reset(&pdev->dev, axi_dmac_disable, __dmac);
> > if (ret)
> > return ret;
> >
> > - regmap = devm_regmap_init_mmio(&pdev->dev, dmac->base,
> > - &axi_dmac_regmap_config);
> > + ret = devm_request_irq(&pdev->dev, __dmac->irq, axi_dmac_interrupt_handler,
> > + IRQF_SHARED, dev_name(&pdev->dev), __dmac);
> > + if (ret)
> > + return ret;
> > +
> > + regmap = devm_regmap_init_mmio(&pdev->dev, __dmac->base,
> > + &axi_dmac_regmap_config);
> >
> > return PTR_ERR_OR_ZERO(regmap);
> > }
> >
> > --
> > 2.53.0
> >
^ permalink raw reply
* [PATCH v3 5/5] riscv: dts: spacemit: Add PDMA controller node for K3 SoC
From: Troy Mitchell @ 2026-03-31 8:27 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Yixun Lan, Guodong Xu, Michael Turquette,
Stephen Boyd, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti
Cc: dmaengine, devicetree, linux-riscv, spacemit, linux-kernel,
linux-clk, Troy Mitchell
In-Reply-To: <20260331-k3-pdma-v3-0-a4e60dd8b4b3@linux.spacemit.com>
Add the Peripheral DMA (PDMA) controller node for the SpacemiT K3 SoC.
The PDMA controller provides general-purpose DMA capabilities for various
peripheral devices across the system to offload CPU data transfers.
Unlike the previous K1 SoC, where some DMA masters had memory addressing
limitations (e.g. restricted to the 0-4GB space) requiring a dedicated dma-bus
with dma-ranges to restrict memory allocations, the K3 DMA masters have
full memory addressing capabilities. Therefore, the PDMA node is now
instantiated directly under the main soc bus.
Signed-off-by: Troy Mitchell <troy.mitchell@linux.spacemit.com>
---
arch/riscv/boot/dts/spacemit/k3.dtsi | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/arch/riscv/boot/dts/spacemit/k3.dtsi b/arch/riscv/boot/dts/spacemit/k3.dtsi
index a3a8ceddabec..cd321975fc18 100644
--- a/arch/riscv/boot/dts/spacemit/k3.dtsi
+++ b/arch/riscv/boot/dts/spacemit/k3.dtsi
@@ -438,6 +438,17 @@ soc: soc {
dma-noncoherent;
ranges;
+ pdma: dma-controller@d4000000 {
+ compatible = "spacemit,k3-pdma";
+ reg = <0x0 0xd4000000 0x0 0x4000>;
+ clocks = <&syscon_apmu CLK_APMU_DMA>;
+ resets = <&syscon_apmu RESET_APMU_DMA>;
+ interrupts = <72 IRQ_TYPE_LEVEL_HIGH>;
+ dma-channels = <16>;
+ #dma-cells = <1>;
+ status = "disabled";
+ };
+
syscon_apbc: system-controller@d4015000 {
compatible = "spacemit,k3-syscon-apbc";
reg = <0x0 0xd4015000 0x0 0x1000>;
--
2.53.0
^ permalink raw reply related
* [PATCH v3 2/5] dmaengine: mmp_pdma: support variable extended DRCMR base
From: Troy Mitchell @ 2026-03-31 8:27 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Yixun Lan, Guodong Xu, Michael Turquette,
Stephen Boyd, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti
Cc: dmaengine, devicetree, linux-riscv, spacemit, linux-kernel,
linux-clk, Troy Mitchell
In-Reply-To: <20260331-k3-pdma-v3-0-a4e60dd8b4b3@linux.spacemit.com>
From: Guodong Xu <guodong@riscstar.com>
DRCMR base address for extended DMA request numbers (which means bigger
or equal to 64) varies in different PMDA hardware implementation.
One such different PDMA implementation is found in SpacemiT's K3. In
this patch is for preparation the adding of K3 PDMA support.
Signed-off-by: Guodong Xu <guodong@riscstar.com>
Signed-off-by: Troy Mitchell <troy.mitchell@linux.spacemit.com>
---
drivers/dma/mmp_pdma.c | 19 ++++++++++++++++---
1 file changed, 16 insertions(+), 3 deletions(-)
diff --git a/drivers/dma/mmp_pdma.c b/drivers/dma/mmp_pdma.c
index d12e729ee12c..6112369006ee 100644
--- a/drivers/dma/mmp_pdma.c
+++ b/drivers/dma/mmp_pdma.c
@@ -51,7 +51,9 @@
#define DCSR_CMPST BIT(10) /* The Descriptor Compare Status */
#define DCSR_EORINTR BIT(9) /* The end of Receive */
-#define DRCMR(n) ((((n) < 64) ? 0x0100 : 0x1100) + (((n) & 0x3f) << 2))
+#define DRCMR_BASE 0x0100
+#define DRCMR_EXT_BASE_DEFAULT 0x1100
+#define DRCMR_REQ_LIMIT 64
#define DRCMR_MAPVLD BIT(7) /* Map Valid (read / write) */
#define DRCMR_CHLNUM 0x1f /* mask for Channel Number (read / write) */
@@ -154,6 +156,7 @@ struct mmp_pdma_phy {
* @run_bits: Control bits in DCSR register for channel start/stop
* @dma_width: DMA addressing width in bits (32 or 64). Determines the
* DMA mask capability of the controller hardware.
+ * @drcmr_ext_base: Base DRCMR address for extended requests
*/
struct mmp_pdma_ops {
/* Hardware Register Operations */
@@ -174,6 +177,7 @@ struct mmp_pdma_ops {
/* Controller Configuration */
u32 run_bits;
u32 dma_width;
+ u32 drcmr_ext_base;
};
struct mmp_pdma_device {
@@ -195,6 +199,13 @@ struct mmp_pdma_device {
#define to_mmp_pdma_dev(dmadev) \
container_of(dmadev, struct mmp_pdma_device, device)
+static u32 mmp_pdma_get_drcmr(struct mmp_pdma_device *pdev, u32 drcmr)
+{
+ if (drcmr < DRCMR_REQ_LIMIT)
+ return DRCMR_BASE + (drcmr << 2);
+ return pdev->ops->drcmr_ext_base + ((drcmr - DRCMR_REQ_LIMIT) << 2);
+}
+
/* For 32-bit PDMA */
static void write_next_addr_32(struct mmp_pdma_phy *phy, dma_addr_t addr)
{
@@ -301,7 +312,7 @@ static void enable_chan(struct mmp_pdma_phy *phy)
pdev = to_mmp_pdma_dev(phy->vchan->chan.device);
- reg = DRCMR(phy->vchan->drcmr);
+ reg = mmp_pdma_get_drcmr(pdev, phy->vchan->drcmr);
writel(DRCMR_MAPVLD | phy->idx, phy->base + reg);
dalgn = readl(phy->base + DALGN);
@@ -437,7 +448,7 @@ static void mmp_pdma_free_phy(struct mmp_pdma_chan *pchan)
return;
/* clear the channel mapping in DRCMR */
- reg = DRCMR(pchan->drcmr);
+ reg = mmp_pdma_get_drcmr(pdev, pchan->drcmr);
writel(0, pchan->phy->base + reg);
spin_lock_irqsave(&pdev->phy_lock, flags);
@@ -1179,6 +1190,7 @@ static const struct mmp_pdma_ops marvell_pdma_v1_ops = {
.get_desc_dst_addr = get_desc_dst_addr_32,
.run_bits = (DCSR_RUN),
.dma_width = 32,
+ .drcmr_ext_base = DRCMR_EXT_BASE_DEFAULT,
};
static const struct mmp_pdma_ops spacemit_k1_pdma_ops = {
@@ -1192,6 +1204,7 @@ static const struct mmp_pdma_ops spacemit_k1_pdma_ops = {
.get_desc_dst_addr = get_desc_dst_addr_64,
.run_bits = (DCSR_RUN | DCSR_LPAEEN),
.dma_width = 64,
+ .drcmr_ext_base = DRCMR_EXT_BASE_DEFAULT,
};
static const struct of_device_id mmp_pdma_dt_ids[] = {
--
2.53.0
^ permalink raw reply related
* [PATCH v3 4/5] clk: spacemit: k3: mark top_dclk as CLK_IS_CRITICAL
From: Troy Mitchell @ 2026-03-31 8:27 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Yixun Lan, Guodong Xu, Michael Turquette,
Stephen Boyd, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti
Cc: dmaengine, devicetree, linux-riscv, spacemit, linux-kernel,
linux-clk, Troy Mitchell
In-Reply-To: <20260331-k3-pdma-v3-0-a4e60dd8b4b3@linux.spacemit.com>
top_dclk is the DDR bus clock. If it is gated by clk_disable_unused,
all memory-mapped bus transactions cease to function, causing DMA
engines to hang and general system instability.
Mark it CLK_IS_CRITICAL so the CCF never gates it during the
unused clock sweep.
Fixes: e371a77255b8 ("clk: spacemit: k3: add the clock tree")
Signed-off-by: Troy Mitchell <troy.mitchell@linux.spacemit.com>
---
drivers/clk/spacemit/ccu-k3.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/clk/spacemit/ccu-k3.c b/drivers/clk/spacemit/ccu-k3.c
index e98afd59f05c..bb8b75bdbdb3 100644
--- a/drivers/clk/spacemit/ccu-k3.c
+++ b/drivers/clk/spacemit/ccu-k3.c
@@ -846,7 +846,7 @@ static const struct clk_parent_data top_parents[] = {
CCU_PARENT_HW(pll6_d3),
};
CCU_MUX_DIV_GATE_FC_DEFINE(top_dclk, top_parents, APMU_TOP_DCLK_CTRL, 5, 3,
- BIT(8), 2, 3, BIT(1), 0);
+ BIT(8), 2, 3, BIT(1), CLK_IS_CRITICAL);
static const struct clk_parent_data ucie_parents[] = {
CCU_PARENT_HW(pll1_d8_307p2),
--
2.53.0
^ permalink raw reply related
* [PATCH v3 3/5] dmaengine: mmp_pdma: add Spacemit K3 support
From: Troy Mitchell @ 2026-03-31 8:27 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Yixun Lan, Guodong Xu, Michael Turquette,
Stephen Boyd, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti
Cc: dmaengine, devicetree, linux-riscv, spacemit, linux-kernel,
linux-clk, Troy Mitchell
In-Reply-To: <20260331-k3-pdma-v3-0-a4e60dd8b4b3@linux.spacemit.com>
From: Guodong Xu <guodong@riscstar.com>
SpacemiT K3 reuses most of the PDMA IP design found on K1, with one difference
being the extended DRCMR base address. This patch adds "spacemit,k3-pdma"
compatible string and it defines a new mmp_pdma_ops for k3 pdma.
Signed-off-by: Guodong Xu <guodong@riscstar.com>
Signed-off-by: Troy Mitchell <troy.mitchell@linux.spacemit.com>
---
drivers/dma/mmp_pdma.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/drivers/dma/mmp_pdma.c b/drivers/dma/mmp_pdma.c
index 6112369006ee..386e85cd4882 100644
--- a/drivers/dma/mmp_pdma.c
+++ b/drivers/dma/mmp_pdma.c
@@ -52,6 +52,7 @@
#define DCSR_EORINTR BIT(9) /* The end of Receive */
#define DRCMR_BASE 0x0100
+#define DRCMR_EXT_BASE_K3 0x1000
#define DRCMR_EXT_BASE_DEFAULT 0x1100
#define DRCMR_REQ_LIMIT 64
#define DRCMR_MAPVLD BIT(7) /* Map Valid (read / write) */
@@ -1207,6 +1208,20 @@ static const struct mmp_pdma_ops spacemit_k1_pdma_ops = {
.drcmr_ext_base = DRCMR_EXT_BASE_DEFAULT,
};
+static const struct mmp_pdma_ops spacemit_k3_pdma_ops = {
+ .write_next_addr = write_next_addr_64,
+ .read_src_addr = read_src_addr_64,
+ .read_dst_addr = read_dst_addr_64,
+ .set_desc_next_addr = set_desc_next_addr_64,
+ .set_desc_src_addr = set_desc_src_addr_64,
+ .set_desc_dst_addr = set_desc_dst_addr_64,
+ .get_desc_src_addr = get_desc_src_addr_64,
+ .get_desc_dst_addr = get_desc_dst_addr_64,
+ .run_bits = (DCSR_RUN | DCSR_LPAEEN | DCSR_EORIRQEN | DCSR_EORSTOPEN),
+ .dma_width = 64,
+ .drcmr_ext_base = DRCMR_EXT_BASE_K3,
+};
+
static const struct of_device_id mmp_pdma_dt_ids[] = {
{
.compatible = "marvell,pdma-1.0",
@@ -1214,6 +1229,9 @@ static const struct of_device_id mmp_pdma_dt_ids[] = {
}, {
.compatible = "spacemit,k1-pdma",
.data = &spacemit_k1_pdma_ops
+ }, {
+ .compatible = "spacemit,k3-pdma",
+ .data = &spacemit_k3_pdma_ops
}, {
/* sentinel */
}
--
2.53.0
^ permalink raw reply related
* [PATCH v3 1/5] dt-bindings: dmaengine: Add SpacemiT K3 DMA compatible string
From: Troy Mitchell @ 2026-03-31 8:27 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Yixun Lan, Guodong Xu, Michael Turquette,
Stephen Boyd, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti
Cc: dmaengine, devicetree, linux-riscv, spacemit, linux-kernel,
linux-clk, Troy Mitchell
In-Reply-To: <20260331-k3-pdma-v3-0-a4e60dd8b4b3@linux.spacemit.com>
From: Guodong Xu <guodong@riscstar.com>
Add the "spacemit,k3-pdma" compatible string for the SpacemiT K3 SoC.
While the K3 PDMA IP reuses most of the design found on the earlier K1 SoC,
a new compatible string is required due to the following hardware differences:
- Variable extended DRCMR base: The DRCMR (DMA Request/Command Register) base
address for extended DMA request numbers (>= 64) differs from the K1
implementation, requiring different driver ops.
- Memory addressing capabilities: Unlike the K1 SoC, which had memory addressing
limitations (e.g., restricted to the 0-4GB space) and required a dedicated
dma-bus with dma-ranges to restrict memory allocations, the K3 DMA masters
possess full memory addressing capabilities.
Signed-off-by: Guodong Xu <guodong@riscstar.com>
Signed-off-by: Troy Mitchell <troy.mitchell@linux.spacemit.com>
---
Documentation/devicetree/bindings/dma/spacemit,k1-pdma.yaml | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/dma/spacemit,k1-pdma.yaml b/Documentation/devicetree/bindings/dma/spacemit,k1-pdma.yaml
index ec06235baf5c..62ce6d81526b 100644
--- a/Documentation/devicetree/bindings/dma/spacemit,k1-pdma.yaml
+++ b/Documentation/devicetree/bindings/dma/spacemit,k1-pdma.yaml
@@ -14,7 +14,9 @@ allOf:
properties:
compatible:
- const: spacemit,k1-pdma
+ enum:
+ - spacemit,k1-pdma
+ - spacemit,k3-pdma
reg:
maxItems: 1
--
2.53.0
^ permalink raw reply related
* [PATCH v3 0/5] dmaengine: Add Peripheral DMA support for SpacemiT K3 SoC
From: Troy Mitchell @ 2026-03-31 8:27 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Yixun Lan, Guodong Xu, Michael Turquette,
Stephen Boyd, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti
Cc: dmaengine, devicetree, linux-riscv, spacemit, linux-kernel,
linux-clk, Troy Mitchell
Hi all,
This patch series introduces Peripheral DMA (PDMA) support for the
SpacemiT K3 SoC, leveraging the existing mmp_pdma driver.
The K3 PDMA IP is largely based on the design found in the previous
SpacemiT K1 SoC, but introduces a few key architectural differences:
1. It features a variable extended DRCMR base address for DMA request
numbers (>= 64) depending on the hardware implementation.
2. Unlike the K1 SoC, where some DMA masters had memory addressing
limitations (requiring a dedicated dma-bus), the K3 DMA masters
have full memory addressing capabilities.
The series is structured as follows:
- Patch 1: Introduce the necessary dt-bindings: K3 compatible string.
- Patch 2-3: Refactor the mmp_pdma driver to support variable extended
DRCMR bases, and add the specific implementation/ops for the K3 SoC.
- Patch 4: Fixes a critical clock issue where the DDR bus clock
(top_dclk) could be gated by CCF, which would cause DMA engines to
hang and lead to system instability.
- Patch 5: Finally, instantiates the PDMA controller node in the
SpacemiT K3 device tree.
---
Changes in v3:
- Removed the dt-bindings patches related to the DMA number.
- patch 1/5:
- update commit message
- patch 2-5: nothing
- Link to v2: https://lore.kernel.org/r/20260326-k3-pdma-v2-0-ca94ca7bb595@linux.spacemit.com
Changes in v2:
- patch 1-6 are added in this version
- patch 7/7
- update commit message
- using k3 compatible string
- Link to v1: https://lore.kernel.org/all/20260317-k3-pdma-v1-1-f39d3e97b53a@linux.spacemit.com/
---
Guodong Xu (3):
dt-bindings: dmaengine: Add SpacemiT K3 DMA compatible string
dmaengine: mmp_pdma: support variable extended DRCMR base
dmaengine: mmp_pdma: add Spacemit K3 support
Troy Mitchell (2):
clk: spacemit: k3: mark top_dclk as CLK_IS_CRITICAL
riscv: dts: spacemit: Add PDMA controller node for K3 SoC
.../devicetree/bindings/dma/spacemit,k1-pdma.yaml | 4 ++-
arch/riscv/boot/dts/spacemit/k3.dtsi | 11 +++++++
drivers/clk/spacemit/ccu-k3.c | 2 +-
drivers/dma/mmp_pdma.c | 37 ++++++++++++++++++++--
4 files changed, 49 insertions(+), 5 deletions(-)
---
base-commit: 02f90981a67f3b9ee7d6684e7503a4fed7aade0c
change-id: 20260317-k3-pdma-7c1734431436
Best regards,
--
Troy Mitchell <troy.mitchell@linux.spacemit.com>
^ permalink raw reply
* Re: [PATCHv2] dmaengine: hsu: use kzalloc_flex()
From: Andy Shevchenko @ 2026-03-31 4:28 UTC (permalink / raw)
To: Rosen Penev
Cc: dmaengine, Andy Shevchenko, Vinod Koul, Frank Li, Kees Cook,
Gustavo A. R. Silva,
open list:INTEL MID (Mobile Internet Device) PLATFORM,
open list:KERNEL HARDENING (not covered by other areas):Keyword:b__counted_by(_le|_be)?b
In-Reply-To: <CAKxU2N_SXeEgwZ5e1eARpK5jAorx-ycnPdf=Ut2jUvSM2xYZFw@mail.gmail.com>
On Mon, Mar 30, 2026 at 11:41 PM Rosen Penev <rosenp@gmail.com> wrote:
> On Mon, Mar 30, 2026 at 1:46 AM Andy Shevchenko
> <andy.shevchenko@gmail.com> wrote:
> > On Sat, Mar 28, 2026 at 9:17 PM Rosen Penev <rosenp@gmail.com> wrote:
...
> > > - hsu = devm_kzalloc(chip->dev, sizeof(*hsu), GFP_KERNEL);
> > > + /* Calculate nr_channels from the IO space length */
> > > + nr_channels = (chip->length - chip->offset) / HSU_DMA_CHAN_LENGTH;
> > > + hsu = devm_kzalloc(chip->dev, struct_size(hsu, chan, nr_channels), GFP_KERNEL);
> > > if (!hsu)
> > > return -ENOMEM;
> > >
> > > - chip->hsu = hsu;
> > > -
> > > - /* Calculate nr_channels from the IO space length */
> > > - hsu->nr_channels = (chip->length - chip->offset) / HSU_DMA_CHAN_LENGTH;
> > > + hsu->nr_channels = nr_channels;
> > >
> > > - hsu->chan = devm_kcalloc(chip->dev, hsu->nr_channels,
> > > - sizeof(*hsu->chan), GFP_KERNEL);
> > > - if (!hsu->chan)
> > > - return -ENOMEM;
> > > + chip->hsu = hsu;
> >
> > Don't know these _flex() APIs enough, but can we leave the chip->hsu =
> > hsu; in the same place as it's now?
> __counted_by requires the first assignment after allocation to be the
> counting variable. The _flex macros do this automatically for GCC15
> and above.
Why? The hsu member has nothing to do with VLA, where is this
requirement coming from? My understanding is that the check should
imply the minimum sizeof of the data structure and the compiler should
know that way before doing any allocations.
My understanding seems in align with what Gustavo blogged:
https://people.kernel.org/gustavoars/how-to-use-the-new-counted_by-attribute-in-c-and-linux
The same is written in the GCC patch description
https://gcc.gnu.org/pipermail/gcc-patches/2024-May/653123.html
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCHv3] dmaengine: hsu: use kzalloc_flex()
From: Andy Shevchenko @ 2026-03-31 4:22 UTC (permalink / raw)
To: Rosen Penev
Cc: dmaengine, Andy Shevchenko, Vinod Koul, Frank Li, Kees Cook,
Gustavo A. R. Silva,
open list:INTEL MID (Mobile Internet Device) PLATFORM,
open list:KERNEL HARDENING (not covered by other areas):Keyword:b__counted_by(_le|_be)?b
In-Reply-To: <20260330204357.4476-1-rosenp@gmail.com>
On Mon, Mar 30, 2026 at 11:44 PM Rosen Penev <rosenp@gmail.com> wrote:
>
> Simplifies allocations by using a flexible array member in this struct.
>
> Remove hsu_dma_alloc_desc(). It now offers no readability advantages in
> this single usage.
>
> Add __counted_by to get extra runtime analysis. Assign counting variable
> after allocation as required by __counted_by.
>
> Apply the exact same treatment to struct hsu_dma and devm_kzalloc().
>
> Signed-off-by: Rosen Penev <rosenp@gmail.com>
> ---
> v3: update description.
> v2: address review comments.
Wait a bit, I still get it unclear. Let's continue the discussion in v2.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox