From nobody Thu Oct 2 03:36:19 2025 Received: from relmlie5.idc.renesas.com (relmlor1.renesas.com [210.160.252.171]) by smtp.subspace.kernel.org (Postfix) with ESMTP id CC9891CAA92; Tue, 23 Sep 2025 16:06:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=210.160.252.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758643581; cv=none; b=gGqnDgBIVh08DH9C4OcfSveVeA1VtkTXmvlFFQpn3hZONPV5urDKp2obWbFaj0ERbhzw6WiY9TisWSzppxs/o3vmvwZqsYLq+XUcs9pmEc3jPo85Iy2fZxk0ZqkBHeei+poJmORfw3SLKSIZ9Cs8qUX7oU5g+bryF5l8rd+zymE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758643581; c=relaxed/simple; bh=GHLGTILQlTv8YD3GAt+6w8bpMvd0SKNS6A3xo7wTiaw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=Y/tNu22YbFVNQEXwszfiCNVPenGxJ2MBOURogBKm8roerdEuMZTyonwEboHY1/PvxzgWJfLj6FiHEUpsmzT3CwgiB9VdkTmCVipXjl/tKyM2NFmagytZ2TGOLkmZNaYoOFpCkOzSCXYrPQVHf1HFdhPs2BQ8BpHhZhVZhrh/YAQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=renesas.com; spf=pass smtp.mailfrom=renesas.com; arc=none smtp.client-ip=210.160.252.171 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=renesas.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=renesas.com X-CSE-ConnectionGUID: OW6lnDmzQoiAEfcfak5bHQ== X-CSE-MsgGUID: jvz/5zRNQDaQj0Pql0ZjgQ== Received: from unknown (HELO relmlir5.idc.renesas.com) ([10.200.68.151]) by relmlie5.idc.renesas.com with ESMTP; 24 Sep 2025 01:06:18 +0900 Received: from demon-pc.localdomain (unknown [10.226.93.64]) by relmlir5.idc.renesas.com (Postfix) with ESMTP id 358114008A2F; Wed, 24 Sep 2025 01:06:12 +0900 (JST) From: Cosmin Tanislav To: Cc: Cosmin Tanislav , Jonathan Cameron , David Lechner , =?UTF-8?q?Nuno=20S=C3=A1?= , Andy Shevchenko , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Geert Uytterhoeven , Magnus Damm , Michael Turquette , Stephen Boyd , Lad Prabhakar , linux-iio@vger.kernel.org, linux-renesas-soc@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-clk@vger.kernel.org Subject: [PATCH 3/7] iio: adc: add RZ/T2H / RZ/N2H ADC driver Date: Tue, 23 Sep 2025 19:05:17 +0300 Message-ID: <20250923160524.1096720-4-cosmin-gabriel.tanislav.xa@renesas.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20250923160524.1096720-1-cosmin-gabriel.tanislav.xa@renesas.com> References: <20250923160524.1096720-1-cosmin-gabriel.tanislav.xa@renesas.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Add support for the A/D 12-Bit successive approximation converters found in the Renesas RZ/T2H (R9A09G077) and RZ/N2H (R9A09G087) SoCs. RZ/T2H has two ADCs with 4 channels and one with 6. RZ/N2H has two ADCs with 4 channels and one with 15. Conversions can be performed in single or continuous mode. Result of the conversion is stored in a 16-bit data register corresponding to each channel. The conversions can be started by a software trigger, a synchronous trigger (from MTU or from ELC) or an asynchronous external trigger (from ADTRGn# pin). Only single mode with software trigger is supported for now. Signed-off-by: Cosmin Tanislav --- MAINTAINERS | 1 + drivers/iio/adc/Kconfig | 10 ++ drivers/iio/adc/Makefile | 1 + drivers/iio/adc/rzt2h_adc.c | 328 ++++++++++++++++++++++++++++++++++++ 4 files changed, 340 insertions(+) create mode 100644 drivers/iio/adc/rzt2h_adc.c diff --git a/MAINTAINERS b/MAINTAINERS index 07e0d37cf468..d550399dc390 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21828,6 +21828,7 @@ L: linux-iio@vger.kernel.org L: linux-renesas-soc@vger.kernel.org S: Supported F: Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml +F: drivers/iio/adc/rzt2h_adc.c =20 RENESAS RTCA-3 RTC DRIVER M: Claudiu Beznea diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 58a14e6833f6..cab5eeba48fe 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -1403,6 +1403,16 @@ config RZG2L_ADC To compile this driver as a module, choose M here: the module will be called rzg2l_adc. =20 +config RZT2H_ADC + tristate "Renesas RZ/T2H / RZ/N2H ADC driver" + select IIO_ADC_HELPER + help + Say yes here to build support for the ADC found in Renesas + RZ/T2H / RZ/N2H SoCs. + + To compile this driver as a module, choose M here: the + module will be called rzt2h_adc. + config SC27XX_ADC tristate "Spreadtrum SC27xx series PMICs ADC" depends on MFD_SC27XX_PMIC || COMPILE_TEST diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index d008f78dc010..ed647a734c51 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -123,6 +123,7 @@ obj-$(CONFIG_ROHM_BD79112) +=3D rohm-bd79112.o obj-$(CONFIG_ROHM_BD79124) +=3D rohm-bd79124.o obj-$(CONFIG_ROCKCHIP_SARADC) +=3D rockchip_saradc.o obj-$(CONFIG_RZG2L_ADC) +=3D rzg2l_adc.o +obj-$(CONFIG_RZT2H_ADC) +=3D rzt2h_adc.o obj-$(CONFIG_SC27XX_ADC) +=3D sc27xx_adc.o obj-$(CONFIG_SD_ADC_MODULATOR) +=3D sd_adc_modulator.o obj-$(CONFIG_SOPHGO_CV1800B_ADC) +=3D sophgo-cv1800b-adc.o diff --git a/drivers/iio/adc/rzt2h_adc.c b/drivers/iio/adc/rzt2h_adc.c new file mode 100644 index 000000000000..d855a79b3d96 --- /dev/null +++ b/drivers/iio/adc/rzt2h_adc.c @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RZT2H_NAME "rzt2h-adc" + +#define RZT2H_ADCSR_REG 0x00 +#define RZT2H_ADCSR_ADIE_MASK BIT(12) +#define RZT2H_ADCSR_ADCS_MASK GENMASK(14, 13) +#define RZT2H_ADCSR_ADCS_SINGLE 0b00 +#define RZT2H_ADCSR_ADST_MASK BIT(15) + +#define RZT2H_ADANSA0_REG 0x04 +#define RZT2H_ADANSA0_CH_MASK(x) BIT(x) + +#define RZT2H_ADDR_REG(x) (0x20 + 0x2 * (x)) + +#define RZT2H_ADCALCTL_REG 0x1f0 +#define RZT2H_ADCALCTL_CAL_MASK BIT(0) +#define RZT2H_ADCALCTL_CAL_RDY_MASK BIT(1) +#define RZT2H_ADCALCTL_CAL_ERR_MASK BIT(2) + +#define RZT2H_ADC_MAX_CHANNELS 16 +#define RZT2H_ADC_VREF_MV 1800 +#define RZT2H_ADC_RESOLUTION 12 + +struct rzt2h_adc { + void __iomem *base; + struct device *dev; + + 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 data[RZT2H_ADC_MAX_CHANNELS]; +}; + +static void rzt2h_adc_start_stop(struct rzt2h_adc *adc, bool start, + unsigned int conversion_type) +{ + u16 mask; + u16 reg; + + reg =3D readw(adc->base + RZT2H_ADCSR_REG); + + if (start) { + /* Set conversion type */ + reg &=3D ~RZT2H_ADCSR_ADCS_MASK; + reg |=3D FIELD_PREP(RZT2H_ADCSR_ADCS_MASK, conversion_type); + } + + /* Toggle end of conversion interrupt and start bit. */ + mask =3D RZT2H_ADCSR_ADIE_MASK | RZT2H_ADCSR_ADST_MASK; + if (start) + reg |=3D mask; + else + reg &=3D ~mask; + + writew(reg, adc->base + RZT2H_ADCSR_REG); +} + +static void rzt2h_adc_start(struct rzt2h_adc *adc, unsigned int conversion= _type) +{ + rzt2h_adc_start_stop(adc, true, conversion_type); +} + +static void rzt2h_adc_stop(struct rzt2h_adc *adc) +{ + rzt2h_adc_start_stop(adc, false, 0); +} + +static int rzt2h_adc_read_single(struct rzt2h_adc *adc, unsigned int ch, i= nt *val) +{ + int ret; + + ret =3D pm_runtime_resume_and_get(adc->dev); + if (ret) + return ret; + + guard(mutex)(&adc->lock); + + reinit_completion(&adc->completion); + + /* Enable a single channel */ + writew(RZT2H_ADANSA0_CH_MASK(ch), adc->base + RZT2H_ADANSA0_REG); + + rzt2h_adc_start(adc, RZT2H_ADCSR_ADCS_SINGLE); + + /* + * Datasheet Page 2770, Table 41.1: + * 0.32us per channel when sample-and-hold circuits are not in use. + */ + ret =3D wait_for_completion_timeout(&adc->completion, usecs_to_jiffies(1)= ); + if (!ret) { + ret =3D -ETIMEDOUT; + goto disable; + } + + *val =3D adc->data[ch]; + ret =3D IIO_VAL_INT; + +disable: + rzt2h_adc_stop(adc); + + pm_runtime_put_autosuspend(adc->dev); + + return ret; +} + +static void rzt2h_adc_set_cal(struct rzt2h_adc *adc, bool cal) +{ + u16 val; + + val =3D readw(adc->base + RZT2H_ADCALCTL_REG); + if (cal) + val |=3D RZT2H_ADCALCTL_CAL_MASK; + else + val &=3D ~RZT2H_ADCALCTL_CAL_MASK; + + writew(val, adc->base + RZT2H_ADCALCTL_REG); +} + +static int rzt2h_adc_calibrate(struct rzt2h_adc *adc) +{ + u16 val; + int ret; + + rzt2h_adc_set_cal(adc, true); + + ret =3D read_poll_timeout(readw, val, val & RZT2H_ADCALCTL_CAL_RDY_MASK, + 200, 1000, true, adc->base + RZT2H_ADCALCTL_REG); + if (ret) { + dev_err(adc->dev, "Calibration timed out: %d\n", ret); + return ret; + } + + if (val & RZT2H_ADCALCTL_CAL_ERR_MASK) { + dev_err(adc->dev, "Calibration failed\n"); + return -EINVAL; + } + + rzt2h_adc_set_cal(adc, false); + + 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) +{ + struct rzt2h_adc *adc =3D iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return rzt2h_adc_read_single(adc, chan->channel, val); + case IIO_CHAN_INFO_SCALE: + *val =3D RZT2H_ADC_VREF_MV; + *val2 =3D RZT2H_ADC_RESOLUTION; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static const struct iio_info rzt2h_adc_iio_info =3D { + .read_raw =3D rzt2h_adc_read_raw, +}; + +static irqreturn_t rzt2h_adc_isr(int irq, void *private) +{ + struct rzt2h_adc *adc =3D private; + unsigned long enabled_channels; + unsigned int ch; + + enabled_channels =3D readw(adc->base + RZT2H_ADANSA0_REG); + if (!enabled_channels) + return IRQ_NONE; + + for_each_set_bit(ch, &enabled_channels, adc->num_channels) + adc->data[ch] =3D readw(adc->base + RZT2H_ADDR_REG(ch)); + + complete(&adc->completion); + + return IRQ_HANDLED; +} + +static const struct iio_chan_spec rzt2h_adc_chan_template =3D { + .indexed =3D 1, + .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .type =3D IIO_VOLTAGE, +}; + +static int rzt2h_adc_parse_properties(struct rzt2h_adc *adc) +{ + struct iio_chan_spec *chan_array; + u32 max_channels; + int ret; + + ret =3D device_property_read_u32(adc->dev, "renesas,max-channels", + &max_channels); + if (ret) + return dev_err_probe(adc->dev, ret, + "Failed to find max-channels property"); + + ret =3D devm_iio_adc_device_alloc_chaninfo_se(adc->dev, + &rzt2h_adc_chan_template, + max_channels - 1, + &chan_array); + if (ret < 0) + return dev_err_probe(adc->dev, ret, "Failed to read channel info"); + + adc->num_channels =3D ret; + adc->channels =3D chan_array; + + return 0; +} + +static int rzt2h_adc_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + struct iio_dev *indio_dev; + struct rzt2h_adc *adc; + int ret; + int irq; + + indio_dev =3D devm_iio_device_alloc(dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc =3D iio_priv(indio_dev); + adc->dev =3D dev; + init_completion(&adc->completion); + + ret =3D devm_mutex_init(dev, &adc->lock); + if (ret) + return ret; + + platform_set_drvdata(pdev, indio_dev); + + ret =3D rzt2h_adc_parse_properties(adc); + if (ret) + return ret; + + adc->base =3D devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(adc->base)) + return PTR_ERR(adc->base); + + pm_runtime_set_autosuspend_delay(dev, 300); + pm_runtime_use_autosuspend(dev); + ret =3D devm_pm_runtime_enable(dev); + if (ret) + return ret; + + irq =3D platform_get_irq_byname(pdev, "adi"); + if (irq < 0) + return irq; + + ret =3D devm_request_irq(dev, irq, rzt2h_adc_isr, 0, dev_name(dev), adc); + if (ret) + return ret; + + indio_dev->name =3D RZT2H_NAME; + indio_dev->info =3D &rzt2h_adc_iio_info; + indio_dev->modes =3D INDIO_DIRECT_MODE; + indio_dev->channels =3D adc->channels; + indio_dev->num_channels =3D adc->num_channels; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id rzt2h_adc_match[] =3D { + { .compatible =3D "renesas,r9a09g077-adc" }, + { .compatible =3D "renesas,r9a09g087-adc" }, + { } +}; +MODULE_DEVICE_TABLE(of, rzt2h_adc_match); + +static int rzt2h_adc_pm_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev =3D dev_get_drvdata(dev); + struct rzt2h_adc *adc =3D iio_priv(indio_dev); + + /* + * Datasheet Page 2810, Section 41.5.6: + * After release from the module-stop state, wait for at least + * 0.5 =C2=B5s before starting A/D conversion. + */ + fsleep(1); + + return rzt2h_adc_calibrate(adc); +} + +static const struct dev_pm_ops rzt2h_adc_pm_ops =3D { + RUNTIME_PM_OPS(NULL, rzt2h_adc_pm_runtime_resume, NULL) +}; + +static struct platform_driver rzt2h_adc_driver =3D { + .probe =3D rzt2h_adc_probe, + .driver =3D { + .name =3D RZT2H_NAME, + .of_match_table =3D rzt2h_adc_match, + .pm =3D pm_ptr(&rzt2h_adc_pm_ops), + }, +}; + +module_platform_driver(rzt2h_adc_driver); + +MODULE_AUTHOR("Cosmin Tanislav "); +MODULE_DESCRIPTION("Renesas RZ/T2H / RZ/N2H ADC driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_DRIVER"); --=20 2.51.0