From nobody Wed Oct 1 21:34:41 2025 Received: from relmlie6.idc.renesas.com (relmlor2.renesas.com [210.160.252.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 6DAE12E972A; Wed, 1 Oct 2025 12:24:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=210.160.252.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1759321464; cv=none; b=WzKiexrlz5Po5aNzCSmFvY02gr/qhryr2OhlHQf+5erFc2z+oubT4rISkraR2+l0g0NR/2tWt1STRUVPt72wrDmdrKdcfVO9OoIgASBw8x2J+lpm5RgQqRNtT/ro9c4PZu2QFMNsbcBMcQUo74bDGBszxEJsa94RXBB50xT9eDc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1759321464; c=relaxed/simple; bh=ku74DRQ2So9UvEZbVBSMEgJfC8IVF7mG30/ps6F3RA8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=Gaw5SVwaGO+fawiMKQhUHHMrBgW+Uq42ltYvhBLAo2/9TYF3HJ/HhqQojgOueps5d9zOqEBGaev5c5A54sQnJ4duNkvduFVrdoXzx0XozRGMyqOtjtxge+YfmQefU5khOFxW3g5qWiNH/kqCTz/zV5wrMcYRdPoeFkIO5rvSzbI= 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.172 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: ibLXakYwSFC9EfU5qP/6LQ== X-CSE-MsgGUID: DI3RAIZNQ/qbwdOlaWRtHw== Received: from unknown (HELO relmlir6.idc.renesas.com) ([10.200.68.152]) by relmlie6.idc.renesas.com with ESMTP; 01 Oct 2025 21:24:18 +0900 Received: from demon-pc.localdomain (unknown [10.226.93.1]) by relmlir6.idc.renesas.com (Postfix) with ESMTP id 2ED284196E62; Wed, 1 Oct 2025 21:24:13 +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 , linux-iio@vger.kernel.org, linux-renesas-soc@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v3 3/7] iio: adc: add RZ/T2H / RZ/N2H ADC driver Date: Wed, 1 Oct 2025 15:23:10 +0300 Message-ID: <20251001122326.4024391-4-cosmin-gabriel.tanislav.xa@renesas.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251001122326.4024391-1-cosmin-gabriel.tanislav.xa@renesas.com> References: <20251001122326.4024391-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 Reviewed-by: Nuno S=C3=A1 --- MAINTAINERS | 1 + drivers/iio/adc/Kconfig | 10 ++ drivers/iio/adc/Makefile | 1 + drivers/iio/adc/rzt2h_adc.c | 309 ++++++++++++++++++++++++++++++++++++ 4 files changed, 321 insertions(+) create mode 100644 drivers/iio/adc/rzt2h_adc.c diff --git a/MAINTAINERS b/MAINTAINERS index 069c8030fd84..95d85d3f2803 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21838,6 +21838,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..51d1852d814d --- /dev/null +++ b/drivers/iio/adc/rzt2h_adc.c @@ -0,0 +1,309 @@ +// 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; + unsigned int max_channels; +}; + +static void rzt2h_adc_start(struct rzt2h_adc *adc, unsigned int conversion= _type) +{ + u16 reg; + + reg =3D readw(adc->base + RZT2H_ADCSR_REG); + + /* Set conversion type */ + FIELD_MODIFY(RZT2H_ADCSR_ADCS_MASK, ®, conversion_type); + + /* Set end of conversion interrupt and start bit. */ + reg |=3D RZT2H_ADCSR_ADIE_MASK | RZT2H_ADCSR_ADST_MASK; + + writew(reg, adc->base + RZT2H_ADCSR_REG); +} + +static void rzt2h_adc_stop(struct rzt2h_adc *adc) +{ + u16 reg; + + reg =3D readw(adc->base + RZT2H_ADCSR_REG); + + /* Clear end of conversion interrupt and start bit. */ + reg &=3D ~(RZT2H_ADCSR_ADIE_MASK | RZT2H_ADCSR_ADST_MASK); + + writew(reg, adc->base + RZT2H_ADCSR_REG); +} + +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; + + mutex_lock(&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 readw(adc->base + RZT2H_ADDR_REG(ch)); + ret =3D IIO_VAL_INT; + +disable: + rzt2h_adc_stop(adc); + + mutex_unlock(&adc->lock); + + 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; + } + + rzt2h_adc_set_cal(adc, false); + + if (val & RZT2H_ADCALCTL_CAL_ERR_MASK) { + dev_err(adc->dev, "Calibration failed\n"); + return -EINVAL; + } + + 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; + + 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; + unsigned int i; + int ret; + + ret =3D devm_iio_adc_device_alloc_chaninfo_se(adc->dev, + &rzt2h_adc_chan_template, + RZT2H_ADC_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; + + for (i =3D 0; i < adc->num_channels; i++) + if (chan_array[i].channel + 1 > adc->max_channels) + adc->max_channels =3D chan_array[i].channel + 1; + + 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, 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, adc); + + 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 rzt2h_adc *adc =3D dev_get_drvdata(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