RE: [PATCH v2 8/8] iio: adc: rzt2h: Add support for RZ/V2H(P) SoC
From: Biju Das
Date: Mon Oct 12 2026 - 02:38:40 EST
Hi Prabhakar,
> -----Original Message-----
> From: Prabhakar <prabhakar.csengg@xxxxxxxxx>
> Sent: 09 October 2026 19:41
> Subject: [PATCH v2 8/8] iio: adc: rzt2h: Add support for RZ/V2H(P) SoC
>
> From: Lad Prabhakar <prabhakar.mahadev-lad.rj@xxxxxxxxxxxxxx>
>
> Add support for the ADC found on the Renesas RZ/V2H(P) SoC, which shares the RZ/T2H ADC IP but differs in
> channel count, sampling, calibration and resolution support.
>
> The RZ/V2H(P) ADC provides 8 channels, has a fixed conversion time of
> 20 ADCLK cycles, does not support calibration, and supports selectable 12-bit and 8-bit conversion
> resolution.
>
> Expose the fixed sampling frequency as a read-only attribute and allow the conversion resolution to be
> changed through the scale attribute.
> Use extended scan types so the reported scan type follows the selected resolution, and prevent resolution
> changes while buffered capture is active.
>
> Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@xxxxxxxxxxxxxx>
> ---
> v1->v2:
> - Updated Kconfig description
> - Included array_size.h
> - Dropped unnecessary extra line in rzt2h_adc_write_scale
> ---
> drivers/iio/adc/Kconfig | 10 +-
> drivers/iio/adc/rzt2h_adc.c | 201 +++++++++++++++++++++++++++++++++++-
> 2 files changed, 205 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index a802d8ee4dd9..9a58f8b5868b 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -1587,14 +1587,18 @@ config RZN1_ADC
> module will be called rzn1-adc.
>
> config RZT2H_ADC
> - tristate "Renesas RZ/T2H / RZ/N2H ADC driver"
> + tristate "Renesas RZ/T2H / RZ/N2H (and similar SoCs) ADC driver"
> depends on ARCH_RENESAS || COMPILE_TEST
> select IIO_ADC_HELPER
> select IIO_BUFFER
> select IIO_KFIFO_BUF
> + select POWER_SEQUENCING
> help
> - Say yes here to build support for the ADC found in Renesas
> - RZ/T2H / RZ/N2H SoCs.
> + Say yes here to build support for the ADC found in Renesas SoCs:
> + - RZ/N2H
> + - RZ/T2H
> + - RZ/V2H(P)
> + - RZ/V2N
>
> To compile this driver as a module, choose M here: the
> module will be called rzt2h_adc.
> diff --git a/drivers/iio/adc/rzt2h_adc.c b/drivers/iio/adc/rzt2h_adc.c index 0703c2025e3f..be8273f3b61d
> 100644
> --- a/drivers/iio/adc/rzt2h_adc.c
> +++ b/drivers/iio/adc/rzt2h_adc.c
> @@ -1,5 +1,6 @@
> // SPDX-License-Identifier: GPL-2.0
>
> +#include <linux/array_size.h>
> #include <linux/bitfield.h>
> #include <linux/cleanup.h>
> #include <linux/clk.h>
> @@ -20,7 +21,9 @@
> #include <linux/platform_device.h>
> #include <linux/pm_runtime.h>
> #include <linux/property.h>
> +#include <linux/pwrseq/consumer.h>
> #include <linux/reset.h>
> +#include <linux/units.h>
> #include <linux/time64.h>
>
> #define RZT2H_ADCSR_REG 0x00
> @@ -33,6 +36,11 @@
> #define RZT2H_ADANSA0_REG 0x04
> #define RZT2H_ADANSA0_CH_MASK(x) BIT(x)
>
> +#define RZT2H_ADCER_REG 0x0e
> +#define RZT2H_ADCER_ADPRC_MASK GENMASK(2, 1)
> +#define RZT2H_ADCER_ADPRC_12BIT 0b00
> +#define RZT2H_ADCER_ADPRC_8BIT 0b10
> +
> #define RZT2H_ADDR_REG(x) (0x20 + 0x2 * (x))
>
> #define RZT2H_ADSSTRn(n) (0xe0 + 0x1 * (n))
> @@ -60,13 +68,29 @@
> RZT2H_ADC_DMA_PERIOD_SAMPLES)
> #define RZT2H_ADC_STOP_CYCLES 2
>
> +/*
> + * RZ/V2H has no sample state register, each A/D channel conversion
> +takes a
> + * fixed 20 ADCLK cycles (sampling + comparison), giving a conversion
> +rate of
> + * ADCLK / 20.
> + */
> +#define RZV2H_ADC_CONV_CYCLES 20
> +
> #define RZT2H_ADC_VREF_mV 1800
>
> #define RZT2H_ADC_FEATURE_CALIBRATION BIT(0)
> #define RZT2H_ADC_FEATURE_SAMPLE_STATE BIT(1)
> +/* Selectable 12-bit / 8-bit conversion (ADCER.ADPRC) */
> +#define RZT2H_ADC_FEATURE_RES_SELECT BIT(2)
> +/* Power-down mode controlled by the SYS power sequencer (SY_MSTP_ADA) */
> +#define RZT2H_ADC_FEATURE_POWER_DOWN BIT(3)
>
> struct rzt2h_adc;
>
> +enum rzt2h_adc_resolution {
> + RZT2H_ADC_RES_12BIT,
> + RZT2H_ADC_RES_8BIT,
> +};
> +
> struct rzt2h_adc_chip_info {
> const struct iio_chan_spec *chan_template;
> int (*sst_to_freq)(struct rzt2h_adc *adc, u8 sst); @@ -100,6 +124,7 @@ struct rzt2h_adc {
> struct device *dev;
> const struct rzt2h_adc_chip_info *chip_info;
> struct reset_control *rstc;
> + struct pwrseq_desc *pwrseq;
>
> phys_addr_t phys_base;
> struct rzt2h_adc_dma dma;
> @@ -108,6 +133,7 @@ struct rzt2h_adc {
> struct mutex lock;
>
> const struct iio_chan_spec *channels;
> + enum rzt2h_adc_resolution resolution;
> unsigned int num_channels;
> u16 buf[RZT2H_ADC_MAX_CHANNELS];
>
> @@ -118,6 +144,11 @@ struct rzt2h_adc {
> int irq;
> };
>
> +static const int rzv2h_adc_scale_avail[] = {
> + RZT2H_ADC_VREF_mV, 12,
> + RZT2H_ADC_VREF_mV, 8,
> +};
> +
> static void rzt2h_adc_start(struct rzt2h_adc *adc, unsigned int conversion_type) {
> u16 reg;
> @@ -163,6 +194,23 @@ static void rzt2h_adc_set_sst(struct rzt2h_adc *adc, unsigned int ch, u8 sst)
> writeb(sst, adc->base + RZT2H_ADSSTRn(ch)); }
>
> +static void rzt2h_adc_set_resolution(struct rzt2h_adc *adc) {
> + u16 reg;
> +
> + if (!(adc->chip_info->features & RZT2H_ADC_FEATURE_RES_SELECT))
> + return;
> +
> + reg = readw(adc->base + RZT2H_ADCER_REG);
> +
> + if (adc->resolution == RZT2H_ADC_RES_8BIT)
> + FIELD_MODIFY(RZT2H_ADCER_ADPRC_MASK, ®, RZT2H_ADCER_ADPRC_8BIT);
> + else
> + FIELD_MODIFY(RZT2H_ADCER_ADPRC_MASK, ®, RZT2H_ADCER_ADPRC_12BIT);
> +
> + writew(reg, adc->base + RZT2H_ADCER_REG); }
> +
> static int rzt2h_adc_read_single(struct rzt2h_adc *adc, unsigned int ch, int *val) {
> int ret;
> @@ -179,12 +227,14 @@ static int rzt2h_adc_read_single(struct rzt2h_adc *adc, unsigned int ch, int *va
> writew(RZT2H_ADANSA0_CH_MASK(ch), adc->base + RZT2H_ADANSA0_REG);
>
> rzt2h_adc_set_sst(adc, ch, adc->sst[ch]);
> + rzt2h_adc_set_resolution(adc);
>
> rzt2h_adc_start(adc, RZT2H_ADCSR_ADCS_SINGLE);
>
> /*
> * Conversion can take up to ~4.3us at the maximum configurable ADSSTRn
> - * (ADSSTRn + 13 cycles at 62.5 MHz), which rounds up to 1 jiffy. A bare
> + * (ADSSTRn + 13 cycles at 62.5 MHz) on RZ/T2H, and up to 4.0us (20
> + * cycles at 5 MHz) on RZ/V2H, which rounds up to 1 jiffy. A bare
> * 1-jiffy timeout can expire almost immediately if it's armed right
> * before a tick, so add one more jiffy to guarantee the conversion time
> * actually elapses.
> @@ -472,6 +522,8 @@ static int rzt2h_adc_buffer_postenable(struct iio_dev *indio_dev)
>
> writew(val, adc->base + RZT2H_ADANSA0_REG);
>
> + rzt2h_adc_set_resolution(adc);
> +
> adc->dma.period_index = 0;
> atomic_set(&adc->dma.pending_periods, 0);
>
> @@ -509,6 +561,11 @@ static int rzt2h_adc_sst_to_freq(struct rzt2h_adc *adc, u8 sst)
> return adc->adclk_rate / (RZT2H_ADC_CONV_CYCLES_BASE + sst); }
>
> +static int rzv2h_adc_sst_to_freq(struct rzt2h_adc *adc, u8 sst) {
> + return DIV_ROUND_CLOSEST(adc->adclk_rate, RZV2H_ADC_CONV_CYCLES); }
> +
> static u8 rzt2h_adc_freq_to_sst(struct rzt2h_adc *adc, int freq) {
> unsigned int cycles;
> @@ -521,6 +578,45 @@ static u8 rzt2h_adc_freq_to_sst(struct rzt2h_adc *adc, int freq)
> return cycles - RZT2H_ADC_CONV_CYCLES_BASE; }
>
> +static int rzt2h_adc_get_current_scan_type(const struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan) {
> + struct rzt2h_adc *adc = iio_priv(indio_dev);
> +
> + return adc->resolution;
> +}
> +
> +static int rzt2h_adc_write_scale(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int val, int val2)
> +{
> + struct rzt2h_adc *adc = iio_priv(indio_dev);
> + unsigned int i;
> + u64 scale;
> + u32 rem;
> +
> + if (!(adc->chip_info->features & RZT2H_ADC_FEATURE_RES_SELECT))
> + return -EINVAL;
> +
> + for (i = 0; i < chan->num_ext_scan_type; i++) {
> + scale = (u64)RZT2H_ADC_VREF_mV * NANO >>
> + chan->ext_scan_type[i].realbits;
> + if (div_u64_rem(scale, NANO, &rem) == val && rem == val2)
> + break;
> + }
> + if (i == chan->num_ext_scan_type)
> + return -EINVAL;
> +
> + /* The scan type changes with the resolution. */
> + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> + if (IIO_DEV_ACQUIRE_FAILED(claim))
> + return -EBUSY;
> +
> + adc->resolution = i;
> +
> + 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) @@ -576,11 +672,25 @@ static int
> rzt2h_adc_write_raw(struct iio_dev *indio_dev,
>
> return 0;
> }
> + case IIO_CHAN_INFO_SCALE:
> + return rzt2h_adc_write_scale(indio_dev, chan, val, val2);
> default:
> return -EINVAL;
> }
> }
>
> +static int rzt2h_adc_write_raw_get_fmt(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + long mask)
> +{
> + switch (mask) {
> + case IIO_CHAN_INFO_SCALE:
> + return IIO_VAL_INT_PLUS_NANO;
> + default:
> + return IIO_VAL_INT_PLUS_MICRO;
> + }
> +}
> +
> static int rzt2h_adc_read_avail(struct iio_dev *indio_dev,
> struct iio_chan_spec const *chan,
> const int **vals, int *type, int *length, @@ -594,6 +704,11 @@ static int
> rzt2h_adc_read_avail(struct iio_dev *indio_dev,
> *type = IIO_VAL_INT;
> *length = ARRAY_SIZE(adc->samp_freq_avail);
> return IIO_AVAIL_RANGE;
> + case IIO_CHAN_INFO_SCALE:
> + *vals = rzv2h_adc_scale_avail;
> + *type = IIO_VAL_FRACTIONAL_LOG2;
> + *length = ARRAY_SIZE(rzv2h_adc_scale_avail);
> + return IIO_AVAIL_LIST;
> default:
> return -EINVAL;
> }
> @@ -610,6 +725,14 @@ static const struct iio_info rzt2h_adc_iio_info = {
> .read_avail = rzt2h_adc_read_avail,
> };
>
> +static const struct iio_info rzv2h_adc_iio_info = {
> + .read_raw = rzt2h_adc_read_raw,
> + .write_raw = rzt2h_adc_write_raw,
> + .write_raw_get_fmt = rzt2h_adc_write_raw_get_fmt,
> + .read_avail = rzt2h_adc_read_avail,
> + .get_current_scan_type = rzt2h_adc_get_current_scan_type, };
> +
> static irqreturn_t rzt2h_adc_isr(int irq, void *private) {
> struct rzt2h_adc *adc = private;
> @@ -634,6 +757,33 @@ static const struct iio_chan_spec rzt2h_adc_chan_template = {
> },
> };
>
> +static const struct iio_scan_type rzv2h_adc_scan_types[] = {
> + [RZT2H_ADC_RES_12BIT] = {
> + .sign = 'u',
> + .realbits = 12,
> + .storagebits = 16,
> + .endianness = IIO_CPU,
> + },
> + [RZT2H_ADC_RES_8BIT] = {
> + .sign = 'u',
> + .realbits = 8,
> + .storagebits = 16,
> + .endianness = IIO_CPU,
> + },
> +};
> +
> +static const struct iio_chan_spec rzv2h_adc_chan_template = {
> + .indexed = 1,
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
> + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE),
> + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
> + .type = IIO_VOLTAGE,
> + .has_ext_scan_type = 1,
> + .ext_scan_type = rzv2h_adc_scan_types,
> + .num_ext_scan_type = ARRAY_SIZE(rzv2h_adc_scan_types), };
> +
> static int rzt2h_adc_parse_properties(struct rzt2h_adc *adc) {
> struct iio_chan_spec *chan_array;
> @@ -702,6 +852,21 @@ static int rzt2h_adc_setup_dma(struct iio_dev *indio_dev)
> &rzt2h_adc_buffer_setup_ops, NULL); }
>
> +static int rzt2h_adc_get_pwrseq(struct rzt2h_adc *adc) {
> + struct device *dev = adc->dev;
> +
> + if (!(adc->chip_info->features & RZT2H_ADC_FEATURE_POWER_DOWN))
> + return 0;
> +
> + adc->pwrseq = devm_pwrseq_get(dev, "adc-power");
> + if (IS_ERR(adc->pwrseq))
> + return dev_err_probe(dev, PTR_ERR(adc->pwrseq),
> + "failed to get power sequencer\n");
> +
> + return 0;
> +}
> +
> static int rzt2h_adc_probe(struct platform_device *pdev) {
> const struct rzt2h_adc_chip_info *chip_info; @@ -736,6 +901,10 @@ static int
> rzt2h_adc_probe(struct platform_device *pdev)
> if (IS_ERR(adc->rstc))
> return PTR_ERR(adc->rstc);
>
> + ret = rzt2h_adc_get_pwrseq(adc);
> + if (ret)
> + return ret;
> +
> ret = rzt2h_adc_parse_properties(adc);
> if (ret)
> return ret;
> @@ -787,6 +956,14 @@ static int rzt2h_adc_probe(struct platform_device *pdev)
> return devm_iio_device_register(dev, indio_dev); }
>
> +static const struct rzt2h_adc_chip_info rzv2h_adc_info = {
> + .chan_template = &rzv2h_adc_chan_template,
> + .sst_to_freq = rzv2h_adc_sst_to_freq,
> + .info = &rzv2h_adc_iio_info,
> + .num_channels = 8,
> + .features = RZT2H_ADC_FEATURE_RES_SELECT |
> +RZT2H_ADC_FEATURE_POWER_DOWN, };
> +
> static const struct rzt2h_adc_chip_info rzt2h_adc_info = {
> .chan_template = &rzt2h_adc_chan_template,
> .sst_to_freq = rzt2h_adc_sst_to_freq,
> @@ -796,6 +973,7 @@ static const struct rzt2h_adc_chip_info rzt2h_adc_info = { };
>
> static const struct of_device_id rzt2h_adc_match[] = {
> + { .compatible = "renesas,r9a09g057-adc", .data = &rzv2h_adc_info },
> { .compatible = "renesas,r9a09g077-adc", .data = &rzt2h_adc_info },
> { }
> };
> @@ -804,8 +982,17 @@ MODULE_DEVICE_TABLE(of, rzt2h_adc_match); static int
> rzt2h_adc_pm_runtime_suspend(struct device *dev) {
> struct rzt2h_adc *adc = dev_get_drvdata(dev);
> + int ret;
> +
> + ret = pwrseq_disable(adc->pwrseq);
> + if (ret)
> + return ret;
> +
> + ret = reset_control_assert(adc->rstc);
I guess to apply assert reset, IP needs power.
So, you need to asset first and then power off.
Cheers,
Biju
> + if (ret)
> + pwrseq_enable(adc->pwrseq);
>
> - return reset_control_assert(adc->rstc);
> + return ret;
> }
>
> static int rzt2h_adc_pm_runtime_resume(struct device *dev) @@ -818,6 +1005,12 @@ static int
> rzt2h_adc_pm_runtime_resume(struct device *dev)
> if (ret)
> return ret;
>
> + ret = pwrseq_enable(adc->pwrseq);
> + if (ret) {
> + reset_control_assert(adc->rstc);
> + return ret;
> + }
> +
> if (!(chip_info->features & RZT2H_ADC_FEATURE_CALIBRATION))
> return 0;
>
> @@ -829,8 +1022,10 @@ static int rzt2h_adc_pm_runtime_resume(struct device *dev)
> fsleep(1);
>
> ret = rzt2h_adc_calibrate(adc);
> - if (ret)
> + if (ret) {
> + pwrseq_disable(adc->pwrseq);
> reset_control_assert(adc->rstc);
> + }
>
> return ret;
> }
> --
> 2.55.0