* [PATCH 1/9] iio: adc: rzt2h: remove unused struct rzt2h_adc::max_channels
2026-08-28 14:59 [PATCH 0/9] iio: adc: rzt2h: add DMA buffer support Cosmin Tanislav
@ 2026-08-28 14:59 ` Cosmin Tanislav
2026-08-28 14:59 ` [PATCH 2/9] iio: adc: rzt2h: store IRQ in private state Cosmin Tanislav
` (7 subsequent siblings)
8 siblings, 0 replies; 18+ messages in thread
From: Cosmin Tanislav @ 2026-08-28 14:59 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Geert Uytterhoeven, Magnus Damm
Cc: linux-iio, linux-renesas-soc, devicetree, linux-kernel,
Cosmin Tanislav
struct rzt2h_adc::max_channels is unused, and is probably a leftover
of the devm_iio_adc_device_alloc_chaninfo_se() conversion done after the
initial submission. Remove it and the logic used to set it.
Signed-off-by: Cosmin Tanislav <cosmin-gabriel.tanislav.xa@renesas.com>
---
drivers/iio/adc/rzt2h_adc.c | 6 ------
1 file changed, 6 deletions(-)
diff --git a/drivers/iio/adc/rzt2h_adc.c b/drivers/iio/adc/rzt2h_adc.c
index 4e0eb02d3d14..42ff6bf2ab08 100644
--- a/drivers/iio/adc/rzt2h_adc.c
+++ b/drivers/iio/adc/rzt2h_adc.c
@@ -42,7 +42,6 @@ struct rzt2h_adc {
const struct iio_chan_spec *channels;
unsigned int num_channels;
- unsigned int max_channels;
};
static void rzt2h_adc_start(struct rzt2h_adc *adc, unsigned int conversion_type)
@@ -190,7 +189,6 @@ static const struct iio_chan_spec rzt2h_adc_chan_template = {
static int rzt2h_adc_parse_properties(struct rzt2h_adc *adc)
{
struct iio_chan_spec *chan_array;
- unsigned int i;
int ret;
ret = devm_iio_adc_device_alloc_chaninfo_se(adc->dev,
@@ -203,10 +201,6 @@ static int rzt2h_adc_parse_properties(struct rzt2h_adc *adc)
adc->num_channels = ret;
adc->channels = chan_array;
- for (i = 0; i < adc->num_channels; i++)
- if (chan_array[i].channel + 1 > adc->max_channels)
- adc->max_channels = chan_array[i].channel + 1;
-
return 0;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 18+ messages in thread* [PATCH 2/9] iio: adc: rzt2h: store IRQ in private state
2026-08-28 14:59 [PATCH 0/9] iio: adc: rzt2h: add DMA buffer support Cosmin Tanislav
2026-08-28 14:59 ` [PATCH 1/9] iio: adc: rzt2h: remove unused struct rzt2h_adc::max_channels Cosmin Tanislav
@ 2026-08-28 14:59 ` Cosmin Tanislav
2026-08-28 14:59 ` [PATCH 3/9] iio: adc: rzt2h: store the physical address " Cosmin Tanislav
` (6 subsequent siblings)
8 siblings, 0 replies; 18+ messages in thread
From: Cosmin Tanislav @ 2026-08-28 14:59 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Geert Uytterhoeven, Magnus Damm
Cc: linux-iio, linux-renesas-soc, devicetree, linux-kernel,
Cosmin Tanislav
To prepare for adding support for DMA transfers, store the IRQ in
private state to disable it while DMA is in progress. The RZ/T2H ICU
does not mask the interrupts itself when they are being used for DMA
transfers.
Signed-off-by: Cosmin Tanislav <cosmin-gabriel.tanislav.xa@renesas.com>
---
drivers/iio/adc/rzt2h_adc.c | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/drivers/iio/adc/rzt2h_adc.c b/drivers/iio/adc/rzt2h_adc.c
index 42ff6bf2ab08..470b16b5b9c8 100644
--- a/drivers/iio/adc/rzt2h_adc.c
+++ b/drivers/iio/adc/rzt2h_adc.c
@@ -42,6 +42,8 @@ struct rzt2h_adc {
const struct iio_chan_spec *channels;
unsigned int num_channels;
+
+ int irq;
};
static void rzt2h_adc_start(struct rzt2h_adc *adc, unsigned int conversion_type)
@@ -209,7 +211,7 @@ static int rzt2h_adc_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
struct iio_dev *indio_dev;
struct rzt2h_adc *adc;
- int ret, irq;
+ int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*adc));
if (!indio_dev)
@@ -239,11 +241,11 @@ static int rzt2h_adc_probe(struct platform_device *pdev)
if (ret)
return ret;
- irq = platform_get_irq_byname(pdev, "adi");
- if (irq < 0)
- return irq;
+ adc->irq = platform_get_irq_byname(pdev, "adi");
+ if (adc->irq < 0)
+ return adc->irq;
- ret = devm_request_irq(dev, irq, rzt2h_adc_isr, 0, dev_name(dev), adc);
+ ret = devm_request_irq(dev, adc->irq, rzt2h_adc_isr, 0, dev_name(dev), adc);
if (ret)
return ret;
--
2.55.0
^ permalink raw reply related [flat|nested] 18+ messages in thread* [PATCH 3/9] iio: adc: rzt2h: store the physical address in private state
2026-08-28 14:59 [PATCH 0/9] iio: adc: rzt2h: add DMA buffer support Cosmin Tanislav
2026-08-28 14:59 ` [PATCH 1/9] iio: adc: rzt2h: remove unused struct rzt2h_adc::max_channels Cosmin Tanislav
2026-08-28 14:59 ` [PATCH 2/9] iio: adc: rzt2h: store IRQ in private state Cosmin Tanislav
@ 2026-08-28 14:59 ` Cosmin Tanislav
2026-08-28 14:59 ` [PATCH 4/9] iio: adc: rzt2h: claim direct mode on single reads Cosmin Tanislav
` (5 subsequent siblings)
8 siblings, 0 replies; 18+ messages in thread
From: Cosmin Tanislav @ 2026-08-28 14:59 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Geert Uytterhoeven, Magnus Damm
Cc: linux-iio, linux-renesas-soc, devicetree, linux-kernel,
Cosmin Tanislav
To prepare for adding support for DMA-based transfers, store the
physical address of the device in struct rzt2h_adc::phys_base.
Signed-off-by: Cosmin Tanislav <cosmin-gabriel.tanislav.xa@renesas.com>
---
drivers/iio/adc/rzt2h_adc.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/drivers/iio/adc/rzt2h_adc.c b/drivers/iio/adc/rzt2h_adc.c
index 470b16b5b9c8..995308a419f1 100644
--- a/drivers/iio/adc/rzt2h_adc.c
+++ b/drivers/iio/adc/rzt2h_adc.c
@@ -36,6 +36,7 @@ struct rzt2h_adc {
void __iomem *base;
struct device *dev;
+ phys_addr_t phys_base;
struct completion completion;
/* lock to protect against multiple access to the device */
struct mutex lock;
@@ -211,6 +212,7 @@ static int rzt2h_adc_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
struct iio_dev *indio_dev;
struct rzt2h_adc *adc;
+ struct resource *res;
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*adc));
@@ -231,10 +233,12 @@ static int rzt2h_adc_probe(struct platform_device *pdev)
if (ret)
return ret;
- adc->base = devm_platform_ioremap_resource(pdev, 0);
+ adc->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(adc->base))
return PTR_ERR(adc->base);
+ adc->phys_base = res->start;
+
pm_runtime_set_autosuspend_delay(dev, 300);
pm_runtime_use_autosuspend(dev);
ret = devm_pm_runtime_enable(dev);
--
2.55.0
^ permalink raw reply related [flat|nested] 18+ messages in thread* [PATCH 4/9] iio: adc: rzt2h: claim direct mode on single reads
2026-08-28 14:59 [PATCH 0/9] iio: adc: rzt2h: add DMA buffer support Cosmin Tanislav
` (2 preceding siblings ...)
2026-08-28 14:59 ` [PATCH 3/9] iio: adc: rzt2h: store the physical address " Cosmin Tanislav
@ 2026-08-28 14:59 ` Cosmin Tanislav
2026-08-28 15:15 ` sashiko-bot
2026-08-28 14:59 ` [PATCH 5/9] iio: adc: rzt2h: implement DMA buffer support Cosmin Tanislav
` (4 subsequent siblings)
8 siblings, 1 reply; 18+ messages in thread
From: Cosmin Tanislav @ 2026-08-28 14:59 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Geert Uytterhoeven, Magnus Damm
Cc: linux-iio, linux-renesas-soc, devicetree, linux-kernel,
Cosmin Tanislav
Claim direct mode around the single read to prevent it from conflicting
with an ongoing buffered read.
Signed-off-by: Cosmin Tanislav <cosmin-gabriel.tanislav.xa@renesas.com>
---
drivers/iio/adc/rzt2h_adc.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/drivers/iio/adc/rzt2h_adc.c b/drivers/iio/adc/rzt2h_adc.c
index 995308a419f1..95bcebdc02cb 100644
--- a/drivers/iio/adc/rzt2h_adc.c
+++ b/drivers/iio/adc/rzt2h_adc.c
@@ -158,8 +158,13 @@ static int rzt2h_adc_read_raw(struct iio_dev *indio_dev,
struct rzt2h_adc *adc = iio_priv(indio_dev);
switch (mask) {
- case IIO_CHAN_INFO_RAW:
+ case IIO_CHAN_INFO_RAW: {
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
return rzt2h_adc_read_single(adc, chan->channel, val);
+ }
case IIO_CHAN_INFO_SCALE:
*val = 1800;
*val2 = 12;
--
2.55.0
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH 4/9] iio: adc: rzt2h: claim direct mode on single reads
2026-08-28 14:59 ` [PATCH 4/9] iio: adc: rzt2h: claim direct mode on single reads Cosmin Tanislav
@ 2026-08-28 15:15 ` sashiko-bot
0 siblings, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-08-28 15:15 UTC (permalink / raw)
To: Cosmin Tanislav; +Cc: robh, conor+dt, devicetree, wsa+renesas
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Unsafe 1-jiffy hardware timeout causes spurious failures.
- [High] Late interrupt triggers spurious completion for subsequent reads on timeout.
--
commit 804ca0ac39c62632fae66702cb8c967efd8b7dee
Author: Cosmin Tanislav <cosmin-gabriel.tanislav.xa@renesas.com>
iio: adc: rzt2h: claim direct mode on single reads
Claim direct mode around the single read to prevent it from conflicting
with an ongoing buffered read.
> diff --git a/drivers/iio/adc/rzt2h_adc.c b/drivers/iio/adc/rzt2h_adc.c
> index 995308a419f12..95bcebdc02cb3 100644
> --- a/drivers/iio/adc/rzt2h_adc.c
> +++ b/drivers/iio/adc/rzt2h_adc.c
> @@ -158,8 +158,13 @@ static int rzt2h_adc_read_raw(struct iio_dev *indio_dev,
> struct rzt2h_adc *adc = iio_priv(indio_dev);
>
> switch (mask) {
> - case IIO_CHAN_INFO_RAW:
> + case IIO_CHAN_INFO_RAW: {
> + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> + if (IIO_DEV_ACQUIRE_FAILED(claim))
> + return -EBUSY;
> +
> return rzt2h_adc_read_single(adc, chan->channel, val);
[Severity: High]
This isn't a bug introduced by this patch, but this is a pre-existing issue
in rzt2h_adc_read_single(). Can the 1-jiffy timeout expire instantaneously
and cause spurious failures?
Looking at rzt2h_adc_read_single():
ret = wait_for_completion_timeout(&adc->completion, usecs_to_jiffies(1));
if (!ret) {
ret = -ETIMEDOUT;
goto disable;
}
A 1-jiffy timeout can expire almost instantaneously if the next timer tick
occurs immediately after the call. Even though the conversion takes ~0.32us,
could it be outraced by the timer tick, causing the driver to incorrectly assume
a hardware failure and abort the read?
[Severity: High]
This is also a pre-existing issue, but does a timeout in rzt2h_adc_read_single()
leave a pending interrupt that could corrupt subsequent reads?
If a timeout occurs, the cleanup path calls rzt2h_adc_stop() to disable device
interrupts, but it does not synchronize with the IRQ controller. A pending or
concurrently running ISR will unconditionally signal completion:
static irqreturn_t rzt2h_adc_isr(int irq, void *private)
{
struct rzt2h_adc *adc = private;
complete(&adc->completion);
return IRQ_HANDLED;
}
If this late ISR executes immediately after a subsequent read calls
reinit_completion(), could the new read falsely complete instantly and return
stale or garbage data from the ADC before the hardware actually finishes?
> + }
> case IIO_CHAN_INFO_SCALE:
> *val = 1800;
> *val2 = 12;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260828145943.2077589-1-cosmin-gabriel.tanislav.xa@renesas.com?part=4
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH 5/9] iio: adc: rzt2h: implement DMA buffer support
2026-08-28 14:59 [PATCH 0/9] iio: adc: rzt2h: add DMA buffer support Cosmin Tanislav
` (3 preceding siblings ...)
2026-08-28 14:59 ` [PATCH 4/9] iio: adc: rzt2h: claim direct mode on single reads Cosmin Tanislav
@ 2026-08-28 14:59 ` Cosmin Tanislav
2026-08-28 15:13 ` sashiko-bot
2026-08-29 1:20 ` Jonathan Cameron
2026-08-28 14:59 ` [PATCH 6/9] iio: adc: rzt2h: expose sampling frequency Cosmin Tanislav
` (3 subsequent siblings)
8 siblings, 2 replies; 18+ messages in thread
From: Cosmin Tanislav @ 2026-08-28 14:59 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Geert Uytterhoeven, Magnus Damm
Cc: linux-iio, linux-renesas-soc, devicetree, linux-kernel,
Cosmin Tanislav
Implement buffered capture using a cyclic DMA transfer into a kfifo
buffer to support continuous high-rate sampling.
On buffer enable, switch the ADC to continuous conversion mode and start
a cyclic DMA transfer over the active channels.
Because the DMA controller does not support native scatter-gather, and
because of the cyclic DMA setup, transfers must be done in widths
covering all the enabled channels.
Since DMA transfer width must be a power of two and aligned to its size,
cover the smallest power-of-two-aligned group of channel registers
spanning the enabled channels.
Split the cyclic buffer into fixed-size periods. On each period
completion, bump a pending counter and wake a consumer kthread from the
DMA callback.
For every completed period, gather the enabled channels out of the DMA
layout into the scan layout the IIO core expects and push each scan
with iio_push_to_buffers().
If the consumer kthread falls behind by a full buffer, drop the oldest
periods.
Because the DMA transfer must cover all channels between the first and
last enabled ones, skip disabled channels while compacting.
Also, the DMA controller transfers data in 32-bit words, but the ADC's
data registers are 16-bit wide, causing adjacent channel data to be
swapped. Swap consecutive channels while compacting to account for this.
Allocate the DMA buffer via dma_alloc_noncoherent() and synchronise it
per period to allow it to be cached by the CPU while compacting.
Disable the completion IRQ for the duration of the DMA transfer, as the
ICU does not mask this event from reaching the GIC even if it is being
used to drive the DMA capture.
Signed-off-by: Cosmin Tanislav <cosmin-gabriel.tanislav.xa@renesas.com>
---
drivers/iio/adc/Kconfig | 2 +
drivers/iio/adc/rzt2h_adc.c | 339 ++++++++++++++++++++++++++++++++++++
2 files changed, 341 insertions(+)
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 415e519ad4eb..6c7b30d2b6e7 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1551,6 +1551,8 @@ config RZT2H_ADC
tristate "Renesas RZ/T2H / RZ/N2H ADC driver"
depends on ARCH_RENESAS || COMPILE_TEST
select IIO_ADC_HELPER
+ select IIO_BUFFER
+ select IIO_KFIFO_BUF
help
Say yes here to build support for the ADC found in Renesas
RZ/T2H / RZ/N2H SoCs.
diff --git a/drivers/iio/adc/rzt2h_adc.c b/drivers/iio/adc/rzt2h_adc.c
index 95bcebdc02cb..d76226375f22 100644
--- a/drivers/iio/adc/rzt2h_adc.c
+++ b/drivers/iio/adc/rzt2h_adc.c
@@ -4,11 +4,16 @@
#include <linux/cleanup.h>
#include <linux/completion.h>
#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
#include <linux/iio/adc-helpers.h>
+#include <linux/iio/buffer.h>
#include <linux/iio/iio.h>
+#include <linux/iio/kfifo_buf.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/iopoll.h>
+#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
@@ -18,6 +23,7 @@
#define RZT2H_ADCSR_ADIE_MASK BIT(12)
#define RZT2H_ADCSR_ADCS_MASK GENMASK(14, 13)
#define RZT2H_ADCSR_ADCS_SINGLE 0b00
+#define RZT2H_ADCSR_ADCS_CONTINUOUS 0b10
#define RZT2H_ADCSR_ADST_MASK BIT(15)
#define RZT2H_ADANSA0_REG 0x04
@@ -31,18 +37,47 @@
#define RZT2H_ADCALCTL_CAL_ERR_MASK BIT(2)
#define RZT2H_ADC_MAX_CHANNELS 16
+#define RZT2H_ADC_CHANNEL_BYTES sizeof(u16)
+#define RZT2H_ADC_DMA_PERIOD_SAMPLES 128
+#define RZT2H_ADC_DMA_PERIODS 64
+#define RZT2H_ADC_DMA_BUFFER_SAMPLES (RZT2H_ADC_DMA_PERIODS * \
+ RZT2H_ADC_DMA_PERIOD_SAMPLES)
+#define RZT2H_ADC_DMA_BUFFER_SIZE (RZT2H_ADC_DMA_BUFFER_SAMPLES * \
+ RZT2H_ADC_MAX_CHANNELS * \
+ RZT2H_ADC_CHANNEL_BYTES)
+
+struct rzt2h_adc_dma {
+ struct dma_chan *chan;
+ u16 *buf;
+ dma_addr_t addr;
+
+ unsigned int period_index;
+ unsigned int period_bytes;
+ unsigned int first_chan;
+ unsigned int sample_chans;
+
+ u8 gather[RZT2H_ADC_MAX_CHANNELS];
+ unsigned int gather_len;
+
+ atomic_t pending_periods;
+
+ wait_queue_head_t wq;
+ struct task_struct *thread;
+};
struct rzt2h_adc {
void __iomem *base;
struct device *dev;
phys_addr_t phys_base;
+ struct rzt2h_adc_dma dma;
struct completion completion;
/* lock to protect against multiple access to the device */
struct mutex lock;
const struct iio_chan_spec *channels;
unsigned int num_channels;
+ u16 buf[RZT2H_ADC_MAX_CHANNELS];
int irq;
};
@@ -151,6 +186,253 @@ static int rzt2h_adc_calibrate(struct rzt2h_adc *adc)
return 0;
}
+static void rzt2h_adc_push_period(struct iio_dev *indio_dev, u16 *period,
+ dma_addr_t addr)
+{
+ struct rzt2h_adc *adc = iio_priv(indio_dev);
+ u16 *dst = adc->buf;
+ u16 *src = period;
+
+ dma_sync_single_for_cpu(adc->dev, addr, adc->dma.period_bytes,
+ DMA_FROM_DEVICE);
+
+ for (unsigned int sample = 0; sample < RZT2H_ADC_DMA_PERIOD_SAMPLES; sample++) {
+ for (unsigned int i = 0; i < adc->dma.gather_len; i++)
+ dst[i] = src[adc->dma.gather[i]];
+
+ src += adc->dma.sample_chans;
+
+ iio_push_to_buffers(indio_dev, adc->buf);
+ }
+}
+
+static void rzt2h_adc_advance_period_index(struct rzt2h_adc *adc, unsigned int i)
+{
+ adc->dma.period_index += i;
+ adc->dma.period_index %= RZT2H_ADC_DMA_PERIODS;
+}
+
+static void rzt2h_adc_dma_thread_loop(struct iio_dev *indio_dev)
+{
+ struct rzt2h_adc *adc = iio_priv(indio_dev);
+ int pending, drop;
+ dma_addr_t addr;
+ u16 *period;
+
+ pending = atomic_xchg(&adc->dma.pending_periods, 0);
+
+ if (pending >= RZT2H_ADC_DMA_PERIODS) {
+ drop = pending - RZT2H_ADC_DMA_PERIODS + 1;
+
+ rzt2h_adc_advance_period_index(adc, drop);
+ pending -= drop;
+ }
+
+ for (unsigned int i = 0; i < pending; i++) {
+ unsigned int backlog = atomic_read(&adc->dma.pending_periods) +
+ pending - i;
+
+ /*
+ * Bail if enough new periods have completed since reading the
+ * pending_periods that the next period about to be read is at
+ * risk of being overwritten.
+ */
+ if (backlog >= RZT2H_ADC_DMA_PERIODS)
+ break;
+
+ period = adc->dma.buf + adc->dma.period_index *
+ RZT2H_ADC_DMA_PERIOD_SAMPLES * adc->dma.sample_chans;
+ addr = adc->dma.addr + adc->dma.period_index *
+ adc->dma.period_bytes;
+
+ rzt2h_adc_push_period(indio_dev, period, addr);
+ rzt2h_adc_advance_period_index(adc, 1);
+ }
+}
+
+static int rzt2h_adc_dma_thread(void *data)
+{
+ struct iio_dev *indio_dev = data;
+ struct rzt2h_adc *adc = iio_priv(indio_dev);
+
+ while (!kthread_should_stop()) {
+ wait_event_interruptible(adc->dma.wq,
+ atomic_read(&adc->dma.pending_periods) ||
+ kthread_should_stop());
+
+ if (kthread_should_stop())
+ break;
+
+ rzt2h_adc_dma_thread_loop(indio_dev);
+ }
+
+ return 0;
+}
+
+static void rzt2h_adc_dma_callback(void *data)
+{
+ struct iio_dev *indio_dev = data;
+ struct rzt2h_adc *adc = iio_priv(indio_dev);
+
+ atomic_inc(&adc->dma.pending_periods);
+ wake_up(&adc->dma.wq);
+}
+
+static void rzt2h_adc_dma_calc_layout(struct iio_dev *indio_dev)
+{
+ struct rzt2h_adc *adc = iio_priv(indio_dev);
+ unsigned int hi = 0, lo = RZT2H_ADC_MAX_CHANNELS - 1;
+ const struct iio_chan_spec *chan;
+ unsigned int sample_chans;
+ unsigned int first_chan;
+ unsigned int scan_index;
+ unsigned int swap;
+ unsigned int idx;
+
+ /* Find the lowest and highest enabled channel. */
+ iio_for_each_active_channel(indio_dev, scan_index) {
+ chan = &indio_dev->channels[scan_index];
+
+ lo = min_t(unsigned int, lo, chan->channel);
+ hi = max_t(unsigned int, hi, chan->channel);
+ }
+
+ /*
+ * The DMA has no scatter-gather and transfers must have a power-of-two
+ * width, so pick the smallest power-of-two-aligned block of channels
+ * that covers all enabled channels.
+ */
+ for (sample_chans = 1; sample_chans < RZT2H_ADC_MAX_CHANNELS; sample_chans <<= 1) {
+ first_chan = round_down(lo, sample_chans);
+
+ if (first_chan + sample_chans > hi)
+ break;
+ }
+
+ /*
+ * Build a table to map each enabled channel to its position in the
+ * transferred block, it will be used later to extract only the enabled
+ * channels out of it.
+ * The DMA moves data in 32-bit words, which swaps each pair of adjacent
+ * 16-bit channels. Undo it.
+ */
+ adc->dma.gather_len = 0;
+ swap = sample_chans > 1;
+ iio_for_each_active_channel(indio_dev, scan_index) {
+ chan = &indio_dev->channels[scan_index];
+ idx = chan->channel - first_chan;
+
+ adc->dma.gather[adc->dma.gather_len++] = idx ^ swap;
+ }
+
+ adc->dma.first_chan = first_chan;
+ adc->dma.sample_chans = sample_chans;
+ adc->dma.period_bytes = RZT2H_ADC_DMA_PERIOD_SAMPLES * sample_chans *
+ RZT2H_ADC_CHANNEL_BYTES;
+}
+
+static int rzt2h_adc_start_dma(struct iio_dev *indio_dev)
+{
+ struct rzt2h_adc *adc = iio_priv(indio_dev);
+ struct dma_async_tx_descriptor *desc;
+ struct dma_slave_config config = {};
+ unsigned int buffer_bytes;
+ dma_cookie_t cookie;
+ int ret;
+
+ rzt2h_adc_dma_calc_layout(indio_dev);
+
+ config.src_addr = adc->phys_base + RZT2H_ADDR_REG(adc->dma.first_chan);
+ config.src_addr_width = adc->dma.sample_chans * RZT2H_ADC_CHANNEL_BYTES;
+
+ buffer_bytes = RZT2H_ADC_DMA_PERIODS * adc->dma.period_bytes;
+
+ ret = dmaengine_slave_config(adc->dma.chan, &config);
+ if (ret)
+ return ret;
+
+ desc = dmaengine_prep_dma_cyclic(adc->dma.chan, adc->dma.addr,
+ buffer_bytes, adc->dma.period_bytes,
+ DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT);
+ if (!desc)
+ return -EBUSY;
+
+ desc->callback = rzt2h_adc_dma_callback;
+ desc->callback_param = indio_dev;
+
+ cookie = dmaengine_submit(desc);
+ ret = dma_submit_error(cookie);
+ if (ret) {
+ dmaengine_terminate_sync(adc->dma.chan);
+ return ret;
+ }
+
+ adc->dma.thread = kthread_run(rzt2h_adc_dma_thread, indio_dev,
+ "rzt2h-adc-dma");
+ if (IS_ERR(adc->dma.thread)) {
+ dmaengine_terminate_sync(adc->dma.chan);
+ return PTR_ERR(adc->dma.thread);
+ }
+
+ disable_irq(adc->irq);
+
+ dma_async_issue_pending(adc->dma.chan);
+
+ return 0;
+}
+
+static int rzt2h_adc_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct rzt2h_adc *adc = iio_priv(indio_dev);
+ const struct iio_chan_spec *chan;
+ struct device *dev = adc->dev;
+ unsigned int scan_index;
+ u16 val = 0;
+ int ret;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ return ret;
+
+ iio_for_each_active_channel(indio_dev, scan_index) {
+ chan = &indio_dev->channels[scan_index];
+ val |= RZT2H_ADANSA0_CH_MASK(chan->channel);
+ }
+
+ writew(val, adc->base + RZT2H_ADANSA0_REG);
+
+ adc->dma.period_index = 0;
+ atomic_set(&adc->dma.pending_periods, 0);
+
+ ret = rzt2h_adc_start_dma(indio_dev);
+ if (ret) {
+ pm_runtime_put_autosuspend(dev);
+ return ret;
+ }
+
+ rzt2h_adc_start(adc, RZT2H_ADCSR_ADCS_CONTINUOUS);
+
+ return 0;
+}
+
+static int rzt2h_adc_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct rzt2h_adc *adc = iio_priv(indio_dev);
+ struct device *dev = adc->dev;
+
+ rzt2h_adc_stop(adc);
+
+ dmaengine_terminate_sync(adc->dma.chan);
+
+ kthread_stop(adc->dma.thread);
+
+ enable_irq(adc->irq);
+
+ pm_runtime_put_autosuspend(dev);
+
+ return 0;
+}
+
static int rzt2h_adc_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
@@ -174,6 +456,11 @@ static int rzt2h_adc_read_raw(struct iio_dev *indio_dev,
}
}
+static const struct iio_buffer_setup_ops rzt2h_adc_buffer_setup_ops = {
+ .postenable = rzt2h_adc_buffer_postenable,
+ .predisable = rzt2h_adc_buffer_predisable,
+};
+
static const struct iio_info rzt2h_adc_iio_info = {
.read_raw = rzt2h_adc_read_raw,
};
@@ -192,6 +479,12 @@ static const struct iio_chan_spec rzt2h_adc_chan_template = {
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
.type = IIO_VOLTAGE,
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 12,
+ .storagebits = 16,
+ .endianness = IIO_CPU,
+ },
};
static int rzt2h_adc_parse_properties(struct rzt2h_adc *adc)
@@ -209,9 +502,50 @@ static int rzt2h_adc_parse_properties(struct rzt2h_adc *adc)
adc->num_channels = ret;
adc->channels = chan_array;
+ for (unsigned int i = 0; i < adc->num_channels; i++)
+ chan_array[i].scan_index = i;
+
return 0;
}
+static void rzt2h_adc_free_dma_buf(void *p)
+{
+ struct rzt2h_adc *adc = p;
+
+ dma_free_noncoherent(adc->dev, RZT2H_ADC_DMA_BUFFER_SIZE,
+ adc->dma.buf, adc->dma.addr, DMA_FROM_DEVICE);
+}
+
+static int rzt2h_adc_setup_dma(struct iio_dev *indio_dev)
+{
+ struct rzt2h_adc *adc = iio_priv(indio_dev);
+ struct device *dev = adc->dev;
+ int ret;
+
+ adc->dma.chan = devm_dma_request_chan(adc->dev, "rx");
+ if (IS_ERR(adc->dma.chan)) {
+ ret = PTR_ERR(adc->dma.chan);
+ if (ret != -ENODEV)
+ return dev_err_probe(adc->dev, ret, "DMA channel request failed\n");
+
+ adc->dma.chan = NULL;
+ return 0;
+ }
+
+ adc->dma.buf = dma_alloc_noncoherent(adc->dev, RZT2H_ADC_DMA_BUFFER_SIZE,
+ &adc->dma.addr, DMA_FROM_DEVICE,
+ GFP_KERNEL);
+ if (!adc->dma.buf)
+ return -ENOMEM;
+
+ ret = devm_add_action_or_reset(adc->dev, rzt2h_adc_free_dma_buf, adc);
+ if (ret)
+ return ret;
+
+ return devm_iio_kfifo_buffer_setup_ext(dev, indio_dev,
+ &rzt2h_adc_buffer_setup_ops, NULL);
+}
+
static int rzt2h_adc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -227,6 +561,7 @@ static int rzt2h_adc_probe(struct platform_device *pdev)
adc = iio_priv(indio_dev);
adc->dev = dev;
init_completion(&adc->completion);
+ init_waitqueue_head(&adc->dma.wq);
ret = devm_mutex_init(dev, &adc->lock);
if (ret)
@@ -264,6 +599,10 @@ static int rzt2h_adc_probe(struct platform_device *pdev)
indio_dev->channels = adc->channels;
indio_dev->num_channels = adc->num_channels;
+ ret = rzt2h_adc_setup_dma(indio_dev);
+ if (ret)
+ return ret;
+
return devm_iio_device_register(dev, indio_dev);
}
--
2.55.0
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH 5/9] iio: adc: rzt2h: implement DMA buffer support
2026-08-28 14:59 ` [PATCH 5/9] iio: adc: rzt2h: implement DMA buffer support Cosmin Tanislav
@ 2026-08-28 15:13 ` sashiko-bot
2026-08-29 1:20 ` Jonathan Cameron
1 sibling, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-08-28 15:13 UTC (permalink / raw)
To: Cosmin Tanislav; +Cc: wsa+renesas, robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] The cyclic DMA buffer read pointer (`period_index`) permanently loses synchronization with the hardware write pointer if the consumer loop aborts early.
- [Medium] Missing dma_sync_single_for_device() call after CPU accesses non-coherent DMA memory.
- [Medium] Kthread will enter a 100% CPU infinite loop if it receives a signal.
--
commit 39a43c5f71d2917000960d73a8ad0233a9d65f07
Author: Cosmin Tanislav <cosmin-gabriel.tanislav.xa@renesas.com>
iio: adc: rzt2h: implement DMA buffer support
This commit implements buffered capture using a cyclic DMA transfer into
a kfifo buffer for the RZ/T2H ADC. It allocates a non-coherent DMA buffer
and handles periodic data pushes via a kthread.
> diff --git a/drivers/iio/adc/rzt2h_adc.c b/drivers/iio/adc/rzt2h_adc.c
> index 95bcebdc02cb3..d76226375f220 100644
> --- a/drivers/iio/adc/rzt2h_adc.c
> +++ b/drivers/iio/adc/rzt2h_adc.c
[ ... ]
> @@ -151,6 +186,253 @@ static int rzt2h_adc_calibrate(struct rzt2h_adc *adc)
> return 0;
> }
>
> +static void rzt2h_adc_push_period(struct iio_dev *indio_dev, u16 *period,
> + dma_addr_t addr)
> +{
> + struct rzt2h_adc *adc = iio_priv(indio_dev);
> + u16 *dst = adc->buf;
> + u16 *src = period;
> +
> + dma_sync_single_for_cpu(adc->dev, addr, adc->dma.period_bytes,
> + DMA_FROM_DEVICE);
> +
> + for (unsigned int sample = 0; sample < RZT2H_ADC_DMA_PERIOD_SAMPLES; sample++) {
> + for (unsigned int i = 0; i < adc->dma.gather_len; i++)
> + dst[i] = src[adc->dma.gather[i]];
> +
> + src += adc->dma.sample_chans;
> +
> + iio_push_to_buffers(indio_dev, adc->buf);
> + }
[Severity: Medium]
Does this code miss a call to dma_sync_single_for_device()?
The DMA API strictly requires that ownership of non-coherent mapped memory
be transferred back to the device after CPU access. Failing to do so
violates the API contract and can lead to the CPU reading stale data due
to speculative prefetches that overwrite the device's DMA data later.
> +}
> +
> +static void rzt2h_adc_advance_period_index(struct rzt2h_adc *adc, unsigned int i)
> +{
> + adc->dma.period_index += i;
> + adc->dma.period_index %= RZT2H_ADC_DMA_PERIODS;
> +}
> +
> +static void rzt2h_adc_dma_thread_loop(struct iio_dev *indio_dev)
> +{
> + struct rzt2h_adc *adc = iio_priv(indio_dev);
> + int pending, drop;
> + dma_addr_t addr;
> + u16 *period;
> +
> + pending = atomic_xchg(&adc->dma.pending_periods, 0);
> +
> + if (pending >= RZT2H_ADC_DMA_PERIODS) {
> + drop = pending - RZT2H_ADC_DMA_PERIODS + 1;
> +
> + rzt2h_adc_advance_period_index(adc, drop);
> + pending -= drop;
> + }
> +
> + for (unsigned int i = 0; i < pending; i++) {
> + unsigned int backlog = atomic_read(&adc->dma.pending_periods) +
> + pending - i;
> +
> + /*
> + * Bail if enough new periods have completed since reading the
> + * pending_periods that the next period about to be read is at
> + * risk of being overwritten.
> + */
> + if (backlog >= RZT2H_ADC_DMA_PERIODS)
> + break;
[Severity: High]
Does breaking early here cause the read and write pointers to permanently
lose synchronization?
Since all pending periods were extracted using atomic_xchg() at the start
of this loop, breaking out without advancing period_index for the skipped
periods or restoring the pending count permanently discards those periods.
The cyclic DMA buffer read pointer (period_index) will permanently lose
synchronization with the hardware write pointer. The consumer kthread will
then continuously read from the wrong offsets in the DMA cyclic buffer,
pushing stale or corrupted data to userspace without recovering.
> +
> + period = adc->dma.buf + adc->dma.period_index *
> + RZT2H_ADC_DMA_PERIOD_SAMPLES * adc->dma.sample_chans;
> + addr = adc->dma.addr + adc->dma.period_index *
> + adc->dma.period_bytes;
> +
> + rzt2h_adc_push_period(indio_dev, period, addr);
> + rzt2h_adc_advance_period_index(adc, 1);
> + }
> +}
> +
> +static int rzt2h_adc_dma_thread(void *data)
> +{
> + struct iio_dev *indio_dev = data;
> + struct rzt2h_adc *adc = iio_priv(indio_dev);
> +
> + while (!kthread_should_stop()) {
> + wait_event_interruptible(adc->dma.wq,
> + atomic_read(&adc->dma.pending_periods) ||
> + kthread_should_stop());
[Severity: Medium]
Can this kthread spin in an infinite loop consuming 100% CPU if it receives
a signal?
By using wait_event_interruptible(), if a signal is delivered to the
kthread (such as SIGKILL), it will return immediately with -ERESTARTSYS.
Since the thread does not check signal_pending() or clear the signal,
and kthread_should_stop() remains false, the loop will instantly restart,
returning immediately again and spinning forever.
> +
> + if (kthread_should_stop())
> + break;
> +
> + rzt2h_adc_dma_thread_loop(indio_dev);
> + }
> +
> + return 0;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260828145943.2077589-1-cosmin-gabriel.tanislav.xa@renesas.com?part=5
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH 5/9] iio: adc: rzt2h: implement DMA buffer support
2026-08-28 14:59 ` [PATCH 5/9] iio: adc: rzt2h: implement DMA buffer support Cosmin Tanislav
2026-08-28 15:13 ` sashiko-bot
@ 2026-08-29 1:20 ` Jonathan Cameron
2026-08-29 1:24 ` Jonathan Cameron
1 sibling, 1 reply; 18+ messages in thread
From: Jonathan Cameron @ 2026-08-29 1:20 UTC (permalink / raw)
To: Cosmin Tanislav
Cc: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Geert Uytterhoeven, Magnus Damm, linux-iio, linux-renesas-soc,
devicetree, linux-kernel
> Implement buffered capture using a cyclic DMA transfer into a kfifo
> buffer to support continuous high-rate sampling.
>
> On buffer enable, switch the ADC to continuous conversion mode and start
> a cyclic DMA transfer over the active channels.
>
> Because the DMA controller does not support native scatter-gather, and
> because of the cyclic DMA setup, transfers must be done in widths
> covering all the enabled channels.
>
> Since DMA transfer width must be a power of two and aligned to its size,
> cover the smallest power-of-two-aligned group of channel registers
> spanning the enabled channels.
>
> Split the cyclic buffer into fixed-size periods. On each period
> completion, bump a pending counter and wake a consumer kthread from the
> DMA callback.
>
> For every completed period, gather the enabled channels out of the DMA
> layout into the scan layout the IIO core expects and push each scan
> with iio_push_to_buffers().
Could you instead use the available_scan_masks infrastructure. bit annoying
to specify the full list but isn't that long I think with 16 channels
16 x single
8 x double,
4 x quads
2 x octect
1 x all of them.
The the IIO demux in (sits behind the push_to_buffers path if
we have available_scan_masks set) will then deal with repacking
the data if necessary.
>
> If the consumer kthread falls behind by a full buffer, drop the oldest
> periods.
>
> Because the DMA transfer must cover all channels between the first and
> last enabled ones, skip disabled channels while compacting.
>
> Also, the DMA controller transfers data in 32-bit words, but the ADC's
> data registers are 16-bit wide, causing adjacent channel data to be
> swapped. Swap consecutive channels while compacting to account for this.
>
> Allocate the DMA buffer via dma_alloc_noncoherent() and synchronise it
> per period to allow it to be cached by the CPU while compacting.
>
> Disable the completion IRQ for the duration of the DMA transfer, as the
> ICU does not mask this event from reaching the GIC even if it is being
> used to drive the DMA capture.
>
> Signed-off-by: Cosmin Tanislav <cosmin-gabriel.tanislav.xa@renesas.com>
I'm messing around with b4 review tui and sashiko integration. I've
left the Sashiko comments in here as I'm out of time today to look
at them in detial.
>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 415e519ad4eb..6c7b30d2b6e7 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -1551,6 +1551,8 @@ config RZT2H_ADC
> tristate "Renesas RZ/T2H / RZ/N2H ADC driver"
> depends on ARCH_RENESAS || COMPILE_TEST
> select IIO_ADC_HELPER
> + select IIO_BUFFER
> + select IIO_KFIFO_BUF
> help
> Say yes here to build support for the ADC found in Renesas
> RZ/T2H / RZ/N2H SoCs.
> diff --git a/drivers/iio/adc/rzt2h_adc.c b/drivers/iio/adc/rzt2h_adc.c
> index 95bcebdc02cb..d76226375f22 100644
> --- a/drivers/iio/adc/rzt2h_adc.c
> +++ b/drivers/iio/adc/rzt2h_adc.c
> @@ -4,11 +4,16 @@
> #include <linux/cleanup.h>
> #include <linux/completion.h>
> #include <linux/delay.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/dmaengine.h>
> #include <linux/iio/adc-helpers.h>
> +#include <linux/iio/buffer.h>
> #include <linux/iio/iio.h>
> +#include <linux/iio/kfifo_buf.h>
> #include <linux/interrupt.h>
> #include <linux/io.h>
> #include <linux/iopoll.h>
> +#include <linux/kthread.h>
> #include <linux/module.h>
> #include <linux/platform_device.h>
> #include <linux/pm_runtime.h>
> @@ -18,6 +23,7 @@
> #define RZT2H_ADCSR_ADIE_MASK BIT(12)
> #define RZT2H_ADCSR_ADCS_MASK GENMASK(14, 13)
> #define RZT2H_ADCSR_ADCS_SINGLE 0b00
> +#define RZT2H_ADCSR_ADCS_CONTINUOUS 0b10
> #define RZT2H_ADCSR_ADST_MASK BIT(15)
>
> #define RZT2H_ADANSA0_REG 0x04
> @@ -31,18 +37,47 @@
> #define RZT2H_ADCALCTL_CAL_ERR_MASK BIT(2)
>
> #define RZT2H_ADC_MAX_CHANNELS 16
> +#define RZT2H_ADC_CHANNEL_BYTES sizeof(u16)
> +#define RZT2H_ADC_DMA_PERIOD_SAMPLES 128
> +#define RZT2H_ADC_DMA_PERIODS 64
> +#define RZT2H_ADC_DMA_BUFFER_SAMPLES (RZT2H_ADC_DMA_PERIODS * \
> + RZT2H_ADC_DMA_PERIOD_SAMPLES)
> +#define RZT2H_ADC_DMA_BUFFER_SIZE (RZT2H_ADC_DMA_BUFFER_SAMPLES * \
> + RZT2H_ADC_MAX_CHANNELS * \
> + RZT2H_ADC_CHANNEL_BYTES)
> +
> +struct rzt2h_adc_dma {
> + struct dma_chan *chan;
> + u16 *buf;
> + dma_addr_t addr;
> +
> + unsigned int period_index;
> + unsigned int period_bytes;
> + unsigned int first_chan;
> + unsigned int sample_chans;
> +
> + u8 gather[RZT2H_ADC_MAX_CHANNELS];
> + unsigned int gather_len;
> +
> + atomic_t pending_periods;
> +
> + wait_queue_head_t wq;
> + struct task_struct *thread;
> +};
>
> struct rzt2h_adc {
> void __iomem *base;
> struct device *dev;
>
> phys_addr_t phys_base;
> + struct rzt2h_adc_dma dma;
> struct completion completion;
> /* lock to protect against multiple access to the device */
> struct mutex lock;
>
> const struct iio_chan_spec *channels;
> unsigned int num_channels;
> + u16 buf[RZT2H_ADC_MAX_CHANNELS];
>
> int irq;
> };
> @@ -151,6 +186,253 @@ static int rzt2h_adc_calibrate(struct rzt2h_adc *adc)
> return 0;
> }
>
> +static void rzt2h_adc_push_period(struct iio_dev *indio_dev, u16 *period,
> + dma_addr_t addr)
> +{
> + struct rzt2h_adc *adc = iio_priv(indio_dev);
> + u16 *dst = adc->buf;
> + u16 *src = period;
> +
> + dma_sync_single_for_cpu(adc->dev, addr, adc->dma.period_bytes,
> + DMA_FROM_DEVICE);
> +
> + for (unsigned int sample = 0; sample < RZT2H_ADC_DMA_PERIOD_SAMPLES; sample++) {
> + for (unsigned int i = 0; i < adc->dma.gather_len; i++)
> + dst[i] = src[adc->dma.gather[i]];
> +
> + src += adc->dma.sample_chans;
> +
> + iio_push_to_buffers(indio_dev, adc->buf);
> + }
> +}
> +
> +static void rzt2h_adc_advance_period_index(struct rzt2h_adc *adc, unsigned int i)
> +{
> + adc->dma.period_index += i;
> + adc->dma.period_index %= RZT2H_ADC_DMA_PERIODS;
> +}
> +
> +static void rzt2h_adc_dma_thread_loop(struct iio_dev *indio_dev)
> +{
> + struct rzt2h_adc *adc = iio_priv(indio_dev);
> + int pending, drop;
> + dma_addr_t addr;
> + u16 *period;
> +
> + pending = atomic_xchg(&adc->dma.pending_periods, 0);
> +
> + if (pending >= RZT2H_ADC_DMA_PERIODS) {
Add a comment here to say what is being dropped and why.
> + drop = pending - RZT2H_ADC_DMA_PERIODS + 1;
> +
> + rzt2h_adc_advance_period_index(adc, drop);
> + pending -= drop;
> + }
> +
> + for (unsigned int i = 0; i < pending; i++) {
> + unsigned int backlog = atomic_read(&adc->dma.pending_periods) +
> + pending - i;
> +
> + /*
> + * Bail if enough new periods have completed since reading the
> + * pending_periods that the next period about to be read is at
> + * risk of being overwritten.
> + */
> + if (backlog >= RZT2H_ADC_DMA_PERIODS)
> + break;
> +
> + period = adc->dma.buf + adc->dma.period_index *
> + RZT2H_ADC_DMA_PERIOD_SAMPLES * adc->dma.sample_chans;
> + addr = adc->dma.addr + adc->dma.period_index *
> + adc->dma.period_bytes;
> +
> + rzt2h_adc_push_period(indio_dev, period, addr);
> + rzt2h_adc_advance_period_index(adc, 1);
> + }
> +}
> +
> +static int rzt2h_adc_dma_thread(void *data)
> +{
> + struct iio_dev *indio_dev = data;
> + struct rzt2h_adc *adc = iio_priv(indio_dev);
> +
> + while (!kthread_should_stop()) {
> + wait_event_interruptible(adc->dma.wq,
> + atomic_read(&adc->dma.pending_periods) ||
> + kthread_should_stop());
I've not thought that much about the following but it seems plausible so please
take a look
> +
> + if (kthread_should_stop())
> + break;
> +
> + rzt2h_adc_dma_thread_loop(indio_dev);
> + }
> +
> + return 0;
> +}
> +
> +static void rzt2h_adc_dma_callback(void *data)
> +{
> + struct iio_dev *indio_dev = data;
> + struct rzt2h_adc *adc = iio_priv(indio_dev);
> +
> + atomic_inc(&adc->dma.pending_periods);
> + wake_up(&adc->dma.wq);
> +}
> +
> +static void rzt2h_adc_dma_calc_layout(struct iio_dev *indio_dev)
> +{
> + struct rzt2h_adc *adc = iio_priv(indio_dev);
> + unsigned int hi = 0, lo = RZT2H_ADC_MAX_CHANNELS - 1;
> + const struct iio_chan_spec *chan;
> + unsigned int sample_chans;
> + unsigned int first_chan;
> + unsigned int scan_index;
> + unsigned int swap;
> + unsigned int idx;
As mentioned at the top, I think you can probably avoid all this complexity.
Lots of devices have restrictions on combinations of channels that
are enabled together. For that we have available_scan_masks
and the demux stuff in the IIO core.
> +
> + /* Find the lowest and highest enabled channel. */
> + iio_for_each_active_channel(indio_dev, scan_index) {
> + chan = &indio_dev->channels[scan_index];
> +
> + lo = min_t(unsigned int, lo, chan->channel);
> + hi = max_t(unsigned int, hi, chan->channel);
> + }
> +
> + /*
> + * The DMA has no scatter-gather and transfers must have a power-of-two
> + * width, so pick the smallest power-of-two-aligned block of channels
> + * that covers all enabled channels.
> + */
> + for (sample_chans = 1; sample_chans < RZT2H_ADC_MAX_CHANNELS; sample_chans <<= 1) {
> + first_chan = round_down(lo, sample_chans);
> +
> + if (first_chan + sample_chans > hi)
> + break;
> + }
> +
> + /*
> + * Build a table to map each enabled channel to its position in the
> + * transferred block, it will be used later to extract only the enabled
> + * channels out of it.
> + * The DMA moves data in 32-bit words, which swaps each pair of adjacent
> + * 16-bit channels. Undo it.
> + */
> + adc->dma.gather_len = 0;
> + swap = sample_chans > 1;
> + iio_for_each_active_channel(indio_dev, scan_index) {
> + chan = &indio_dev->channels[scan_index];
> + idx = chan->channel - first_chan;
> +
> + adc->dma.gather[adc->dma.gather_len++] = idx ^ swap;
> + }
> +
> + adc->dma.first_chan = first_chan;
> + adc->dma.sample_chans = sample_chans;
> + adc->dma.period_bytes = RZT2H_ADC_DMA_PERIOD_SAMPLES * sample_chans *
> + RZT2H_ADC_CHANNEL_BYTES;
> +}
> +
> +static int rzt2h_adc_start_dma(struct iio_dev *indio_dev)
> +{
> + struct rzt2h_adc *adc = iio_priv(indio_dev);
> + struct dma_async_tx_descriptor *desc;
> + struct dma_slave_config config = {};
> + unsigned int buffer_bytes;
> + dma_cookie_t cookie;
> + int ret;
> +
> + rzt2h_adc_dma_calc_layout(indio_dev);
> +
> + config.src_addr = adc->phys_base + RZT2H_ADDR_REG(adc->dma.first_chan);
> + config.src_addr_width = adc->dma.sample_chans * RZT2H_ADC_CHANNEL_BYTES;
config = (struct dma_slave_config) {
.src_addr = adc->phys_base + RZT2H_ADDR_REG(adc->dma.first_chan),
.src_addr_width = adc->dma.sample_chans * RZT2H_ADC_CHANNEL_BYTES,
};
keeps all the filling in of info together and avoids need to do = { }
above then overwrite some of the zeroed memory.
> +
> + buffer_bytes = RZT2H_ADC_DMA_PERIODS * adc->dma.period_bytes;
> +
> + ret = dmaengine_slave_config(adc->dma.chan, &config);
> + if (ret)
> + return ret;
> +
> + desc = dmaengine_prep_dma_cyclic(adc->dma.chan, adc->dma.addr,
> + buffer_bytes, adc->dma.period_bytes,
> + DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT);
> + if (!desc)
> + return -EBUSY;
> +
> + desc->callback = rzt2h_adc_dma_callback;
> + desc->callback_param = indio_dev;
> +
> + cookie = dmaengine_submit(desc);
> + ret = dma_submit_error(cookie);
> + if (ret) {
> + dmaengine_terminate_sync(adc->dma.chan);
> + return ret;
> + }
> +
> + adc->dma.thread = kthread_run(rzt2h_adc_dma_thread, indio_dev,
> + "rzt2h-adc-dma");
> + if (IS_ERR(adc->dma.thread)) {
> + dmaengine_terminate_sync(adc->dma.chan);
> + return PTR_ERR(adc->dma.thread);
> + }
> +
> + disable_irq(adc->irq);
> +
> + dma_async_issue_pending(adc->dma.chan);
> +
> + return 0;
> +}
> +
> +static int rzt2h_adc_setup_dma(struct iio_dev *indio_dev)
> +{
> + struct rzt2h_adc *adc = iio_priv(indio_dev);
> + struct device *dev = adc->dev;
> + int ret;
> +
> + adc->dma.chan = devm_dma_request_chan(adc->dev, "rx");
> + if (IS_ERR(adc->dma.chan)) {
> + ret = PTR_ERR(adc->dma.chan);
> + if (ret != -ENODEV)
> + return dev_err_probe(adc->dev, ret, "DMA channel request failed\n");
You have dev that can be used here.
> +
> + adc->dma.chan = NULL;
> + return 0;
> + }
> +
> + adc->dma.buf = dma_alloc_noncoherent(adc->dev, RZT2H_ADC_DMA_BUFFER_SIZE,
Same here.
--
Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH 5/9] iio: adc: rzt2h: implement DMA buffer support
2026-08-29 1:20 ` Jonathan Cameron
@ 2026-08-29 1:24 ` Jonathan Cameron
0 siblings, 0 replies; 18+ messages in thread
From: Jonathan Cameron @ 2026-08-29 1:24 UTC (permalink / raw)
To: Cosmin Tanislav
Cc: David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Geert Uytterhoeven,
Magnus Damm, linux-iio, linux-renesas-soc, devicetree,
linux-kernel
On Sat, 29 Aug 2026 02:20:43 +0100
Jonathan Cameron <jic23@kernel.org> wrote:
> > Implement buffered capture using a cyclic DMA transfer into a kfifo
> > buffer to support continuous high-rate sampling.
> >
> > On buffer enable, switch the ADC to continuous conversion mode and start
> > a cyclic DMA transfer over the active channels.
> >
> > Because the DMA controller does not support native scatter-gather, and
> > because of the cyclic DMA setup, transfers must be done in widths
> > covering all the enabled channels.
> >
> > Since DMA transfer width must be a power of two and aligned to its size,
> > cover the smallest power-of-two-aligned group of channel registers
> > spanning the enabled channels.
> >
> > Split the cyclic buffer into fixed-size periods. On each period
> > completion, bump a pending counter and wake a consumer kthread from the
> > DMA callback.
> >
> > For every completed period, gather the enabled channels out of the DMA
> > layout into the scan layout the IIO core expects and push each scan
> > with iio_push_to_buffers().
>
> Could you instead use the available_scan_masks infrastructure. bit annoying
> to specify the full list but isn't that long I think with 16 channels
>
> 16 x single
> 8 x double,
> 4 x quads
> 2 x octect
> 1 x all of them.
>
> The the IIO demux in (sits behind the push_to_buffers path if
> we have available_scan_masks set) will then deal with repacking
> the data if necessary.
>
> >
> > If the consumer kthread falls behind by a full buffer, drop the oldest
> > periods.
> >
> > Because the DMA transfer must cover all channels between the first and
> > last enabled ones, skip disabled channels while compacting.
> >
> > Also, the DMA controller transfers data in 32-bit words, but the ADC's
> > data registers are 16-bit wide, causing adjacent channel data to be
> > swapped. Swap consecutive channels while compacting to account for this.
> >
> > Allocate the DMA buffer via dma_alloc_noncoherent() and synchronise it
> > per period to allow it to be cached by the CPU while compacting.
> >
> > Disable the completion IRQ for the duration of the DMA transfer, as the
> > ICU does not mask this event from reaching the GIC even if it is being
> > used to drive the DMA capture.
> >
> > Signed-off-by: Cosmin Tanislav <cosmin-gabriel.tanislav.xa@renesas.com>
>
> I'm messing around with b4 review tui and sashiko integration. I've
> left the Sashiko comments in here as I'm out of time today to look
> at them in detial.
Apparently I got the options wrong to include the sashiko replies :(
So take a look at :
https://sashiko.dev/#/patchset/20260828145943.2077589-1-cosmin-gabriel.tanislav.xa%40renesas.com
> >
> > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> > index 415e519ad4eb..6c7b30d2b6e7 100644
> > --- a/drivers/iio/adc/Kconfig
> > +++ b/drivers/iio/adc/Kconfig
> > @@ -1551,6 +1551,8 @@ config RZT2H_ADC
> > tristate "Renesas RZ/T2H / RZ/N2H ADC driver"
> > depends on ARCH_RENESAS || COMPILE_TEST
> > select IIO_ADC_HELPER
> > + select IIO_BUFFER
> > + select IIO_KFIFO_BUF
> > help
> > Say yes here to build support for the ADC found in Renesas
> > RZ/T2H / RZ/N2H SoCs.
> > diff --git a/drivers/iio/adc/rzt2h_adc.c b/drivers/iio/adc/rzt2h_adc.c
> > index 95bcebdc02cb..d76226375f22 100644
> > --- a/drivers/iio/adc/rzt2h_adc.c
> > +++ b/drivers/iio/adc/rzt2h_adc.c
> > @@ -4,11 +4,16 @@
> > #include <linux/cleanup.h>
> > #include <linux/completion.h>
> > #include <linux/delay.h>
> > +#include <linux/dma-mapping.h>
> > +#include <linux/dmaengine.h>
> > #include <linux/iio/adc-helpers.h>
> > +#include <linux/iio/buffer.h>
> > #include <linux/iio/iio.h>
> > +#include <linux/iio/kfifo_buf.h>
> > #include <linux/interrupt.h>
> > #include <linux/io.h>
> > #include <linux/iopoll.h>
> > +#include <linux/kthread.h>
> > #include <linux/module.h>
> > #include <linux/platform_device.h>
> > #include <linux/pm_runtime.h>
> > @@ -18,6 +23,7 @@
> > #define RZT2H_ADCSR_ADIE_MASK BIT(12)
> > #define RZT2H_ADCSR_ADCS_MASK GENMASK(14, 13)
> > #define RZT2H_ADCSR_ADCS_SINGLE 0b00
> > +#define RZT2H_ADCSR_ADCS_CONTINUOUS 0b10
> > #define RZT2H_ADCSR_ADST_MASK BIT(15)
> >
> > #define RZT2H_ADANSA0_REG 0x04
> > @@ -31,18 +37,47 @@
> > #define RZT2H_ADCALCTL_CAL_ERR_MASK BIT(2)
> >
> > #define RZT2H_ADC_MAX_CHANNELS 16
> > +#define RZT2H_ADC_CHANNEL_BYTES sizeof(u16)
> > +#define RZT2H_ADC_DMA_PERIOD_SAMPLES 128
> > +#define RZT2H_ADC_DMA_PERIODS 64
> > +#define RZT2H_ADC_DMA_BUFFER_SAMPLES (RZT2H_ADC_DMA_PERIODS * \
> > + RZT2H_ADC_DMA_PERIOD_SAMPLES)
> > +#define RZT2H_ADC_DMA_BUFFER_SIZE (RZT2H_ADC_DMA_BUFFER_SAMPLES * \
> > + RZT2H_ADC_MAX_CHANNELS * \
> > + RZT2H_ADC_CHANNEL_BYTES)
> > +
> > +struct rzt2h_adc_dma {
> > + struct dma_chan *chan;
> > + u16 *buf;
> > + dma_addr_t addr;
> > +
> > + unsigned int period_index;
> > + unsigned int period_bytes;
> > + unsigned int first_chan;
> > + unsigned int sample_chans;
> > +
> > + u8 gather[RZT2H_ADC_MAX_CHANNELS];
> > + unsigned int gather_len;
> > +
> > + atomic_t pending_periods;
> > +
> > + wait_queue_head_t wq;
> > + struct task_struct *thread;
> > +};
> >
> > struct rzt2h_adc {
> > void __iomem *base;
> > struct device *dev;
> >
> > phys_addr_t phys_base;
> > + struct rzt2h_adc_dma dma;
> > struct completion completion;
> > /* lock to protect against multiple access to the device */
> > struct mutex lock;
> >
> > const struct iio_chan_spec *channels;
> > unsigned int num_channels;
> > + u16 buf[RZT2H_ADC_MAX_CHANNELS];
> >
> > int irq;
> > };
> > @@ -151,6 +186,253 @@ static int rzt2h_adc_calibrate(struct rzt2h_adc *adc)
> > return 0;
> > }
> >
> > +static void rzt2h_adc_push_period(struct iio_dev *indio_dev, u16 *period,
> > + dma_addr_t addr)
> > +{
> > + struct rzt2h_adc *adc = iio_priv(indio_dev);
> > + u16 *dst = adc->buf;
> > + u16 *src = period;
> > +
> > + dma_sync_single_for_cpu(adc->dev, addr, adc->dma.period_bytes,
> > + DMA_FROM_DEVICE);
> > +
> > + for (unsigned int sample = 0; sample < RZT2H_ADC_DMA_PERIOD_SAMPLES; sample++) {
> > + for (unsigned int i = 0; i < adc->dma.gather_len; i++)
> > + dst[i] = src[adc->dma.gather[i]];
> > +
> > + src += adc->dma.sample_chans;
> > +
> > + iio_push_to_buffers(indio_dev, adc->buf);
> > + }
>
>
>
> > +}
> > +
> > +static void rzt2h_adc_advance_period_index(struct rzt2h_adc *adc, unsigned int i)
> > +{
> > + adc->dma.period_index += i;
> > + adc->dma.period_index %= RZT2H_ADC_DMA_PERIODS;
> > +}
> > +
> > +static void rzt2h_adc_dma_thread_loop(struct iio_dev *indio_dev)
> > +{
> > + struct rzt2h_adc *adc = iio_priv(indio_dev);
> > + int pending, drop;
> > + dma_addr_t addr;
> > + u16 *period;
> > +
> > + pending = atomic_xchg(&adc->dma.pending_periods, 0);
> > +
> > + if (pending >= RZT2H_ADC_DMA_PERIODS) {
>
> Add a comment here to say what is being dropped and why.
>
> > + drop = pending - RZT2H_ADC_DMA_PERIODS + 1;
> > +
> > + rzt2h_adc_advance_period_index(adc, drop);
> > + pending -= drop;
> > + }
> > +
> > + for (unsigned int i = 0; i < pending; i++) {
> > + unsigned int backlog = atomic_read(&adc->dma.pending_periods) +
> > + pending - i;
> > +
> > + /*
> > + * Bail if enough new periods have completed since reading the
> > + * pending_periods that the next period about to be read is at
> > + * risk of being overwritten.
> > + */
> > + if (backlog >= RZT2H_ADC_DMA_PERIODS)
> > + break;
>
>
> > +
> > + period = adc->dma.buf + adc->dma.period_index *
> > + RZT2H_ADC_DMA_PERIOD_SAMPLES * adc->dma.sample_chans;
> > + addr = adc->dma.addr + adc->dma.period_index *
> > + adc->dma.period_bytes;
> > +
> > + rzt2h_adc_push_period(indio_dev, period, addr);
> > + rzt2h_adc_advance_period_index(adc, 1);
> > + }
> > +}
> > +
> > +static int rzt2h_adc_dma_thread(void *data)
> > +{
> > + struct iio_dev *indio_dev = data;
> > + struct rzt2h_adc *adc = iio_priv(indio_dev);
> > +
> > + while (!kthread_should_stop()) {
> > + wait_event_interruptible(adc->dma.wq,
> > + atomic_read(&adc->dma.pending_periods) ||
> > + kthread_should_stop());
>
> I've not thought that much about the following but it seems plausible so please
> take a look
>
>
> > +
> > + if (kthread_should_stop())
> > + break;
> > +
> > + rzt2h_adc_dma_thread_loop(indio_dev);
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static void rzt2h_adc_dma_callback(void *data)
> > +{
> > + struct iio_dev *indio_dev = data;
> > + struct rzt2h_adc *adc = iio_priv(indio_dev);
> > +
> > + atomic_inc(&adc->dma.pending_periods);
> > + wake_up(&adc->dma.wq);
> > +}
> > +
> > +static void rzt2h_adc_dma_calc_layout(struct iio_dev *indio_dev)
> > +{
> > + struct rzt2h_adc *adc = iio_priv(indio_dev);
> > + unsigned int hi = 0, lo = RZT2H_ADC_MAX_CHANNELS - 1;
> > + const struct iio_chan_spec *chan;
> > + unsigned int sample_chans;
> > + unsigned int first_chan;
> > + unsigned int scan_index;
> > + unsigned int swap;
> > + unsigned int idx;
>
> As mentioned at the top, I think you can probably avoid all this complexity.
> Lots of devices have restrictions on combinations of channels that
> are enabled together. For that we have available_scan_masks
> and the demux stuff in the IIO core.
>
> > +
> > + /* Find the lowest and highest enabled channel. */
> > + iio_for_each_active_channel(indio_dev, scan_index) {
> > + chan = &indio_dev->channels[scan_index];
> > +
> > + lo = min_t(unsigned int, lo, chan->channel);
> > + hi = max_t(unsigned int, hi, chan->channel);
> > + }
> > +
> > + /*
> > + * The DMA has no scatter-gather and transfers must have a power-of-two
> > + * width, so pick the smallest power-of-two-aligned block of channels
> > + * that covers all enabled channels.
> > + */
> > + for (sample_chans = 1; sample_chans < RZT2H_ADC_MAX_CHANNELS; sample_chans <<= 1) {
> > + first_chan = round_down(lo, sample_chans);
> > +
> > + if (first_chan + sample_chans > hi)
> > + break;
> > + }
> > +
> > + /*
> > + * Build a table to map each enabled channel to its position in the
> > + * transferred block, it will be used later to extract only the enabled
> > + * channels out of it.
> > + * The DMA moves data in 32-bit words, which swaps each pair of adjacent
> > + * 16-bit channels. Undo it.
> > + */
> > + adc->dma.gather_len = 0;
> > + swap = sample_chans > 1;
> > + iio_for_each_active_channel(indio_dev, scan_index) {
> > + chan = &indio_dev->channels[scan_index];
> > + idx = chan->channel - first_chan;
> > +
> > + adc->dma.gather[adc->dma.gather_len++] = idx ^ swap;
> > + }
> > +
> > + adc->dma.first_chan = first_chan;
> > + adc->dma.sample_chans = sample_chans;
> > + adc->dma.period_bytes = RZT2H_ADC_DMA_PERIOD_SAMPLES * sample_chans *
> > + RZT2H_ADC_CHANNEL_BYTES;
> > +}
> > +
> > +static int rzt2h_adc_start_dma(struct iio_dev *indio_dev)
> > +{
> > + struct rzt2h_adc *adc = iio_priv(indio_dev);
> > + struct dma_async_tx_descriptor *desc;
> > + struct dma_slave_config config = {};
> > + unsigned int buffer_bytes;
> > + dma_cookie_t cookie;
> > + int ret;
> > +
> > + rzt2h_adc_dma_calc_layout(indio_dev);
> > +
> > + config.src_addr = adc->phys_base + RZT2H_ADDR_REG(adc->dma.first_chan);
> > + config.src_addr_width = adc->dma.sample_chans * RZT2H_ADC_CHANNEL_BYTES;
>
> config = (struct dma_slave_config) {
> .src_addr = adc->phys_base + RZT2H_ADDR_REG(adc->dma.first_chan),
> .src_addr_width = adc->dma.sample_chans * RZT2H_ADC_CHANNEL_BYTES,
> };
>
> keeps all the filling in of info together and avoids need to do = { }
> above then overwrite some of the zeroed memory.
>
> > +
> > + buffer_bytes = RZT2H_ADC_DMA_PERIODS * adc->dma.period_bytes;
> > +
> > + ret = dmaengine_slave_config(adc->dma.chan, &config);
> > + if (ret)
> > + return ret;
> > +
> > + desc = dmaengine_prep_dma_cyclic(adc->dma.chan, adc->dma.addr,
> > + buffer_bytes, adc->dma.period_bytes,
> > + DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT);
> > + if (!desc)
> > + return -EBUSY;
> > +
> > + desc->callback = rzt2h_adc_dma_callback;
> > + desc->callback_param = indio_dev;
> > +
> > + cookie = dmaengine_submit(desc);
> > + ret = dma_submit_error(cookie);
> > + if (ret) {
> > + dmaengine_terminate_sync(adc->dma.chan);
> > + return ret;
> > + }
> > +
> > + adc->dma.thread = kthread_run(rzt2h_adc_dma_thread, indio_dev,
> > + "rzt2h-adc-dma");
> > + if (IS_ERR(adc->dma.thread)) {
> > + dmaengine_terminate_sync(adc->dma.chan);
> > + return PTR_ERR(adc->dma.thread);
> > + }
> > +
> > + disable_irq(adc->irq);
> > +
> > + dma_async_issue_pending(adc->dma.chan);
> > +
> > + return 0;
> > +}
> > +
>
> > +static int rzt2h_adc_setup_dma(struct iio_dev *indio_dev)
> > +{
> > + struct rzt2h_adc *adc = iio_priv(indio_dev);
> > + struct device *dev = adc->dev;
> > + int ret;
> > +
> > + adc->dma.chan = devm_dma_request_chan(adc->dev, "rx");
> > + if (IS_ERR(adc->dma.chan)) {
> > + ret = PTR_ERR(adc->dma.chan);
> > + if (ret != -ENODEV)
> > + return dev_err_probe(adc->dev, ret, "DMA channel request failed\n");
>
> You have dev that can be used here.
>
> > +
> > + adc->dma.chan = NULL;
> > + return 0;
> > + }
> > +
> > + adc->dma.buf = dma_alloc_noncoherent(adc->dev, RZT2H_ADC_DMA_BUFFER_SIZE,
>
> Same here.
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH 6/9] iio: adc: rzt2h: expose sampling frequency
2026-08-28 14:59 [PATCH 0/9] iio: adc: rzt2h: add DMA buffer support Cosmin Tanislav
` (4 preceding siblings ...)
2026-08-28 14:59 ` [PATCH 5/9] iio: adc: rzt2h: implement DMA buffer support Cosmin Tanislav
@ 2026-08-28 14:59 ` Cosmin Tanislav
2026-08-28 15:14 ` sashiko-bot
2026-08-28 14:59 ` [PATCH 7/9] dt-bindings: iio: adc: renesas,r9a09g077-adc: document DMA support Cosmin Tanislav
` (2 subsequent siblings)
8 siblings, 1 reply; 18+ messages in thread
From: Cosmin Tanislav @ 2026-08-28 14:59 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Geert Uytterhoeven, Magnus Damm
Cc: linux-iio, linux-renesas-soc, devicetree, linux-kernel,
Cosmin Tanislav
Expose the sampling frequency as a per-channel IIO_CHAN_INFO_SAMP_FREQ
to let userspace control conversion time.
Each channel conversion takes a fixed 13 ADCLK cycles plus the sample
time programmed in ADSSTRn, giving a rate of ADCLK / (13 + ADSSTRn).
Read the ADCLK rate from the "adclk" clock to derive the frequency.
Claim direct mode while writing so the rate cannot change during a
capture.
Program the sample time into ADSSTRn for each enabled channel on single
reads and on buffer enable.
Signed-off-by: Cosmin Tanislav <cosmin-gabriel.tanislav.xa@renesas.com>
---
drivers/iio/adc/rzt2h_adc.c | 111 +++++++++++++++++++++++++++++++++++-
1 file changed, 109 insertions(+), 2 deletions(-)
diff --git a/drivers/iio/adc/rzt2h_adc.c b/drivers/iio/adc/rzt2h_adc.c
index d76226375f22..2e13f931a2e9 100644
--- a/drivers/iio/adc/rzt2h_adc.c
+++ b/drivers/iio/adc/rzt2h_adc.c
@@ -2,6 +2,7 @@
#include <linux/bitfield.h>
#include <linux/cleanup.h>
+#include <linux/clk.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
@@ -31,6 +32,18 @@
#define RZT2H_ADDR_REG(x) (0x20 + 0x2 * (x))
+#define RZT2H_ADSSTRn(n) (0xe0 + 0x1 * (n))
+
+/*
+ * Each A/D channel conversion takes a fixed base of 13 ADCLK cycles plus the
+ * sample time programmed in ADSSTRn, giving a conversion rate of
+ * ADCLK / (13 + ADSSTRn).
+ */
+#define RZT2H_ADC_CONV_CYCLES_BASE 13
+#define RZT2H_ADC_SST_MIN 0x7
+#define RZT2H_ADC_SST_MAX 0xff
+#define RZT2H_ADC_SST_DEFAULT 0xb
+
#define RZT2H_ADCALCTL_REG 0x1f0
#define RZT2H_ADCALCTL_CAL_MASK BIT(0)
#define RZT2H_ADCALCTL_CAL_RDY_MASK BIT(1)
@@ -79,6 +92,10 @@ struct rzt2h_adc {
unsigned int num_channels;
u16 buf[RZT2H_ADC_MAX_CHANNELS];
+ unsigned long adclk_rate;
+ int samp_freq_avail[3];
+ u8 sst[RZT2H_ADC_MAX_CHANNELS];
+
int irq;
};
@@ -109,6 +126,11 @@ static void rzt2h_adc_stop(struct rzt2h_adc *adc)
writew(reg, adc->base + RZT2H_ADCSR_REG);
}
+static void rzt2h_adc_set_sst(struct rzt2h_adc *adc, unsigned int ch, u8 sst)
+{
+ writeb(sst, adc->base + RZT2H_ADSSTRn(ch));
+}
+
static int rzt2h_adc_read_single(struct rzt2h_adc *adc, unsigned int ch, int *val)
{
int ret;
@@ -124,6 +146,8 @@ static int rzt2h_adc_read_single(struct rzt2h_adc *adc, unsigned int ch, int *va
/* Enable a single channel */
writew(RZT2H_ADANSA0_CH_MASK(ch), adc->base + RZT2H_ADANSA0_REG);
+ rzt2h_adc_set_sst(adc, ch, adc->sst[ch]);
+
rzt2h_adc_start(adc, RZT2H_ADCSR_ADCS_SINGLE);
/*
@@ -397,6 +421,7 @@ static int rzt2h_adc_buffer_postenable(struct iio_dev *indio_dev)
iio_for_each_active_channel(indio_dev, scan_index) {
chan = &indio_dev->channels[scan_index];
val |= RZT2H_ADANSA0_CH_MASK(chan->channel);
+ rzt2h_adc_set_sst(adc, chan->channel, adc->sst[chan->channel]);
}
writew(val, adc->base + RZT2H_ADANSA0_REG);
@@ -433,6 +458,23 @@ static int rzt2h_adc_buffer_predisable(struct iio_dev *indio_dev)
return 0;
}
+static int rzt2h_adc_sst_to_freq(struct rzt2h_adc *adc, u8 sst)
+{
+ return adc->adclk_rate / (RZT2H_ADC_CONV_CYCLES_BASE + sst);
+}
+
+static u8 rzt2h_adc_freq_to_sst(struct rzt2h_adc *adc, int freq)
+{
+ unsigned int cycles;
+
+ cycles = DIV_ROUND_CLOSEST(adc->adclk_rate, freq);
+ cycles = clamp_val(cycles,
+ RZT2H_ADC_CONV_CYCLES_BASE + RZT2H_ADC_SST_MIN,
+ RZT2H_ADC_CONV_CYCLES_BASE + RZT2H_ADC_SST_MAX);
+
+ return cycles - RZT2H_ADC_CONV_CYCLES_BASE;
+}
+
static int rzt2h_adc_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
@@ -451,6 +493,51 @@ static int rzt2h_adc_read_raw(struct iio_dev *indio_dev,
*val = 1800;
*val2 = 12;
return IIO_VAL_FRACTIONAL_LOG2;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = rzt2h_adc_sst_to_freq(adc, adc->sst[chan->channel]);
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int rzt2h_adc_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct rzt2h_adc *adc = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ: {
+ if (val <= 0)
+ return -EINVAL;
+
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ adc->sst[chan->channel] = rzt2h_adc_freq_to_sst(adc, val);
+
+ return 0;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int rzt2h_adc_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ struct rzt2h_adc *adc = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *vals = adc->samp_freq_avail;
+ *type = IIO_VAL_INT;
+ *length = ARRAY_SIZE(adc->samp_freq_avail);
+ return IIO_AVAIL_RANGE;
default:
return -EINVAL;
}
@@ -463,6 +550,8 @@ static const struct iio_buffer_setup_ops rzt2h_adc_buffer_setup_ops = {
static const struct iio_info rzt2h_adc_iio_info = {
.read_raw = rzt2h_adc_read_raw,
+ .write_raw = rzt2h_adc_write_raw,
+ .read_avail = rzt2h_adc_read_avail,
};
static irqreturn_t rzt2h_adc_isr(int irq, void *private)
@@ -477,7 +566,9 @@ static irqreturn_t rzt2h_adc_isr(int irq, void *private)
static const struct iio_chan_spec rzt2h_adc_chan_template = {
.indexed = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
- BIT(IIO_CHAN_INFO_SCALE),
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
.type = IIO_VOLTAGE,
.scan_type = {
.sign = 'u',
@@ -502,8 +593,11 @@ static int rzt2h_adc_parse_properties(struct rzt2h_adc *adc)
adc->num_channels = ret;
adc->channels = chan_array;
- for (unsigned int i = 0; i < adc->num_channels; i++)
+ for (unsigned int i = 0; i < adc->num_channels; i++) {
+ adc->sst[chan_array[i].channel] = RZT2H_ADC_SST_DEFAULT;
+
chan_array[i].scan_index = i;
+ }
return 0;
}
@@ -552,6 +646,7 @@ static int rzt2h_adc_probe(struct platform_device *pdev)
struct iio_dev *indio_dev;
struct rzt2h_adc *adc;
struct resource *res;
+ struct clk *adclk;
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*adc));
@@ -579,6 +674,18 @@ static int rzt2h_adc_probe(struct platform_device *pdev)
adc->phys_base = res->start;
+ adclk = devm_clk_get(dev, "adclk");
+ if (IS_ERR(adclk))
+ return dev_err_probe(dev, PTR_ERR(adclk), "failed to get adclk\n");
+
+ adc->adclk_rate = clk_get_rate(adclk);
+ if (!adc->adclk_rate)
+ return dev_err_probe(dev, -EINVAL, "invalid adclk rate\n");
+
+ adc->samp_freq_avail[0] = rzt2h_adc_sst_to_freq(adc, RZT2H_ADC_SST_MAX);
+ adc->samp_freq_avail[1] = 1;
+ adc->samp_freq_avail[2] = rzt2h_adc_sst_to_freq(adc, RZT2H_ADC_SST_MIN);
+
pm_runtime_set_autosuspend_delay(dev, 300);
pm_runtime_use_autosuspend(dev);
ret = devm_pm_runtime_enable(dev);
--
2.55.0
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH 6/9] iio: adc: rzt2h: expose sampling frequency
2026-08-28 14:59 ` [PATCH 6/9] iio: adc: rzt2h: expose sampling frequency Cosmin Tanislav
@ 2026-08-28 15:14 ` sashiko-bot
0 siblings, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-08-28 15:14 UTC (permalink / raw)
To: Cosmin Tanislav; +Cc: robh, devicetree, wsa+renesas, conor+dt
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Spurious -ETIMEDOUT on single reads due to extremely short 1-jiffy completion timeout
--
commit c401ad2e533dc2bc5a1b011d098327c8d7400f4f
Author: Cosmin Tanislav <cosmin-gabriel.tanislav.xa@renesas.com>
iio: adc: rzt2h: expose sampling frequency
This patch exposes the sampling frequency as a per-channel
IIO_CHAN_INFO_SAMP_FREQ attribute to allow userspace control over conversion
time. It derives the frequency from the ADCLK rate and programs the sample
time into ADSSTRn for each enabled channel.
> diff --git a/drivers/iio/adc/rzt2h_adc.c b/drivers/iio/adc/rzt2h_adc.c
> index d76226375f220..2e13f931a2e9a 100644
> --- a/drivers/iio/adc/rzt2h_adc.c
> +++ b/drivers/iio/adc/rzt2h_adc.c
[ ... ]
> @@ -124,6 +146,8 @@ static int rzt2h_adc_read_single(struct rzt2h_adc *adc, unsigned int ch, int *va
> /* Enable a single channel */
> writew(RZT2H_ADANSA0_CH_MASK(ch), adc->base + RZT2H_ADANSA0_REG);
>
> + rzt2h_adc_set_sst(adc, ch, adc->sst[ch]);
> +
> rzt2h_adc_start(adc, RZT2H_ADCSR_ADCS_SINGLE);
>
> /*
[Severity: High]
This is a pre-existing issue, but does the newly introduced configurable
sampling frequency exacerbate a spurious timeout bug here?
In rzt2h_adc_read_single(), right below this hunk, the code calls:
ret = wait_for_completion_timeout(&adc->completion, usecs_to_jiffies(1));
Because usecs_to_jiffies(1) evaluates to 1, the timeout is set to exactly
1 jiffy, which will expire at the very next timer tick.
If the tick fires immediately after the wait starts, it aborts almost
instantly (returning 0) before the hardware interrupt arrives, causing the
kernel to intermittently and incorrectly return -ETIMEDOUT to userspace
instead of the actual ADC reading.
Since the patch now allows increasing the hardware conversion delay (up to
268 cycles or ~5.36us at 50MHz ADCLK), does this widen the race window and
significantly increase the probability of this spurious timeout occurring?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260828145943.2077589-1-cosmin-gabriel.tanislav.xa@renesas.com?part=6
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH 7/9] dt-bindings: iio: adc: renesas,r9a09g077-adc: document DMA support
2026-08-28 14:59 [PATCH 0/9] iio: adc: rzt2h: add DMA buffer support Cosmin Tanislav
` (5 preceding siblings ...)
2026-08-28 14:59 ` [PATCH 6/9] iio: adc: rzt2h: expose sampling frequency Cosmin Tanislav
@ 2026-08-28 14:59 ` Cosmin Tanislav
2026-08-28 15:14 ` sashiko-bot
2026-08-28 16:21 ` Conor Dooley
2026-08-28 14:59 ` [PATCH 8/9] arm64: dts: renesas: r9a09g077: Wire up DMA support for ADC Cosmin Tanislav
2026-08-28 14:59 ` [PATCH 9/9] arm64: dts: renesas: r9a09g087: " Cosmin Tanislav
8 siblings, 2 replies; 18+ messages in thread
From: Cosmin Tanislav @ 2026-08-28 14:59 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Geert Uytterhoeven, Magnus Damm
Cc: linux-iio, linux-renesas-soc, devicetree, linux-kernel,
Cosmin Tanislav
The ADC can transfer conversion results to memory over DMA.
Allow up to three DMA specifiers, as the ADC conversion DMA request can
be routed to any of the SoC's three DMA controllers.
Signed-off-by: Cosmin Tanislav <cosmin-gabriel.tanislav.xa@renesas.com>
---
.../bindings/iio/adc/renesas,r9a09g077-adc.yaml | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml b/Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml
index dc0206b28231..4b5934ac296b 100644
--- a/Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml
+++ b/Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml
@@ -57,6 +57,18 @@ properties:
- const: adclk
- const: pclk
+ dmas:
+ minItems: 1
+ maxItems: 3
+ description:
+ Must contain a list of references to DMA RX specifiers.
+
+ dma-names:
+ minItems: 1
+ maxItems: 3
+ items:
+ const: rx
+
power-domains:
maxItems: 1
--
2.55.0
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH 7/9] dt-bindings: iio: adc: renesas,r9a09g077-adc: document DMA support
2026-08-28 14:59 ` [PATCH 7/9] dt-bindings: iio: adc: renesas,r9a09g077-adc: document DMA support Cosmin Tanislav
@ 2026-08-28 15:14 ` sashiko-bot
2026-08-28 16:21 ` Conor Dooley
1 sibling, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-08-28 15:14 UTC (permalink / raw)
To: Cosmin Tanislav; +Cc: conor+dt, devicetree, robh, wsa+renesas
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [Medium] The `dma-names` property schema dictates `maxItems: 3` and `items: const: rx`, which inherently conflicts with dt-schema's `*-names` uniqueness constraint, making it impossible to pass validation for multiple DMA channels.
Pre-existing issues:
- [High] The consumer's software read pointer (`period_index`) permanently desynchronizes from the hardware DMA write position when periods are abandoned during a backlog break, causing permanent data corruption.
--
commit 0d72d8676d8ea20bbe7d6a24553312b902a516ce
Author: Cosmin Tanislav <cosmin-gabriel.tanislav.xa@renesas.com>
dt-bindings: iio: adc: renesas,r9a09g077-adc: document DMA support
The ADC can transfer conversion results to memory over DMA. Allow up to three
DMA specifiers, as the ADC conversion DMA request can be routed to any of the
SoC's three DMA controllers.
> diff --git a/Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml b/Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml
> index dc0206b282315..4b5934ac296b8 100644
> --- a/Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml
> +++ b/Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml
> @@ -57,6 +57,18 @@ properties:
> - const: adclk
> - const: pclk
>
> + dmas:
> + minItems: 1
> + maxItems: 3
> + description:
> + Must contain a list of references to DMA RX specifiers.
> +
> + dma-names:
> + minItems: 1
> + maxItems: 3
> + items:
> + const: rx
[Severity: Medium]
Will this unconditionally fail device tree validation when multiple DMA channels
are specified?
The core dt-schema strictly applies uniqueItems: true to all properties
ending in -names. A device tree specifying multiple DMA references as
intended by this binding (e.g., dma-names = "rx", "rx", "rx") will trigger
a validation error due to the duplicated "rx" items.
[Severity: High]
This is a pre-existing issue, but does the software read pointer permanently
desynchronize from the hardware DMA write position when periods are abandoned?
In drivers/iio/adc/rzt2h_adc.c:rzt2h_adc_dma_thread_loop():
for (unsigned int i = 0; i < pending; i++) {
unsigned int backlog = atomic_read(&adc->dma.pending_periods) +
pending - i;
/*
* Bail if enough new periods have completed since reading the
* pending_periods that the next period about to be read is at
* risk of being overwritten.
*/
if (backlog >= RZT2H_ADC_DMA_PERIODS)
break;
...
rzt2h_adc_advance_period_index(adc, 1);
}
The thread extracts all pending periods before this loop. If a backlog
accumulates, the loop breaks, discarding the remaining periods from the
extracted pending count. However, it fails to invoke
rzt2h_adc_advance_period_index(adc, pending - i) for those discarded periods.
The next time the loop runs, won't it resume reading from the old,
unadvanced period_index, corrupting all future ADC data reads?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260828145943.2077589-1-cosmin-gabriel.tanislav.xa@renesas.com?part=7
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH 7/9] dt-bindings: iio: adc: renesas,r9a09g077-adc: document DMA support
2026-08-28 14:59 ` [PATCH 7/9] dt-bindings: iio: adc: renesas,r9a09g077-adc: document DMA support Cosmin Tanislav
2026-08-28 15:14 ` sashiko-bot
@ 2026-08-28 16:21 ` Conor Dooley
2026-08-28 17:17 ` Geert Uytterhoeven
1 sibling, 1 reply; 18+ messages in thread
From: Conor Dooley @ 2026-08-28 16:21 UTC (permalink / raw)
To: Cosmin Tanislav
Cc: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Geert Uytterhoeven, Magnus Damm, linux-iio, linux-renesas-soc,
devicetree, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 1424 bytes --]
On Fri, Aug 28, 2026 at 05:59:40PM +0300, Cosmin Tanislav wrote:
> The ADC can transfer conversion results to memory over DMA.
>
> Allow up to three DMA specifiers, as the ADC conversion DMA request can
> be routed to any of the SoC's three DMA controllers.
>
> Signed-off-by: Cosmin Tanislav <cosmin-gabriel.tanislav.xa@renesas.com>
> ---
> .../bindings/iio/adc/renesas,r9a09g077-adc.yaml | 12 ++++++++++++
> 1 file changed, 12 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml b/Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml
> index dc0206b28231..4b5934ac296b 100644
> --- a/Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml
> +++ b/Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml
> @@ -57,6 +57,18 @@ properties:
> - const: adclk
> - const: pclk
>
> + dmas:
> + minItems: 1
> + maxItems: 3
> + description:
> + Must contain a list of references to DMA RX specifiers.
> +
> + dma-names:
> + minItems: 1
> + maxItems: 3
> + items:
> + const: rx
How does this make any sense?
If the device can make use of three DMA controllers at once, you need 3
names. If it cannot, then just limit this to 1.
pw-bot: changes-requested
Thanks,
Conor.
> +
> power-domains:
> maxItems: 1
>
> --
> 2.55.0
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 7/9] dt-bindings: iio: adc: renesas,r9a09g077-adc: document DMA support
2026-08-28 16:21 ` Conor Dooley
@ 2026-08-28 17:17 ` Geert Uytterhoeven
0 siblings, 0 replies; 18+ messages in thread
From: Geert Uytterhoeven @ 2026-08-28 17:17 UTC (permalink / raw)
To: Conor Dooley
Cc: Cosmin Tanislav, Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Geert Uytterhoeven, Magnus Damm, linux-iio, linux-renesas-soc,
devicetree, linux-kernel
Hi Conor,
On Fri, 28 Aug 2026 at 18:21, Conor Dooley <conor@kernel.org> wrote:
> On Fri, Aug 28, 2026 at 05:59:40PM +0300, Cosmin Tanislav wrote:
> > The ADC can transfer conversion results to memory over DMA.
> >
> > Allow up to three DMA specifiers, as the ADC conversion DMA request can
> > be routed to any of the SoC's three DMA controllers.
> >
> > Signed-off-by: Cosmin Tanislav <cosmin-gabriel.tanislav.xa@renesas.com>
> > --- a/Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml
> > +++ b/Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml
> > @@ -57,6 +57,18 @@ properties:
> > - const: adclk
> > - const: pclk
> >
> > + dmas:
> > + minItems: 1
> > + maxItems: 3
> > + description:
> > + Must contain a list of references to DMA RX specifiers.
> > +
> > + dma-names:
> > + minItems: 1
> > + maxItems: 3
> > + items:
> > + const: rx
>
> How does this make any sense?
Each entry provides the same functionality; the DMA core picks one that
is available. This was one of the design requirements when support
for DMACs was added to DT.
> If the device can make use of three DMA controllers at once, you need 3
> names. If it cannot, then just limit this to 1.
How do you expect that to work with multiple (the number is not fixed)
different names?
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH 8/9] arm64: dts: renesas: r9a09g077: Wire up DMA support for ADC
2026-08-28 14:59 [PATCH 0/9] iio: adc: rzt2h: add DMA buffer support Cosmin Tanislav
` (6 preceding siblings ...)
2026-08-28 14:59 ` [PATCH 7/9] dt-bindings: iio: adc: renesas,r9a09g077-adc: document DMA support Cosmin Tanislav
@ 2026-08-28 14:59 ` Cosmin Tanislav
2026-08-28 14:59 ` [PATCH 9/9] arm64: dts: renesas: r9a09g087: " Cosmin Tanislav
8 siblings, 0 replies; 18+ messages in thread
From: Cosmin Tanislav @ 2026-08-28 14:59 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Geert Uytterhoeven, Magnus Damm
Cc: linux-iio, linux-renesas-soc, devicetree, linux-kernel,
Cosmin Tanislav
RZ/T2H (R9A09G077) has three DMA controllers that can be used by
peripherals like the ADC to offload data transfers from the CPU.
Wire up the DMA channels for the ADC peripherals.
Signed-off-by: Cosmin Tanislav <cosmin-gabriel.tanislav.xa@renesas.com>
---
arch/arm64/boot/dts/renesas/r9a09g077.dtsi | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/arch/arm64/boot/dts/renesas/r9a09g077.dtsi b/arch/arm64/boot/dts/renesas/r9a09g077.dtsi
index cd34df024769..f54f8d640f02 100644
--- a/arch/arm64/boot/dts/renesas/r9a09g077.dtsi
+++ b/arch/arm64/boot/dts/renesas/r9a09g077.dtsi
@@ -1235,6 +1235,8 @@ adc0: adc@90014000 {
clocks = <&cpg CPG_CORE R9A09G077_CLK_PCLKL>,
<&cpg CPG_MOD 206>;
clock-names = "adclk", "pclk";
+ dmas = <&dmac0 0x26ba>, <&dmac1 0x26ba>, <&dmac2 0x26ba>;
+ dma-names = "rx", "rx", "rx";
power-domains = <&cpg>;
#address-cells = <1>;
#size-cells = <0>;
@@ -1257,6 +1259,8 @@ adc1: adc@90014400 {
clocks = <&cpg CPG_CORE R9A09G077_CLK_PCLKL>,
<&cpg CPG_MOD 207>;
clock-names = "adclk", "pclk";
+ dmas = <&dmac0 0x26bf>, <&dmac1 0x26bf>, <&dmac2 0x26bf>;
+ dma-names = "rx", "rx", "rx";
power-domains = <&cpg>;
#address-cells = <1>;
#size-cells = <0>;
@@ -1279,6 +1283,8 @@ adc2: adc@80008000 {
clocks = <&cpg CPG_CORE R9A09G077_CLK_PCLKL>,
<&cpg CPG_MOD 225>;
clock-names = "adclk", "pclk";
+ dmas = <&dmac0 0x26c4>, <&dmac1 0x26c4>, <&dmac2 0x26c4>;
+ dma-names = "rx", "rx", "rx";
power-domains = <&cpg>;
#address-cells = <1>;
#size-cells = <0>;
--
2.55.0
^ permalink raw reply related [flat|nested] 18+ messages in thread* [PATCH 9/9] arm64: dts: renesas: r9a09g087: Wire up DMA support for ADC
2026-08-28 14:59 [PATCH 0/9] iio: adc: rzt2h: add DMA buffer support Cosmin Tanislav
` (7 preceding siblings ...)
2026-08-28 14:59 ` [PATCH 8/9] arm64: dts: renesas: r9a09g077: Wire up DMA support for ADC Cosmin Tanislav
@ 2026-08-28 14:59 ` Cosmin Tanislav
8 siblings, 0 replies; 18+ messages in thread
From: Cosmin Tanislav @ 2026-08-28 14:59 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Geert Uytterhoeven, Magnus Damm
Cc: linux-iio, linux-renesas-soc, devicetree, linux-kernel,
Cosmin Tanislav
RZ/N2H (R9A09G087) has three DMA controllers that can be used by
peripherals like the ADC to offload data transfers from the CPU.
Wire up the DMA channels for the ADC peripherals.
Signed-off-by: Cosmin Tanislav <cosmin-gabriel.tanislav.xa@renesas.com>
---
arch/arm64/boot/dts/renesas/r9a09g087.dtsi | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/arch/arm64/boot/dts/renesas/r9a09g087.dtsi b/arch/arm64/boot/dts/renesas/r9a09g087.dtsi
index 57ac38df3e51..9e8c6610f0da 100644
--- a/arch/arm64/boot/dts/renesas/r9a09g087.dtsi
+++ b/arch/arm64/boot/dts/renesas/r9a09g087.dtsi
@@ -1238,6 +1238,8 @@ adc0: adc@90014000 {
clocks = <&cpg CPG_CORE R9A09G087_CLK_PCLKL>,
<&cpg CPG_MOD 206>;
clock-names = "adclk", "pclk";
+ dmas = <&dmac0 0x26ba>, <&dmac1 0x26ba>, <&dmac2 0x26ba>;
+ dma-names = "rx", "rx", "rx";
power-domains = <&cpg>;
#address-cells = <1>;
#size-cells = <0>;
@@ -1260,6 +1262,8 @@ adc1: adc@90014400 {
clocks = <&cpg CPG_CORE R9A09G087_CLK_PCLKL>,
<&cpg CPG_MOD 207>;
clock-names = "adclk", "pclk";
+ dmas = <&dmac0 0x26bf>, <&dmac1 0x26bf>, <&dmac2 0x26bf>;
+ dma-names = "rx", "rx", "rx";
power-domains = <&cpg>;
#address-cells = <1>;
#size-cells = <0>;
@@ -1282,6 +1286,8 @@ adc2: adc@80008000 {
clocks = <&cpg CPG_CORE R9A09G087_CLK_PCLKL>,
<&cpg CPG_MOD 225>;
clock-names = "adclk", "pclk";
+ dmas = <&dmac0 0x26c4>, <&dmac1 0x26c4>, <&dmac2 0x26c4>;
+ dma-names = "rx", "rx", "rx";
power-domains = <&cpg>;
#address-cells = <1>;
#size-cells = <0>;
--
2.55.0
^ permalink raw reply related [flat|nested] 18+ messages in thread